Merge branch 'ibb' into 'master'

lumi created

Add an IBB plugin, implementing XEP-0047

See merge request !12

Change summary

Cargo.toml                     |   4 
examples/client.rs             |   2 
src/components/mod.rs          |   2 
src/components/stanza_error.rs | 172 -----------------------------------
src/plugins/ibb.rs             | 175 ++++++++++++++++++++++++++++++++++++
src/plugins/mod.rs             |   1 
6 files changed, 180 insertions(+), 176 deletions(-)

Detailed changes

Cargo.toml πŸ”—

@@ -1,7 +1,7 @@
 [package]
 name = "xmpp"
 version = "0.2.0"
-authors = ["lumi <lumi@pew.im>"]
+authors = ["lumi <lumi@pew.im>", "Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"]
 description = "A type-safe rust XMPP library. Still under development."
 homepage = "https://gitlab.com/lumi/xmpp-rs"
 repository = "https://gitlab.com/lumi/xmpp-rs"
@@ -20,7 +20,7 @@ xmpp-parsers = "0.3.0"
 openssl = "0.9.12"
 base64 = "0.5.2"
 minidom = "0.4.1"
-jid = "0.2.0"
+jid = "0.2.1"
 sasl = "0.4.0"
 sha-1 = "0.3.3"
 

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);

src/components/stanza_error.rs πŸ”—

@@ -1,172 +0,0 @@
-use ns;
-use minidom::Element;
-use util::{FromElement, FromParentElement};
-use std::str::FromStr;
-
-#[derive(Copy, Clone, Debug)]
-pub enum ErrorType {
-    Auth,
-    Cancel,
-    Continue,
-    Modify,
-    Wait,
-}
-
-impl FromStr for ErrorType {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<ErrorType, ()> {
-        Ok(match s {
-            "auth" => ErrorType::Auth,
-            "cancel" => ErrorType::Cancel,
-            "continue" => ErrorType::Continue,
-            "modify" => ErrorType::Modify,
-            "wait" => ErrorType::Wait,
-            _ => { return Err(()); },
-        })
-    }
-}
-
-#[derive(Clone, Debug)]
-pub enum Condition {
-    BadRequest,
-    Conflict,
-    FeatureNotImplemented,
-    Forbidden,
-    Gone(Option<String>),
-    InternalServerError,
-    ItemNotFound,
-    JidMalformed,
-    NotAcceptable,
-    NotAllowed,
-    NotAuthorized,
-    PolicyViolation,
-    RecipientUnavailable,
-    Redirect(Option<String>),
-    RegistrationRequired,
-    RemoteServerNotFound,
-    RemoteServerTimeout,
-    ResourceConstraint,
-    ServiceUnavailable,
-    SubscriptionRequired,
-    UndefinedCondition,
-    UnexpectedRequest,
-}
-
-impl FromParentElement for Condition {
-    type Err = ();
-
-    fn from_parent_element(elem: &Element) -> Result<Condition, ()> {
-        if elem.has_child("bad-request", ns::STANZAS) {
-            Ok(Condition::BadRequest)
-        }
-        else if elem.has_child("conflict", ns::STANZAS) {
-            Ok(Condition::Conflict)
-        }
-        else if elem.has_child("feature-not-implemented", ns::STANZAS) {
-            Ok(Condition::FeatureNotImplemented)
-        }
-        else if elem.has_child("forbidden", ns::STANZAS) {
-            Ok(Condition::Forbidden)
-        }
-        else if let Some(alt) = elem.get_child("gone", ns::STANZAS) {
-            let text = alt.text();
-            let inner = if text == "" { None } else { Some(text) };
-            Ok(Condition::Gone(inner))
-        }
-        else if elem.has_child("internal-server-error", ns::STANZAS) {
-            Ok(Condition::InternalServerError)
-        }
-        else if elem.has_child("item-not-found", ns::STANZAS) {
-            Ok(Condition::ItemNotFound)
-        }
-        else if elem.has_child("jid-malformed", ns::STANZAS) {
-            Ok(Condition::JidMalformed)
-        }
-        else if elem.has_child("not-acceptable", ns::STANZAS) {
-            Ok(Condition::NotAcceptable)
-        }
-        else if elem.has_child("not-allowed", ns::STANZAS) {
-            Ok(Condition::NotAllowed)
-        }
-        else if elem.has_child("not-authorized", ns::STANZAS) {
-            Ok(Condition::NotAuthorized)
-        }
-        else if elem.has_child("policy-violation", ns::STANZAS) {
-            Ok(Condition::PolicyViolation)
-        }
-        else if elem.has_child("recipient-unavailable", ns::STANZAS) {
-            Ok(Condition::RecipientUnavailable)
-        }
-        else if let Some(alt) = elem.get_child("redirect", ns::STANZAS) {
-            let text = alt.text();
-            let inner = if text == "" { None } else { Some(text) };
-            Ok(Condition::Redirect(inner))
-        }
-        else if elem.has_child("registration-required", ns::STANZAS) {
-            Ok(Condition::RegistrationRequired)
-        }
-        else if elem.has_child("remote-server-not-found", ns::STANZAS) {
-            Ok(Condition::RemoteServerNotFound)
-        }
-        else if elem.has_child("remote-server-timeout", ns::STANZAS) {
-            Ok(Condition::RemoteServerTimeout)
-        }
-        else if elem.has_child("resource-constraint", ns::STANZAS) {
-            Ok(Condition::ResourceConstraint)
-        }
-        else if elem.has_child("service-unavailable", ns::STANZAS) {
-            Ok(Condition::ServiceUnavailable)
-        }
-        else if elem.has_child("subscription-required", ns::STANZAS) {
-            Ok(Condition::SubscriptionRequired)
-        }
-        else if elem.has_child("undefined-condition", ns::STANZAS) {
-            Ok(Condition::UndefinedCondition)
-        }
-        else if elem.has_child("unexpected-request", ns::STANZAS) {
-            Ok(Condition::UnexpectedRequest)
-        }
-        else {
-            Err(())
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct StanzaError {
-    error_type: ErrorType,
-    text: Option<String>,
-    condition: Condition,
-}
-
-impl StanzaError {
-    pub fn new(error_type: ErrorType, text: Option<String>, condition: Condition) -> StanzaError {
-        StanzaError {
-            error_type: error_type,
-            text: text,
-            condition: condition,
-        }
-    }
-}
-
-impl FromElement for StanzaError {
-    type Err = ();
-
-    fn from_element(elem: &Element) -> Result<StanzaError, ()> {
-        if elem.is("error", ns::STANZAS) {
-            let error_type = elem.attr("type").ok_or(())?;
-            let err: ErrorType = error_type.parse().map_err(|_| ())?;
-            let condition: Condition = Condition::from_parent_element(elem)?;
-            let text = elem.get_child("text", ns::STANZAS).map(|c| c.text());
-            Ok(StanzaError {
-                error_type: err,
-                text: text,
-                condition: condition,
-            })
-        }
-        else {
-            Err(())
-        }
-    }
-}

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<u8>,
+}
+
+#[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<Mutex<HashMap<(Jid, String), Session>>>,
+}
+
+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,
+]);

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;