diff --git a/examples/client.rs b/examples/client.rs index 9b8f668..49548b1 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -6,6 +6,7 @@ use xmpp::plugins::stanza::StanzaPlugin; use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; use xmpp::plugins::presence::{PresencePlugin, Type}; +use xmpp::plugins::ibb::IbbPlugin; use xmpp::plugins::ping::{PingPlugin, PingEvent}; use xmpp::event::{Priority, Propagation}; @@ -22,6 +23,7 @@ fn main() { client.register_plugin(UnhandledIqPlugin::new()); client.register_plugin(MessagingPlugin::new()); client.register_plugin(PresencePlugin::new()); + client.register_plugin(IbbPlugin::new()); client.register_plugin(PingPlugin::new()); client.register_handler(Priority::Max, |e: &MessageEvent| { println!("{:?}", e); diff --git a/src/plugins/ibb.rs b/src/plugins/ibb.rs new file mode 100644 index 0000000..8000038 --- /dev/null +++ b/src/plugins/ibb.rs @@ -0,0 +1,175 @@ +use std::collections::{HashMap, BTreeMap}; +use std::collections::hash_map::Entry; +use std::convert::TryFrom; +use std::sync::{Mutex, Arc}; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; + +use plugins::stanza::Iq; +use xmpp_parsers::iq::{IqType, IqPayload}; +use xmpp_parsers::ibb::{IBB, Stanza}; +use xmpp_parsers::stanza_error::{StanzaError, ErrorType, DefinedCondition}; + +#[derive(Debug, Clone)] +pub struct Session { + stanza: Stanza, + block_size: u16, + cur_seq: u16, +} + +#[derive(Debug)] +pub struct IbbOpen { + pub session: Session, +} + +#[derive(Debug)] +pub struct IbbData { + pub session: Session, + pub data: Vec, +} + +#[derive(Debug)] +pub struct IbbClose { + pub session: Session, +} + +impl Event for IbbOpen {} +impl Event for IbbData {} +impl Event for IbbClose {} + +fn generate_error(type_: ErrorType, defined_condition: DefinedCondition, text: &str) -> StanzaError { + StanzaError { + type_: type_, + defined_condition: defined_condition, + texts: { + let mut texts = BTreeMap::new(); + texts.insert(String::new(), String::from(text)); + texts + }, + by: None, + other: None, + } +} + +pub struct IbbPlugin { + proxy: PluginProxy, + sessions: Arc>>, +} + +impl IbbPlugin { + pub fn new() -> IbbPlugin { + IbbPlugin { + proxy: PluginProxy::new(), + sessions: Arc::new(Mutex::new(HashMap::new())), + } + } + + fn handle_ibb(&self, from: Jid, ibb: IBB) -> Result<(), StanzaError> { + let mut sessions = self.sessions.lock().unwrap(); + match ibb { + IBB::Open { block_size, sid, stanza } => { + match sessions.entry((from.clone(), sid.clone())) { + Entry::Vacant(_) => Ok(()), + Entry::Occupied(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "This session is already open." + )), + }?; + let session = Session { + stanza, + block_size, + cur_seq: 65535u16, + }; + sessions.insert((from, sid), session.clone()); + self.proxy.dispatch(IbbOpen { + session: session, + }); + }, + IBB::Data { seq, sid, data } => { + let entry = match sessions.entry((from, sid)) { + Entry::Occupied(entry) => Ok(entry), + Entry::Vacant(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::ItemNotFound, + "This session doesn’t exist." + )), + }?; + let mut session = entry.into_mut(); + if session.stanza != Stanza::Iq { + return Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "Wrong stanza type." + )) + } + let cur_seq = session.cur_seq.wrapping_add(1); + if seq != cur_seq { + return Err(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + "Wrong seq number." + )) + } + session.cur_seq = cur_seq; + self.proxy.dispatch(IbbData { + session: session.clone(), + data, + }); + }, + IBB::Close { sid } => { + let entry = match sessions.entry((from, sid)) { + Entry::Occupied(entry) => Ok(entry), + Entry::Vacant(_) => Err(generate_error( + ErrorType::Cancel, + DefinedCondition::ItemNotFound, + "This session doesn’t exist." + )), + }?; + let session = entry.remove(); + self.proxy.dispatch(IbbClose { + session, + }); + }, + } + Ok(()) + } + + fn handle_iq(&self, iq: &Iq) -> Propagation { + let iq = iq.clone(); + if let IqType::Set(payload) = iq.payload { + let from = iq.from.unwrap(); + let id = iq.id.unwrap(); + // TODO: use an intermediate plugin to parse this payload. + let payload = match IqPayload::try_from(payload) { + Ok(IqPayload::IBB(ibb)) => { + match self.handle_ibb(from.clone(), ibb) { + Ok(_) => IqType::Result(None), + Err(error) => IqType::Error(error), + } + }, + Err(err) => IqType::Error(generate_error( + ErrorType::Cancel, + DefinedCondition::NotAcceptable, + format!("{:?}", err).as_ref() + )), + Ok(_) => return Propagation::Continue, + }; + self.proxy.send(Iq { + from: None, + to: Some(from), + id: Some(id), + payload: payload, + }.into()); + Propagation::Stop + } else { + Propagation::Continue + } + } +} + +impl_plugin!(IbbPlugin, proxy, [ + (Iq, Priority::Default) => handle_iq, +]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 8bbb3af..d2846f5 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,6 +1,7 @@ pub mod messaging; pub mod presence; pub mod ping; +pub mod ibb; pub mod stanza; pub mod stanza_debug; pub mod unhandled_iq;