From e6863bd9ddf00b13e01f5125b70ed6f6036bfd6b Mon Sep 17 00:00:00 2001 From: xmppftw Date: Sun, 11 Aug 2024 11:53:20 +0200 Subject: [PATCH] Reorganized client modules --- tokio-xmpp/src/client/connect.rs | 37 ----- tokio-xmpp/src/client/{auth.rs => login.rs} | 37 ++++- tokio-xmpp/src/client/mod.rs | 143 +++++++++++++++++- .../src/client/{async_client.rs => stream.rs} | 139 +---------------- tokio-xmpp/src/lib.rs | 5 +- 5 files changed, 183 insertions(+), 178 deletions(-) delete mode 100644 tokio-xmpp/src/client/connect.rs rename tokio-xmpp/src/client/{auth.rs => login.rs} (75%) rename tokio-xmpp/src/client/{async_client.rs => stream.rs} (62%) diff --git a/tokio-xmpp/src/client/connect.rs b/tokio-xmpp/src/client/connect.rs deleted file mode 100644 index 7e02e1000f5b3f1977c5bb212779e629c440d133..0000000000000000000000000000000000000000 --- a/tokio-xmpp/src/client/connect.rs +++ /dev/null @@ -1,37 +0,0 @@ -use sasl::common::Credentials; -use xmpp_parsers::{jid::Jid, ns}; - -use crate::{ - client::{auth::auth, bind::bind}, - connect::ServerConnector, - proto::XmppStream, - Error, -}; - -/// Log into an XMPP server as a client with a jid+pass -/// does channel binding if supported -pub async fn client_login( - server: C, - jid: Jid, - password: String, -) -> Result, Error> { - let username = jid.node().unwrap().as_str(); - let password = password; - - let xmpp_stream = server.connect(&jid, ns::JABBER_CLIENT).await?; - - let channel_binding = C::channel_binding(xmpp_stream.stream.get_ref())?; - - let creds = Credentials::default() - .with_username(username) - .with_password(password) - .with_channel_binding(channel_binding); - // Authenticated (unspecified) stream - let stream = auth(xmpp_stream, creds).await?; - // Authenticated XmppStream - let xmpp_stream = XmppStream::start(stream, jid, ns::JABBER_CLIENT.to_owned()).await?; - - // XmppStream bound to user session - let xmpp_stream = bind(xmpp_stream).await?; - Ok(xmpp_stream) -} diff --git a/tokio-xmpp/src/client/auth.rs b/tokio-xmpp/src/client/login.rs similarity index 75% rename from tokio-xmpp/src/client/auth.rs rename to tokio-xmpp/src/client/login.rs index 217d496f6eacf50cdbbce4b726e258a39c81e454..5e4d12191ddfe65c3a8053a19c88ff12ddab4bf2 100644 --- a/tokio-xmpp/src/client/auth.rs +++ b/tokio-xmpp/src/client/login.rs @@ -7,9 +7,14 @@ use std::collections::HashSet; use std::str::FromStr; use tokio::io::{AsyncRead, AsyncWrite}; use xmpp_parsers::sasl::{Auth, Challenge, Failure, Mechanism as XMPPMechanism, Response, Success}; +use xmpp_parsers::{jid::Jid, ns}; -use crate::error::{AuthError, Error, ProtocolError}; -use crate::proto::{Packet, XmppStream}; +use crate::{ + client::bind::bind, + connect::ServerConnector, + error::{AuthError, Error, ProtocolError}, + proto::{Packet, XmppStream}, +}; pub async fn auth( mut stream: XmppStream, @@ -82,3 +87,31 @@ pub async fn auth( Err(AuthError::NoMechanism.into()) } + +/// Log into an XMPP server as a client with a jid+pass +/// does channel binding if supported +pub async fn client_login( + server: C, + jid: Jid, + password: String, +) -> Result, Error> { + let username = jid.node().unwrap().as_str(); + let password = password; + + let xmpp_stream = server.connect(&jid, ns::JABBER_CLIENT).await?; + + let channel_binding = C::channel_binding(xmpp_stream.stream.get_ref())?; + + let creds = Credentials::default() + .with_username(username) + .with_password(password) + .with_channel_binding(channel_binding); + // Authenticated (unspecified) stream + let stream = auth(xmpp_stream, creds).await?; + // Authenticated XmppStream + let xmpp_stream = XmppStream::start(stream, jid, ns::JABBER_CLIENT.to_owned()).await?; + + // XmppStream bound to user session + let xmpp_stream = bind(xmpp_stream).await?; + Ok(xmpp_stream) +} diff --git a/tokio-xmpp/src/client/mod.rs b/tokio-xmpp/src/client/mod.rs index 8eddd11f1ba54a5320430fc8767f6b7e89edb2bb..3633aa1010e1872f6fe178d5de9da5bf3f57ffa6 100644 --- a/tokio-xmpp/src/client/mod.rs +++ b/tokio-xmpp/src/client/mod.rs @@ -1,6 +1,143 @@ -mod auth; +use futures::sink::SinkExt; +use minidom::Element; +use xmpp_parsers::{jid::Jid, ns, stream_features::StreamFeatures}; + +use crate::{ + client::{login::client_login, stream::ClientState}, + connect::ServerConnector, + error::Error, + proto::{add_stanza_id, Packet}, +}; + +#[cfg(any(feature = "starttls", feature = "insecure-tcp"))] +use crate::connect::DnsConfig; +#[cfg(feature = "starttls")] +use crate::connect::StartTlsServerConnector; +#[cfg(feature = "insecure-tcp")] +use crate::connect::TcpServerConnector; + mod bind; +mod login; +mod stream; + +/// XMPP client connection and state +/// +/// It is able to reconnect. TODO: implement session management. +/// +/// This implements the `futures` crate's [`Stream`](#impl-Stream) and +/// [`Sink`](#impl-Sink) traits. +pub struct Client { + jid: Jid, + password: String, + connector: C, + state: ClientState, + reconnect: bool, + // TODO: tls_required=true +} + +impl Client { + /// Set whether to reconnect (`true`) or let the stream end + /// (`false`) when a connection to the server has ended. + pub fn set_reconnect(&mut self, reconnect: bool) -> &mut Self { + self.reconnect = reconnect; + self + } + + /// Get the client's bound JID (the one reported by the XMPP + /// server). + pub fn bound_jid(&self) -> Option<&Jid> { + match self.state { + ClientState::Connected(ref stream) => Some(&stream.jid), + _ => None, + } + } + + /// Send stanza + pub async fn send_stanza(&mut self, stanza: Element) -> Result<(), Error> { + self.send(Packet::Stanza(add_stanza_id(stanza, ns::JABBER_CLIENT))) + .await + } + + /// Get the stream features (``) of the underlying stream + pub fn get_stream_features(&self) -> Option<&StreamFeatures> { + match self.state { + ClientState::Connected(ref stream) => Some(&stream.stream_features), + _ => None, + } + } + + /// End connection by sending `` + /// + /// You may expect the server to respond with the same. This + /// client will then drop its connection. + /// + /// Make sure to disable reconnect. + pub async fn send_end(&mut self) -> Result<(), Error> { + self.send(Packet::StreamEnd).await + } +} + +#[cfg(feature = "starttls")] +impl Client { + /// Start a new XMPP client using StartTLS transport and autoreconnect + /// + /// Start polling the returned instance so that it will connect + /// and yield events. + pub fn new, P: Into>(jid: J, password: P) -> Self { + let jid = jid.into(); + let mut client = Self::new_starttls( + jid.clone(), + password, + DnsConfig::srv(&jid.domain().to_string(), "_xmpp-client._tcp", 5222), + ); + client.set_reconnect(true); + client + } + + /// Start a new XMPP client with StartTLS transport and specific DNS config + pub fn new_starttls, P: Into>( + jid: J, + password: P, + dns_config: DnsConfig, + ) -> Self { + Self::new_with_connector(jid, password, StartTlsServerConnector::from(dns_config)) + } +} + +#[cfg(feature = "insecure-tcp")] +impl Client { + /// Start a new XMPP client with plaintext insecure connection and specific DNS config + pub fn new_plaintext, P: Into>( + jid: J, + password: P, + dns_config: DnsConfig, + ) -> Self { + Self::new_with_connector(jid, password, TcpServerConnector::from(dns_config)) + } +} -pub(crate) mod connect; +impl Client { + /// Start a new client given that the JID is already parsed. + pub fn new_with_connector, P: Into>( + jid: J, + password: P, + connector: C, + ) -> Self { + let jid = jid.into(); + let password = password.into(); -pub mod async_client; + let connect = tokio::spawn(client_login( + connector.clone(), + jid.clone(), + password.clone(), + )); + let client = Client { + jid, + password, + connector, + state: ClientState::Connecting(connect), + reconnect: false, + }; + client + } +} diff --git a/tokio-xmpp/src/client/async_client.rs b/tokio-xmpp/src/client/stream.rs similarity index 62% rename from tokio-xmpp/src/client/async_client.rs rename to tokio-xmpp/src/client/stream.rs index 27dfef808cd0be6d4f26fc767f6446f4ba680353..b185bd136e8c4033c48689ab6f16589b8c2d2f42 100644 --- a/tokio-xmpp/src/client/async_client.rs +++ b/tokio-xmpp/src/client/stream.rs @@ -1,153 +1,24 @@ -use futures::{sink::SinkExt, task::Poll, Future, Sink, Stream}; -use minidom::Element; +use futures::{task::Poll, Future, Sink, Stream}; use std::mem::replace; use std::pin::Pin; use std::task::Context; use tokio::task::JoinHandle; -use xmpp_parsers::{jid::Jid, ns, stream_features::StreamFeatures}; use crate::{ - client::connect::client_login, + client::login::client_login, connect::{AsyncReadAndWrite, ServerConnector}, error::{Error, ProtocolError}, - proto::{add_stanza_id, Packet, XmppStream}, - Event, + proto::{Packet, XmppStream}, + Client, Event, }; -#[cfg(any(feature = "starttls", feature = "insecure-tcp"))] -use crate::connect::DnsConfig; -#[cfg(feature = "starttls")] -use crate::connect::StartTlsServerConnector; -#[cfg(feature = "insecure-tcp")] -use crate::connect::TcpServerConnector; - -/// XMPP client connection and state -/// -/// It is able to reconnect. TODO: implement session management. -/// -/// This implements the `futures` crate's [`Stream`](#impl-Stream) and -/// [`Sink`](#impl-Sink) traits. -pub struct Client { - jid: Jid, - password: String, - connector: C, - state: ClientState, - reconnect: bool, - // TODO: tls_required=true -} - -enum ClientState { +pub(crate) enum ClientState { Invalid, Disconnected, Connecting(JoinHandle, Error>>), Connected(XmppStream), } -#[cfg(feature = "starttls")] -impl Client { - /// Start a new XMPP client using StartTLS transport and autoreconnect - /// - /// Start polling the returned instance so that it will connect - /// and yield events. - pub fn new, P: Into>(jid: J, password: P) -> Self { - let jid = jid.into(); - let mut client = Self::new_starttls( - jid.clone(), - password, - DnsConfig::srv(&jid.domain().to_string(), "_xmpp-client._tcp", 5222), - ); - client.set_reconnect(true); - client - } - - /// Start a new XMPP client with StartTLS transport and specific DNS config - pub fn new_starttls, P: Into>( - jid: J, - password: P, - dns_config: DnsConfig, - ) -> Self { - Self::new_with_connector(jid, password, StartTlsServerConnector::from(dns_config)) - } -} - -#[cfg(feature = "insecure-tcp")] -impl Client { - /// Start a new XMPP client with plaintext insecure connection and specific DNS config - pub fn new_plaintext, P: Into>( - jid: J, - password: P, - dns_config: DnsConfig, - ) -> Self { - Self::new_with_connector(jid, password, TcpServerConnector::from(dns_config)) - } -} - -impl Client { - /// Start a new client given that the JID is already parsed. - pub fn new_with_connector, P: Into>( - jid: J, - password: P, - connector: C, - ) -> Self { - let jid = jid.into(); - let password = password.into(); - - let connect = tokio::spawn(client_login( - connector.clone(), - jid.clone(), - password.clone(), - )); - let client = Client { - jid, - password, - connector, - state: ClientState::Connecting(connect), - reconnect: false, - }; - client - } - - /// Set whether to reconnect (`true`) or let the stream end - /// (`false`) when a connection to the server has ended. - pub fn set_reconnect(&mut self, reconnect: bool) -> &mut Self { - self.reconnect = reconnect; - self - } - - /// Get the client's bound JID (the one reported by the XMPP - /// server). - pub fn bound_jid(&self) -> Option<&Jid> { - match self.state { - ClientState::Connected(ref stream) => Some(&stream.jid), - _ => None, - } - } - - /// Send stanza - pub async fn send_stanza(&mut self, stanza: Element) -> Result<(), Error> { - self.send(Packet::Stanza(add_stanza_id(stanza, ns::JABBER_CLIENT))) - .await - } - - /// Get the stream features (``) of the underlying stream - pub fn get_stream_features(&self) -> Option<&StreamFeatures> { - match self.state { - ClientState::Connected(ref stream) => Some(&stream.stream_features), - _ => None, - } - } - - /// End connection by sending `` - /// - /// You may expect the server to respond with the same. This - /// client will then drop its connection. - /// - /// Make sure to disable reconnect. - pub async fn send_end(&mut self) -> Result<(), Error> { - self.send(Packet::StreamEnd).await - } -} - /// Incoming XMPP events /// /// In an `async fn` you may want to use this with `use diff --git a/tokio-xmpp/src/lib.rs b/tokio-xmpp/src/lib.rs index 6a093db9b57b9b3d0eae5f1bff3468efa9b90f80..347e164c6705f3b7eb5070dd90b47891d1a4e3aa 100644 --- a/tokio-xmpp/src/lib.rs +++ b/tokio-xmpp/src/lib.rs @@ -48,16 +48,17 @@ compile_error!( mod event; pub use event::Event; -mod client; pub mod connect; pub mod proto; -pub use client::async_client::Client; +mod client; +pub use client::Client; #[cfg(feature = "insecure-tcp")] mod component; #[cfg(feature = "insecure-tcp")] pub use crate::component::Component; + /// Detailed error types pub mod error;