diff --git a/examples/echo_bot.rs b/examples/echo_bot.rs index 4f5f044..df33cf0 100644 --- a/examples/echo_bot.rs +++ b/examples/echo_bot.rs @@ -35,6 +35,9 @@ fn main() { let username = jid.node.as_ref().unwrap().to_owned(); stream.auth(username, password).expect("auth") }).and_then(|stream| { + stream.bind() + }).and_then(|stream| { + println!("Bound to {}", stream.jid); stream.for_each(|event| { match event { Packet::Stanza(el) => println!("<< {}", el), diff --git a/src/client_bind.rs b/src/client_bind.rs new file mode 100644 index 0000000..24238f6 --- /dev/null +++ b/src/client_bind.rs @@ -0,0 +1,138 @@ +use std::mem::replace; +use std::error::Error; +use std::str::FromStr; +use futures::*; +use futures::sink; +use tokio_io::{AsyncRead, AsyncWrite}; +use xml; +use jid::Jid; + +use xmpp_codec::*; +use xmpp_stream::*; + +const NS_XMPP_BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind"; +const BIND_REQ_ID: &str = "resource-bind"; + +pub enum ClientBind { + Unsupported(XMPPStream), + WaitSend(sink::Send>), + WaitRecv(XMPPStream), + Invalid, +} + +impl ClientBind { + /// Consumes and returns the stream to express that you cannot use + /// the stream for anything else until the resource binding + /// req/resp are done. + pub fn new(stream: XMPPStream) -> Self { + match stream.stream_features.get_child("bind", Some(NS_XMPP_BIND)) { + None => + // No resource binding available, + // return the (probably // usable) stream immediately + ClientBind::Unsupported(stream), + Some(_) => { + println!("Bind is supported!"); + + let iq = make_bind_request(stream.jid.resource.as_ref()); + println!("Send {}", iq); + let send = stream.send(Packet::Stanza(iq)); + ClientBind::WaitSend(send) + }, + } + } +} + +fn make_bind_request(resource: Option<&String>) -> xml::Element { + let mut iq = xml::Element::new( + "iq".to_owned(), + None, + vec![("type".to_owned(), None, "set".to_owned()), + ("id".to_owned(), None, BIND_REQ_ID.to_owned())] + ); + { + let bind_el = iq.tag( + xml::Element::new( + "bind".to_owned(), + Some(NS_XMPP_BIND.to_owned()), + vec![] + )); + resource.map(|resource| { + let resource_el = bind_el.tag( + xml::Element::new( + "resource".to_owned(), + Some(NS_XMPP_BIND.to_owned()), + vec![] + )); + resource_el.text(resource.clone()); + }); + } + iq +} + +impl Future for ClientBind { + type Item = XMPPStream; + type Error = String; + + fn poll(&mut self) -> Poll { + let state = replace(self, ClientBind::Invalid); + + match state { + ClientBind::Unsupported(stream) => + Ok(Async::Ready(stream)), + ClientBind::WaitSend(mut send) => { + match send.poll() { + Ok(Async::Ready(stream)) => { + replace(self, ClientBind::WaitRecv(stream)); + self.poll() + }, + Ok(Async::NotReady) => { + replace(self, ClientBind::WaitSend(send)); + Ok(Async::NotReady) + }, + Err(e) => + Err(e.description().to_owned()), + } + }, + ClientBind::WaitRecv(mut stream) => { + match stream.poll() { + Ok(Async::Ready(Some(Packet::Stanza(ref iq)))) + if iq.name == "iq" + && iq.get_attribute("id", None) == Some(BIND_REQ_ID) => { + match iq.get_attribute("type", None) { + Some("result") => { + get_bind_response_jid(&iq) + .map(|jid| stream.jid = jid); + Ok(Async::Ready(stream)) + }, + _ => + Err("resource bind response".to_owned()), + } + }, + Ok(Async::Ready(_)) => { + replace(self, ClientBind::WaitRecv(stream)); + self.poll() + }, + Ok(_) => { + replace(self, ClientBind::WaitRecv(stream)); + Ok(Async::NotReady) + }, + Err(e) => + Err(e.description().to_owned()), + } + }, + ClientBind::Invalid => + unreachable!(), + } + } +} + +fn get_bind_response_jid(iq: &xml::Element) -> Option { + iq.get_child("bind", Some(NS_XMPP_BIND)) + .and_then(|bind_el| + bind_el.get_child("jid", Some(NS_XMPP_BIND)) + ) + .and_then(|jid_el| + Jid::from_str(&jid_el.content_str()) + .ok() + ) +} diff --git a/src/lib.rs b/src/lib.rs index e04778a..12b710e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ mod starttls; pub use starttls::*; mod client_auth; pub use client_auth::*; +mod client_bind; +pub use client_bind::*; // type FullClient = sasl::Client> diff --git a/src/xmpp_stream.rs b/src/xmpp_stream.rs index c84777f..6eb2b24 100644 --- a/src/xmpp_stream.rs +++ b/src/xmpp_stream.rs @@ -11,6 +11,7 @@ use xmpp_codec::*; use stream_start::*; use starttls::{NS_XMPP_TLS, StartTlsClient}; use client_auth::ClientAuth; +use client_bind::ClientBind; pub const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams"; @@ -59,6 +60,10 @@ impl XMPPStream { .with_channel_binding(ChannelBinding::None); ClientAuth::new(self, creds) } + + pub fn bind(self) -> ClientBind { + ClientBind::new(self) + } } /// Proxy to self.stream