From 66bc970d902d2e5fc2a5ac2f7ff2ea08d0d4e958 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 28 May 2017 17:35:00 +0100 Subject: [PATCH] add a roster plugin --- src/plugins/mod.rs | 1 + src/plugins/roster.rs | 186 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 src/plugins/roster.rs diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index fca357fa..22b282e5 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,5 +1,6 @@ pub mod messaging; pub mod presence; +pub mod roster; pub mod disco; pub mod ping; pub mod ibb; diff --git a/src/plugins/roster.rs b/src/plugins/roster.rs new file mode 100644 index 00000000..8bd8166c --- /dev/null +++ b/src/plugins/roster.rs @@ -0,0 +1,186 @@ +use std::collections::HashMap; +use std::convert::TryFrom; +use std::sync::Mutex; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; + +use plugins::stanza::Iq; +use plugins::disco::DiscoPlugin; +use xmpp_parsers::iq::{IqType, IqPayload}; +use xmpp_parsers::roster::{Roster, Item, Subscription}; +use xmpp_parsers::ns; + +#[derive(Debug)] +pub struct RosterReceived { + pub ver: Option, + pub jids: HashMap, +} + +#[derive(Debug)] +pub enum RosterPush { + Added(Item), + Modified(Item), + Removed(Item), +} + +impl Event for RosterReceived {} +impl Event for RosterPush {} + +pub struct RosterPlugin { + proxy: PluginProxy, + current_version: Mutex>, + // TODO: allow for a different backing store. + jids: Mutex>, +} + +impl RosterPlugin { + pub fn new(ver: Option) -> RosterPlugin { + RosterPlugin { + proxy: PluginProxy::new(), + current_version: Mutex::new(ver), + jids: Mutex::new(HashMap::new()), + } + } + + // TODO: make that called automatically after plugins are created. + pub fn init(&self) { + if let Some(disco) = self.proxy.plugin::() { + disco.add_feature(ns::IBB); + } else { + panic!("Please handle dependencies in the correct order."); + } + } + + // TODO: make that called automatically before removal. + pub fn deinit(&self) { + if let Some(disco) = self.proxy.plugin::() { + disco.remove_feature(ns::IBB); + } else { + panic!("Please handle dependencies in the correct order."); + } + } + + pub fn send_roster_get(&self, ver: Option) { + let iq = Iq { + from: None, + to: None, + id: Some(self.proxy.gen_id()), + payload: IqType::Get(Roster { + ver, + items: vec!(), + }.into()), + }; + self.proxy.send(iq.into()); + } + + // TODO: use a better error type. + pub fn send_roster_set(&self, to: Option, item: Item) -> Result<(), String> { + if item.subscription.is_some() && item.subscription != Some(Subscription::Remove) { + return Err(String::from("Subscription must be either nothing or Remove.")); + } + let iq = Iq { + from: None, + to, + id: Some(self.proxy.gen_id()), + payload: IqType::Set(Roster { + ver: None, + items: vec!(item), + }.into()), + }; + self.proxy.send(iq.into()); + Ok(()) + } + + fn handle_roster_reply(&self, roster: Roster) { + // TODO: handle the same-ver case! + let mut current_version = self.current_version.lock().unwrap(); + *current_version = roster.ver; + let mut jids = self.jids.lock().unwrap(); + jids.clear(); + for item in roster.items { + jids.insert(item.jid.clone(), item); + } + self.proxy.dispatch(RosterReceived { + ver: current_version.clone(), + jids: jids.clone(), + }); + } + + fn handle_roster_push(&self, roster: Roster) -> Result<(), String> { + let item = roster.items.get(0); + if item.is_none() || roster.items.len() != 1 { + return Err(String::from("Server sent an invalid roster push!")); + } + let item = item.unwrap().clone(); + let mut jids = self.jids.lock().unwrap(); + let previous = jids.insert(item.jid.clone(), item.clone()); + if previous.is_none() { + assert!(item.subscription != Some(Subscription::Remove)); + self.proxy.dispatch(RosterPush::Added(item)); + } else { + if item.subscription == Some(Subscription::Remove) { + self.proxy.dispatch(RosterPush::Removed(item)); + } else { + self.proxy.dispatch(RosterPush::Modified(item)); + } + } + Ok(()) + } + + fn handle_iq(&self, iq: &Iq) -> Propagation { + let jid = self.proxy.get_own_jid(); + let jid = Jid::bare(jid.node.unwrap(), jid.domain); + if iq.from.is_some() && iq.from != Some(jid) { + // Not from our roster. + return Propagation::Continue; + } + let iq = iq.clone(); + let id = iq.id.unwrap(); + match iq.payload { + IqType::Result(Some(payload)) => { + match IqPayload::try_from(payload) { + Ok(IqPayload::Roster(roster)) => { + self.handle_roster_reply(roster); + Propagation::Stop + }, + Ok(_) + | Err(_) => Propagation::Continue, + } + }, + IqType::Set(payload) => { + match IqPayload::try_from(payload) { + Ok(IqPayload::Roster(roster)) => { + let payload = match self.handle_roster_push(roster) { + Ok(_) => IqType::Result(None), + Err(string) => { + // The specification says that the server should ignore an error. + println!("{}", string); + IqType::Result(None) + }, + }; + self.proxy.send(Iq { + from: None, + to: None, + id: Some(id), + payload: payload, + }.into()); + Propagation::Stop + }, + Ok(_) + | Err(_) => return Propagation::Continue, + } + }, + IqType::Result(None) + | IqType::Get(_) + | IqType::Error(_) => { + Propagation::Continue + }, + } + } +} + +impl_plugin!(RosterPlugin, proxy, [ + (Iq, Priority::Default) => handle_iq, +]);