diff options
author | 2024-12-04 02:09:07 +0000 | |
---|---|---|
committer | 2024-12-04 02:09:07 +0000 | |
commit | 4886396044356d2676a77c3900af796fe7641f42 (patch) | |
tree | 685c67b7db0f22a7262fc6431d7849d63c510e66 /src/client.rs | |
parent | e0373c0520e7fae792bc907e9c500ab846d34e31 (diff) | |
download | luz-4886396044356d2676a77c3900af796fe7641f42.tar.gz luz-4886396044356d2676a77c3900af796fe7641f42.tar.bz2 luz-4886396044356d2676a77c3900af796fe7641f42.zip |
implement client
Diffstat (limited to 'src/client.rs')
-rw-r--r-- | src/client.rs | 234 |
1 files changed, 160 insertions, 74 deletions
diff --git a/src/client.rs b/src/client.rs index 2908346..5351b34 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,10 +1,11 @@ -use std::sync::Arc; +use std::{pin::pin, sync::Arc, task::Poll}; -use futures::{Sink, Stream}; +use futures::{Sink, Stream, StreamExt}; use rsasl::config::SASLConfig; use crate::{ connection::{Tls, Unencrypted}, + jid::ParseError, stanza::{ client::Stanza, sasl::Mechanisms, @@ -15,14 +16,146 @@ use crate::{ // feed it client stanzas, receive client stanzas pub struct JabberClient { - connection: JabberState, + connection: ConnectionState, jid: JID, password: Arc<SASLConfig>, server: String, } -pub enum JabberState { +impl JabberClient { + pub fn new( + jid: impl TryInto<JID, Error = ParseError>, + password: impl ToString, + ) -> Result<JabberClient> { + let jid = jid.try_into()?; + let sasl_config = SASLConfig::with_credentials( + None, + jid.localpart.clone().ok_or(Error::NoLocalpart)?, + password.to_string(), + )?; + Ok(JabberClient { + connection: ConnectionState::Disconnected, + jid: jid.clone(), + password: sasl_config, + server: jid.domainpart, + }) + } + + pub async fn connect(&mut self) -> Result<()> { + match &self.connection { + ConnectionState::Disconnected => { + self.connection = ConnectionState::Disconnected + .connect(&mut self.jid, self.password.clone(), &mut self.server) + .await?; + Ok(()) + } + ConnectionState::Connecting(_connecting) => Err(Error::AlreadyConnecting), + ConnectionState::Connected(_jabber_stream) => Ok(()), + } + } +} + +impl Stream for JabberClient { + type Item = Result<Stanza>; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll<Option<Self::Item>> { + let mut client = pin!(self); + match &mut client.connection { + ConnectionState::Disconnected => Poll::Pending, + ConnectionState::Connecting(_connecting) => Poll::Pending, + ConnectionState::Connected(jabber_stream) => jabber_stream.poll_next_unpin(cx), + } + } +} + +pub enum ConnectionState { Disconnected, + Connecting(Connecting), + Connected(JabberStream<Tls>), +} + +impl ConnectionState { + pub async fn connect( + mut self, + jid: &mut JID, + auth: Arc<SASLConfig>, + server: &mut String, + ) -> Result<Self> { + loop { + match self { + ConnectionState::Disconnected => { + self = ConnectionState::Connecting(Connecting::start(&server).await?); + } + ConnectionState::Connecting(connecting) => match connecting { + Connecting::InsecureConnectionEstablised(tcp_stream) => { + self = ConnectionState::Connecting(Connecting::InsecureStreamStarted( + JabberStream::start_stream(tcp_stream, server).await?, + )) + } + Connecting::InsecureStreamStarted(jabber_stream) => { + self = ConnectionState::Connecting(Connecting::InsecureGotFeatures( + jabber_stream.get_features().await?, + )) + } + Connecting::InsecureGotFeatures((features, jabber_stream)) => { + match features.negotiate()? { + Feature::StartTls(_start_tls) => { + self = + ConnectionState::Connecting(Connecting::StartTls(jabber_stream)) + } + // TODO: better error + _ => return Err(Error::TlsRequired), + } + } + Connecting::StartTls(jabber_stream) => { + self = ConnectionState::Connecting(Connecting::ConnectionEstablished( + jabber_stream.starttls(&server).await?, + )) + } + Connecting::ConnectionEstablished(tls_stream) => { + self = ConnectionState::Connecting(Connecting::StreamStarted( + JabberStream::start_stream(tls_stream, server).await?, + )) + } + Connecting::StreamStarted(jabber_stream) => { + self = ConnectionState::Connecting(Connecting::GotFeatures( + jabber_stream.get_features().await?, + )) + } + Connecting::GotFeatures((features, jabber_stream)) => { + match features.negotiate()? { + Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls), + Feature::Sasl(mechanisms) => { + self = ConnectionState::Connecting(Connecting::Sasl( + mechanisms, + jabber_stream, + )) + } + Feature::Bind => { + self = ConnectionState::Connecting(Connecting::Bind(jabber_stream)) + } + Feature::Unknown => return Err(Error::Unsupported), + } + } + Connecting::Sasl(mechanisms, jabber_stream) => { + self = ConnectionState::Connecting(Connecting::ConnectionEstablished( + jabber_stream.sasl(mechanisms, auth.clone()).await?, + )) + } + Connecting::Bind(jabber_stream) => { + self = ConnectionState::Connected(jabber_stream.bind(jid).await?) + } + }, + connected => return Ok(connected), + } + } + } +} + +pub enum Connecting { InsecureConnectionEstablised(Unencrypted), InsecureStreamStarted(JabberStream<Unencrypted>), InsecureGotFeatures((Features, JabberStream<Unencrypted>)), @@ -32,67 +165,15 @@ pub enum JabberState { GotFeatures((Features, JabberStream<Tls>)), Sasl(Mechanisms, JabberStream<Tls>), Bind(JabberStream<Tls>), - // when it's bound, can stream stanzas and sink stanzas - Bound(JabberStream<Tls>), } -impl JabberState { - pub async fn advance_state( - self, - jid: &mut JID, - auth: Arc<SASLConfig>, - server: &mut String, - ) -> Result<JabberState> { - match self { - JabberState::Disconnected => match Connection::connect(server).await? { - Connection::Encrypted(tls_stream) => { - Ok(JabberState::ConnectionEstablished(tls_stream)) - } - Connection::Unencrypted(tcp_stream) => { - Ok(JabberState::InsecureConnectionEstablised(tcp_stream)) - } - }, - JabberState::InsecureConnectionEstablised(tcp_stream) => Ok({ - JabberState::InsecureStreamStarted( - JabberStream::start_stream(tcp_stream, server).await?, - ) - }), - JabberState::InsecureStreamStarted(jabber_stream) => Ok( - JabberState::InsecureGotFeatures(jabber_stream.get_features().await?), - ), - JabberState::InsecureGotFeatures((features, jabber_stream)) => { - match features.negotiate()? { - Feature::StartTls(_start_tls) => Ok(JabberState::StartTls(jabber_stream)), - // TODO: better error - _ => return Err(Error::TlsRequired), - } - } - JabberState::StartTls(jabber_stream) => Ok(JabberState::ConnectionEstablished( - jabber_stream.starttls(server).await?, - )), - JabberState::ConnectionEstablished(tls_stream) => Ok(JabberState::StreamStarted( - JabberStream::start_stream(tls_stream, server).await?, - )), - JabberState::StreamStarted(jabber_stream) => Ok(JabberState::GotFeatures( - jabber_stream.get_features().await?, - )), - JabberState::GotFeatures((features, jabber_stream)) => match features.negotiate()? { - Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls), - Feature::Sasl(mechanisms) => { - return Ok(JabberState::Sasl(mechanisms, jabber_stream)) - } - Feature::Bind => return Ok(JabberState::Bind(jabber_stream)), - Feature::Unknown => return Err(Error::Unsupported), - }, - JabberState::Sasl(mechanisms, jabber_stream) => { - return Ok(JabberState::ConnectionEstablished( - jabber_stream.sasl(mechanisms, auth).await?, - )) +impl Connecting { + pub async fn start(server: &str) -> Result<Self> { + match Connection::connect(server).await? { + Connection::Encrypted(tls_stream) => Ok(Connecting::ConnectionEstablished(tls_stream)), + Connection::Unencrypted(tcp_stream) => { + Ok(Connecting::InsecureConnectionEstablised(tcp_stream)) } - JabberState::Bind(jabber_stream) => { - Ok(JabberState::Bound(jabber_stream.bind(jid).await?)) - } - JabberState::Bound(jabber_stream) => Ok(JabberState::Bound(jabber_stream)), } } } @@ -126,7 +207,7 @@ impl Features { } } -pub enum InsecureJabberConnection { +pub enum InsecureConnecting { Disconnected, ConnectionEstablished(Connection), PreStarttls(JabberStream<Unencrypted>), @@ -136,17 +217,6 @@ pub enum InsecureJabberConnection { Bound(JabberStream<Tls>), } -impl Stream for JabberClient { - type Item = Stanza; - - fn poll_next( - self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll<Option<Self::Item>> { - todo!() - } -} - impl Sink<Stanza> for JabberClient { type Error = Error; @@ -178,3 +248,19 @@ impl Sink<Stanza> for JabberClient { todo!() } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::JabberClient; + use test_log::test; + use tokio::time::sleep; + + #[test(tokio::test)] + async fn login() { + let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap(); + client.connect().await.unwrap(); + sleep(Duration::from_secs(5)).await + } +} |