Compare commits
3 Commits
4886396044
...
1b91ff6904
Author | SHA1 | Date |
---|---|---|
cel 🌸 | 1b91ff6904 | |
cel 🌸 | 03764f8ced | |
cel 🌸 | 21f10a0b43 |
32
Cargo.toml
32
Cargo.toml
|
@ -1,27 +1,7 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "luz"
|
|
||||||
authors = ["cel <cel@blos.sm>"]
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
members = [
|
||||||
|
"luz",
|
||||||
[dependencies]
|
"jabber",
|
||||||
async-recursion = "1.0.4"
|
"stanza", "jid",
|
||||||
async-trait = "0.1.68"
|
]
|
||||||
lazy_static = "1.4.0"
|
|
||||||
nanoid = "0.4.0"
|
|
||||||
# TODO: remove unneeded features
|
|
||||||
rsasl = { version = "2.0.1", path = "../rsasl", default_features = false, features = ["provider_base64", "plain", "config_builder", "scram-sha-1"] }
|
|
||||||
tokio = { version = "1.28", features = ["full"] }
|
|
||||||
tokio-native-tls = "0.3.1"
|
|
||||||
tracing = "0.1.40"
|
|
||||||
trust-dns-resolver = "0.22.0"
|
|
||||||
try_map = "0.3.1"
|
|
||||||
peanuts = { version = "0.1.0", path = "../peanuts" }
|
|
||||||
futures = "0.3.31"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
test-log = { version = "0.2", features = ["trace"] }
|
|
||||||
env_logger = "*"
|
|
||||||
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# jabber client library
|
||||||
|
|
||||||
|
## TODO:
|
||||||
|
|
||||||
|
- [ ] error states for all negotiation parts
|
||||||
|
- [ ] better errors
|
||||||
|
- [x] rename structs
|
||||||
|
- [x] remove commented code
|
||||||
|
- [ ] asynchronous connect (with take_mut?)
|
||||||
|
- [ ] split into separate crates: stanza, jabber, and luz
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "jabber"
|
||||||
|
authors = ["cel <cel@bunny.garden>"]
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-recursion = "1.0.4"
|
||||||
|
async-trait = "0.1.68"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
nanoid = "0.4.0"
|
||||||
|
# TODO: remove unneeded features
|
||||||
|
rsasl = { version = "2.0.1", path = "../../rsasl", default_features = false, features = ["provider_base64", "plain", "config_builder", "scram-sha-1"] }
|
||||||
|
tokio = { version = "1.28", features = ["full"] }
|
||||||
|
tokio-native-tls = "0.3.1"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
trust-dns-resolver = "0.22.0"
|
||||||
|
try_map = "0.3.1"
|
||||||
|
stanza = { version = "0.1.0", path = "../stanza" }
|
||||||
|
peanuts = { version = "0.1.0", path = "../../peanuts" }
|
||||||
|
jid = { version = "0.1.0", path = "../jid" }
|
||||||
|
futures = "0.3.31"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
test-log = { version = "0.2", features = ["trace"] }
|
||||||
|
env_logger = "*"
|
||||||
|
tracing-subscriber = {version = "0.3", default-features = false, features = ["env-filter", "fmt"]}
|
|
@ -1,16 +1,16 @@
|
||||||
use std::{pin::pin, sync::Arc, task::Poll};
|
use std::{pin::pin, sync::Arc, task::Poll};
|
||||||
|
|
||||||
use futures::{Sink, Stream, StreamExt};
|
use futures::{Sink, Stream, StreamExt};
|
||||||
|
use jid::ParseError;
|
||||||
use rsasl::config::SASLConfig;
|
use rsasl::config::SASLConfig;
|
||||||
|
use stanza::{
|
||||||
use crate::{
|
|
||||||
connection::{Tls, Unencrypted},
|
|
||||||
jid::ParseError,
|
|
||||||
stanza::{
|
|
||||||
client::Stanza,
|
client::Stanza,
|
||||||
sasl::Mechanisms,
|
sasl::Mechanisms,
|
||||||
stream::{Feature, Features},
|
stream::{Feature, Features},
|
||||||
},
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
connection::{Tls, Unencrypted},
|
||||||
Connection, Error, JabberStream, Result, JID,
|
Connection, Error, JabberStream, Result, JID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ impl JabberClient {
|
||||||
pub async fn connect(&mut self) -> Result<()> {
|
pub async fn connect(&mut self) -> Result<()> {
|
||||||
match &self.connection {
|
match &self.connection {
|
||||||
ConnectionState::Disconnected => {
|
ConnectionState::Disconnected => {
|
||||||
|
// TODO: actually set the self.connection as it is connecting, make more asynchronous (mutex while connecting?)
|
||||||
|
// perhaps use take_mut?
|
||||||
self.connection = ConnectionState::Disconnected
|
self.connection = ConnectionState::Disconnected
|
||||||
.connect(&mut self.jid, self.password.clone(), &mut self.server)
|
.connect(&mut self.jid, self.password.clone(), &mut self.server)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -53,6 +55,16 @@ impl JabberClient {
|
||||||
ConnectionState::Connected(_jabber_stream) => Ok(()),
|
ConnectionState::Connected(_jabber_stream) => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> {
|
||||||
|
match &mut self.connection {
|
||||||
|
ConnectionState::Disconnected => return Err(Error::Disconnected),
|
||||||
|
ConnectionState::Connecting(_connecting) => return Err(Error::Connecting),
|
||||||
|
ConnectionState::Connected(jabber_stream) => {
|
||||||
|
Ok(jabber_stream.send_stanza(stanza).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for JabberClient {
|
impl Stream for JabberClient {
|
||||||
|
@ -101,7 +113,7 @@ impl ConnectionState {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Connecting::InsecureGotFeatures((features, jabber_stream)) => {
|
Connecting::InsecureGotFeatures((features, jabber_stream)) => {
|
||||||
match features.negotiate()? {
|
match features.negotiate().ok_or(Error::Negotiation)? {
|
||||||
Feature::StartTls(_start_tls) => {
|
Feature::StartTls(_start_tls) => {
|
||||||
self =
|
self =
|
||||||
ConnectionState::Connecting(Connecting::StartTls(jabber_stream))
|
ConnectionState::Connecting(Connecting::StartTls(jabber_stream))
|
||||||
|
@ -126,7 +138,7 @@ impl ConnectionState {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Connecting::GotFeatures((features, jabber_stream)) => {
|
Connecting::GotFeatures((features, jabber_stream)) => {
|
||||||
match features.negotiate()? {
|
match features.negotiate().ok_or(Error::Negotiation)? {
|
||||||
Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls),
|
Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls),
|
||||||
Feature::Sasl(mechanisms) => {
|
Feature::Sasl(mechanisms) => {
|
||||||
self = ConnectionState::Connecting(Connecting::Sasl(
|
self = ConnectionState::Connecting(Connecting::Sasl(
|
||||||
|
@ -178,35 +190,6 @@ impl Connecting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 InsecureConnecting {
|
pub enum InsecureConnecting {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
ConnectionEstablished(Connection),
|
ConnectionEstablished(Connection),
|
||||||
|
@ -217,38 +200,6 @@ pub enum InsecureConnecting {
|
||||||
Bound(JabberStream<Tls>),
|
Bound(JabberStream<Tls>),
|
||||||
}
|
}
|
||||||
|
|
||||||
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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
|
@ -1,9 +1,10 @@
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
|
||||||
|
use jid::ParseError;
|
||||||
use rsasl::mechname::MechanismNameError;
|
use rsasl::mechname::MechanismNameError;
|
||||||
|
use stanza::client::error::Error as ClientError;
|
||||||
use crate::stanza::client::error::Error as ClientError;
|
use stanza::sasl::Failure;
|
||||||
use crate::{jid::ParseError, stanza::sasl::Failure};
|
use stanza::stream::Error as StreamError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -22,7 +23,10 @@ pub enum Error {
|
||||||
JID(ParseError),
|
JID(ParseError),
|
||||||
Authentication(Failure),
|
Authentication(Failure),
|
||||||
ClientError(ClientError),
|
ClientError(ClientError),
|
||||||
|
StreamError(StreamError),
|
||||||
MissingError,
|
MissingError,
|
||||||
|
Disconnected,
|
||||||
|
Connecting,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
|
@ -2,26 +2,25 @@ use std::pin::pin;
|
||||||
use std::str::{self, FromStr};
|
use std::str::{self, FromStr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_recursion::async_recursion;
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use jid::JID;
|
||||||
use peanuts::element::{FromContent, IntoElement};
|
use peanuts::element::{FromContent, IntoElement};
|
||||||
use peanuts::{Reader, Writer};
|
use peanuts::{Reader, Writer};
|
||||||
use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
|
use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
|
||||||
|
use stanza::bind::{Bind, BindType, FullJidType, ResourceType};
|
||||||
|
use stanza::client::iq::{Iq, IqType, Query};
|
||||||
|
use stanza::client::Stanza;
|
||||||
|
use stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
||||||
|
use stanza::starttls::{Proceed, StartTls};
|
||||||
|
use stanza::stream::{Features, Stream};
|
||||||
|
use stanza::XML_VERSION;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
||||||
use tokio_native_tls::native_tls::TlsConnector;
|
use tokio_native_tls::native_tls::TlsConnector;
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use crate::connection::{Tls, Unencrypted};
|
use crate::connection::{Tls, Unencrypted};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType};
|
use crate::Result;
|
||||||
use crate::stanza::client::iq::{Iq, IqType, Query};
|
|
||||||
use crate::stanza::client::Stanza;
|
|
||||||
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
|
||||||
use crate::stanza::starttls::{Proceed, StartTls};
|
|
||||||
use crate::stanza::stream::{Feature, Features, Stream};
|
|
||||||
use crate::stanza::XML_VERSION;
|
|
||||||
use crate::JID;
|
|
||||||
use crate::{Connection, Result};
|
|
||||||
|
|
||||||
// open stream (streams started)
|
// open stream (streams started)
|
||||||
pub struct JabberStream<S> {
|
pub struct JabberStream<S> {
|
||||||
|
@ -50,6 +49,7 @@ where
|
||||||
S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug,
|
S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug,
|
||||||
JabberStream<S>: std::fmt::Debug,
|
JabberStream<S>: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
|
#[instrument]
|
||||||
pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> {
|
pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> {
|
||||||
let sasl = SASLClient::new(sasl_config);
|
let sasl = SASLClient::new(sasl_config);
|
||||||
let mut offered_mechs: Vec<&Mechname> = Vec::new();
|
let mut offered_mechs: Vec<&Mechname> = Vec::new();
|
||||||
|
@ -139,6 +139,7 @@ where
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
pub async fn bind(mut self, jid: &mut JID) -> Result<Self> {
|
pub async fn bind(mut self, jid: &mut JID) -> Result<Self> {
|
||||||
let iq_id = nanoid::nanoid!();
|
let iq_id = nanoid::nanoid!();
|
||||||
if let Some(resource) = &jid.resourcepart {
|
if let Some(resource) = &jid.resourcepart {
|
||||||
|
@ -266,6 +267,7 @@ where
|
||||||
Ok(Self { reader, writer })
|
Ok(Self { reader, writer })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
pub async fn get_features(mut self) -> Result<(Features, Self)> {
|
pub async fn get_features(mut self) -> Result<(Features, Self)> {
|
||||||
debug!("getting features");
|
debug!("getting features");
|
||||||
let features: Features = self.reader.read().await?;
|
let features: Features = self.reader.read().await?;
|
||||||
|
@ -276,91 +278,16 @@ where
|
||||||
pub fn into_inner(self) -> S {
|
pub fn into_inner(self) -> S {
|
||||||
self.reader.into_inner().unsplit(self.writer.into_inner())
|
self.reader.into_inner().unsplit(self.writer.into_inner())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> {
|
||||||
|
self.writer.write(stanza).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JabberStream<Unencrypted> {
|
impl JabberStream<Unencrypted> {
|
||||||
// pub async fn negotiate<S: AsyncRead + AsyncWrite + Unpin>(
|
#[instrument]
|
||||||
// mut self,
|
pub async fn starttls(mut self, domain: impl AsRef<str> + std::fmt::Debug) -> Result<Tls> {
|
||||||
// features: Features,
|
|
||||||
// ) -> Result<Feature> {
|
|
||||||
// // TODO: timeout
|
|
||||||
// if let Some(Feature::StartTls(_)) = features
|
|
||||||
// .features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::StartTls(_s)))
|
|
||||||
// {
|
|
||||||
// return Ok(self);
|
|
||||||
// } else {
|
|
||||||
// // TODO: better error
|
|
||||||
// return Err(Error::TlsRequired);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[async_recursion]
|
|
||||||
// pub async fn negotiate_tls_optional(mut self) -> Result<Connection> {
|
|
||||||
// self.start_stream().await?;
|
|
||||||
// // TODO: timeout
|
|
||||||
// let features = self.get_features().await?.features;
|
|
||||||
// if let Some(Feature::StartTls(_)) = features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::StartTls(_s)))
|
|
||||||
// {
|
|
||||||
// let jabber = self.starttls().await?;
|
|
||||||
// let jabber = jabber.negotiate().await?;
|
|
||||||
// return Ok(Connection::Encrypted(jabber));
|
|
||||||
// } else if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
|
||||||
// self.auth.clone(),
|
|
||||||
// features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Sasl(_))),
|
|
||||||
// ) {
|
|
||||||
// self.sasl(mechanisms.clone(), sasl_config).await?;
|
|
||||||
// let jabber = self.negotiate_tls_optional().await?;
|
|
||||||
// Ok(jabber)
|
|
||||||
// } else if let Some(Feature::Bind) = features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Bind))
|
|
||||||
// {
|
|
||||||
// self.bind().await?;
|
|
||||||
// Ok(Connection::Unencrypted(self))
|
|
||||||
// } else {
|
|
||||||
// // TODO: better error
|
|
||||||
// return Err(Error::Negotiation);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JabberStream<Tls> {
|
|
||||||
// #[async_recursion]
|
|
||||||
// pub async fn negotiate(mut self) -> Result<JabberStream<Tls>> {
|
|
||||||
// self.start_stream().await?;
|
|
||||||
// let features = self.get_features().await?.features;
|
|
||||||
|
|
||||||
// if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
|
||||||
// self.auth.clone(),
|
|
||||||
// features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Sasl(_))),
|
|
||||||
// ) {
|
|
||||||
// // TODO: avoid clone
|
|
||||||
// self.sasl(mechanisms.clone(), sasl_config).await?;
|
|
||||||
// let jabber = self.negotiate().await?;
|
|
||||||
// Ok(jabber)
|
|
||||||
// } else if let Some(Feature::Bind) = features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Bind))
|
|
||||||
// {
|
|
||||||
// self.bind().await?;
|
|
||||||
// Ok(self)
|
|
||||||
// } else {
|
|
||||||
// // TODO: better error
|
|
||||||
// return Err(Error::Negotiation);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JabberStream<Unencrypted> {
|
|
||||||
pub async fn starttls(mut self, domain: impl AsRef<str>) -> Result<Tls> {
|
|
||||||
self.writer
|
self.writer
|
||||||
.write_full(&StartTls { required: false })
|
.write_full(&StartTls { required: false })
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -372,8 +299,6 @@ impl JabberStream<Unencrypted> {
|
||||||
.connect(domain.as_ref(), stream)
|
.connect(domain.as_ref(), stream)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// let (read, write) = tokio::io::split(tlsstream);
|
|
||||||
// let client = JabberStream::new(read, write);
|
|
||||||
return Ok(tls_stream);
|
return Ok(tls_stream);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Connection);
|
return Err(Error::Connection);
|
|
@ -5,14 +5,12 @@
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod jabber;
|
pub mod jabber_stream;
|
||||||
pub mod jid;
|
|
||||||
pub mod stanza;
|
|
||||||
|
|
||||||
pub use connection::Connection;
|
pub use connection::Connection;
|
||||||
use connection::Tls;
|
use connection::Tls;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use jabber::JabberStream;
|
pub use jabber_stream::JabberStream;
|
||||||
pub use jid::JID;
|
pub use jid::JID;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "jid"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -19,15 +19,6 @@ pub enum ParseError {
|
||||||
Malformed(String),
|
Malformed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError> for peanuts::Error {
|
|
||||||
fn from(e: ParseError) -> Self {
|
|
||||||
match e {
|
|
||||||
ParseError::Empty => peanuts::Error::DeserializeError("".to_string()),
|
|
||||||
ParseError::Malformed(e) => peanuts::Error::DeserializeError(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JID {
|
impl JID {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
localpart: Option<String>,
|
localpart: Option<String>,
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "luz"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "stanza"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
peanuts = { version = "0.1.0", path = "../../peanuts" }
|
||||||
|
jid = { version = "0.1.0", path = "../jid" }
|
|
@ -1,10 +1,9 @@
|
||||||
|
use jid::JID;
|
||||||
use peanuts::{
|
use peanuts::{
|
||||||
element::{FromElement, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::JID;
|
|
||||||
|
|
||||||
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind";
|
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
|
@ -3,8 +3,8 @@ use std::str::FromStr;
|
||||||
use peanuts::element::{FromElement, IntoElement};
|
use peanuts::element::{FromElement, IntoElement};
|
||||||
use peanuts::{DeserializeError, Element};
|
use peanuts::{DeserializeError, Element};
|
||||||
|
|
||||||
use crate::stanza::stanza_error::Error as StanzaError;
|
use crate::stanza_error::Error as StanzaError;
|
||||||
use crate::stanza::stanza_error::Text;
|
use crate::stanza_error::Text;
|
||||||
|
|
||||||
use super::XMLNS;
|
use super::XMLNS;
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use jid::JID;
|
||||||
use peanuts::{
|
use peanuts::{
|
||||||
element::{FromElement, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
DeserializeError, Element, XML_NS,
|
DeserializeError, Element, XML_NS,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
stanza::{
|
|
||||||
bind::{self, Bind},
|
bind::{self, Bind},
|
||||||
client::error::Error,
|
client::error::Error,
|
||||||
},
|
|
||||||
JID,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::XMLNS;
|
use super::XMLNS;
|
|
@ -1,12 +1,11 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use jid::JID;
|
||||||
use peanuts::{
|
use peanuts::{
|
||||||
element::{FromElement, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
DeserializeError, Element, XML_NS,
|
DeserializeError, Element, XML_NS,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::JID;
|
|
||||||
|
|
||||||
use super::XMLNS;
|
use super::XMLNS;
|
||||||
|
|
||||||
pub struct Message {
|
pub struct Message {
|
|
@ -1,12 +1,11 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use jid::JID;
|
||||||
use peanuts::{
|
use peanuts::{
|
||||||
element::{FromElement, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
DeserializeError, Element, XML_NS,
|
DeserializeError, Element, XML_NS,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::JID;
|
|
||||||
|
|
||||||
use super::{error::Error, XMLNS};
|
use super::{error::Error, XMLNS};
|
||||||
|
|
||||||
pub struct Presence {
|
pub struct Presence {
|
|
@ -4,7 +4,6 @@ use peanuts::{
|
||||||
element::{FromElement, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
DeserializeError, Element,
|
DeserializeError, Element,
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
|
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
|
||||||
|
|
||||||
|
@ -17,15 +16,11 @@ impl FromElement for Mechanisms {
|
||||||
fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
|
fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
|
||||||
element.check_name("mechanisms")?;
|
element.check_name("mechanisms")?;
|
||||||
element.check_namespace(XMLNS)?;
|
element.check_namespace(XMLNS)?;
|
||||||
debug!("getting mechanisms");
|
|
||||||
let mechanisms: Vec<Mechanism> = element.pop_children()?;
|
let mechanisms: Vec<Mechanism> = element.pop_children()?;
|
||||||
debug!("gottting mechanisms");
|
|
||||||
let mechanisms = mechanisms
|
let mechanisms = mechanisms
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|Mechanism(mechanism)| mechanism)
|
.map(|Mechanism(mechanism)| mechanism)
|
||||||
.collect();
|
.collect();
|
||||||
debug!("gottting mechanisms");
|
|
||||||
|
|
||||||
Ok(Mechanisms { mechanisms })
|
Ok(Mechanisms { mechanisms })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +130,6 @@ pub enum ServerResponse {
|
||||||
|
|
||||||
impl FromElement for ServerResponse {
|
impl FromElement for ServerResponse {
|
||||||
fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
|
fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
|
||||||
debug!("identification: {:?}", element.identify());
|
|
||||||
match element.identify() {
|
match element.identify() {
|
||||||
(Some(XMLNS), "challenge") => {
|
(Some(XMLNS), "challenge") => {
|
||||||
Ok(ServerResponse::Challenge(Challenge::from_element(element)?))
|
Ok(ServerResponse::Challenge(Challenge::from_element(element)?))
|
|
@ -1,12 +1,10 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use jid::JID;
|
||||||
use peanuts::element::{Content, ElementBuilder, FromElement, IntoElement, NamespaceDeclaration};
|
use peanuts::element::{Content, ElementBuilder, FromElement, IntoElement, NamespaceDeclaration};
|
||||||
use peanuts::XML_NS;
|
|
||||||
use peanuts::{element::Name, Element};
|
use peanuts::{element::Name, Element};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::stanza::bind;
|
use crate::bind;
|
||||||
use crate::JID;
|
|
||||||
|
|
||||||
use super::sasl::{self, Mechanisms};
|
use super::sasl::{self, Mechanisms};
|
||||||
use super::starttls::{self, StartTls};
|
use super::starttls::{self, StartTls};
|
||||||
|
@ -99,6 +97,34 @@ pub struct Features {
|
||||||
pub features: Vec<Feature>,
|
pub features: Vec<Feature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Features {
|
||||||
|
pub fn negotiate(self) -> Option<Feature> {
|
||||||
|
if let Some(Feature::StartTls(s)) = self
|
||||||
|
.features
|
||||||
|
.iter()
|
||||||
|
.find(|feature| matches!(feature, Feature::StartTls(_s)))
|
||||||
|
{
|
||||||
|
// TODO: avoid clone
|
||||||
|
return Some(Feature::StartTls(s.clone()));
|
||||||
|
} else if let Some(Feature::Sasl(mechanisms)) = self
|
||||||
|
.features
|
||||||
|
.iter()
|
||||||
|
.find(|feature| matches!(feature, Feature::Sasl(_)))
|
||||||
|
{
|
||||||
|
// TODO: avoid clone
|
||||||
|
return Some(Feature::Sasl(mechanisms.clone()));
|
||||||
|
} else if let Some(Feature::Bind) = self
|
||||||
|
.features
|
||||||
|
.into_iter()
|
||||||
|
.find(|feature| matches!(feature, Feature::Bind))
|
||||||
|
{
|
||||||
|
Some(Feature::Bind)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoElement for Features {
|
impl IntoElement for Features {
|
||||||
fn builder(&self) -> ElementBuilder {
|
fn builder(&self) -> ElementBuilder {
|
||||||
Element::builder("features", Some(XMLNS)).push_children(self.features.clone())
|
Element::builder("features", Some(XMLNS)).push_children(self.features.clone())
|
||||||
|
@ -112,9 +138,7 @@ impl FromElement for Features {
|
||||||
element.check_namespace(XMLNS)?;
|
element.check_namespace(XMLNS)?;
|
||||||
element.check_name("features")?;
|
element.check_name("features")?;
|
||||||
|
|
||||||
debug!("got features stanza");
|
|
||||||
let features = element.children()?;
|
let features = element.children()?;
|
||||||
debug!("got features period");
|
|
||||||
|
|
||||||
Ok(Features { features })
|
Ok(Features { features })
|
||||||
}
|
}
|
||||||
|
@ -141,29 +165,20 @@ impl IntoElement for Feature {
|
||||||
|
|
||||||
impl FromElement for Feature {
|
impl FromElement for Feature {
|
||||||
fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
|
fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
|
||||||
let identity = element.identify();
|
|
||||||
debug!("identity: {:?}", identity);
|
|
||||||
match element.identify() {
|
match element.identify() {
|
||||||
(Some(starttls::XMLNS), "starttls") => {
|
(Some(starttls::XMLNS), "starttls") => {
|
||||||
debug!("identified starttls");
|
|
||||||
Ok(Feature::StartTls(StartTls::from_element(element)?))
|
Ok(Feature::StartTls(StartTls::from_element(element)?))
|
||||||
}
|
}
|
||||||
(Some(sasl::XMLNS), "mechanisms") => {
|
(Some(sasl::XMLNS), "mechanisms") => {
|
||||||
debug!("identified mechanisms");
|
|
||||||
Ok(Feature::Sasl(Mechanisms::from_element(element)?))
|
Ok(Feature::Sasl(Mechanisms::from_element(element)?))
|
||||||
}
|
}
|
||||||
(Some(bind::XMLNS), "bind") => {
|
(Some(bind::XMLNS), "bind") => Ok(Feature::Bind),
|
||||||
debug!("identified bind");
|
_ => Ok(Feature::Unknown),
|
||||||
Ok(Feature::Bind)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
debug!("identified unknown feature");
|
|
||||||
Ok(Feature::Unknown)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
error: StreamError,
|
error: StreamError,
|
||||||
text: Option<Text>,
|
text: Option<Text>,
|
|
@ -5,7 +5,7 @@ use peanuts::{
|
||||||
|
|
||||||
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-streams";
|
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-streams";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
BadFormat,
|
BadFormat,
|
||||||
BadNamespacePrefix,
|
BadNamespacePrefix,
|
||||||
|
@ -110,7 +110,7 @@ impl IntoElement for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
lang: Option<String>,
|
lang: Option<String>,
|
Loading…
Reference in New Issue