From e0373c0520e7fae792bc907e9c500ab846d34e31 Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Tue, 3 Dec 2024 23:57:04 +0000 Subject: WIP: connecting fsm --- src/client.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 src/client.rs (limited to 'src/client.rs') diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..2908346 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,180 @@ +use std::sync::Arc; + +use futures::{Sink, Stream}; +use rsasl::config::SASLConfig; + +use crate::{ + connection::{Tls, Unencrypted}, + stanza::{ + client::Stanza, + sasl::Mechanisms, + stream::{Feature, Features}, + }, + Connection, Error, JabberStream, Result, JID, +}; + +// feed it client stanzas, receive client stanzas +pub struct JabberClient { + connection: JabberState, + jid: JID, + password: Arc, + server: String, +} + +pub enum JabberState { + Disconnected, + InsecureConnectionEstablised(Unencrypted), + InsecureStreamStarted(JabberStream), + InsecureGotFeatures((Features, JabberStream)), + StartTls(JabberStream), + ConnectionEstablished(Tls), + StreamStarted(JabberStream), + GotFeatures((Features, JabberStream)), + Sasl(Mechanisms, JabberStream), + Bind(JabberStream), + // when it's bound, can stream stanzas and sink stanzas + Bound(JabberStream), +} + +impl JabberState { + pub async fn advance_state( + self, + jid: &mut JID, + auth: Arc, + server: &mut String, + ) -> Result { + 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?, + )) + } + JabberState::Bind(jabber_stream) => { + Ok(JabberState::Bound(jabber_stream.bind(jid).await?)) + } + JabberState::Bound(jabber_stream) => Ok(JabberState::Bound(jabber_stream)), + } + } +} + +impl Features { + pub fn negotiate(self) -> Result { + if let Some(Feature::StartTls(s)) = self + .features + .iter() + .find(|feature| matches!(feature, Feature::StartTls(_s))) + { + // TODO: avoid clone + return Ok(Feature::StartTls(s.clone())); + } else if let Some(Feature::Sasl(mechanisms)) = self + .features + .iter() + .find(|feature| matches!(feature, Feature::Sasl(_))) + { + // TODO: avoid clone + return Ok(Feature::Sasl(mechanisms.clone())); + } else if let Some(Feature::Bind) = self + .features + .into_iter() + .find(|feature| matches!(feature, Feature::Bind)) + { + Ok(Feature::Bind) + } else { + // TODO: better error + return Err(Error::Negotiation); + } + } +} + +pub enum InsecureJabberConnection { + Disconnected, + ConnectionEstablished(Connection), + PreStarttls(JabberStream), + PreAuthenticated(JabberStream), + Authenticated(Tls), + PreBound(JabberStream), + Bound(JabberStream), +} + +impl Stream for JabberClient { + type Item = Stanza; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } +} + +impl Sink for JabberClient { + type Error = Error; + + fn poll_ready( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } + + fn start_send( + self: std::pin::Pin<&mut Self>, + item: Stanza, + ) -> std::result::Result<(), Self::Error> { + todo!() + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } +} -- cgit