diff options
author | 2025-03-28 22:44:11 +0000 | |
---|---|---|
committer | 2025-03-28 22:44:11 +0000 | |
commit | ba5ad94525940e3e34983425961550c67afc49ae (patch) | |
tree | ed0c93a75fac64d0d807ae77f222c501dd7da904 | |
parent | 20df3b7d6f3213bc3f5a90cc36363865b1b9e966 (diff) | |
download | luz-ba5ad94525940e3e34983425961550c67afc49ae.tar.gz luz-ba5ad94525940e3e34983425961550c67afc49ae.tar.bz2 luz-ba5ad94525940e3e34983425961550c67afc49ae.zip |
feat(filamento): disco info requests
-rw-r--r-- | filamento/examples/example.rs | 8 | ||||
-rw-r--r-- | filamento/src/disco.rs | 43 | ||||
-rw-r--r-- | filamento/src/error.rs | 25 | ||||
-rw-r--r-- | filamento/src/lib.rs | 24 | ||||
-rw-r--r-- | filamento/src/logic/offline.rs | 5 | ||||
-rw-r--r-- | filamento/src/logic/online.rs | 82 |
6 files changed, 175 insertions, 12 deletions
diff --git a/filamento/examples/example.rs b/filamento/examples/example.rs index b1ab6ce..10267bc 100644 --- a/filamento/examples/example.rs +++ b/filamento/examples/example.rs @@ -20,7 +20,7 @@ async fn main() { }); client.connect().await.unwrap(); - tokio::time::sleep(Duration::from_secs(15)).await; + tokio::time::sleep(Duration::from_secs(5)).await; info!("sending message"); client .send_message( @@ -31,5 +31,9 @@ async fn main() { ) .await .unwrap(); - println!("sent message"); + info!("sent message"); + info!("sending disco query"); + let info = client.disco_info(None).await.unwrap(); + tokio::time::sleep(Duration::from_secs(5)).await; + info!("got disco result: {:#?}", info); } diff --git a/filamento/src/disco.rs b/filamento/src/disco.rs index 86e339e..ccc99cb 100644 --- a/filamento/src/disco.rs +++ b/filamento/src/disco.rs @@ -4,6 +4,7 @@ use stanza::xep_0030::{info, items}; pub use feature::Feature; pub use identity::Identity; +#[derive(Debug, Clone)] pub struct Info { node: Option<String>, features: Vec<Feature>, @@ -109,6 +110,7 @@ mod feature { use stanza::xep_0030::info; // https://xmpp.org/registrar/disco-features.html + #[derive(Debug, Clone)] pub enum Feature { DNSSRV, FullUnicode, @@ -527,6 +529,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Amp { Errors, Action(AmpAction), @@ -543,6 +546,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum AmpAction { Alert, Drop, @@ -562,6 +566,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum AmpCondition { Deliver, ExpireAt, @@ -581,6 +586,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Bytestreams { UDP, } @@ -594,6 +600,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum CapsVersion { One(Option<CapsOne>), Two(Option<CapsTwo>), @@ -614,6 +621,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum CapsOne { Optimize, } @@ -627,6 +635,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum CapsTwo { Optimize, } @@ -640,6 +649,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Disco { Info, Items, @@ -655,6 +665,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum MUC { Admin, Owner, @@ -704,6 +715,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum PubSub { AccessAuthorize, AccessOpen, @@ -821,6 +833,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum SOAP { Fault, } @@ -834,6 +847,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum WaitingList { Schemes(WaitingListSchemes), } @@ -846,6 +860,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum WaitingListSchemes { Mailto, Tel, @@ -863,6 +878,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Component { Accept, Connect, @@ -878,6 +894,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Iq { Auth, Gateway, @@ -911,6 +928,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum X { Data, Encrypted, @@ -930,6 +948,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum XMPPSASL { C2S, S2S, @@ -945,6 +964,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum XMPPTLS { C2S, S2S, @@ -960,6 +980,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Archive { Auto, Manage, @@ -979,6 +1000,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Avatar { Data, Metadata, @@ -994,6 +1016,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum Jingle { Apps(JingleApps), } @@ -1006,6 +1029,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum JingleApps { RTP(JingleAppsRTP), } @@ -1018,6 +1042,7 @@ mod feature { } } + #[derive(Debug, Clone)] pub enum JingleAppsRTP { Audio, Video, @@ -1037,6 +1062,7 @@ mod feature { mod identity { use stanza::xep_0030::info; + #[derive(Debug, Clone)] pub struct Identity { name: Option<String>, category: Category, @@ -1065,6 +1091,7 @@ mod identity { // TODO: separate crate for disco registry /// categories taken from [XMPP Disco Categories](https://xmpp.org/registrar/disco-categories.html) + #[derive(Debug, Clone)] pub enum Category { Account(Account), Auth(Auth), @@ -1165,6 +1192,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Account { Admin, Anonymous, @@ -1195,6 +1223,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Auth { Cert, Generic, @@ -1234,6 +1263,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Authz { Ephemeral, Other(String), @@ -1258,6 +1288,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Automation { CommandList, CommandNode, @@ -1294,6 +1325,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Client { Bot, Console, @@ -1342,6 +1374,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Collaboration { Whiteboard, Other(String), @@ -1366,6 +1399,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Component { Archive, C2S, @@ -1417,6 +1451,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Conference { IRC, Text, @@ -1444,6 +1479,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Directory { Chatroom, Group, @@ -1477,6 +1513,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Gateway { Aim, Discord, @@ -1579,6 +1616,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Headline { NewMail, RSS, @@ -1609,6 +1647,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Hierarchy { Branch, Leaf, @@ -1636,6 +1675,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Proxy { Bytestreams, Other(String), @@ -1660,6 +1700,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum PubSub { Collection, Leaf, @@ -1694,6 +1735,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Server { IM, Other(String), @@ -1718,6 +1760,7 @@ mod identity { } } + #[derive(Debug, Clone)] pub enum Store { Berkeley, File, diff --git a/filamento/src/error.rs b/filamento/src/error.rs index ccb4406..8e2e4be 100644 --- a/filamento/src/error.rs +++ b/filamento/src/error.rs @@ -1,7 +1,8 @@ use std::sync::Arc; +use jid::JID; use lampada::error::{ConnectionError, ReadError, WriteError}; -use stanza::client::Stanza; +use stanza::client::{Stanza, iq::Query}; use thiserror::Error; pub use lampada::error::CommandError; @@ -80,8 +81,6 @@ pub enum RosterError { // TODO: display for stanza, to show as xml, same for read error types. #[error("unexpected reply: {0:?}")] UnexpectedStanza(Stanza), - #[error("stream read: {0}")] - Read(#[from] ReadError), #[error("stanza error: {0}")] StanzaError(#[from] stanza::client::error::Error), #[error("could not reply to roster push: {0}")] @@ -89,6 +88,26 @@ pub enum RosterError { } #[derive(Debug, Error, Clone)] +pub enum DiscoError { + #[error("write error: {0}")] + Write(#[from] WriteError), + #[error("iq response: {0}")] + IqResponse(#[from] RequestError), + #[error("reply from incorrect entity: {0}")] + IncorrectEntity(JID), + #[error("unexpected reply: {0:?}")] + UnexpectedStanza(Stanza), + #[error("stanza error: {0}")] + StanzaError(#[from] stanza::client::error::Error), + #[error("disco result missing query item")] + MissingQuery, + #[error("disco error missing error")] + MissingError, + #[error("received mismatched query")] + MismatchedQuery(Query), +} + +#[derive(Debug, Error, Clone)] pub enum RequestError { #[error("sending request: {0}")] Write(#[from] WriteError), diff --git a/filamento/src/lib.rs b/filamento/src/lib.rs index bc946ae..ed33e99 100644 --- a/filamento/src/lib.rs +++ b/filamento/src/lib.rs @@ -9,8 +9,9 @@ use std::{ use chat::{Body, Chat, Message}; use chrono::Utc; use db::Db; +use disco::Info; use error::{ - ConnectionJobError, DatabaseError, Error, IqError, MessageRecvError, PresenceError, + ConnectionJobError, DatabaseError, DiscoError, Error, IqError, MessageRecvError, PresenceError, RosterError, StatusError, }; use futures::FutureExt; @@ -98,8 +99,13 @@ pub enum Command { /// 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, oneshot::Sender<Result<(), WriteError>>), - /// disco info request - DiscoInfo(JID, oneshot::Sender<Result>), + /// disco info query + DiscoInfo( + Option<JID>, + oneshot::Sender<Result<disco::Info, DiscoError>>, + ), + // /// disco items query + // DiscoItems(JID, oneshot::Sender<Result<disco::Info, DiscoError>>), } #[derive(Debug, Clone)] @@ -473,6 +479,18 @@ impl Client { .map_err(|e| CommandError::Actor(Into::<ActorError>::into(e)))??; Ok(result) } + + pub async fn disco_info(&self, jid: Option<JID>) -> Result<Info, CommandError<DiscoError>> { + let (send, recv) = oneshot::channel(); + self.send(CoreClientCommand::Command(Command::DiscoInfo(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)))??; + Ok(result) + } } impl From<Command> for CoreClientCommand<Command> { diff --git a/filamento/src/logic/offline.rs b/filamento/src/logic/offline.rs index e864f22..7dfb394 100644 --- a/filamento/src/logic/offline.rs +++ b/filamento/src/logic/offline.rs @@ -2,7 +2,7 @@ use lampada::error::WriteError; use crate::{ Command, - error::{DatabaseError, Error, RosterError, StatusError}, + error::{DatabaseError, DiscoError, Error, RosterError, StatusError}, presence::Online, roster::Contact, }; @@ -113,6 +113,9 @@ pub async fn handle_offline_result(logic: &ClientLogic, command: Command) -> Res Command::SendPresence(_jid, _presence, sender) => { sender.send(Err(WriteError::Disconnected)); } + Command::DiscoInfo(_jid, sender) => { + sender.send(Err(DiscoError::Write(WriteError::Disconnected))); + } } Ok(()) } diff --git a/filamento/src/logic/online.rs b/filamento/src/logic/online.rs index 05d3f2b..c76907b 100644 --- a/filamento/src/logic/online.rs +++ b/filamento/src/logic/online.rs @@ -4,18 +4,20 @@ use lampada::{Connected, WriteMessage, error::WriteError}; use stanza::{ client::{ Stanza, - iq::{self, Iq, IqType}, + iq::{self, Iq, IqType, Query}, }, + xep_0030::info, xep_0203::Delay, }; use tokio::sync::oneshot; -use tracing::{debug, info}; +use tracing::{debug, error, info}; use uuid::Uuid; use crate::{ Command, UpdateMessage, chat::{Body, Message}, - error::{DatabaseError, Error, MessageSendError, RosterError, StatusError}, + disco::Info, + error::{DatabaseError, DiscoError, Error, MessageSendError, RosterError, StatusError}, presence::{Online, Presence, PresenceType}, roster::{Contact, ContactUpdate}, }; @@ -532,6 +534,76 @@ pub async fn handle_send_presence( Ok(()) } +// TODO: cache disco infos +pub async fn handle_disco_info( + logic: &ClientLogic, + connection: Connected, + jid: Option<JID>, +) -> Result<Info, DiscoError> { + let id = Uuid::new_v4().to_string(); + let request = Iq { + from: Some(connection.jid().clone()), + id: id.clone(), + to: jid.clone(), + r#type: IqType::Get, + lang: None, + query: Some(Query::DiscoInfo(info::Query { + node: None, + features: Vec::new(), + identities: Vec::new(), + })), + errors: Vec::new(), + }; + match logic + .pending() + .request(&connection, Stanza::Iq(request), id) + .await? + { + Stanza::Iq(Iq { + from, + r#type, + query, + mut errors, + .. + // 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 == jid || { + if jid == None { + from == Some(connection.jid().as_bare()) + } else { + false + } + } { + match r#type { + IqType::Result => { + if let Some(query) = query { + match query { + Query::DiscoInfo(info) => Ok(info.into()), + q => Err(DiscoError::MismatchedQuery(q)), + } + } else { + Err(DiscoError::MissingQuery) + } + } + IqType::Error => { + if let Some(error) = errors.pop() { + Err(error.into()) + } else { + Err(DiscoError::MissingError) + } + } + _ => unreachable!(), + } + } else { + Err(DiscoError::IncorrectEntity( + from.unwrap_or_else(|| connection.jid().as_bare()), + )) + } + } + s => Err(DiscoError::UnexpectedStanza(s)), + } +} + // TODO: could probably macro-ise? pub async fn handle_online_result( logic: &ClientLogic, @@ -629,6 +701,10 @@ pub async fn handle_online_result( let result = handle_send_presence(connection, jid, presence).await; let _ = sender.send(result); } + Command::DiscoInfo(jid, sender) => { + let result = handle_disco_info(logic, connection, jid).await; + let _ = sender.send(result); + } } Ok(()) } |