From 1b21ebc6b085a043eb33cd4795f054ae7d1f048a Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 11 Jun 2017 19:36:35 +0100 Subject: [PATCH 1/2] add a disco result event to the disco plugin --- src/plugins/disco.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/plugins/disco.rs b/src/plugins/disco.rs index 2ad839d..712d945 100644 --- a/src/plugins/disco.rs +++ b/src/plugins/disco.rs @@ -6,7 +6,7 @@ use event::{Event, Priority, Propagation}; use jid::Jid; use plugins::stanza::Iq; -use xmpp_parsers::iq::{IqType, IqPayload}; +use xmpp_parsers::iq::IqType; use xmpp_parsers::disco::{Disco, Identity, Feature}; use xmpp_parsers::data_forms::DataForm; use xmpp_parsers::ns; @@ -18,7 +18,15 @@ pub struct DiscoInfoRequest { pub node: Option, } +#[derive(Debug)] +pub struct DiscoInfoResult { + pub from: Jid, + pub id: String, + pub disco: Disco, +} + impl Event for DiscoInfoRequest {} +impl Event for DiscoInfoResult {} pub struct DiscoPlugin { proxy: PluginProxy, @@ -88,15 +96,23 @@ impl DiscoPlugin { fn handle_iq(&self, iq: &Iq) -> Propagation { let iq = iq.clone(); if let IqType::Get(payload) = iq.payload { - // TODO: use an intermediate plugin to parse this payload. - if let Ok(IqPayload::Disco(disco)) = IqPayload::try_from(payload) { - self.proxy.dispatch(DiscoInfoRequest { // TODO: safety!!! + if let Ok(disco) = Disco::try_from(payload) { + self.proxy.dispatch(DiscoInfoRequest { from: iq.from.unwrap(), id: iq.id.unwrap(), node: disco.node, }); return Propagation::Stop; } + } else if let IqType::Result(Some(payload)) = iq.payload { + if let Ok(disco) = Disco::try_from(payload) { + self.proxy.dispatch(DiscoInfoResult { + from: iq.from.unwrap(), + id: iq.id.unwrap(), + disco: disco, + }); + return Propagation::Stop; + } } Propagation::Continue } From 0fbe09ad59948402647b81ae6b7c69852776d306 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 11 Jun 2017 19:36:55 +0100 Subject: [PATCH 2/2] add a plugin to query and cache caps --- examples/client.rs | 2 + src/plugins/caps.rs | 140 ++++++++++++++++++++++++++++++++++++++++++++ src/plugins/mod.rs | 1 + 3 files changed, 143 insertions(+) create mode 100644 src/plugins/caps.rs diff --git a/examples/client.rs b/examples/client.rs index db1567c..e4d7728 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -8,6 +8,7 @@ use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; use xmpp::plugins::presence::{PresencePlugin, Type, Show}; use xmpp::plugins::disco::DiscoPlugin; +use xmpp::plugins::caps::CapsPlugin; use xmpp::plugins::ibb::IbbPlugin; use xmpp::plugins::ping::PingPlugin; use xmpp::event::{Priority, Propagation}; @@ -29,6 +30,7 @@ fn main() { client.register_plugin(MessagingPlugin::new()); client.register_plugin(PresencePlugin::new()); client.register_plugin(DiscoPlugin::new("client", "bot", "en", "xmpp-rs")); + client.register_plugin(CapsPlugin::new()); client.register_plugin(IbbPlugin::new()); client.register_plugin(PingPlugin::new()); client.plugin::().init(); diff --git a/src/plugins/caps.rs b/src/plugins/caps.rs new file mode 100644 index 0000000..1f44881 --- /dev/null +++ b/src/plugins/caps.rs @@ -0,0 +1,140 @@ +use std::collections::HashMap; +use std::convert::TryFrom; +use std::sync::{Mutex, Arc}; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; +use base64; + +use plugins::stanza::{Presence, Iq}; +use plugins::disco::DiscoInfoResult; +use xmpp_parsers::presence::Type as PresenceType; +use xmpp_parsers::iq::IqType; +use xmpp_parsers::disco::Disco; +use xmpp_parsers::caps::Caps; + +#[derive(Debug)] +pub struct DiscoInfoRequest { + pub from: Jid, + pub id: String, + pub node: Option, +} + +impl Event for DiscoInfoRequest {} + +pub struct CapsPlugin { + proxy: PluginProxy, + pending: Arc>>, + cache: Arc>>, +} + +impl CapsPlugin { + pub fn new() -> CapsPlugin { + CapsPlugin { + proxy: PluginProxy::new(), + pending: Arc::new(Mutex::new(HashMap::new())), + cache: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn handle_presence(&self, presence: &Presence) -> Propagation { + let presence = presence.clone(); + match presence.type_ { + PresenceType::None => for payload in presence.payloads { + let caps = match Caps::try_from(payload) { + Ok(caps) => caps, + Err(_) => continue, + }; + let recipient = presence.from.unwrap(); + let node = format!("{}#{}", caps.node, base64::encode(&caps.hash.hash)); + { + let cache = self.cache.lock().unwrap(); + if cache.contains_key(&(recipient.clone(), node.clone())) { + break; + } + } + let id = self.proxy.gen_id(); + { + let mut pending = self.pending.lock().unwrap(); + pending.insert(recipient.clone(), (id.clone(), node.clone())); + } + let disco = Disco { + node: Some(node), + identities: vec!(), + features: vec!(), + extensions: vec!(), + }; + self.proxy.send(Iq { + to: Some(recipient), + from: None, + id: Some(id), + payload: IqType::Get(disco.into()), + }.into()); + break; + }, + PresenceType::Unavailable + | PresenceType::Error => { + let recipient = presence.from.unwrap(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&recipient); + if previous.is_none() { + // This wasn’t one of our requests. + return Propagation::Continue; + } + // TODO: maybe add a negative cache? + }, + _ => (), + } + Propagation::Continue + } + + fn handle_result(&self, result: &DiscoInfoResult) -> Propagation { + let from = result.from.clone(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&from.clone()); + if let Some((id, node)) = previous { + if id != result.id { + return Propagation::Continue; + } + if Some(node.clone()) != result.disco.node { + // TODO: make that a debug log. + println!("Wrong node in result!"); + return Propagation::Continue; + } + { + let mut cache = self.cache.lock().unwrap(); + cache.insert((from, node), result.disco.clone()); + } + } else { + // TODO: make that a debug log. + println!("No such request from us."); + return Propagation::Continue; + } + Propagation::Stop + } + + // This is only for errors. + // TODO: also do the same thing for timeouts. + fn handle_iq(&self, iq: &Iq) -> Propagation { + let iq = iq.clone(); + if let IqType::Error(_) = iq.payload { + let from = iq.from.unwrap(); + let mut pending = self.pending.lock().unwrap(); + let previous = pending.remove(&from.clone()); + if previous.is_none() { + // This wasn’t one of our requests. + return Propagation::Continue; + } + // TODO: maybe add a negative cache? + return Propagation::Stop; + } + Propagation::Continue + } +} + +impl_plugin!(CapsPlugin, proxy, [ + (Presence, Priority::Default) => handle_presence, + (Iq, Priority::Default) => handle_iq, + (DiscoInfoResult, Priority::Default) => handle_result, +]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 22b282e..a6c8b7c 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -2,6 +2,7 @@ pub mod messaging; pub mod presence; pub mod roster; pub mod disco; +pub mod caps; pub mod ping; pub mod ibb; pub mod stanza;