aboutsummaryrefslogtreecommitdiffstats
path: root/src/client.rs
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2024-12-03 23:57:04 +0000
committerLibravatar cel 🌸 <cel@bunny.garden>2024-12-03 23:57:04 +0000
commite0373c0520e7fae792bc907e9c500ab846d34e31 (patch)
treefcec4d201c85ac951500f6678824024be87a1b5e /src/client.rs
parent7c2577d196c059ab6e2d5b0efe5e036bdad75be7 (diff)
downloadluz-e0373c0520e7fae792bc907e9c500ab846d34e31.tar.gz
luz-e0373c0520e7fae792bc907e9c500ab846d34e31.tar.bz2
luz-e0373c0520e7fae792bc907e9c500ab846d34e31.zip
WIP: connecting fsm
Diffstat (limited to 'src/client.rs')
-rw-r--r--src/client.rs180
1 files changed, 180 insertions, 0 deletions
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<SASLConfig>,
+ server: String,
+}
+
+pub enum JabberState {
+ Disconnected,
+ InsecureConnectionEstablised(Unencrypted),
+ InsecureStreamStarted(JabberStream<Unencrypted>),
+ InsecureGotFeatures((Features, JabberStream<Unencrypted>)),
+ StartTls(JabberStream<Unencrypted>),
+ ConnectionEstablished(Tls),
+ StreamStarted(JabberStream<Tls>),
+ 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?,
+ ))
+ }
+ 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<Feature> {
+ 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<Unencrypted>),
+ PreAuthenticated(JabberStream<Tls>),
+ Authenticated(Tls),
+ PreBound(JabberStream<Tls>),
+ 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;
+
+ fn poll_ready(
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> std::task::Poll<std::result::Result<(), Self::Error>> {
+ 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<std::result::Result<(), Self::Error>> {
+ todo!()
+ }
+
+ fn poll_close(
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> std::task::Poll<std::result::Result<(), Self::Error>> {
+ todo!()
+ }
+}