aboutsummaryrefslogtreecommitdiffstats
path: root/src/client.rs
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2024-12-04 02:09:07 +0000
committerLibravatar cel 🌸 <cel@bunny.garden>2024-12-04 02:09:07 +0000
commit4886396044356d2676a77c3900af796fe7641f42 (patch)
tree685c67b7db0f22a7262fc6431d7849d63c510e66 /src/client.rs
parente0373c0520e7fae792bc907e9c500ab846d34e31 (diff)
downloadluz-4886396044356d2676a77c3900af796fe7641f42.tar.gz
luz-4886396044356d2676a77c3900af796fe7641f42.tar.bz2
luz-4886396044356d2676a77c3900af796fe7641f42.zip
implement client
Diffstat (limited to 'src/client.rs')
-rw-r--r--src/client.rs234
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
+ }
+}