This commit is contained in:
Astro 2018-08-02 19:58:19 +02:00
parent 0e4941dc7a
commit b929a3c71e
7 changed files with 57 additions and 3 deletions

View file

@ -22,7 +22,9 @@ use self::auth::ClientAuth;
mod bind; mod bind;
use self::bind::ClientBind; use self::bind::ClientBind;
/// XMPP client connection and state
pub struct Client { pub struct Client {
/// The client's current Jabber-Id
pub jid: Jid, pub jid: Jid,
state: ClientState, state: ClientState,
} }
@ -38,6 +40,10 @@ enum ClientState {
} }
impl Client { impl Client {
/// Start a new XMPP client
///
/// Start polling the returned instance so that it will connect
/// and yield events.
pub fn new(jid: &str, password: &str, handle: Handle) -> Result<Self, JidParseError> { pub fn new(jid: &str, password: &str, handle: Handle) -> Result<Self, JidParseError> {
let jid = try!(Jid::from_str(jid)); let jid = try!(Jid::from_str(jid));
let password = password.to_owned(); let password = password.to_owned();

View file

@ -1,3 +1,6 @@
//! Components in XMPP are services/gateways that are logged into an
//! XMPP server under a JID consisting of just a domain name. They are
//! allowed to use any user and resource identifiers in their stanzas.
use std::mem::replace; use std::mem::replace;
use std::str::FromStr; use std::str::FromStr;
use std::error::Error; use std::error::Error;
@ -16,7 +19,9 @@ use super::event::Event;
mod auth; mod auth;
use self::auth::ComponentAuth; use self::auth::ComponentAuth;
/// Component connection to an XMPP server
pub struct Component { pub struct Component {
/// The component's Jabber-Id
pub jid: Jid, pub jid: Jid,
state: ComponentState, state: ComponentState,
} }
@ -32,6 +37,10 @@ enum ComponentState {
} }
impl Component { impl Component {
/// Start a new XMPP component
///
/// Start polling the returned instance so that it will connect
/// and yield events.
pub fn new(jid: &str, password: &str, server: &str, port: u16, handle: Handle) -> Result<Self, JidParseError> { pub fn new(jid: &str, password: &str, server: &str, port: u16, handle: Handle) -> Result<Self, JidParseError> {
let jid = try!(Jid::from_str(jid)); let jid = try!(Jid::from_str(jid));
let password = password.to_owned(); let password = password.to_owned();

View file

@ -1,13 +1,18 @@
use minidom::Element; use minidom::Element;
/// High-level event on the Stream implemented by Client and Component
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
/// Stream is connected and initialized
Online, Online,
/// Stream end
Disconnected, Disconnected,
/// Received stanza/nonza
Stanza(Element), Stanza(Element),
} }
impl Event { impl Event {
/// `Online` event?
pub fn is_online(&self) -> bool { pub fn is_online(&self) -> bool {
match *self { match *self {
Event::Online => true, Event::Online => true,
@ -15,6 +20,7 @@ impl Event {
} }
} }
/// `Stanza` event?
pub fn is_stanza(&self, name: &str) -> bool { pub fn is_stanza(&self, name: &str) -> bool {
match *self { match *self {
Event::Stanza(ref stanza) => stanza.name() == name, Event::Stanza(ref stanza) => stanza.name() == name,
@ -22,6 +28,7 @@ impl Event {
} }
} }
/// If this is a `Stanza` event, get its data
pub fn as_stanza(&self) -> Option<&Element> { pub fn as_stanza(&self) -> Option<&Element> {
match *self { match *self {
Event::Stanza(ref stanza) => Some(stanza), Event::Stanza(ref stanza) => Some(stanza),
@ -29,6 +36,7 @@ impl Event {
} }
} }
/// If this is a `Stanza` event, unwrap into its data
pub fn into_stanza(self) -> Option<Element> { pub fn into_stanza(self) -> Option<Element> {
match self { match self {
Event::Stanza(stanza) => Some(stanza), Event::Stanza(stanza) => Some(stanza),

View file

@ -1,3 +1,7 @@
#![deny(unsafe_code, unused, missing_docs)]
//! XMPP implemeentation with asynchronous I/O using Tokio.
extern crate futures; extern crate futures;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;

View file

@ -11,10 +11,11 @@ use jid::Jid;
use xmpp_codec::Packet; use xmpp_codec::Packet;
use xmpp_stream::XMPPStream; use xmpp_stream::XMPPStream;
/// XMPP TLS XML namespace
pub const NS_XMPP_TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls"; pub const NS_XMPP_TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls";
/// XMPP stream that switches to TLS if available in received features
pub struct StartTlsClient<S: AsyncRead + AsyncWrite> { pub struct StartTlsClient<S: AsyncRead + AsyncWrite> {
state: StartTlsClientState<S>, state: StartTlsClientState<S>,
jid: Jid, jid: Jid,

View file

@ -1,3 +1,5 @@
//! XML stream parser for XMPP
use std; use std;
use std::default::Default; use std::default::Default;
use std::iter::FromIterator; use std::iter::FromIterator;
@ -15,17 +17,22 @@ use xml5ever::interface::Attribute;
use bytes::{BytesMut, BufMut}; use bytes::{BytesMut, BufMut};
use quick_xml::Writer as EventWriter; use quick_xml::Writer as EventWriter;
// const NS_XMLNS: &'static str = "http://www.w3.org/2000/xmlns/"; /// Anything that can be sent or received on an XMPP/XML stream
#[derive(Debug)] #[derive(Debug)]
pub enum Packet { pub enum Packet {
/// General error (`InvalidInput`)
Error(Box<std::error::Error>), Error(Box<std::error::Error>),
/// `<stream:stream>` start tag
StreamStart(HashMap<String, String>), StreamStart(HashMap<String, String>),
/// A complete stanza or nonza
Stanza(Element), Stanza(Element),
/// Plain text (think whitespace keep-alive)
Text(String), Text(String),
/// `</stream:stream>` closing tag
StreamEnd, StreamEnd,
} }
/// Parser state
struct ParserSink { struct ParserSink {
// Ready stanzas, shared with XMPPCodec // Ready stanzas, shared with XMPPCodec
queue: Rc<RefCell<VecDeque<Packet>>>, queue: Rc<RefCell<VecDeque<Packet>>>,
@ -47,6 +54,7 @@ impl ParserSink {
self.queue.borrow_mut().push_back(pkt); self.queue.borrow_mut().push_back(pkt);
} }
/// Lookup XML namespace declaration for given prefix (or no prefix)
fn lookup_ns(&self, prefix: &Option<String>) -> Option<&str> { fn lookup_ns(&self, prefix: &Option<String>) -> Option<&str> {
for nss in self.ns_stack.iter().rev() { for nss in self.ns_stack.iter().rev() {
if let Some(ns) = nss.get(prefix) { if let Some(ns) = nss.get(prefix) {
@ -166,6 +174,7 @@ impl TokenSink for ParserSink {
// } // }
} }
/// Stateful encoder/decoder for a bytestream from/to XMPP `Packet`
pub struct XMPPCodec { pub struct XMPPCodec {
/// Outgoing /// Outgoing
ns: Option<String>, ns: Option<String>,
@ -179,6 +188,7 @@ pub struct XMPPCodec {
} }
impl XMPPCodec { impl XMPPCodec {
/// Constructor
pub fn new() -> Self { pub fn new() -> Self {
let queue = Rc::new(RefCell::new(VecDeque::new())); let queue = Rc::new(RefCell::new(VecDeque::new()));
let sink = ParserSink::new(queue.clone()); let sink = ParserSink::new(queue.clone());
@ -300,6 +310,7 @@ impl Encoder for XMPPCodec {
} }
} }
/// Write XML-escaped text string
pub fn write_text<W: Write>(text: &str, writer: &mut W) -> Result<(), std::fmt::Error> { pub fn write_text<W: Write>(text: &str, writer: &mut W) -> Result<(), std::fmt::Error> {
write!(writer, "{}", escape(text)) write!(writer, "{}", escape(text))
} }

View file

@ -1,3 +1,5 @@
//! `XMPPStream` is the common container for all XMPP network connections
use futures::{Poll, Stream, Sink, StartSend}; use futures::{Poll, Stream, Sink, StartSend};
use futures::sink::Send; use futures::sink::Send;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
@ -8,16 +10,26 @@ use jid::Jid;
use xmpp_codec::{XMPPCodec, Packet}; use xmpp_codec::{XMPPCodec, Packet};
use stream_start::StreamStart; use stream_start::StreamStart;
/// <stream:stream> namespace
pub const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams"; pub const NS_XMPP_STREAM: &str = "http://etherx.jabber.org/streams";
/// Wraps a `stream`
pub struct XMPPStream<S> { pub struct XMPPStream<S> {
/// The local Jabber-Id
pub jid: Jid, pub jid: Jid,
/// Codec instance
pub stream: Framed<S, XMPPCodec>, pub stream: Framed<S, XMPPCodec>,
/// `<stream:features/>` for XMPP version 1.0
pub stream_features: Element, pub stream_features: Element,
/// Root namespace
///
/// This is different for either c2s, s2s, or component
/// connections.
pub ns: String, pub ns: String,
} }
impl<S: AsyncRead + AsyncWrite> XMPPStream<S> { impl<S: AsyncRead + AsyncWrite> XMPPStream<S> {
/// Constructor
pub fn new(jid: Jid, pub fn new(jid: Jid,
stream: Framed<S, XMPPCodec>, stream: Framed<S, XMPPCodec>,
ns: String, ns: String,
@ -25,15 +37,18 @@ impl<S: AsyncRead + AsyncWrite> XMPPStream<S> {
XMPPStream { jid, stream, stream_features, ns } XMPPStream { jid, stream, stream_features, ns }
} }
/// Send a `<stream:stream>` start tag
pub fn start(stream: S, jid: Jid, ns: String) -> StreamStart<S> { pub fn start(stream: S, jid: Jid, ns: String) -> StreamStart<S> {
let xmpp_stream = Framed::new(stream, XMPPCodec::new()); let xmpp_stream = Framed::new(stream, XMPPCodec::new());
StreamStart::from_stream(xmpp_stream, jid, ns) StreamStart::from_stream(xmpp_stream, jid, ns)
} }
/// Unwraps the inner stream
pub fn into_inner(self) -> S { pub fn into_inner(self) -> S {
self.stream.into_inner() self.stream.into_inner()
} }
/// Re-run `start()`
pub fn restart(self) -> StreamStart<S> { pub fn restart(self) -> StreamStart<S> {
Self::start(self.stream.into_inner(), self.jid, self.ns) Self::start(self.stream.into_inner(), self.jid, self.ns)
} }