diff --git a/Cargo.toml b/Cargo.toml index 02a706d..f348e86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,4 @@ xmpp-parsers = "0.11" idna = "0.1" try_from = "0.2" quick-xml = "0.12" +derive-error = "0.0.4" diff --git a/src/client/auth.rs b/src/client/auth.rs index 0dc66d1..97a6a12 100644 --- a/src/client/auth.rs +++ b/src/client/auth.rs @@ -13,9 +13,11 @@ use try_from::TryFrom; use xmpp_codec::Packet; use xmpp_stream::XMPPStream; use stream_start::StreamStart; +use {Error, AuthError, ProtocolError}; const NS_XMPP_SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; + pub struct ClientAuth { state: ClientAuthState, mechanism: Box, @@ -29,7 +31,7 @@ enum ClientAuthState { } impl ClientAuth { - pub fn new(stream: XMPPStream, creds: Credentials) -> Result { + pub fn new(stream: XMPPStream, creds: Credentials) -> Result { let mechs: Vec> = vec![ Box::new(Scram::::from_credentials(creds.clone()).unwrap()), Box::new(Scram::::from_credentials(creds.clone()).unwrap()), @@ -40,7 +42,7 @@ impl ClientAuth { let mech_names: Vec = match stream.stream_features.get_child("mechanisms", NS_XMPP_SASL) { None => - return Err("No auth mechanisms".to_owned()), + return Err(AuthError::NoMechanism.into()), Some(mechs) => mechs.children() .filter(|child| child.is("mechanism", NS_XMPP_SASL)) @@ -53,13 +55,18 @@ impl ClientAuth { let name = mech.name().to_owned(); if mech_names.iter().any(|name1| *name1 == name) { // println!("SASL mechanism selected: {:?}", name); - let initial = mech.initial()?; + let initial = match mech.initial() { + Ok(initial) => initial, + Err(e) => return Err(AuthError::Sasl(e).into()), + }; let mut this = ClientAuth { state: ClientAuthState::Invalid, mechanism: mech, }; - let mechanism = XMPPMechanism::from_str(&name) - .map_err(|e| format!("{:?}", e))?; + let mechanism = match XMPPMechanism::from_str(&name) { + Ok(mechanism) => mechanism, + Err(e) => return Err(ProtocolError::Parsers(e).into()), + }; this.send( stream, Auth { @@ -71,7 +78,7 @@ impl ClientAuth { } } - Err("No supported SASL mechanism available".to_owned()) + Err(AuthError::NoMechanism.into()) } fn send>(&mut self, stream: XMPPStream, nonza: N) { @@ -83,7 +90,7 @@ impl ClientAuth { impl Future for ClientAuth { type Item = XMPPStream; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll { let state = replace(&mut self.state, ClientAuthState::Invalid); @@ -100,13 +107,14 @@ impl Future for ClientAuth { Ok(Async::NotReady) }, Err(e) => - Err(format!("{}", e)), + Err(e.into()), }, ClientAuthState::WaitRecv(mut stream) => match stream.poll() { Ok(Async::Ready(Some(Packet::Stanza(stanza)))) => { if let Ok(challenge) = Challenge::try_from(stanza.clone()) { - let response = self.mechanism.response(&challenge.data)?; + let response = self.mechanism.response(&challenge.data) + .map_err(AuthError::Sasl)?; self.send(stream, Response { data: response }); self.poll() } else if let Ok(_) = Success::try_from(stanza.clone()) { @@ -114,8 +122,7 @@ impl Future for ClientAuth { self.state = ClientAuthState::Start(start); self.poll() } else if let Ok(failure) = Failure::try_from(stanza) { - let e = format!("{:?}", failure.defined_condition); - Err(e) + Err(AuthError::Fail(failure.defined_condition).into()) } else { Ok(Async::NotReady) } @@ -129,7 +136,7 @@ impl Future for ClientAuth { Ok(Async::NotReady) }, Err(e) => - Err(format!("{}", e)), + Err(ProtocolError::Parser(e).into()) }, ClientAuthState::Start(mut start) => match start.poll() { @@ -140,7 +147,7 @@ impl Future for ClientAuth { Ok(Async::NotReady) }, Err(e) => - Err(format!("{}", e)), + Err(e.into()) }, ClientAuthState::Invalid => unreachable!(), diff --git a/src/client/bind.rs b/src/client/bind.rs index 1ef268c..974860b 100644 --- a/src/client/bind.rs +++ b/src/client/bind.rs @@ -1,5 +1,4 @@ use std::mem::replace; -use std::error::Error; use futures::{Future, Poll, Async, sink, Stream}; use tokio_io::{AsyncRead, AsyncWrite}; use xmpp_parsers::iq::{Iq, IqType}; @@ -8,6 +7,7 @@ use try_from::TryFrom; use xmpp_codec::Packet; use xmpp_stream::XMPPStream; +use {Error, ProtocolError}; const NS_XMPP_BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind"; const BIND_REQ_ID: &str = "resource-bind"; @@ -42,7 +42,7 @@ impl ClientBind { impl Future for ClientBind { type Item = XMPPStream; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll { let state = replace(self, ClientBind::Invalid); @@ -61,7 +61,7 @@ impl Future for ClientBind { Ok(Async::NotReady) }, Err(e) => - Err(e.description().to_owned()), + Err(e.into()) } }, ClientBind::WaitRecv(mut stream) => { @@ -80,7 +80,7 @@ impl Future for ClientBind { Ok(Async::Ready(stream)) }, _ => - Err("resource bind response".to_owned()), + Err(ProtocolError::InvalidBindResponse.into()), } } else { Ok(Async::NotReady) @@ -96,7 +96,7 @@ impl Future for ClientBind { Ok(Async::NotReady) }, Err(e) => - Err(e.description().to_owned()), + Err(e.into()), } }, ClientBind::Invalid => diff --git a/src/client/mod.rs b/src/client/mod.rs index 44dc8d0..b50e539 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,11 +1,11 @@ use std::mem::replace; use std::str::FromStr; -use std::error::Error; +use std::error::Error as StdError; use tokio_core::reactor::Handle; use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_tls::TlsStream; -use futures::{future, Future, Stream, Poll, Async, Sink, StartSend, AsyncSink}; +use futures::{Future, Stream, Poll, Async, Sink, StartSend, AsyncSink, done}; use minidom::Element; use jid::{Jid, JidParseError}; use sasl::common::{Credentials, ChannelBinding}; @@ -16,6 +16,7 @@ use super::xmpp_stream; use super::starttls::{NS_XMPP_TLS, StartTlsClient}; use super::happy_eyeballs::Connecter; use super::event::Event; +use super::{Error, ProtocolError}; mod auth; use self::auth::ClientAuth; @@ -35,7 +36,7 @@ const NS_JABBER_CLIENT: &str = "jabber:client"; enum ClientState { Invalid, Disconnected, - Connecting(Box>), + Connecting(Box>), Connected(XMPPStream), } @@ -50,47 +51,47 @@ impl Client { let connect = Self::make_connect(jid.clone(), password.clone(), handle); Ok(Client { jid, - state: ClientState::Connecting(connect), + state: ClientState::Connecting(Box::new(connect)), }) } - fn make_connect(jid: Jid, password: String, handle: Handle) -> Box> { + fn make_connect(jid: Jid, password: String, handle: Handle) -> impl Future { let username = jid.node.as_ref().unwrap().to_owned(); let jid1 = jid.clone(); let jid2 = jid.clone(); let password = password; - let domain = match idna::domain_to_ascii(&jid.domain) { - Ok(domain) => - domain, - Err(e) => - return Box::new(future::err(format!("{:?}", e))), - }; - Box::new( - Connecter::from_lookup(handle, &domain, "_xmpp-client._tcp", 5222) - .expect("Connector::from_lookup") - .and_then(move |tcp_stream| - xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_CLIENT.to_owned()) - .map_err(|e| format!("{}", e)) - ).and_then(|xmpp_stream| { - if Self::can_starttls(&xmpp_stream) { - Ok(Self::starttls(xmpp_stream)) - } else { - Err("No STARTTLS".to_owned()) - } - }).and_then(|starttls| - starttls - ).and_then(|tls_stream| - XMPPStream::start(tls_stream, jid2, NS_JABBER_CLIENT.to_owned()) - .map_err(|e| format!("{}", e)) - ).and_then(move |xmpp_stream| { - Self::auth(xmpp_stream, username, password).expect("auth") - }).and_then(|xmpp_stream| { - Self::bind(xmpp_stream) - }).and_then(|xmpp_stream| { - // println!("Bound to {}", xmpp_stream.jid); - Ok(xmpp_stream) - }) - ) + done(idna::domain_to_ascii(&jid.domain)) + .map_err(|_| Error::Idna) + .and_then(|domain| + done(Connecter::from_lookup(handle, &domain, "_xmpp-client._tcp", 5222)) + .map_err(Error::Domain) + ) + .and_then(|connecter| + connecter + .map_err(Error::Connection) + ).and_then(move |tcp_stream| + xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_CLIENT.to_owned()) + ).and_then(|xmpp_stream| { + if Self::can_starttls(&xmpp_stream) { + Ok(Self::starttls(xmpp_stream)) + } else { + Err(Error::Protocol(ProtocolError::NoTls)) + } + }).and_then(|starttls| + // TODO: flatten? + starttls + ).and_then(|tls_stream| + XMPPStream::start(tls_stream, jid2, NS_JABBER_CLIENT.to_owned()) + ).and_then(move |xmpp_stream| + done(Self::auth(xmpp_stream, username, password)) + // TODO: flatten? + ).and_then(|auth| auth) + .and_then(|xmpp_stream| { + Self::bind(xmpp_stream) + }).and_then(|xmpp_stream| { + // println!("Bound to {}", xmpp_stream.jid); + Ok(xmpp_stream) + }) } fn can_starttls(stream: &xmpp_stream::XMPPStream) -> bool { @@ -103,7 +104,7 @@ impl Client { StartTlsClient::from_stream(stream) } - fn auth(stream: xmpp_stream::XMPPStream, username: String, password: String) -> Result, String> { + fn auth(stream: xmpp_stream::XMPPStream, username: String, password: String) -> Result, Error> { let creds = Credentials::default() .with_username(username) .with_password(password) @@ -118,14 +119,14 @@ impl Client { impl Stream for Client { type Item = Event; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { let state = replace(&mut self.state, ClientState::Invalid); match state { ClientState::Invalid => - Err("invalid client state".to_owned()), + Err(Error::InvalidState), ClientState::Disconnected => Ok(Async::Ready(None)), ClientState::Connecting(mut connect) => { @@ -148,7 +149,7 @@ impl Stream for Client { Ok(Async::NotReady) => (), Ok(Async::Ready(())) => (), Err(e) => - return Err(e.description().to_owned()), + return Err(Error::Io(e)), }; // Poll stream @@ -168,7 +169,7 @@ impl Stream for Client { Ok(Async::NotReady) }, Err(e) => - Err(e.description().to_owned()), + Err(e.into()), } }, } diff --git a/src/component/auth.rs b/src/component/auth.rs index 7801c92..3b66511 100644 --- a/src/component/auth.rs +++ b/src/component/auth.rs @@ -5,6 +5,7 @@ use xmpp_parsers::component::Handshake; use xmpp_codec::Packet; use xmpp_stream::XMPPStream; +use {Error, AuthError}; const NS_JABBER_COMPONENT_ACCEPT: &str = "jabber:component:accept"; @@ -19,7 +20,8 @@ enum ComponentAuthState { } impl ComponentAuth { - pub fn new(stream: XMPPStream, password: String) -> Result { + // TODO: doesn't have to be a Result<> actually + pub fn new(stream: XMPPStream, password: String) -> Result { // FIXME: huge hack, shouldn’t be an element! let sid = stream.stream_features.name().to_owned(); let mut this = ComponentAuth { @@ -42,7 +44,7 @@ impl ComponentAuth { impl Future for ComponentAuth { type Item = XMPPStream; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll { let state = replace(&mut self.state, ComponentAuthState::Invalid); @@ -59,7 +61,7 @@ impl Future for ComponentAuth { Ok(Async::NotReady) }, Err(e) => - Err(format!("{}", e)), + Err(e.into()), }, ComponentAuthState::WaitRecv(mut stream) => match stream.poll() { @@ -72,8 +74,7 @@ impl Future for ComponentAuth { Ok(Async::Ready(Some(Packet::Stanza(ref stanza)))) if stanza.is("error", "http://etherx.jabber.org/streams") => { - let e = "Authentication failure"; - Err(e.to_owned()) + Err(AuthError::ComponentFail.into()) }, Ok(Async::Ready(event)) => { println!("ComponentAuth ignore {:?}", event); @@ -84,7 +85,7 @@ impl Future for ComponentAuth { Ok(Async::NotReady) }, Err(e) => - Err(format!("{}", e)), + Err(e.into()), }, ComponentAuthState::Invalid => unreachable!(), diff --git a/src/component/mod.rs b/src/component/mod.rs index bf9a005..08ea634 100644 --- a/src/component/mod.rs +++ b/src/component/mod.rs @@ -3,11 +3,11 @@ //! allowed to use any user and resource identifiers in their stanzas. use std::mem::replace; use std::str::FromStr; -use std::error::Error; +use std::error::Error as StdError; use tokio_core::reactor::Handle; use tokio_core::net::TcpStream; use tokio_io::{AsyncRead, AsyncWrite}; -use futures::{Future, Stream, Poll, Async, Sink, StartSend, AsyncSink}; +use futures::{Future, Stream, Poll, Async, Sink, StartSend, AsyncSink, done}; use minidom::Element; use jid::{Jid, JidParseError}; @@ -15,6 +15,7 @@ use super::xmpp_codec::Packet; use super::xmpp_stream; use super::happy_eyeballs::Connecter; use super::event::Event; +use super::Error; mod auth; use self::auth::ComponentAuth; @@ -32,7 +33,7 @@ const NS_JABBER_COMPONENT_ACCEPT: &str = "jabber:component:accept"; enum ComponentState { Invalid, Disconnected, - Connecting(Box>), + Connecting(Box>), Connected(XMPPStream), } @@ -47,43 +48,39 @@ impl Component { let connect = Self::make_connect(jid.clone(), password, server, port, handle); Ok(Component { jid, - state: ComponentState::Connecting(connect), + state: ComponentState::Connecting(Box::new(connect)), }) } - fn make_connect(jid: Jid, password: String, server: &str, port: u16, handle: Handle) -> Box> { + fn make_connect(jid: Jid, password: String, server: &str, port: u16, handle: Handle) -> impl Future { let jid1 = jid.clone(); let password = password; - Box::new( - Connecter::from_lookup(handle, server, "_xmpp-component._tcp", port) - .expect("Connector::from_lookup") - .and_then(move |tcp_stream| { - xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_COMPONENT_ACCEPT.to_owned()) - .map_err(|e| format!("{}", e)) - }).and_then(move |xmpp_stream| { - Self::auth(xmpp_stream, password).expect("auth") - }).and_then(|xmpp_stream| { - // println!("Bound to {}", xmpp_stream.jid); - Ok(xmpp_stream) - }) - ) + done(Connecter::from_lookup(handle, server, "_xmpp-component._tcp", port)) + .map_err(Error::Domain) + .and_then(|connecter| connecter + .map_err(Error::Connection) + ).and_then(move |tcp_stream| { + xmpp_stream::XMPPStream::start(tcp_stream, jid1, NS_JABBER_COMPONENT_ACCEPT.to_owned()) + }).and_then(move |xmpp_stream| { + Self::auth(xmpp_stream, password).expect("auth") + }) } - fn auth(stream: xmpp_stream::XMPPStream, password: String) -> Result, String> { + fn auth(stream: xmpp_stream::XMPPStream, password: String) -> Result, Error> { ComponentAuth::new(stream, password) } } impl Stream for Component { type Item = Event; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll, Self::Error> { let state = replace(&mut self.state, ComponentState::Invalid); match state { ComponentState::Invalid => - Err("invalid client state".to_owned()), + Err(Error::InvalidState), ComponentState::Disconnected => Ok(Async::Ready(None)), ComponentState::Connecting(mut connect) => { @@ -106,7 +103,7 @@ impl Stream for Component { Ok(Async::NotReady) => (), Ok(Async::Ready(())) => (), Err(e) => - return Err(e.description().to_owned()), + return Err(e.into()), }; // Poll stream @@ -129,7 +126,7 @@ impl Stream for Component { Ok(Async::NotReady) }, Err(e) => - Err(e.description().to_owned()), + Err(e.into()), } }, } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..7384f92 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,94 @@ +use std::io::Error as IoError; +use std::error::Error as StdError; +use std::str::Utf8Error; +use std::borrow::Cow; +use std::fmt; +use domain::resolv::error::Error as DNSError; +use domain::bits::name::FromStrError; +use native_tls::Error as TlsError; +use xmpp_parsers::error::Error as ParsersError; +use xmpp_parsers::sasl::DefinedCondition as SaslDefinedCondition; + +#[derive(Debug, Error)] +pub enum Error { + Io(IoError), + Connection(ConnecterError), + /// DNS label conversion error, no details available from module + /// `idna` + Idna, + Domain(FromStrError), + Protocol(ProtocolError), + Auth(AuthError), + Tls(TlsError), + /// Shoud never happen + InvalidState, +} + +/// Causes for stream parsing errors +#[derive(Debug, Error)] +pub enum ParserError { + /// Encoding error + Utf8(Utf8Error), + /// XML parse error + Parse(ParseError), + /// Illegal `` + ShortTag, + /// Required by `impl Decoder` + IO(IoError), +} + +impl From for Error { + fn from(e: ParserError) -> Self { + ProtocolError::Parser(e).into() + } +} + +/// XML parse error wrapper type +#[derive(Debug)] +pub struct ParseError(pub Cow<'static, str>); + +impl StdError for ParseError { + fn description(&self) -> &str { + self.0.as_ref() + } + fn cause(&self) -> Option<&StdError> { + None + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[derive(Debug, Error)] +pub enum ProtocolError { + Parser(ParserError), + #[error(non_std)] + Parsers(ParsersError), + NoTls, + InvalidBindResponse, + NoStreamNamespace, + NoStreamId, + InvalidToken, +} + +#[derive(Debug, Error)] +pub enum AuthError { + /// No SASL mechanism available + NoMechanism, + #[error(no_from, non_std, msg_embedded)] + Sasl(String), + #[error(non_std)] + Fail(SaslDefinedCondition), + #[error(no_from)] + ComponentFail, +} + +#[derive(Debug, Error)] +pub enum ConnecterError { + NoSrv, + AllFailed, + DNS(DNSError), +} diff --git a/src/happy_eyeballs.rs b/src/happy_eyeballs.rs index 7b95f82..4980793 100644 --- a/src/happy_eyeballs.rs +++ b/src/happy_eyeballs.rs @@ -6,7 +6,8 @@ use tokio_core::reactor::Handle; use tokio_core::net::{TcpStream, TcpStreamNew}; use domain::resolv::Resolver; use domain::resolv::lookup::srv::{lookup_srv, LookupSrv, LookupSrvStream}; -use domain::bits::DNameBuf; +use domain::bits::name::{DNameBuf, FromStrError}; +use ConnecterError; pub struct Connecter { handle: Handle, @@ -17,11 +18,9 @@ pub struct Connecter { } impl Connecter { - pub fn from_lookup(handle: Handle, domain: &str, srv: &str, fallback_port: u16) -> Result { - let domain = DNameBuf::from_str(domain) - .map_err(|e| format!("{}", e))?; - let srv = DNameBuf::from_str(srv) - .map_err(|e| format!("{}", e))?; + pub fn from_lookup(handle: Handle, domain: &str, srv: &str, fallback_port: u16) -> Result { + let domain = DNameBuf::from_str(domain)?; + let srv = DNameBuf::from_str(srv)?; let resolver = Resolver::new(&handle); let lookup = lookup_srv(resolver.clone(), srv, domain, fallback_port); @@ -38,7 +37,7 @@ impl Connecter { impl Future for Connecter { type Item = TcpStream; - type Error = String; + type Error = ConnecterError; fn poll(&mut self) -> Poll { match self.lookup.as_mut().map(|lookup| lookup.poll()) { @@ -49,11 +48,11 @@ impl Future for Connecter { Some(srvs) => self.srvs = Some(srvs.to_stream(self.resolver.clone())), None => - return Err("No SRV records".to_owned()), + return Err(ConnecterError::NoSrv), } }, Some(Err(e)) => - return Err(format!("{}", e)), + return Err(e.into()), } match self.srvs.as_mut().map(|srv| srv.poll()) { @@ -71,7 +70,7 @@ impl Future for Connecter { } }, Some(Err(e)) => - return Err(format!("{}", e)), + return Err(e.into()), } let mut connected_stream = None; @@ -101,7 +100,7 @@ impl Future for Connecter { self.srvs.is_none() && self.connects.is_empty() { - return Err("All connection attempts failed".to_owned()); + return Err(ConnecterError::AllFailed); } Ok(Async::NotReady) diff --git a/src/lib.rs b/src/lib.rs index ebba1f1..19a0da6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_code, unused, missing_docs)] +// #![deny(unsafe_code, unused, missing_docs)] //! XMPP implemeentation with asynchronous I/O using Tokio. @@ -18,6 +18,8 @@ extern crate domain; extern crate idna; extern crate xmpp_parsers; extern crate try_from; +#[macro_use] +extern crate derive_error; pub mod xmpp_codec; pub mod xmpp_stream; @@ -31,3 +33,5 @@ mod client; pub use client::Client; mod component; pub use component::Component; +mod error; +pub use error::{Error, ProtocolError, AuthError, ConnecterError, ParseError, ParserError}; diff --git a/src/starttls.rs b/src/starttls.rs index 6ac23ac..444a9cb 100644 --- a/src/starttls.rs +++ b/src/starttls.rs @@ -10,6 +10,7 @@ use jid::Jid; use xmpp_codec::Packet; use xmpp_stream::XMPPStream; +use Error; /// XMPP TLS XML namespace pub const NS_XMPP_TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls"; @@ -48,7 +49,7 @@ impl StartTlsClient { impl Future for StartTlsClient { type Item = TlsStream; - type Error = String; + type Error = Error; fn poll(&mut self) -> Poll { let old_state = replace(&mut self.state, StartTlsClientState::Invalid); @@ -65,7 +66,7 @@ impl Future for StartTlsClient { Ok(Async::NotReady) => (StartTlsClientState::SendStartTls(send), Ok(Async::NotReady)), Err(e) => - (StartTlsClientState::SendStartTls(send), Err(format!("{}", e))), + (StartTlsClientState::SendStartTls(send), Err(e.into())), }, StartTlsClientState::AwaitProceed(mut xmpp_stream) => match xmpp_stream.poll() { @@ -87,7 +88,7 @@ impl Future for StartTlsClient { Ok(_) => (StartTlsClientState::AwaitProceed(xmpp_stream), Ok(Async::NotReady)), Err(e) => - (StartTlsClientState::AwaitProceed(xmpp_stream), Err(format!("{}", e))), + (StartTlsClientState::AwaitProceed(xmpp_stream), Err(Error::Protocol(e.into()))), }, StartTlsClientState::StartingTls(mut connect) => match connect.poll() { @@ -96,7 +97,7 @@ impl Future for StartTlsClient { Ok(Async::NotReady) => (StartTlsClientState::StartingTls(connect), Ok(Async::NotReady)), Err(e) => - (StartTlsClientState::Invalid, Err(format!("{}", e))), + (StartTlsClientState::Invalid, Err(e.into())), }, StartTlsClientState::Invalid => unreachable!(), diff --git a/src/stream_start.rs b/src/stream_start.rs index aa1284e..8871840 100644 --- a/src/stream_start.rs +++ b/src/stream_start.rs @@ -1,13 +1,15 @@ use std::mem::replace; -use std::borrow::Cow; +// use std::error::Error as StdError; +// use std::{fmt, io}; use futures::{Future, Async, Poll, Stream, sink, Sink}; use tokio_io::{AsyncRead, AsyncWrite}; use tokio_codec::Framed; use jid::Jid; use minidom::Element; -use xmpp_codec::{XMPPCodec, Packet, ParserError}; +use xmpp_codec::{XMPPCodec, Packet}; use xmpp_stream::XMPPStream; +use {Error, ProtocolError}; const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams"; @@ -43,7 +45,7 @@ impl StreamStart { impl Future for StreamStart { type Item = XMPPStream; - type Error = ParserError; + type Error = Error; fn poll(&mut self) -> Poll { let old_state = replace(&mut self.state, StreamStartState::Invalid); @@ -67,7 +69,7 @@ impl Future for StreamStart { let stream_ns = match stream_attrs.get("xmlns") { Some(ns) => ns.clone(), None => - return Err(ParserError::Parse(Cow::from("Missing stream namespace"))), + return Err(ProtocolError::NoStreamNamespace.into()), }; if self.ns == "jabber:client" { retry = true; @@ -77,7 +79,7 @@ impl Future for StreamStart { let id = match stream_attrs.get("id") { Some(id) => id.clone(), None => - return Err(ParserError::Parse(Cow::from("No stream id"))), + return Err(ProtocolError::NoStreamId.into()), }; // FIXME: huge hack, shouldn’t be an element! let stream = XMPPStream::new(self.jid.clone(), stream, self.ns.clone(), Element::builder(id).build()); @@ -85,11 +87,11 @@ impl Future for StreamStart { } }, Ok(Async::Ready(_)) => - return Err(ParserError::Parse(Cow::from("Invalid XML event received"))), + return Err(ProtocolError::InvalidToken.into()), Ok(Async::NotReady) => (StreamStartState::RecvStart(stream), Ok(Async::NotReady)), Err(e) => - return Err(e), + return Err(ProtocolError::from(e).into()), }, StreamStartState::RecvFeatures(mut stream, stream_ns) => match stream.poll() { @@ -103,7 +105,7 @@ impl Future for StreamStart { Ok(Async::Ready(_)) | Ok(Async::NotReady) => (StreamStartState::RecvFeatures(stream, stream_ns), Ok(Async::NotReady)), Err(e) => - return Err(e), + return Err(ProtocolError::from(e).into()), }, StreamStartState::Invalid => unreachable!(), @@ -117,3 +119,59 @@ impl Future for StreamStart { } } } + +// #[derive(Debug)] +// pub enum StreamStartError { +// MissingStreamNs, +// MissingStreamId, +// Unexpected, +// Parser(ParserError), +// IO(io::Error), +// } + +// impl From for StreamStartError { +// fn from(e: io::Error) -> Self { +// StreamStartError::IO(e) +// } +// } + +// impl From for StreamStartError { +// fn from(e: ParserError) -> Self { +// match e { +// ParserError::IO(e) => StreamStartError::IO(e), +// _ => StreamStartError::Parser(e) +// } +// } +// } + +// impl StdError for StreamStartError { +// fn description(&self) -> &str { +// match *self { +// StreamStartError::MissingStreamNs => "Missing stream namespace", +// StreamStartError::MissingStreamId => "Missing stream id", +// StreamStartError::Unexpected => "Unexpected", +// StreamStartError::Parser(ref pe) => pe.description(), +// StreamStartError::IO(ref ie) => ie.description(), +// } +// } + +// fn cause(&self) -> Option<&StdError> { +// match *self { +// StreamStartError::Parser(ref pe) => pe.cause(), +// StreamStartError::IO(ref ie) => ie.cause(), +// _ => None, +// } +// } +// } + +// impl fmt::Display for StreamStartError { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// match *self { +// StreamStartError::MissingStreamNs => write!(f, "Missing stream namespace"), +// StreamStartError::MissingStreamId => write!(f, "Missing stream id"), +// StreamStartError::Unexpected => write!(f, "Received unexpected data"), +// StreamStartError::Parser(ref pe) => write!(f, "{}", pe), +// StreamStartError::IO(ref ie) => write!(f, "{}", ie), +// } +// } +// } diff --git a/src/xmpp_codec.rs b/src/xmpp_codec.rs index 09ea1ea..37e5a78 100644 --- a/src/xmpp_codec.rs +++ b/src/xmpp_codec.rs @@ -6,19 +6,17 @@ use std::iter::FromIterator; use std::cell::RefCell; use std::rc::Rc; use std::fmt::Write; -use std::str::{from_utf8, Utf8Error}; +use std::str::from_utf8; use std::io; use std::collections::HashMap; use std::collections::vec_deque::VecDeque; -use std::error::Error as StdError; -use std::fmt; -use std::borrow::Cow; use tokio_codec::{Encoder, Decoder}; use minidom::Element; use xml5ever::tokenizer::{XmlTokenizer, TokenSink, Token, Tag, TagKind}; use xml5ever::interface::Attribute; use bytes::{BytesMut, BufMut}; use quick_xml::Writer as EventWriter; +use {ParserError, ParseError}; /// Anything that can be sent or received on an XMPP/XML stream #[derive(Debug)] @@ -33,55 +31,6 @@ pub enum Packet { StreamEnd, } -/// Causes for stream parsing errors -#[derive(Debug)] -pub enum ParserError { - /// Encoding error - Utf8(Utf8Error), - /// XML parse error - Parse(Cow<'static, str>), - /// Illegal `` - ShortTag, - /// Required by `impl Decoder` - IO(io::Error), -} - -impl From for ParserError { - fn from(e: io::Error) -> Self { - ParserError::IO(e) - } -} - -impl StdError for ParserError { - fn description(&self) -> &str { - match *self { - ParserError::Utf8(ref ue) => ue.description(), - ParserError::Parse(ref pe) => pe, - ParserError::ShortTag => "short tag", - ParserError::IO(ref ie) => ie.description(), - } - } - - fn cause(&self) -> Option<&StdError> { - match *self { - ParserError::Utf8(ref ue) => ue.cause(), - ParserError::IO(ref ie) => ie.cause(), - _ => None, - } - } -} - -impl fmt::Display for ParserError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ParserError::Utf8(ref ue) => write!(f, "{}", ue), - ParserError::Parse(ref pe) => write!(f, "{}", pe), - ParserError::ShortTag => write!(f, "Short tag"), - ParserError::IO(ref ie) => write!(f, "{}", ie), - } - } -} - type QueueItem = Result; /// Parser state @@ -220,7 +169,7 @@ impl TokenSink for ParserSink { self.push_queue(Packet::StreamEnd), Token::ParseError(s) => { // println!("ParseError: {:?}", s); - self.push_queue_error(ParserError::Parse(s)); + self.push_queue_error(ParserError::Parse(ParseError(s))); }, _ => (), }