diff options
-rw-r--r-- | .helix/languages.toml | 4 | ||||
-rw-r--r-- | filamento/src/caps.rs | 2 | ||||
-rw-r--r-- | filamento/src/chat.rs | 10 | ||||
-rw-r--r-- | filamento/src/db.rs | 247 | ||||
-rw-r--r-- | filamento/src/error.rs | 9 | ||||
-rw-r--r-- | filamento/src/lib.rs | 126 | ||||
-rw-r--r-- | filamento/src/logic/local_only.rs | 14 | ||||
-rw-r--r-- | filamento/src/logic/mod.rs | 8 | ||||
-rw-r--r-- | filamento/src/logic/offline.rs | 23 | ||||
-rw-r--r-- | filamento/src/logic/online.rs | 101 | ||||
-rw-r--r-- | filamento/src/logic/process_stanza.rs | 107 | ||||
-rw-r--r-- | filamento/src/roster.rs | 4 | ||||
-rw-r--r-- | filamento/src/user.rs | 4 | ||||
-rw-r--r-- | jid/src/lib.rs | 291 | ||||
-rw-r--r-- | lampada/src/connection/mod.rs | 27 | ||||
-rw-r--r-- | lampada/src/error.rs | 2 | ||||
-rw-r--r-- | lampada/src/lib.rs | 40 | ||||
-rw-r--r-- | luz/src/client/tcp.rs | 19 | ||||
-rw-r--r-- | luz/src/client/ws.rs | 11 | ||||
-rw-r--r-- | luz/src/connection/tcp.rs | 2 | ||||
-rw-r--r-- | luz/src/error.rs | 4 | ||||
-rw-r--r-- | luz/src/jabber_stream.rs | 23 | ||||
-rw-r--r-- | stanza/src/bind.rs | 4 | ||||
-rw-r--r-- | stanza/src/rfc_7395.rs | 10 | ||||
-rw-r--r-- | stanza/src/roster.rs | 4 | ||||
-rw-r--r-- | stanza/src/stream.rs | 17 | ||||
-rw-r--r-- | stanza/src/xep_0060/owner.rs | 4 |
27 files changed, 655 insertions, 462 deletions
diff --git a/.helix/languages.toml b/.helix/languages.toml index 7662fff..02fab3a 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -1,9 +1,9 @@ [language-server.rust-analyzer] command = "rust-analyzer" -environment = { "DATABASE_URL" = "sqlite://filamento/filamento.db" } +# environment = { "DATABASE_URL" = "sqlite://filamento/filamento.db" } # checkOnSave.overrideCommand = "cargo check --message-format=json -p luz", # check.overrideCommand="cargo check --message-format=json -p luz", # check.workspace = false, # cargo.target = "wasm32-unknown-unknown", -config = { cargo.target = "wasm32-unknown-unknown", cargo.features = ["jid/rusqlite", "stanza/rfc_6121", "stanza/xep_0203", "stanza/rfc_7395", "stanza/xep_0030", "stanza/xep_0060", "stanza/xep_0172", "stanza/xep_0390", "stanza/xep_0128", "stanza/xep_0115", "stanza/xep_0084", "uuid/v4", "tokio/full", "rsasl/provider_base64", "rsasl/plain", "rsasl/config_builder", "rsasl/scram-sha-1", "stanza/xep_0156"] } +# config = { cargo.target = "wasm32-unknown-unknown", cargo.features = ["jid/rusqlite", "stanza/rfc_6121", "stanza/xep_0203", "stanza/rfc_7395", "stanza/xep_0030", "stanza/xep_0060", "stanza/xep_0172", "stanza/xep_0390", "stanza/xep_0128", "stanza/xep_0115", "stanza/xep_0084", "uuid/v4", "tokio/full", "rsasl/provider_base64", "rsasl/plain", "rsasl/config_builder", "rsasl/scram-sha-1", "stanza/xep_0156"] } # "sqlx/sqlite", "sqlx/runtime-tokio", "sqlx/uuid", "sqlx/chrono", "jid/sqlx", diff --git a/filamento/src/caps.rs b/filamento/src/caps.rs index 43f1cf4..e0587ff 100644 --- a/filamento/src/caps.rs +++ b/filamento/src/caps.rs @@ -377,7 +377,7 @@ pub fn node_to_hash(node: String) -> Result<Hash, HashNodeConversionError> { #[cfg(test)] mod tests { - use peanuts::{Writer, element::IntoElement, loggable::Loggable}; + use peanuts::Writer; use stanza::{ xep_0004::{Field, FieldType, Value, X, XType}, xep_0030::info::{Feature, Identity}, diff --git a/filamento/src/chat.rs b/filamento/src/chat.rs index 5f58866..c02654f 100644 --- a/filamento/src/chat.rs +++ b/filamento/src/chat.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Write}; use chrono::{DateTime, Utc}; -use jid::JID; +use jid::{BareJID, JID}; use rusqlite::{ ToSql, types::{FromSql, ToSqlOutput, Value}, @@ -15,7 +15,7 @@ pub struct Message { pub id: Uuid, // does not contain full user information // bare jid (for now) - pub from: JID, + pub from: BareJID, pub delivery: Option<Delivery>, pub timestamp: DateTime<Utc>, // TODO: originally_from @@ -97,7 +97,7 @@ pub struct Body { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct Chat { - pub correspondent: JID, + pub correspondent: BareJID, pub have_chatted: bool, // pub unread_messages: i32, // pub latest_message: Message, @@ -109,13 +109,13 @@ pub struct Chat { pub enum ChatUpdate {} impl Chat { - pub fn new(correspondent: JID, have_chatted: bool) -> Self { + pub fn new(correspondent: BareJID, have_chatted: bool) -> Self { Self { correspondent, have_chatted, } } - pub fn correspondent(&self) -> &JID { + pub fn correspondent(&self) -> &BareJID { &self.correspondent } } diff --git a/filamento/src/db.rs b/filamento/src/db.rs index 1b5afe6..385382a 100644 --- a/filamento/src/db.rs +++ b/filamento/src/db.rs @@ -2,7 +2,7 @@ use core::fmt::Display; use std::{collections::HashSet, ops::Deref, path::Path, sync::Arc}; use chrono::{DateTime, Utc}; -use jid::JID; +use jid::{BareJID, FullJID, JID}; use rusqlite::{Connection, OptionalExtension}; use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{mpsc, oneshot}; @@ -58,53 +58,22 @@ impl Db { pub async fn create_connect_and_migrate( path: impl AsRef<Path> + Send, ) -> Result<Self, DatabaseOpenError> { - let (sender, receiver) = mpsc::channel(20); - let (result_send, result_recv) = oneshot::channel(); - spawn_blocking(move || { - let result = DbActor::new(path, receiver).await; - match result { - Ok(a) => { - result_send.send(Ok(())); - a.run() - } - Err(e) => { - result_send.send(Err(e)); - } - } - }); - match result_recv.await { - Ok(r) => match r { - Ok(o) => Ok(Self { sender }), - Err(e) => return Err(e), - }, - Err(e) => return Err(e.into()), - } + let (sender, receiver) = mpsc::unbounded_channel(); + + let actor = DbActor::new(path, receiver)?; + spawn_blocking(move || actor.run()); + + Ok(Self { sender }) } #[cfg(not(target_arch = "wasm32"))] pub async fn create_connect_and_migrate_memory() -> Result<Self, DatabaseOpenError> { - let (sender, receiver) = mpsc::channel(20); - let (result_send, result_recv) = oneshot::channel(); - spawn_blocking(move || { - let result = DbActor::new_memory(receiver).await; - match result { - Ok(a) => { - result_send.send(Ok(())); - // TODO: async run when not wasm - a.run() - } - Err(e) => { - result_send.send(Err(e)); - } - } - }); - match result_recv.await { - Ok(r) => match r { - Ok(o) => Ok(Self { sender }), - Err(e) => return Err(e), - }, - Err(e) => return Err(e.into()), - } + let (sender, receiver) = mpsc::unbounded_channel(); + + let actor = DbActor::new_memory(receiver)?; + spawn_blocking(move || actor.run()); + + Ok(Self { sender }) } /// `file_name` should be a file not in a directory @@ -183,7 +152,7 @@ impl Db { } // TODO: this is not a 'read' user - pub(crate) async fn read_user(&self, user: JID) -> Result<User, Error> { + pub(crate) async fn read_user(&self, user: BareJID) -> Result<User, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadUser { user, result }; self.sender.send(command); @@ -192,7 +161,7 @@ impl Db { } /// returns whether or not the nickname was updated - pub(crate) async fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> { + pub(crate) async fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteUserNick { jid, result }; self.sender.send(command); @@ -201,7 +170,7 @@ impl Db { } /// returns whether or not the nickname was updated - pub(crate) async fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> { + pub(crate) async fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpsertUserNick { jid, nick, result }; self.sender.send(command); @@ -212,7 +181,7 @@ impl Db { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) async fn delete_user_avatar( &self, - jid: JID, + jid: BareJID, ) -> Result<(bool, Option<String>), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteUserAvatar { jid, result }; @@ -224,7 +193,7 @@ impl Db { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) async fn upsert_user_avatar( &self, - jid: JID, + jid: BareJID, avatar: String, ) -> Result<(bool, Option<String>), Error> { let (result, recv) = oneshot::channel(); @@ -259,7 +228,7 @@ impl Db { result } - pub(crate) async fn read_contact(&self, contact: JID) -> Result<Contact, Error> { + pub(crate) async fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadContact { contact, result }; self.sender.send(command); @@ -267,7 +236,10 @@ impl Db { result } - pub(crate) async fn read_contact_opt(&self, contact: JID) -> Result<Option<Contact>, Error> { + pub(crate) async fn read_contact_opt( + &self, + contact: BareJID, + ) -> Result<Option<Contact>, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadContactOpt { contact, result }; self.sender.send(command); @@ -292,7 +264,7 @@ impl Db { result } - pub(crate) async fn delete_contact(&self, contact: JID) -> Result<(), Error> { + pub(crate) async fn delete_contact(&self, contact: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteContact { contact, result }; self.sender.send(command); @@ -336,7 +308,7 @@ impl Db { // TODO: what happens if a correspondent changes from a user to a contact? maybe just have correspondent be a user, then have the client make the user show up as a contact in ui if they are in the loaded roster. - pub(crate) async fn read_chat(&self, chat: JID) -> Result<Chat, Error> { + pub(crate) async fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadChat { chat, result }; self.sender.send(command); @@ -344,7 +316,7 @@ impl Db { result } - pub(crate) async fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> { + pub(crate) async fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::ReadChatAndUser { chat, result }; self.sender.send(command); @@ -352,7 +324,7 @@ impl Db { result } - pub(crate) async fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> { + pub(crate) async fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::MarkChatAsChatted { chat, result }; self.sender.send(command); @@ -363,7 +335,7 @@ impl Db { pub(crate) async fn update_chat_correspondent( &self, old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, ) -> Result<Chat, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpdateChatCorrespondent { @@ -378,7 +350,7 @@ impl Db { // pub(crate) async fn update_chat - pub(crate) async fn delete_chat(&self, chat: JID) -> Result<(), Error> { + pub(crate) async fn delete_chat(&self, chat: BareJID) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::DeleteChat { chat, result }; self.sender.send(command); @@ -434,8 +406,8 @@ impl Db { pub(crate) async fn create_message( &self, message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::CreateMessage { @@ -449,7 +421,7 @@ impl Db { result } - pub(crate) async fn upsert_chat_and_user(&self, chat: JID) -> Result<bool, Error> { + pub(crate) async fn upsert_chat_and_user(&self, chat: BareJID) -> Result<bool, Error> { let (result, recv) = oneshot::channel(); let command = DbCommand::UpsertChatAndUser { chat, result }; self.sender.send(command); @@ -463,10 +435,8 @@ impl Db { &self, message: Message, // TODO: enforce two kinds of jid. bare and full - // must be bare jid - chat: JID, - // full jid - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { tracing::info!("MSGDEBUG create_message_with_user_resource exists"); let (result, recv) = oneshot::channel(); @@ -514,9 +484,9 @@ impl Db { DeleteCachedStatus => delete_cached_status() -> (); UpsertCachedStatus => upsert_cached_status(status: Online) -> (); ReadCachedStatus => read_cached_status() -> Online; - ReadMessageHistoryWithUsers => read_message_history_with_users(chat: JID) -> Vec<(Message, User)>; + ReadMessageHistoryWithUsers => read_message_history_with_users(chat: BareJID) -> Vec<(Message, User)>; // TODO: paging - ReadMessageHistory => read_message_history(chat: JID) -> Vec<Message>; + ReadMessageHistory => read_message_history(chat: BareJID) -> Vec<Message>; ReadMessage => read_message(message: Uuid) -> Message; DeleteMessage => delete_message(message: Uuid) -> () ); @@ -546,24 +516,24 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadUser { - user: JID, + user: BareJID, result: oneshot::Sender<Result<User, Error>>, }, DeleteUserNick { - jid: JID, + jid: BareJID, result: oneshot::Sender<Result<bool, Error>>, }, UpsertUserNick { - jid: JID, + jid: BareJID, nick: String, result: oneshot::Sender<Result<bool, Error>>, }, DeleteUserAvatar { - jid: JID, + jid: BareJID, result: oneshot::Sender<Result<(bool, Option<String>), Error>>, }, UpsertUserAvatar { - jid: JID, + jid: BareJID, avatar: String, result: oneshot::Sender<Result<(bool, Option<String>), Error>>, }, @@ -576,11 +546,11 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadContact { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<Contact, Error>>, }, ReadContactOpt { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<Option<Contact>, Error>>, }, UpdateContact { @@ -592,7 +562,7 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, DeleteContact { - contact: JID, + contact: BareJID, result: oneshot::Sender<Result<(), Error>>, }, ReplaceCachedRoster { @@ -610,24 +580,24 @@ pub enum DbCommand { result: oneshot::Sender<Result<(), Error>>, }, ReadChat { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Chat, Error>>, }, ReadChatAndUser { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(Chat, User), Error>>, }, MarkChatAsChatted { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(), Error>>, }, UpdateChatCorrespondent { old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, result: oneshot::Sender<Result<Chat, Error>>, }, DeleteChat { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<(), Error>>, }, ReadChats { @@ -652,18 +622,18 @@ pub enum DbCommand { // }, CreateMessage { message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, result: oneshot::Sender<Result<(), Error>>, }, UpsertChatAndUser { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<bool, Error>>, }, CreateMessageWithUserResource { message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, result: oneshot::Sender<Result<(), Error>>, }, UpdateMessageDelivery { @@ -680,11 +650,11 @@ pub enum DbCommand { result: oneshot::Sender<Result<Message, Error>>, }, ReadMessageHistory { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Vec<Message>, Error>>, }, ReadMessageHistoryWithUsers { - chat: JID, + chat: BareJID, result: oneshot::Sender<Result<Vec<(Message, User)>, Error>>, }, ReadCachedStatus { @@ -758,17 +728,19 @@ impl Display for DbCommand { impl DbActor { /// must be run in blocking spawn #[cfg(not(target_arch = "wasm32"))] - pub(crate) fn new(path: impl AsRef<Path>, receiver: mpsc::Receiver<DbCommand>) -> Self { + pub(crate) fn new( + path: impl AsRef<Path>, + receiver: mpsc::UnboundedReceiver<DbCommand>, + ) -> Result<Self, DatabaseOpenError> { if let Some(dir) = path.as_ref().parent() { if dir.is_dir() { } else { - tokio::fs::create_dir_all(dir).await?; + std::fs::create_dir_all(dir)?; } - let _file = tokio::fs::OpenOptions::new() + let _file = std::fs::OpenOptions::new() .append(true) .create(true) - .open(path.as_ref()) - .await?; + .open(path.as_ref())?; } let url = format!( "{}", @@ -786,7 +758,9 @@ impl DbActor { /// must be run in blocking spawn #[cfg(not(target_arch = "wasm32"))] - pub(crate) fn new_memory(receiver: mpsc::Receiver<DbCommand>) -> Self { + pub(crate) fn new_memory( + receiver: mpsc::UnboundedReceiver<DbCommand>, + ) -> Result<Self, DatabaseOpenError> { let db = Connection::open_in_memory()?; db.execute_batch(include_str!("../migrations/1.sql"))?; Ok(Self { db, receiver }) @@ -850,7 +824,7 @@ impl DbActor { result.send(self.read_contact(contact)); } DbCommand::ReadContactOpt { contact, result } => { - result.send(self.read_contact_opt(&contact)); + result.send(self.read_contact_opt(contact)); } DbCommand::UpdateContact { contact, result } => { result.send(self.update_contact(contact)); @@ -978,7 +952,7 @@ impl DbActor { } // TODO: this is not a 'read' user - pub(crate) fn read_user(&self, user: JID) -> Result<User, Error> { + pub(crate) fn read_user(&self, user: BareJID) -> Result<User, Error> { let db = &self.db; let user_opt = db .query_row( @@ -1007,7 +981,7 @@ impl DbActor { } /// returns whether or not the nickname was updated - pub(crate) fn delete_user_nick(&self, jid: JID) -> Result<bool, Error> { + pub(crate) fn delete_user_nick(&self, jid: BareJID) -> Result<bool, Error> { let rows_affected; { rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, None::<String>, None::<String>, None::<String>))?; @@ -1020,7 +994,7 @@ impl DbActor { } /// returns whether or not the nickname was updated - pub(crate) fn upsert_user_nick(&self, jid: JID, nick: String) -> Result<bool, Error> { + pub(crate) fn upsert_user_nick(&self, jid: BareJID, nick: String) -> Result<bool, Error> { let rows_affected; { rows_affected = self.db.execute("insert into users (jid, nick) values (?1, ?2) on conflict do update set nick = ?3 where nick is not ?4", (jid, &nick, &nick, &nick))?; @@ -1033,7 +1007,7 @@ impl DbActor { } /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar - pub(crate) fn delete_user_avatar(&self, jid: JID) -> Result<(bool, Option<String>), Error> { + pub(crate) fn delete_user_avatar(&self, jid: BareJID) -> Result<(bool, Option<String>), Error> { let (old_avatar, rows_affected): (Option<String>, _); { let db = &self.db; @@ -1054,7 +1028,7 @@ impl DbActor { /// returns whether or not the avatar was updated, and the file to delete if there existed an old avatar pub(crate) fn upsert_user_avatar( &self, - jid: JID, + jid: BareJID, avatar: String, ) -> Result<(bool, Option<String>), Error> { let (old_avatar, rows_affected): (Option<String>, _); @@ -1108,7 +1082,7 @@ impl DbActor { Ok(()) } - pub(crate) fn read_contact(&self, contact: JID) -> Result<Contact, Error> { + pub(crate) fn read_contact(&self, contact: BareJID) -> Result<Contact, Error> { let db = &self.db; let mut contact_item = db.query_row( "select user_jid, name, subscription from roster where user_jid = ?1", @@ -1130,7 +1104,7 @@ impl DbActor { Ok(contact_item) } - pub(crate) fn read_contact_opt(&self, contact: &JID) -> Result<Option<Contact>, Error> { + pub(crate) fn read_contact_opt(&self, contact: BareJID) -> Result<Option<Contact>, Error> { let db = &self.db; let contact_item = db .query_row( @@ -1210,7 +1184,7 @@ impl DbActor { Ok(()) } - pub(crate) fn delete_contact(&self, contact: JID) -> Result<(), Error> { + pub(crate) fn delete_contact(&self, contact: BareJID) -> Result<(), Error> { self.db .execute("delete from roster where user_jid = ?1", [&contact])?; Ok(()) @@ -1288,19 +1262,21 @@ impl DbActor { // TODO: what happens if a correspondent changes from a user to a contact? maybe just have correspondent be a user, then have the client make the user show up as a contact in ui if they are in the loaded roster. - /// should be a bare jid /// TODO: this is NOT a read - pub(crate) fn read_chat(&self, chat: JID) -> Result<Chat, Error> { - let chat_opt = self.db.query_row( - "select correspondent, have_chatted from chats where correspondent = ?1", - [&chat], - |row| { - Ok(Chat { - correspondent: row.get(0)?, - have_chatted: row.get(1)?, - }) - }, - ).optional()?; + pub(crate) fn read_chat(&self, chat: BareJID) -> Result<Chat, Error> { + let chat_opt = self + .db + .query_row( + "select correspondent, have_chatted from chats where correspondent = ?1", + [&chat], + |row| { + Ok(Chat { + correspondent: row.get(0)?, + have_chatted: row.get(1)?, + }) + }, + ) + .optional()?; match chat_opt { Some(chat) => return Ok(chat), None => { @@ -1314,7 +1290,7 @@ impl DbActor { } } - pub(crate) fn read_chat_and_user(&self, chat: JID) -> Result<(Chat, User), Error> { + pub(crate) fn read_chat_and_user(&self, chat: BareJID) -> Result<(Chat, User), Error> { let user = self.read_user(chat.clone())?; let chat_opt = self.db.query_row( "select correspondent, have_chatted, jid, nick, avatar from chats join users on correspondent = jid where correspondent = ?1", @@ -1346,7 +1322,7 @@ impl DbActor { } } - pub(crate) fn mark_chat_as_chatted(&self, chat: JID) -> Result<(), Error> { + pub(crate) fn mark_chat_as_chatted(&self, chat: BareJID) -> Result<(), Error> { self.db.execute( "update chats set have_chatted = true where correspondent = ?1", [chat], @@ -1357,7 +1333,7 @@ impl DbActor { pub(crate) fn update_chat_correspondent( &self, old_chat: Chat, - new_correspondent: JID, + new_correspondent: BareJID, ) -> Result<Chat, Error> { let new_jid = &new_correspondent; let old_jid = old_chat.correspondent(); @@ -1374,7 +1350,7 @@ impl DbActor { // pub(crate) fn update_chat - pub(crate) fn delete_chat(&self, chat: JID) -> Result<(), Error> { + pub(crate) fn delete_chat(&self, chat: BareJID) -> Result<(), Error> { self.db .execute("delete from chats where correspondent = ?1", [chat])?; Ok(()) @@ -1484,7 +1460,7 @@ impl DbActor { } #[tracing::instrument] - fn read_chat_id(&self, chat: JID) -> Result<Uuid, Error> { + fn read_chat_id(&self, chat: BareJID) -> Result<Uuid, Error> { let chat_id = self.db.query_row( "select id from chats where correspondent = ?1", [chat], @@ -1510,8 +1486,8 @@ impl DbActor { pub(crate) fn create_message( &self, message: Message, - chat: JID, - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let from_jid = from.as_bare(); let chat_id = self.read_chat_id(chat)?; @@ -1520,18 +1496,17 @@ impl DbActor { Ok(()) } - pub(crate) fn upsert_chat_and_user(&self, chat: &JID) -> Result<bool, Error> { - let bare_chat = chat.as_bare(); + pub(crate) fn upsert_chat_and_user(&self, chat: &BareJID) -> Result<bool, Error> { let db = &self.db; db.execute( "insert into users (jid) values (?1) on conflict do nothing", - [&bare_chat], + [&chat], )?; let id = Uuid::new_v4(); - db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &bare_chat, false))?; + db.execute("insert into chats (id, correspondent, have_chatted) values (?1, ?2, ?3) on conflict do nothing", (id, &chat, false))?; let chat = db.query_row( "select correspondent, have_chatted from chats where correspondent = ?1", - [&bare_chat], + [&chat], |row| { Ok(Chat { correspondent: row.get(0)?, @@ -1547,21 +1522,15 @@ impl DbActor { pub(crate) fn create_message_with_user_resource( &self, message: Message, - // TODO: enforce two kinds of jid. bare and full - // must be bare jid - chat: JID, - // full jid - from: JID, + chat: BareJID, + from: FullJID, ) -> Result<(), Error> { let from_jid = from.as_bare(); - let chat = chat.as_bare(); tracing::debug!("creating resource"); - if let Some(resource) = &from.resourcepart { - self.db.execute( - "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing", - (&from_jid, resource), - )?; - } + self.db.execute( + "insert into resources (bare_jid, resource) values (?1, ?2) on conflict do nothing", + (&from_jid, &from.resourcepart), + )?; self.create_message(message, chat, from)?; Ok(()) } @@ -1615,7 +1584,7 @@ impl DbActor { } // TODO: paging - pub(crate) fn read_message_history(&self, chat: JID) -> Result<Vec<Message>, Error> { + pub(crate) fn read_message_history(&self, chat: BareJID) -> Result<Vec<Message>, Error> { let chat_id = self.read_chat_id(chat)?; let messages = self .db @@ -1638,7 +1607,7 @@ impl DbActor { pub(crate) fn read_message_history_with_users( &self, - chat: JID, + chat: BareJID, ) -> Result<Vec<(Message, User)>, Error> { let chat_id = self.read_chat_id(chat)?; let messages = self diff --git a/filamento/src/error.rs b/filamento/src/error.rs index af3320f..721d532 100644 --- a/filamento/src/error.rs +++ b/filamento/src/error.rs @@ -3,6 +3,7 @@ use std::{num::TryFromIntError, string::FromUtf8Error, sync::Arc}; use base64::DecodeError; use image::ImageError; use jid::JID; +use jid::JIDError; use lampada::error::{ActorError, ReadError, WriteError}; use stanza::client::{Stanza, iq::Query}; use thiserror::Error; @@ -297,7 +298,7 @@ pub enum DatabaseOpenError { // #[error("migration: {0}")] // Migration(Arc<rusqlite::migrate::MigrateError>), #[error("io: {0}")] - Io(Arc<tokio::io::Error>), + Io(Arc<std::io::Error>), #[error("invalid path")] InvalidPath, #[error("tokio oneshot recv error: {0}")] @@ -310,8 +311,8 @@ pub enum DatabaseOpenError { // } // } -impl From<tokio::io::Error> for DatabaseOpenError { - fn from(e: tokio::io::Error) -> Self { +impl From<std::io::Error> for DatabaseOpenError { + fn from(e: std::io::Error) -> Self { Self::Io(Arc::new(e)) } } @@ -332,6 +333,8 @@ pub enum PresenceError { MissingFrom, #[error("stanza error: {0:?}")] StanzaError(Option<stanza::client::error::Error>), + #[error("received subscription request from a non-bare jid")] + InvalidSubscriptionRequest(#[from] JIDError), } #[derive(Debug, Error, Clone)] diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs index 068bfe8..d3033b7 100644 --- a/filamento/src/lib.rs +++ b/filamento/src/lib.rs @@ -16,7 +16,7 @@ use error::{ }; use files::FileStore; use futures::FutureExt; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{ Connected, CoreClient, CoreClientCommand, Logic, SupervisorSender, WriteMessage, error::{ActorError, CommandError, ConnectionError, ReadError, WriteError}, @@ -68,45 +68,55 @@ pub enum Command<Fs: FileStore> { oneshot::Sender<Result<Vec<((Chat, User), (Message, User))>, DatabaseError>>, ), /// get a specific chat by jid - GetChat(JID, oneshot::Sender<Result<Chat, DatabaseError>>), + GetChat(BareJID, oneshot::Sender<Result<Chat, DatabaseError>>), /// get a specific chat and user by jid - GetChatAndUser(JID, oneshot::Sender<Result<(Chat, User), DatabaseError>>), + GetChatAndUser( + BareJID, + oneshot::Sender<Result<(Chat, User), DatabaseError>>, + ), /// get message history for chat (does appropriate mam things) GetMessage(Uuid, oneshot::Sender<Result<Message, DatabaseError>>), // TODO: paging and filtering - GetMessages(JID, oneshot::Sender<Result<Vec<Message>, DatabaseError>>), + GetMessages( + BareJID, + oneshot::Sender<Result<Vec<Message>, DatabaseError>>, + ), /// get message history for chat (does appropriate mam things) // TODO: paging and filtering GetMessagesWithUsers( - JID, + BareJID, oneshot::Sender<Result<Vec<(Message, User)>, DatabaseError>>, ), /// delete a chat from your chat history, along with all the corresponding messages - DeleteChat(JID, oneshot::Sender<Result<(), DatabaseError>>), + DeleteChat(BareJID, oneshot::Sender<Result<(), DatabaseError>>), /// delete a message from your chat history DeleteMessage(Uuid, oneshot::Sender<Result<(), DatabaseError>>), /// get a user from your users database - GetUser(JID, oneshot::Sender<Result<User, DatabaseError>>), + GetUser(BareJID, oneshot::Sender<Result<User, DatabaseError>>), /// add a contact to your roster, with a status of none, no subscriptions. - AddContact(JID, oneshot::Sender<Result<(), RosterError>>), + AddContact(BareJID, oneshot::Sender<Result<(), RosterError>>), /// send a friend request i.e. a subscription request with a subscription pre-approval. if not already added to roster server adds to roster. - BuddyRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + BuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// send a subscription request, without pre-approval. if not already added to roster server adds to roster. - SubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + SubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// accept a friend request by accepting a pending subscription and sending a subscription request back. if not already added to roster adds to roster. - AcceptBuddyRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + AcceptBuddyRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// accept a pending subscription and doesn't send a subscription request back. if not already added to roster adds to roster. - AcceptSubscriptionRequest(JID, oneshot::Sender<Result<(), SubscribeError>>), + AcceptSubscriptionRequest(BareJID, oneshot::Sender<Result<(), SubscribeError>>), /// unsubscribe to a contact, but don't remove their subscription. - UnsubscribeFromContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnsubscribeFromContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// stop a contact from being subscribed, but stay subscribed to the contact. - UnsubscribeContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnsubscribeContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// remove subscriptions to and from contact, but keep in roster. - UnfriendContact(JID, oneshot::Sender<Result<(), WriteError>>), + UnfriendContact(BareJID, oneshot::Sender<Result<(), WriteError>>), /// remove a contact from the contact list. will remove subscriptions if not already done then delete contact from roster. - DeleteContact(JID, oneshot::Sender<Result<(), RosterError>>), + DeleteContact(BareJID, oneshot::Sender<Result<(), RosterError>>), /// update contact. contact details will be overwritten with the contents of the contactupdate struct. - UpdateContact(JID, ContactUpdate, oneshot::Sender<Result<(), RosterError>>), + UpdateContact( + BareJID, + ContactUpdate, + oneshot::Sender<Result<(), RosterError>>, + ), /// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence. SetStatus(Online, oneshot::Sender<Result<(), StatusError>>), /// send presence stanza @@ -120,7 +130,7 @@ pub enum Command<Fs: FileStore> { // TODO: should probably make it so people can add non-contact auto presence sharing in the client (most likely through setting an internal setting) /// send a message to a jid (any kind of jid that can receive a message, e.g. a user or a /// chatroom). if disconnected, will be cached so when client connects, message will be sent. - SendMessage(JID, Body), + SendMessage(BareJID, Body), // TODO: resend failed messages // ResendMessage(Uuid), /// disco info query @@ -146,7 +156,7 @@ pub enum Command<Fs: FileStore> { sender: oneshot::Sender<Result<(), PEPError>>, }, GetPEPItem { - jid: Option<JID>, + jid: Option<BareJID>, node: String, id: String, sender: oneshot::Sender<Result<pep::Item, PEPError>>, @@ -171,7 +181,7 @@ pub enum UpdateMessage { Offline(Offline), /// (only update app roster state, don't replace) RosterUpdate(Contact, User), - RosterDelete(JID), + RosterDelete(BareJID), /// presences should be stored with users in the ui, not contacts, as presences can be received from anyone Presence { from: JID, @@ -180,22 +190,23 @@ pub enum UpdateMessage { // TODO: receipts // MessageDispatched(Uuid), Message { - to: JID, + // TODO: rename to chat? + to: BareJID, from: User, message: Message, }, MessageDelivery { id: Uuid, - chat: JID, + chat: BareJID, delivery: Delivery, }, - SubscriptionRequest(jid::JID), + SubscriptionRequest(BareJID), NickChanged { - jid: JID, + jid: BareJID, nick: Option<String>, }, AvatarChanged { - jid: JID, + jid: BareJID, id: Option<String>, }, } @@ -259,7 +270,7 @@ impl<Fs: FileStore + Clone + Send + Sync + 'static> Client<Fs> { timeout: Duration::from_secs(20), }; - let logic = ClientLogic::new(client.clone(), jid.as_bare(), db, update_send, file_store); + let logic = ClientLogic::new(client.clone(), jid.to_bare(), db, update_send, file_store); let actor: CoreClient<ClientLogic<Fs>> = CoreClient::new(jid, password, command_receiver, None, sup_recv, logic); @@ -373,7 +384,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(chats) } - pub async fn get_chat(&self, jid: JID) -> Result<Chat, CommandError<DatabaseError>> { + pub async fn get_chat(&self, jid: BareJID) -> Result<Chat, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetChat(jid, send))) .await @@ -385,12 +396,17 @@ impl<Fs: FileStore> Client<Fs> { Ok(chat) } - pub async fn get_chat_and_user(&self, jid: JID) -> Result<(Chat, User), CommandError<DatabaseError>> { + pub async fn get_chat_and_user( + &self, + jid: BareJID, + ) -> Result<(Chat, User), CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); - self.send(CoreClientCommand::Command(Command::GetChatAndUser(jid, send))) - .await - .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?; - let result= timeout(self.timeout, recv) + self.send(CoreClientCommand::Command(Command::GetChatAndUser( + jid, send, + ))) + .await + .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))?; + let result = timeout(self.timeout, recv) .await .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))? .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??; @@ -411,7 +427,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_messages( &self, - jid: JID, + jid: BareJID, ) -> Result<Vec<Message>, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetMessages(jid, send))) @@ -426,7 +442,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_messages_with_users( &self, - jid: JID, + jid: BareJID, ) -> Result<Vec<(Message, User)>, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetMessagesWithUsers( @@ -441,7 +457,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(messages) } - pub async fn delete_chat(&self, jid: JID) -> Result<(), CommandError<DatabaseError>> { + pub async fn delete_chat(&self, jid: BareJID) -> Result<(), CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::DeleteChat(jid, send))) .await @@ -465,7 +481,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn get_user(&self, jid: JID) -> Result<User, CommandError<DatabaseError>> { + pub async fn get_user(&self, jid: BareJID) -> Result<User, CommandError<DatabaseError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::GetUser(jid, send))) .await @@ -477,7 +493,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn add_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> { + pub async fn add_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::AddContact(jid, send))) .await @@ -489,7 +505,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn buddy_request(&self, jid: BareJID) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::BuddyRequest(jid, send))) .await @@ -501,7 +517,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn subscription_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn subscription_request( + &self, + jid: BareJID, + ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::SubscriptionRequest( jid, send, @@ -515,7 +534,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn accept_buddy_request(&self, jid: JID) -> Result<(), CommandError<SubscribeError>> { + pub async fn accept_buddy_request( + &self, + jid: BareJID, + ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::AcceptBuddyRequest( jid, send, @@ -531,7 +553,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn accept_subscription_request( &self, - jid: JID, + jid: BareJID, ) -> Result<(), CommandError<SubscribeError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command( @@ -546,7 +568,10 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unsubscribe_from_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unsubscribe_from_contact( + &self, + jid: BareJID, + ) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnsubscribeFromContact( jid, send, @@ -560,7 +585,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unsubscribe_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unsubscribe_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnsubscribeContact( jid, send, @@ -574,7 +599,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn unfriend_contact(&self, jid: JID) -> Result<(), CommandError<WriteError>> { + pub async fn unfriend_contact(&self, jid: BareJID) -> Result<(), CommandError<WriteError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::UnfriendContact( jid, send, @@ -588,7 +613,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn delete_contact(&self, jid: JID) -> Result<(), CommandError<RosterError>> { + pub async fn delete_contact(&self, jid: BareJID) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); self.send(CoreClientCommand::Command(Command::DeleteContact( jid, send, @@ -604,7 +629,7 @@ impl<Fs: FileStore> Client<Fs> { pub async fn update_contact( &self, - jid: JID, + jid: BareJID, update: ContactUpdate, ) -> Result<(), CommandError<RosterError>> { let (send, recv) = oneshot::channel(); @@ -632,7 +657,7 @@ impl<Fs: FileStore> Client<Fs> { Ok(result) } - pub async fn send_message(&self, jid: JID, body: Body) -> Result<(), ActorError> { + pub async fn send_message(&self, jid: BareJID, body: Body) -> Result<(), ActorError> { self.send(CoreClientCommand::Command(Command::SendMessage(jid, body))) .await?; Ok(()) @@ -711,7 +736,8 @@ impl<Fs: FileStore> Client<Fs> { pub async fn get_pep_item( &self, - jid: Option<JID>, + // i think this is correct?, should not be able to send pep requests to a full jid. + jid: Option<BareJID>, node: String, id: String, ) -> Result<pep::Item, CommandError<PEPError>> { @@ -811,7 +837,7 @@ mod tests { info!("sending message"); client .send_message( - JID::from_str("cel@blos.sm").unwrap(), + BareJID::from_str("cel@blos.sm").unwrap(), chat::Body { body: "hallo!!!".to_string(), }, @@ -821,7 +847,7 @@ mod tests { info!("sent message"); client .send_message( - JID::from_str("cel@blos.sm").unwrap(), + BareJID::from_str("cel@blos.sm").unwrap(), chat::Body { body: "hallo 2".to_string(), }, diff --git a/filamento/src/logic/local_only.rs b/filamento/src/logic/local_only.rs index f5705f4..7f3a2e6 100644 --- a/filamento/src/logic/local_only.rs +++ b/filamento/src/logic/local_only.rs @@ -1,4 +1,4 @@ -use jid::JID; +use jid::{BareJID, JID}; use uuid::Uuid; use crate::{ @@ -39,14 +39,14 @@ pub async fn handle_get_chats_ordered_with_latest_messages_and_users<Fs: FileSto pub async fn handle_get_chat<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Chat, DatabaseError> { Ok(logic.db().read_chat(jid).await?) } pub async fn handle_get_chat_and_user<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<(Chat, User), DatabaseError> { Ok(logic.db().read_chat_and_user(jid).await?) } @@ -60,21 +60,21 @@ pub async fn handle_get_message<Fs: FileStore + Clone>( pub async fn handle_get_messages<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Vec<Message>, DatabaseError> { Ok(logic.db().read_message_history(jid).await?) } pub async fn handle_get_messages_with_users<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<Vec<(Message, User)>, DatabaseError> { Ok(logic.db().read_message_history_with_users(jid).await?) } pub async fn handle_delete_chat<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<(), DatabaseError> { Ok(logic.db().delete_chat(jid).await?) } @@ -88,7 +88,7 @@ pub async fn handle_delete_messaage<Fs: FileStore + Clone>( pub async fn handle_get_user<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, - jid: JID, + jid: BareJID, ) -> Result<User, DatabaseError> { Ok(logic.db().read_user(jid).await?) } diff --git a/filamento/src/logic/mod.rs b/filamento/src/logic/mod.rs index 5e05dac..146f3b0 100644 --- a/filamento/src/logic/mod.rs +++ b/filamento/src/logic/mod.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, sync::Arc}; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{Connected, Logic, error::ReadError}; use stanza::client::Stanza; use tokio::sync::{Mutex, mpsc, oneshot}; @@ -25,7 +25,7 @@ mod process_stanza; #[derive(Clone)] pub struct ClientLogic<Fs: FileStore> { client: Client<Fs>, - bare_jid: JID, + jid: BareJID, db: Db, pending: Pending, update_sender: mpsc::Sender<UpdateMessage>, @@ -80,7 +80,7 @@ impl Pending { impl<Fs: FileStore> ClientLogic<Fs> { pub fn new( client: Client<Fs>, - bare_jid: JID, + jid: BareJID, db: Db, update_sender: mpsc::Sender<UpdateMessage>, file_store: Fs, @@ -90,7 +90,7 @@ impl<Fs: FileStore> ClientLogic<Fs> { pending: Pending::new(), update_sender, client, - bare_jid, + jid, file_store, } } diff --git a/filamento/src/logic/offline.rs b/filamento/src/logic/offline.rs index 606b04f..aa84f3d 100644 --- a/filamento/src/logic/offline.rs +++ b/filamento/src/logic/offline.rs @@ -1,6 +1,7 @@ use std::process::id; use chrono::Utc; +use jid::FullJID; use lampada::error::WriteError; use tracing::error; use uuid::Uuid; @@ -19,9 +20,13 @@ use crate::{ }; use super::{ + ClientLogic, local_only::{ - handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user, handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages, handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message, handle_get_messages, handle_get_messages_with_users, handle_get_user - }, ClientLogic + handle_delete_chat, handle_delete_messaage, handle_get_chat, handle_get_chat_and_user, + handle_get_chats, handle_get_chats_ordered, handle_get_chats_ordered_with_latest_messages, + handle_get_chats_ordered_with_latest_messages_and_users, handle_get_message, + handle_get_messages, handle_get_messages_with_users, handle_get_user, + }, }; pub async fn handle_offline<Fs: FileStore + Clone>(logic: ClientLogic<Fs>, command: Command<Fs>) { @@ -153,7 +158,7 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( let message = Message { id, - from: logic.bare_jid.clone(), + from: logic.jid.clone(), // TODO: failure reason delivery: Some(Delivery::Failed), timestamp, @@ -163,11 +168,15 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( // TODO: mark these as potentially failed upon client launch if let Err(e) = logic .db() + // TODO: can create message without a resource.... .create_message_with_user_resource( message.clone(), jid.clone(), // TODO: when message is queued and sent, the from must also be updated with the correct resource - logic.bare_jid.clone(), + FullJID { + bare_jid: logic.jid.clone(), + resourcepart: "unsent".to_string(), + }, ) .await { @@ -177,12 +186,12 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( .await; } - let from = match logic.db().read_user(logic.bare_jid.clone()).await { + let from = match logic.db().read_user(logic.jid.clone()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: logic.bare_jid.clone(), + jid: logic.jid.clone(), nick: None, avatar: None, } @@ -192,7 +201,7 @@ pub async fn handle_offline_result<Fs: FileStore + Clone>( logic .update_sender() .send(crate::UpdateMessage::Message { - to: jid.as_bare(), + to: jid, message, from, }) diff --git a/filamento/src/logic/online.rs b/filamento/src/logic/online.rs index 9814ff2..febd3e1 100644 --- a/filamento/src/logic/online.rs +++ b/filamento/src/logic/online.rs @@ -3,7 +3,7 @@ use std::{io::Cursor, time::Duration}; use base64::{prelude::BASE64_STANDARD, Engine}; use chrono::Utc; use image::ImageReader; -use jid::JID; +use jid::{BareJID, JID}; use lampada::{Connected, WriteMessage, error::WriteError}; use sha1::{Digest, Sha1}; use stanza::{ @@ -41,7 +41,7 @@ pub async fn handle_get_roster<Fs: FileStore + Clone>( ) -> Result<Vec<Contact>, RosterError> { let iq_id = Uuid::new_v4().to_string(); let stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.to_string(), to: None, r#type: IqType::Get, @@ -101,7 +101,7 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>( ) -> Result<Vec<(Contact, User)>, RosterError> { let iq_id = Uuid::new_v4().to_string(); let stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.to_string(), to: None, r#type: IqType::Get, @@ -162,11 +162,11 @@ pub async fn handle_get_roster_with_users<Fs: FileStore + Clone>( pub async fn handle_add_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -220,9 +220,10 @@ pub async fn handle_add_contact<Fs: FileStore + Clone>( pub async fn handle_buddy_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let jid: JID = jid.into(); + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), @@ -243,13 +244,13 @@ pub async fn handle_buddy_request<Fs: FileStore + Clone>( pub async fn handle_subscription_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { // TODO: i should probably have builders - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Subscribe), nick, ..Default::default() @@ -261,15 +262,16 @@ pub async fn handle_subscription_request<Fs: FileStore + Clone>( pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { + let jid: JID = jid.into(); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), r#type: Some(stanza::client::presence::PresenceType::Subscribed), ..Default::default() }); connection.write_handle().write(presence).await?; - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid), @@ -284,12 +286,12 @@ pub async fn handle_accept_buddy_request<Fs: FileStore + Clone>( pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), SubscribeError> { - let client_user = logic.db.read_user(logic.bare_jid.clone()).await?; + let client_user = logic.db.read_user(logic.jid.clone()).await?; let nick = client_user.nick.map(|nick| Nick(nick)); let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Subscribe), nick, ..Default::default() @@ -300,10 +302,10 @@ pub async fn handle_accept_subscription_request<Fs: FileStore + Clone>( pub async fn handle_unsubscribe_from_contact( connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), WriteError> { let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribe), ..Default::default() }); @@ -311,9 +313,9 @@ pub async fn handle_unsubscribe_from_contact( Ok(()) } -pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Result<(), WriteError> { +pub async fn handle_unsubscribe_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> { let presence = Stanza::Presence(stanza::client::presence::Presence { - to: Some(jid), + to: Some(jid.into()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribed), ..Default::default() }); @@ -321,7 +323,8 @@ pub async fn handle_unsubscribe_contact(connection: Connected, jid: JID) -> Resu Ok(()) } -pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result<(), WriteError> { +pub async fn handle_unfriend_contact(connection: Connected, jid: BareJID) -> Result<(), WriteError> { + let jid: JID = jid.into(); let presence = Stanza::Presence(stanza::client::presence::Presence { to: Some(jid.clone()), r#type: Some(stanza::client::presence::PresenceType::Unsubscribe), @@ -340,11 +343,11 @@ pub async fn handle_unfriend_contact(connection: Connected, jid: JID) -> Result< pub async fn handle_delete_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -399,7 +402,7 @@ pub async fn handle_delete_contact<Fs: FileStore + Clone>( pub async fn handle_update_contact<Fs: FileStore + Clone>( logic: &ClientLogic<Fs>, connection: Connected, - jid: JID, + jid: BareJID, contact_update: ContactUpdate, ) -> Result<(), RosterError> { let iq_id = Uuid::new_v4().to_string(); @@ -410,7 +413,8 @@ pub async fn handle_update_contact<Fs: FileStore + Clone>( .map(|group| stanza::roster::Group(Some(group))), ); let set_stanza = Stanza::Iq(Iq { - from: Some(connection.jid().clone()), + // TODO: these clones could technically be avoided? + from: Some(connection.jid().clone().into()), id: iq_id.clone(), to: None, r#type: IqType::Set, @@ -474,7 +478,7 @@ pub async fn handle_set_status<Fs: FileStore + Clone>( Ok(()) } -pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: JID, body: Body) { +pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: BareJID, body: Body) { // upsert the chat and user the message will be delivered to. if there is a conflict, it will return whatever was there, otherwise it will return false by default. // let have_chatted = logic.db().upsert_chat_and_user(&jid).await.unwrap_or(false); let have_chatted = match logic.db().upsert_chat_and_user(jid.clone()).await { @@ -490,7 +494,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, let nick; let mark_chat_as_chatted; if have_chatted == false { - match logic.db.read_user(logic.bare_jid.clone()).await { + match logic.db.read_user(logic.jid.clone()).await { Ok(u) => { nick = u.nick.map(|nick| Nick(nick)); mark_chat_as_chatted = true; @@ -513,7 +517,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, let timestamp = Utc::now(); let message = Message { id, - from: connection.jid().as_bare(), + from: connection.jid().to_bare(), body: body.clone(), timestamp, delivery: Some(Delivery::Sending), @@ -532,12 +536,12 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, .await; } - let from = match logic.db().read_user(logic.bare_jid.clone()).await { + let from = match logic.db().read_user(logic.jid.clone()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: logic.bare_jid.clone(), + jid: logic.jid.clone(), nick: None, avatar: None, } @@ -548,7 +552,7 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, logic .update_sender() .send(UpdateMessage::Message { - to: jid.as_bare(), + to: jid.clone(), message, from, }) @@ -556,9 +560,9 @@ pub async fn handle_send_message<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, // prepare the message stanza let message_stanza = Stanza::Message(stanza::client::message::Message { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: Some(id.to_string()), - to: Some(jid.clone()), + to: Some(jid.clone().into()), // TODO: specify message type r#type: stanza::client::message::MessageType::Chat, // TODO: lang ? @@ -639,7 +643,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( ) -> Result<Info, DiscoError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -667,7 +671,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -694,7 +698,7 @@ pub async fn handle_disco_info<Fs: FileStore + Clone>( } } else { Err(DiscoError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -710,7 +714,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( ) -> Result<Items, DiscoError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -736,7 +740,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -763,7 +767,7 @@ pub async fn handle_disco_items<Fs: FileStore + Clone>( } } else { Err(DiscoError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -828,7 +832,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( }, }; let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: None, r#type: IqType::Set, @@ -850,7 +854,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( // TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise. }) if r#type == IqType::Result || r#type == IqType::Error => { if from == None || - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) { match r#type { IqType::Result => { @@ -870,7 +874,7 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( } } else { Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -878,10 +882,11 @@ pub async fn handle_publish_pep_item<Fs: FileStore + Clone>( } } -pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<JID>, node: String, id: String) -> Result<pep::Item, PEPError> { +pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, connection: Connected, jid: Option<BareJID>, node: String, id: String) -> Result<pep::Item, PEPError> { + let jid = jid.map(|jid| Into::<JID>::into(jid)); let stanza_id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: stanza_id.clone(), to: jid.clone(), r#type: IqType::Get, @@ -909,7 +914,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, }) if r#type == IqType::Result || r#type == IqType::Error => { if from == jid || { if jid == None { - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) } else { false } @@ -955,7 +960,7 @@ pub async fn handle_get_pep_item<Fs: FileStore + Clone>(logic: &ClientLogic<Fs>, } else { // TODO: include expected entity Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } @@ -1024,7 +1029,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( ) -> Result<(), PEPError> { let id = Uuid::new_v4().to_string(); let request = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: id.clone(), to: None, r#type: IqType::Set, @@ -1046,7 +1051,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( // TODO: maybe abstract a bunch of these different errors related to iqs into an iq error thing? as in like call iq.result(), get the query from inside, error otherwise. }) if r#type == IqType::Result || r#type == IqType::Error => { if from == None || - from == Some(connection.jid().as_bare()) + from == Some(connection.jid().to_bare().into()) { match r#type { IqType::Result => { @@ -1067,7 +1072,7 @@ pub async fn handle_delete_pep_node<Fs: FileStore + Clone>( } } else { Err(PEPError::IncorrectEntity( - from.unwrap_or_else(|| connection.jid().as_bare()), + from.unwrap_or_else(|| connection.jid().to_bare().into()), )) } } diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs index 30d0830..67b0d3f 100644 --- a/filamento/src/logic/process_stanza.rs +++ b/filamento/src/logic/process_stanza.rs @@ -70,7 +70,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( // TODO: proper id xep .map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4())) .unwrap_or_else(|| Uuid::new_v4()), - from: from.as_bare(), + from: from.to_bare(), timestamp, body: Body { body: body.body.unwrap_or_default(), @@ -80,31 +80,34 @@ pub async fn recv_message<Fs: FileStore + Clone>( // TODO: process message type="error" // save the message to the database - match logic.db().upsert_chat_and_user(from.clone()).await { - Ok(_) => { - if let Err(e) = logic - .db() - .create_message_with_user_resource( - message.clone(), - from.clone(), - from.clone(), - ) - .await - { - error!("failed to create message: {}", e); + match logic.db().upsert_chat_and_user(from.to_bare()).await { + Ok(_) => match from.as_full() { + Ok(from) => { + if let Err(e) = logic + .db() + .create_message_with_user_resource( + message.clone(), + from.to_bare(), + from.clone(), + ) + .await + { + error!("failed to create message: {}", e); + } } - } + Err(e) => error!("failed to create message: {}", e), + }, Err(e) => { error!("failed to upsert chat and user: {}", e); } }; - let from_user = match logic.db().read_user(from.as_bare()).await { + let from_user = match logic.db().read_user(from.to_bare()).await { Ok(u) => u, Err(e) => { error!("{}", e); User { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, avatar: None, } @@ -115,7 +118,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::Message { - to: from.as_bare(), + to: from.to_bare(), from: from_user, message, }) @@ -125,13 +128,13 @@ pub async fn recv_message<Fs: FileStore + Clone>( if let Some(nick) = stanza_message.nick { let nick = nick.0; if nick.is_empty() { - match logic.db().delete_user_nick(from.as_bare()).await { + match logic.db().delete_user_nick(from.to_bare()).await { Ok(changed) => { if changed { logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -145,7 +148,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -154,7 +157,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( } else { match logic .db() - .upsert_user_nick(from.as_bare(), nick.clone()) + .upsert_user_nick(from.to_bare(), nick.clone()) .await { Ok(changed) => { @@ -162,7 +165,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -176,7 +179,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -199,7 +202,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( if nick.is_empty() { match logic .db() - .delete_user_nick(from.as_bare()) + .delete_user_nick(from.to_bare()) .await { Ok(changed) => { @@ -207,7 +210,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -223,7 +226,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: None, }) .await; @@ -233,7 +236,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( match logic .db() .upsert_user_nick( - from.as_bare(), + from.to_bare(), nick.clone(), ) .await @@ -243,7 +246,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -259,7 +262,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( logic .update_sender() .send(UpdateMessage::NickChanged { - jid: from.as_bare(), + jid: from.to_bare(), nick: Some(nick), }) .await; @@ -294,7 +297,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( match logic .db() .upsert_user_avatar( - from.as_bare(), + from.to_bare(), metadata.id.clone(), ) .await @@ -323,7 +326,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( }) { Ok(false) => { // get data - let pep_item = logic.client().get_pep_item(Some(from.as_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?; + let pep_item = logic.client().get_pep_item(Some(from.to_bare()), "urn:xmpp:avatar:data".to_string(), metadata.id.clone()).await.map_err(|err| Into::<AvatarUpdateError<Fs>>::into(err))?; match pep_item { crate::pep::Item::AvatarData(data) => { let data = data.map(|data| data.data_b64).unwrap_or_default().replace("\n", ""); @@ -344,7 +347,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: Some( metadata.id.clone(), ), @@ -371,7 +374,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: Some( metadata.id.clone(), ), @@ -401,7 +404,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( // delete avatar match logic .db() - .delete_user_avatar(from.as_bare()) + .delete_user_avatar(from.to_bare()) .await { Ok((changed, old_avatar)) => { @@ -419,7 +422,7 @@ pub async fn recv_message<Fs: FileStore + Clone>( .update_sender() .send( UpdateMessage::AvatarChanged { - jid: from.as_bare(), + jid: from.to_bare(), id: None, }, ) @@ -488,6 +491,7 @@ pub async fn recv_presence( } stanza::client::presence::PresenceType::Subscribe => { // may get a subscription request from somebody who is not a contact!!! therefore should be its own kind of event + let from = from.try_into()?; Ok(Some(UpdateMessage::SubscriptionRequest(from))) } stanza::client::presence::PresenceType::Unavailable => { @@ -546,7 +550,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( iq: Iq, ) -> Result<Option<UpdateMessage>, IqProcessError> { if let Some(to) = &iq.to { - if *to == *connection.jid() { + // TODO: this clone could mayb b avoided + if *to == connection.jid().clone().into() { } else { return Err(IqProcessError::Iq(IqError::IncorrectAddressee(to.clone()))); } @@ -556,7 +561,9 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // TODO: maybe actually store the server in the connection again LOL + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); let id = iq.id.clone(); debug!("received iq result with id `{}` from {}", id, from); logic @@ -570,7 +577,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); if let Some(query) = iq.query { match query { stanza::client::iq::Query::DiscoInfo(query) => { @@ -594,7 +602,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } Err(_e) => { let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -614,7 +622,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( }, Err(_e) => { let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -634,7 +642,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } }; let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Result, @@ -653,7 +661,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( _ => { warn!("received unsupported iq get from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -677,7 +685,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { info!("received malformed iq query from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -698,7 +706,8 @@ pub async fn recv_iq<Fs: FileStore + Clone>( let from = iq .from .clone() - .unwrap_or_else(|| connection.server().clone()); + // .unwrap_or_else(|| connection.server().clone()); + .unwrap_or_else(|| connection.jid().domainpart.parse().unwrap()); if let Some(query) = iq.query { match query { stanza::client::iq::Query::Roster(mut query) => { @@ -725,7 +734,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( .await; } let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Result, @@ -751,7 +760,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { warn!("received malformed roster push"); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -771,7 +780,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( _ => { warn!("received unsupported iq set from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -791,7 +800,7 @@ pub async fn recv_iq<Fs: FileStore + Clone>( } else { warn!("received malformed iq set from {}", from); let iq = Iq { - from: Some(connection.jid().clone()), + from: Some(connection.jid().clone().into()), id: iq.id, to: iq.from, r#type: IqType::Error, @@ -820,7 +829,7 @@ pub async fn process_stanza<Fs: FileStore + Clone>( Stanza::Presence(presence) => Ok(recv_presence(presence).await?), Stanza::Iq(iq) => Ok(recv_iq(logic, connection.clone(), iq).await?), // unreachable, always caught by lampada - // TODO: make cleaner than this in some way + // TODO: make cleaner than this in some way, by just reexporting a stanza enum from lampada ig. Stanza::Error(error) => { unreachable!() } diff --git a/filamento/src/roster.rs b/filamento/src/roster.rs index 6b61e10..0498278 100644 --- a/filamento/src/roster.rs +++ b/filamento/src/roster.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, fmt::Display}; -use jid::JID; +use jid::BareJID; use rusqlite::{ ToSql, types::{FromSql, ToSqlOutput, Value}, @@ -15,7 +15,7 @@ pub struct ContactUpdate { #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct Contact { // jid is the id used to reference everything, but not the primary key - pub user_jid: JID, + pub user_jid: BareJID, pub subscription: Subscription, /// client user defined name pub name: Option<String>, diff --git a/filamento/src/user.rs b/filamento/src/user.rs index f30933c..f962a4c 100644 --- a/filamento/src/user.rs +++ b/filamento/src/user.rs @@ -1,10 +1,10 @@ -use jid::JID; +use jid::BareJID; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct User { - pub jid: JID, + pub jid: BareJID, pub nick: Option<String>, pub avatar: Option<String>, // pub cached_status_message: Option<String>, diff --git a/jid/src/lib.rs b/jid/src/lib.rs index 47ca497..3b40094 100644 --- a/jid/src/lib.rs +++ b/jid/src/lib.rs @@ -1,15 +1,53 @@ -use std::{borrow::Cow, error::Error, fmt::Display, str::FromStr}; +use std::{borrow::Cow, error::Error, fmt::Display, ops::Deref, str::FromStr}; // #[cfg(feature = "sqlx")] // use sqlx::Sqlite; -#[derive(PartialEq, Debug, Clone, Eq, Hash)] +#[derive(PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct JID { - // TODO: validate localpart (length, char] - pub localpart: Option<String>, - pub domainpart: String, - pub resourcepart: Option<String>, +pub enum JID { + Full(FullJID), + Bare(BareJID), +} + +impl JID { + pub fn resourcepart(&self) -> Option<&String> { + match self { + JID::Full(full_jid) => Some(&full_jid.resourcepart), + JID::Bare(_bare_jid) => None, + } + } +} + +impl From<FullJID> for JID { + fn from(value: FullJID) -> Self { + Self::Full(value) + } +} + +impl From<BareJID> for JID { + fn from(value: BareJID) -> Self { + Self::Bare(value) + } +} + +impl Deref for JID { + type Target = BareJID; + + fn deref(&self) -> &Self::Target { + match self { + JID::Full(full_jid) => full_jid.as_bare(), + JID::Bare(bare_jid) => bare_jid, + } + } +} + +impl Deref for FullJID { + type Target = BareJID; + + fn deref(&self) -> &Self::Target { + &self.bare_jid + } } impl<'a> Into<Cow<'a, str>> for &'a JID { @@ -21,15 +59,45 @@ impl<'a> Into<Cow<'a, str>> for &'a JID { impl Display for JID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + JID::Full(full_jid) => full_jid.fmt(f), + JID::Bare(bare_jid) => bare_jid.fmt(f), + } + } +} + +#[derive(PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FullJID { + pub bare_jid: BareJID, + pub resourcepart: String, +} + +impl Display for FullJID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.bare_jid.fmt(f)?; + f.write_str("/")?; + f.write_str(&self.resourcepart)?; + Ok(()) + } +} + +#[derive(PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BareJID { + // TODO: validate and don't have public fields + // TODO: validate localpart (length, char] + pub localpart: Option<String>, + pub domainpart: String, +} + +impl Display for BareJID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(localpart) = &self.localpart { f.write_str(localpart)?; f.write_str("@")?; } f.write_str(&self.domainpart)?; - if let Some(resourcepart) = &self.resourcepart { - f.write_str("/")?; - f.write_str(resourcepart)?; - } Ok(()) } } @@ -51,52 +119,65 @@ impl rusqlite::types::FromSql for JID { } #[cfg(feature = "rusqlite")] -impl From<ParseError> for rusqlite::types::FromSqlError { - fn from(value: ParseError) -> Self { - Self::Other(Box::new(value)) +impl rusqlite::ToSql for FullJID { + fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> { + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(self.to_string()), + )) } } -// #[cfg(feature = "sqlx")] -// impl sqlx::Type<Sqlite> for JID { -// fn type_info() -> <Sqlite as sqlx::Database>::TypeInfo { -// <&str as sqlx::Type<Sqlite>>::type_info() -// } -// } +#[cfg(feature = "rusqlite")] +impl rusqlite::types::FromSql for FullJID { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { + Ok(JID::from_str(value.as_str()?)?.try_into()?) + } +} -// #[cfg(feature = "sqlx")] -// impl sqlx::Decode<'_, Sqlite> for JID { -// fn decode( -// value: <Sqlite as sqlx::Database>::ValueRef<'_>, -// ) -> Result<Self, sqlx::error::BoxDynError> { -// let value = <&str as sqlx::Decode<Sqlite>>::decode(value)?; +#[cfg(feature = "rusqlite")] +impl rusqlite::ToSql for BareJID { + fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> { + Ok(rusqlite::types::ToSqlOutput::Owned( + rusqlite::types::Value::Text(self.to_string()), + )) + } +} -// Ok(value.parse()?) -// } -// } +#[cfg(feature = "rusqlite")] +impl rusqlite::types::FromSql for BareJID { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { + Ok(JID::from_str(value.as_str()?)?.try_into()?) + } +} -// #[cfg(feature = "sqlx")] -// impl sqlx::Encode<'_, Sqlite> for JID { -// fn encode_by_ref( -// &self, -// buf: &mut <Sqlite as sqlx::Database>::ArgumentBuffer<'_>, -// ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { -// let jid = self.to_string(); -// <String as sqlx::Encode<Sqlite>>::encode(jid, buf) -// } -// } +#[cfg(feature = "rusqlite")] +impl From<ParseError> for rusqlite::types::FromSqlError { + fn from(value: ParseError) -> Self { + Self::Other(Box::new(value)) + } +} + +#[cfg(feature = "rusqlite")] +impl From<JIDError> for rusqlite::types::FromSqlError { + fn from(value: JIDError) -> Self { + Self::Other(Box::new(value)) + } +} #[derive(Debug, Clone)] pub enum JIDError { NoResourcePart, ParseError(ParseError), + ContainsResourcepart, } impl Display for JIDError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + // TODO: separate jid errors? JIDError::NoResourcePart => f.write_str("resourcepart missing"), JIDError::ParseError(parse_error) => parse_error.fmt(f), + JIDError::ContainsResourcepart => f.write_str("contains resourcepart"), } } } @@ -122,36 +203,105 @@ impl Display for ParseError { impl Error for ParseError {} +impl FullJID { + pub fn new(localpart: Option<String>, domainpart: String, resourcepart: String) -> Self { + Self { + bare_jid: BareJID::new(localpart, domainpart), + resourcepart, + } + } + + pub fn as_bare(&self) -> &BareJID { + &self.bare_jid + } + + pub fn to_bare(&self) -> BareJID { + self.bare_jid.clone() + } +} + +impl BareJID { + pub fn new(localpart: Option<String>, domainpart: String) -> Self { + Self { + localpart, + domainpart, + } + } +} + +impl TryFrom<JID> for BareJID { + type Error = JIDError; + + fn try_from(value: JID) -> Result<Self, Self::Error> { + match value { + JID::Full(_full_jid) => Err(JIDError::ContainsResourcepart), + JID::Bare(bare_jid) => Ok(bare_jid), + } + } +} + impl JID { pub fn new( localpart: Option<String>, domainpart: String, resourcepart: Option<String>, ) -> Self { - Self { - localpart, - domainpart: domainpart.parse().unwrap(), - resourcepart, + if let Some(resourcepart) = resourcepart { + Self::Full(FullJID::new(localpart, domainpart, resourcepart)) + } else { + Self::Bare(BareJID::new(localpart, domainpart)) } } - pub fn as_bare(&self) -> Self { - Self { - localpart: self.localpart.clone(), - domainpart: self.domainpart.clone(), - resourcepart: None, + pub fn as_bare(&self) -> &BareJID { + match self { + JID::Full(full_jid) => full_jid.as_bare(), + JID::Bare(bare_jid) => &bare_jid, } } - pub fn as_full(&self) -> Result<&Self, JIDError> { - if let Some(_) = self.resourcepart { - Ok(&self) - } else { - Err(JIDError::NoResourcePart) + pub fn to_bare(&self) -> BareJID { + match self { + JID::Full(full_jid) => full_jid.to_bare(), + JID::Bare(bare_jid) => bare_jid.clone(), + } + } + + pub fn as_full(&self) -> Result<&FullJID, JIDError> { + match self { + JID::Full(full_jid) => Ok(full_jid), + JID::Bare(_bare_jid) => Err(JIDError::NoResourcePart), + } + } +} + +impl TryFrom<JID> for FullJID { + type Error = JIDError; + + fn try_from(value: JID) -> Result<Self, Self::Error> { + match value { + JID::Full(full_jid) => Ok(full_jid), + JID::Bare(_bare_jid) => Err(JIDError::NoResourcePart), } } } +impl FromStr for BareJID { + type Err = JIDError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(JID::from_str(s)?.try_into()?) + } +} + +impl FromStr for FullJID { + type Err = JIDError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(JID::from_str(s)?.try_into()?) + } +} + impl FromStr for JID { type Err = ParseError; @@ -192,6 +342,12 @@ impl FromStr for JID { } } +impl From<ParseError> for JIDError { + fn from(value: ParseError) -> Self { + JIDError::ParseError(value) + } +} + impl TryFrom<String> for JID { type Error = ParseError; @@ -256,3 +412,32 @@ mod tests { ) } } + +// #[cfg(feature = "sqlx")] +// impl sqlx::Type<Sqlite> for JID { +// fn type_info() -> <Sqlite as sqlx::Database>::TypeInfo { +// <&str as sqlx::Type<Sqlite>>::type_info() +// } +// } + +// #[cfg(feature = "sqlx")] +// impl sqlx::Decode<'_, Sqlite> for JID { +// fn decode( +// value: <Sqlite as sqlx::Database>::ValueRef<'_>, +// ) -> Result<Self, sqlx::error::BoxDynError> { +// let value = <&str as sqlx::Decode<Sqlite>>::decode(value)?; + +// Ok(value.parse()?) +// } +// } + +// #[cfg(feature = "sqlx")] +// impl sqlx::Encode<'_, Sqlite> for JID { +// fn encode_by_ref( +// &self, +// buf: &mut <Sqlite as sqlx::Database>::ArgumentBuffer<'_>, +// ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> { +// let jid = self.to_string(); +// <String as sqlx::Encode<Sqlite>>::encode(jid, buf) +// } +// } diff --git a/lampada/src/connection/mod.rs b/lampada/src/connection/mod.rs index 3a3187f..51c3758 100644 --- a/lampada/src/connection/mod.rs +++ b/lampada/src/connection/mod.rs @@ -7,7 +7,7 @@ use std::{ time::Duration, }; -use jid::JID; +use jid::{BareJID, FullJID, JID}; use luz::jabber_stream::bound_stream::BoundJabberStream; use read::{ReadControl, ReadControlHandle, ReadState}; use stanza::{client::Stanza, stream_error::Error as StreamError}; @@ -121,12 +121,11 @@ where break } - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); + let mut jid = self.connected.jid.clone().into(); // TODO: make sure connect_and_login does not modify the jid, but instead returns a jid. or something like that - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -169,12 +168,11 @@ where else => break, }; - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); + let mut jid = self.connected.jid.clone().into(); // TODO: same here - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -214,11 +212,10 @@ where else => break, }; - let mut jid = self.connected.jid.clone(); - let mut domain = jid.domainpart.clone(); - let connection = luz::connect_and_login(&mut jid, &*self.password, &mut domain).await; + let mut jid = self.connected.jid.clone().into(); + let connection = luz::connect_and_login(&jid, &*self.password).await; match connection { - Ok(c) => { + Ok((c, full_jid)) => { let (read, write) = c.split(); let (send, recv) = oneshot::channel(); self.writer_crash = recv; @@ -305,8 +302,7 @@ impl SupervisorHandle { pub fn new<Lgc>( streams: BoundJabberStream, on_crash: oneshot::Sender<()>, - jid: JID, - server: JID, + jid: FullJID, password: Arc<String>, logic: Lgc, ) -> (WriteHandle, Self) @@ -327,7 +323,6 @@ impl SupervisorHandle { let connected = Connected { jid, write_handle: write_handle.clone(), - server, }; let supervisor_sender = SupervisorSender { diff --git a/lampada/src/error.rs b/lampada/src/error.rs index f29d0cc..a5af3a2 100644 --- a/lampada/src/error.rs +++ b/lampada/src/error.rs @@ -24,7 +24,7 @@ pub enum ConnectionError { #[error("disconnected")] Disconnected, #[error("invalid server jid: {0}")] - InvalidServerJID(#[from] jid::ParseError), + InvalidServerJID(#[from] jid::JIDError), } #[derive(Debug, Error, Clone)] diff --git a/lampada/src/lib.rs b/lampada/src/lib.rs index 6b6cbe8..d1d451e 100644 --- a/lampada/src/lib.rs +++ b/lampada/src/lib.rs @@ -12,6 +12,7 @@ pub use connection::write::WriteMessage; pub use connection::SupervisorSender; use error::ConnectionError; use futures::{future::Fuse, FutureExt}; +use jid::{BareJID, FullJID}; use luz::JID; use stanza::client::{ iq::{self, Iq, IqType}, @@ -36,24 +37,18 @@ pub mod error; #[derive(Clone)] pub struct Connected { // full jid will stay stable across reconnections - jid: JID, + jid: FullJID, write_handle: WriteHandle, - // the server jid - server: JID, } impl Connected { - pub fn jid(&self) -> &JID { + pub fn jid(&self) -> &FullJID { &self.jid } pub fn write_handle(&self) -> &WriteHandle { &self.write_handle } - - pub fn server(&self) -> &JID { - &self.server - } } /// everything that a particular xmpp client must implement @@ -227,34 +222,17 @@ where .await; } None => { - let mut jid = self.jid.clone(); - let mut domain = jid.domainpart.clone(); // TODO: check what happens upon reconnection with same resource (this is probably what one wants to do and why jid should be mutated from a bare jid to one with a resource) let streams_result = - luz::connect_and_login(&mut jid, &*self.password, &mut domain) - .await; - let server: JID = match domain.parse() { - Ok(j) => j, - Err(e) => { - self.logic - .clone() - .handle_connection_error(ConnectionError::InvalidServerJID( - e.clone(), - )) - .await; - sender.send(Err(ConnectionError::InvalidServerJID(e))); - continue; - } - }; + luz::connect_and_login(&self.jid, &*self.password).await; match streams_result { - Ok(s) => { + Ok((s, full_jid)) => { debug!("ok stream result"); let (shutdown_send, shutdown_recv) = oneshot::channel::<()>(); let (writer, supervisor) = SupervisorHandle::new( s, shutdown_send, - jid.clone(), - server.clone(), + full_jid.clone(), self.password.clone(), self.logic.clone(), ); @@ -262,18 +240,16 @@ where let shutdown_recv = shutdown_recv.fuse(); self.connection_supervisor_shutdown = shutdown_recv; - let resource = jid.resourcepart.clone().expect("client somehow connected without binding"); let connected = Connected { - jid, + jid: full_jid.clone(), write_handle: writer, - server, }; self.logic.clone().handle_connect(connected.clone()).await; self.connected = Some((connected, supervisor)); // REMEMBER TO NOTIFY IT@S GOOD - sender.send(Ok(resource)); + sender.send(Ok(full_jid.resourcepart)); } Err(e) => { tracing::error!("error: {}", e); diff --git a/luz/src/client/tcp.rs b/luz/src/client/tcp.rs index 4e35ef0..a30eccc 100644 --- a/luz/src/client/tcp.rs +++ b/luz/src/client/tcp.rs @@ -1,3 +1,4 @@ +use jid::FullJID; use rsasl::config::SASLConfig; use stanza::{ sasl::Mechanisms, @@ -10,22 +11,25 @@ use crate::{ }; pub async fn connect_and_login( - jid: &mut JID, + jid: &JID, + // jid: &mut JID, password: impl AsRef<str>, - server: &mut String, -) -> Result<BoundJabberStream> { + // server: &mut String, +) -> Result<(BoundJabberStream, FullJID)> { let auth = SASLConfig::with_credentials( None, jid.localpart.clone().ok_or(Error::NoLocalpart)?, password.as_ref().to_string(), ) .map_err(|e| Error::SASL(e.into()))?; + let mut server = jid.domainpart.clone(); let mut conn_state = Connecting::start(&server).await?; loop { match conn_state { Connecting::InsecureConnectionEstablised(tcp_stream) => { conn_state = Connecting::InsecureStreamStarted( - JabberStream::start_stream(Connection::Unencrypted(tcp_stream), server).await?, + JabberStream::start_stream(Connection::Unencrypted(tcp_stream), &mut server) + .await?, ) } Connecting::InsecureStreamStarted(jabber_stream) => { @@ -46,8 +50,9 @@ pub async fn connect_and_login( )) } Connecting::ConnectionEstablished(connection) => { - conn_state = - Connecting::StreamStarted(JabberStream::start_stream(connection, server).await?) + conn_state = Connecting::StreamStarted( + JabberStream::start_stream(connection, &mut server).await?, + ) } Connecting::StreamStarted(jabber_stream) => { conn_state = Connecting::GotFeatures(jabber_stream.get_features().await?) @@ -68,7 +73,7 @@ pub async fn connect_and_login( ) } Connecting::Bind(jabber_stream) => { - return Ok(jabber_stream.bind(jid).await?.to_bound_jabber()); + return Ok(jabber_stream.bind(jid).await?); } } } diff --git a/luz/src/client/ws.rs b/luz/src/client/ws.rs index 13c3cdf..0ad8d0e 100644 --- a/luz/src/client/ws.rs +++ b/luz/src/client/ws.rs @@ -1,3 +1,4 @@ +use jid::FullJID; use rsasl::config::SASLConfig; use stanza::{ sasl::Mechanisms, @@ -9,22 +10,22 @@ use crate::{ }; pub async fn connect_and_login( - jid: &mut JID, + jid: &JID, password: impl AsRef<str>, - server: &mut String, -) -> Result<BoundJabberStream> { +) -> Result<(BoundJabberStream, FullJID)> { let auth = SASLConfig::with_credentials( None, jid.localpart.clone().ok_or(Error::NoLocalpart)?, password.as_ref().to_string(), ) .map_err(|e| Error::SASL(e.into()))?; + let mut server = jid.domainpart.clone(); let mut conn_state = Connecting::start(&server).await?; loop { match conn_state { Connecting::ConnectionEstablished(ws) => { conn_state = - Connecting::StreamStarted(JabberStream::start_stream(ws, server).await?) + Connecting::StreamStarted(JabberStream::start_stream(ws, &mut server).await?) } Connecting::StreamStarted(jabber_stream) => { conn_state = Connecting::GotFeatures(jabber_stream.get_features().await?) @@ -45,7 +46,7 @@ pub async fn connect_and_login( ) } Connecting::Bind(jabber_stream) => { - return Ok(jabber_stream.bind(jid).await?.to_bound_jabber()); + return Ok(jabber_stream.bind(jid).await?); } } } diff --git a/luz/src/connection/tcp.rs b/luz/src/connection/tcp.rs index a9e81c3..7409a47 100644 --- a/luz/src/connection/tcp.rs +++ b/luz/src/connection/tcp.rs @@ -96,7 +96,7 @@ impl Connection { // } pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> { - let jid: JID = JID::from_str(jid.as_ref())?; + let jid: JID = JID::from_str(jid.as_ref()).map_err(|e| Error::JID(e.into()))?; let server = jid.domainpart.clone(); Self::connect(&server).await } diff --git a/luz/src/error.rs b/luz/src/error.rs index fcb32a0..d9b2930 100644 --- a/luz/src/error.rs +++ b/luz/src/error.rs @@ -1,7 +1,7 @@ use std::str::Utf8Error; use std::sync::Arc; -use jid::ParseError; +use jid::JIDError; use rsasl::mechname::MechanismNameError; use stanza::client::error::Error as ClientError; use stanza::sasl::Failure; @@ -36,7 +36,7 @@ pub enum Error { #[error("sasl error: {0}")] SASL(#[from] SASLError), #[error("jid error: {0}")] - JID(#[from] ParseError), + JID(#[from] JIDError), #[error("client stanza error: {0}")] ClientError(#[from] ClientError), #[error("stream error: {0}")] diff --git a/luz/src/jabber_stream.rs b/luz/src/jabber_stream.rs index f77e6a9..490a0f7 100644 --- a/luz/src/jabber_stream.rs +++ b/luz/src/jabber_stream.rs @@ -7,7 +7,8 @@ mod ws; use std::str::{self, FromStr}; use std::sync::Arc; -use jid::JID; +use bound_stream::BoundJabberStream; +use jid::{FullJID, JID}; use peanuts::IntoElement; #[cfg(target_arch = "wasm32")] use peanuts::WebSocketOnMessageRead; @@ -147,9 +148,9 @@ impl JabberStream { } #[instrument] - pub async fn bind(mut self, jid: &mut JID) -> Result<Self> { + pub async fn bind(mut self, jid: &JID) -> Result<(BoundJabberStream, FullJID)> { let iq_id = uuid::Uuid::new_v4().to_string(); - if let Some(resource) = &jid.resourcepart { + if let Some(resource) = &jid.resourcepart() { let iq = Iq { from: None, id: iq_id.clone(), @@ -176,8 +177,7 @@ impl JabberStream { })), errors: _, } if id == iq_id => { - *jid = new_jid; - return Ok(self); + return Ok((self.to_bound_jabber(), new_jid)); } Iq { from: _, @@ -219,8 +219,7 @@ impl JabberStream { })), errors: _, } if id == iq_id => { - *jid = new_jid; - return Ok(self); + return Ok((self.to_bound_jabber(), new_jid)); } Iq { from: _, @@ -257,7 +256,11 @@ impl JabberStream { id: None, lang: None, version: Some("1.0".to_string()), - to: Some(JID::from_str(server.as_ref())?), + to: Some( + JID::from_str(server.as_ref()) + .map_err(|e| Error::JID(e.into()))? + .try_into()?, + ), }; writer.write(&open).await?; @@ -290,7 +293,9 @@ impl JabberStream { // opening stream element let stream = Stream::new_client( None, - JID::from_str(server.as_ref())?, + JID::from_str(server.as_ref()) + .map_err(|e| Error::JID(e.into()))? + .try_into()?, None, "en".to_string(), ); diff --git a/stanza/src/bind.rs b/stanza/src/bind.rs index 3ce2246..0f0f681 100644 --- a/stanza/src/bind.rs +++ b/stanza/src/bind.rs @@ -1,4 +1,4 @@ -use jid::JID; +use jid::FullJID; use peanuts::{Element, FromElement, IntoElement}; pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; @@ -54,7 +54,7 @@ impl IntoElement for BindType { // minLength 8 maxLength 3071 #[derive(Clone, Debug)] -pub struct FullJidType(pub JID); +pub struct FullJidType(pub FullJID); impl FromElement for FullJidType { fn from_element(mut element: peanuts::Element) -> peanuts::DeserializeResult<Self> { diff --git a/stanza/src/rfc_7395.rs b/stanza/src/rfc_7395.rs index 64d9f70..73e947d 100644 --- a/stanza/src/rfc_7395.rs +++ b/stanza/src/rfc_7395.rs @@ -1,12 +1,12 @@ -use jid::JID; +use jid::BareJID; use peanuts::{Element, ElementBuilder, FromElement, IntoElement}; pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-framing"; #[derive(Debug)] pub struct Open { - pub from: Option<JID>, - pub to: Option<JID>, + pub from: Option<BareJID>, + pub to: Option<BareJID>, pub id: Option<String>, pub version: Option<String>, pub lang: Option<String>, @@ -46,8 +46,8 @@ impl IntoElement for Open { #[derive(Debug, Default)] pub struct Close { - pub from: Option<JID>, - pub to: Option<JID>, + pub from: Option<BareJID>, + pub to: Option<BareJID>, pub id: Option<String>, pub version: Option<String>, pub lang: Option<String>, diff --git a/stanza/src/roster.rs b/stanza/src/roster.rs index 14f65ef..dcbf017 100644 --- a/stanza/src/roster.rs +++ b/stanza/src/roster.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use jid::JID; +use jid::BareJID; use peanuts::{DeserializeError, Element, FromElement, IntoElement}; pub const XMLNS: &str = "jabber:iq:roster"; @@ -38,7 +38,7 @@ pub struct Item { /// signals subscription sub-states (server only) pub ask: bool, /// uniquely identifies item - pub jid: JID, + pub jid: BareJID, /// handle that is determined by user, not contact pub name: Option<String>, /// state of the presence subscription diff --git a/stanza/src/stream.rs b/stanza/src/stream.rs index 5be235a..e2f4f9b 100644 --- a/stanza/src/stream.rs +++ b/stanza/src/stream.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use jid::JID; +use jid::BareJID; use peanuts::{Element, ElementBuilder, FromElement, IntoElement}; use thiserror::Error; @@ -18,8 +18,8 @@ pub const XMLNS: &str = "http://etherx.jabber.org/streams"; // #[peanuts(xmlns = XMLNS)] #[derive(Debug)] pub struct Stream { - pub from: Option<JID>, - to: Option<JID>, + pub from: Option<BareJID>, + to: Option<BareJID>, id: Option<String>, version: Option<String>, // TODO: lang enum @@ -64,8 +64,8 @@ impl IntoElement for Stream { impl<'s> Stream { pub fn new( - from: Option<JID>, - to: Option<JID>, + from: Option<BareJID>, + to: Option<BareJID>, id: Option<String>, version: Option<String>, lang: Option<String>, @@ -81,7 +81,12 @@ impl<'s> Stream { /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute. /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection. - pub fn new_client(from: Option<JID>, to: JID, id: Option<String>, lang: String) -> Self { + pub fn new_client( + from: Option<BareJID>, + to: BareJID, + id: Option<String>, + lang: String, + ) -> Self { Self { from, to: Some(to), diff --git a/stanza/src/xep_0060/owner.rs b/stanza/src/xep_0060/owner.rs index 0617712..4876bf5 100644 --- a/stanza/src/xep_0060/owner.rs +++ b/stanza/src/xep_0060/owner.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use jid::JID; +use jid::{BareJID, JID}; use peanuts::{DeserializeError, Element, FromElement, IntoElement}; use crate::xep_0004::X; @@ -85,7 +85,7 @@ impl IntoElement for Affiliations { #[derive(Clone, Debug)] pub struct Affiliation { affiliation: AffiliationType, - jid: JID, + jid: BareJID, } impl FromElement for Affiliation { |