aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2024-12-04 18:18:37 +0000
committerLibravatar cel 🌸 <cel@bunny.garden>2024-12-04 18:18:37 +0000
commit1b91ff690488b65b552c90bd5392b9a300c8c981 (patch)
tree9c290f69b26eba0393d7bbc05ba29c28ea74a26e /src
parent03764f8cedb3f0a55a61be0f0a59faaa6357a83a (diff)
downloadluz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.gz
luz-1b91ff690488b65b552c90bd5392b9a300c8c981.tar.bz2
luz-1b91ff690488b65b552c90bd5392b9a300c8c981.zip
use cargo workspace
Diffstat (limited to 'src')
-rw-r--r--src/client.rs246
-rw-r--r--src/connection.rs184
-rw-r--r--src/error.rs78
-rw-r--r--src/jabber_stream.rs394
-rw-r--r--src/jid.rs179
-rw-r--r--src/lib.rs36
-rw-r--r--src/stanza/bind.rs99
-rw-r--r--src/stanza/client/error.rs83
-rw-r--r--src/stanza/client/iq.rs124
-rw-r--r--src/stanza/client/message.rs186
-rw-r--r--src/stanza/client/mod.rs61
-rw-r--r--src/stanza/client/presence.rs226
-rw-r--r--src/stanza/mod.rs11
-rw-r--r--src/stanza/sasl.rs246
-rw-r--r--src/stanza/stanza_error.rs126
-rw-r--r--src/stanza/starttls.rs94
-rw-r--r--src/stanza/stream.rs191
-rw-r--r--src/stanza/stream_error.rs137
18 files changed, 0 insertions, 2701 deletions
diff --git a/src/client.rs b/src/client.rs
deleted file mode 100644
index e94008d..0000000
--- a/src/client.rs
+++ /dev/null
@@ -1,246 +0,0 @@
-use std::{pin::pin, sync::Arc, task::Poll};
-
-use futures::{Sink, Stream, StreamExt};
-use rsasl::config::SASLConfig;
-
-use crate::{
- connection::{Tls, Unencrypted},
- jid::ParseError,
- stanza::{
- client::Stanza,
- sasl::Mechanisms,
- stream::{Feature, Features},
- },
- Connection, Error, JabberStream, Result, JID,
-};
-
-// feed it client stanzas, receive client stanzas
-pub struct JabberClient {
- connection: ConnectionState,
- jid: JID,
- password: Arc<SASLConfig>,
- server: String,
-}
-
-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 => {
- // TODO: actually set the self.connection as it is connecting, make more asynchronous (mutex while connecting?)
- // perhaps use take_mut?
- 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(()),
- }
- }
-
- 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 {
- 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>)),
- StartTls(JabberStream<Unencrypted>),
- ConnectionEstablished(Tls),
- StreamStarted(JabberStream<Tls>),
- GotFeatures((Features, JabberStream<Tls>)),
- Sasl(Mechanisms, JabberStream<Tls>),
- Bind(JabberStream<Tls>),
-}
-
-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))
- }
- }
- }
-}
-
-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 {
- Disconnected,
- ConnectionEstablished(Connection),
- PreStarttls(JabberStream<Unencrypted>),
- PreAuthenticated(JabberStream<Tls>),
- Authenticated(Tls),
- PreBound(JabberStream<Tls>),
- Bound(JabberStream<Tls>),
-}
-
-#[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
- }
-}
diff --git a/src/connection.rs b/src/connection.rs
deleted file mode 100644
index bc5a282..0000000
--- a/src/connection.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-use std::net::{IpAddr, SocketAddr};
-use std::str;
-use std::str::FromStr;
-use std::sync::Arc;
-
-use rsasl::config::SASLConfig;
-use tokio::net::TcpStream;
-use tokio_native_tls::native_tls::TlsConnector;
-// TODO: use rustls
-use tokio_native_tls::TlsStream;
-use tracing::{debug, info, instrument, trace};
-
-use crate::Result;
-use crate::{Error, JID};
-
-pub type Tls = TlsStream<TcpStream>;
-pub type Unencrypted = TcpStream;
-
-#[derive(Debug)]
-pub enum Connection {
- Encrypted(Tls),
- Unencrypted(Unencrypted),
-}
-
-impl Connection {
- // #[instrument]
- /// stream not started
- // pub async fn ensure_tls(self) -> Result<Jabber<Tls>> {
- // match self {
- // Connection::Encrypted(j) => Ok(j),
- // Connection::Unencrypted(mut j) => {
- // j.start_stream().await?;
- // info!("upgrading connection to tls");
- // j.get_features().await?;
- // let j = j.starttls().await?;
- // Ok(j)
- // }
- // }
- // }
-
- pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> {
- let jid: JID = JID::from_str(jid.as_ref())?;
- let server = jid.domainpart.clone();
- Self::connect(&server).await
- }
-
- #[instrument]
- pub async fn connect(server: impl AsRef<str> + std::fmt::Debug) -> Result<Self> {
- info!("connecting to {}", server.as_ref());
- let sockets = Self::get_sockets(server.as_ref()).await;
- debug!("discovered sockets: {:?}", sockets);
- for (socket_addr, tls) in sockets {
- match tls {
- true => {
- if let Ok(connection) = Self::connect_tls(socket_addr, server.as_ref()).await {
- info!("connected via encrypted stream to {}", socket_addr);
- // let (readhalf, writehalf) = tokio::io::split(connection);
- return Ok(Self::Encrypted(connection));
- }
- }
- false => {
- if let Ok(connection) = Self::connect_unencrypted(socket_addr).await {
- info!("connected via unencrypted stream to {}", socket_addr);
- // let (readhalf, writehalf) = tokio::io::split(connection);
- return Ok(Self::Unencrypted(connection));
- }
- }
- }
- }
- Err(Error::Connection)
- }
-
- #[instrument]
- async fn get_sockets(address: &str) -> Vec<(SocketAddr, bool)> {
- let mut socket_addrs = Vec::new();
-
- // if it's a socket/ip then just return that
-
- // socket
- trace!("checking if address is a socket address");
- if let Ok(socket_addr) = SocketAddr::from_str(address) {
- debug!("{} is a socket address", address);
- match socket_addr.port() {
- 5223 => socket_addrs.push((socket_addr, true)),
- _ => socket_addrs.push((socket_addr, false)),
- }
-
- return socket_addrs;
- }
- // ip
- trace!("checking if address is an ip");
- if let Ok(ip) = IpAddr::from_str(address) {
- debug!("{} is an ip", address);
- socket_addrs.push((SocketAddr::new(ip, 5222), false));
- socket_addrs.push((SocketAddr::new(ip, 5223), true));
- return socket_addrs;
- }
-
- // otherwise resolve
- debug!("resolving {}", address);
- if let Ok(resolver) = trust_dns_resolver::AsyncResolver::tokio_from_system_conf() {
- if let Ok(lookup) = resolver
- .srv_lookup(format!("_xmpp-client._tcp.{}", address))
- .await
- {
- for srv in lookup {
- resolver
- .lookup_ip(srv.target().to_owned())
- .await
- .map(|ips| {
- for ip in ips {
- socket_addrs.push((SocketAddr::new(ip, srv.port()), false))
- }
- });
- }
- }
- if let Ok(lookup) = resolver
- .srv_lookup(format!("_xmpps-client._tcp.{}", address))
- .await
- {
- for srv in lookup {
- resolver
- .lookup_ip(srv.target().to_owned())
- .await
- .map(|ips| {
- for ip in ips {
- socket_addrs.push((SocketAddr::new(ip, srv.port()), true))
- }
- });
- }
- }
-
- // in case cannot connect through SRV records
- resolver.lookup_ip(address).await.map(|ips| {
- for ip in ips {
- socket_addrs.push((SocketAddr::new(ip, 5222), false));
- socket_addrs.push((SocketAddr::new(ip, 5223), true));
- }
- });
- }
- socket_addrs
- }
-
- /// establishes a connection to the server
- #[instrument]
- pub async fn connect_tls(socket_addr: SocketAddr, domain_name: &str) -> Result<Tls> {
- let socket = TcpStream::connect(socket_addr)
- .await
- .map_err(|_| Error::Connection)?;
- let connector = TlsConnector::new().map_err(|_| Error::Connection)?;
- tokio_native_tls::TlsConnector::from(connector)
- .connect(domain_name, socket)
- .await
- .map_err(|_| Error::Connection)
- }
-
- #[instrument]
- pub async fn connect_unencrypted(socket_addr: SocketAddr) -> Result<Unencrypted> {
- TcpStream::connect(socket_addr)
- .await
- .map_err(|_| Error::Connection)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use test_log::test;
-
- #[test(tokio::test)]
- async fn connect() {
- Connection::connect("blos.sm").await.unwrap();
- }
-
- // #[test(tokio::test)]
- // async fn test_tls() {
- // Connection::connect("blos.sm", None, None)
- // .await
- // .unwrap()
- // .ensure_tls()
- // .await
- // .unwrap();
- // }
-}
diff --git a/src/error.rs b/src/error.rs
deleted file mode 100644
index 8875ebb..0000000
--- a/src/error.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use std::str::Utf8Error;
-
-use rsasl::mechname::MechanismNameError;
-
-use crate::stanza::client::error::Error as ClientError;
-use crate::stanza::stream::Error as StreamError;
-use crate::{jid::ParseError, stanza::sasl::Failure};
-
-#[derive(Debug)]
-pub enum Error {
- Connection,
- Utf8Decode,
- Negotiation,
- TlsRequired,
- AlreadyTls,
- Unsupported,
- NoLocalpart,
- AlreadyConnecting,
- UnexpectedElement(peanuts::Element),
- XML(peanuts::Error),
- Deserialization(peanuts::DeserializeError),
- SASL(SASLError),
- JID(ParseError),
- Authentication(Failure),
- ClientError(ClientError),
- StreamError(StreamError),
- MissingError,
- Disconnected,
- Connecting,
-}
-
-#[derive(Debug)]
-pub enum SASLError {
- SASL(rsasl::prelude::SASLError),
- MechanismName(MechanismNameError),
-}
-
-impl From<rsasl::prelude::SASLError> for Error {
- fn from(e: rsasl::prelude::SASLError) -> Self {
- Self::SASL(SASLError::SASL(e))
- }
-}
-
-impl From<peanuts::DeserializeError> for Error {
- fn from(e: peanuts::DeserializeError) -> Self {
- Error::Deserialization(e)
- }
-}
-
-impl From<MechanismNameError> for Error {
- fn from(e: MechanismNameError) -> Self {
- Self::SASL(SASLError::MechanismName(e))
- }
-}
-
-impl From<SASLError> for Error {
- fn from(e: SASLError) -> Self {
- Self::SASL(e)
- }
-}
-
-impl From<Utf8Error> for Error {
- fn from(_e: Utf8Error) -> Self {
- Self::Utf8Decode
- }
-}
-
-impl From<peanuts::Error> for Error {
- fn from(e: peanuts::Error) -> Self {
- Self::XML(e)
- }
-}
-
-impl From<ParseError> for Error {
- fn from(e: ParseError) -> Self {
- Self::JID(e)
- }
-}
diff --git a/src/jabber_stream.rs b/src/jabber_stream.rs
deleted file mode 100644
index 8ee45b5..0000000
--- a/src/jabber_stream.rs
+++ /dev/null
@@ -1,394 +0,0 @@
-use std::pin::pin;
-use std::str::{self, FromStr};
-use std::sync::Arc;
-
-use async_recursion::async_recursion;
-use futures::StreamExt;
-use peanuts::element::{FromContent, IntoElement};
-use peanuts::{Reader, Writer};
-use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
-use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
-use tokio_native_tls::native_tls::TlsConnector;
-use tracing::{debug, instrument};
-
-use crate::connection::{Tls, Unencrypted};
-use crate::error::Error;
-use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType};
-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)
-pub struct JabberStream<S> {
- reader: Reader<ReadHalf<S>>,
- writer: Writer<WriteHalf<S>>,
-}
-
-impl<S: AsyncRead> futures::Stream for JabberStream<S> {
- type Item = Result<Stanza>;
-
- fn poll_next(
- self: std::pin::Pin<&mut Self>,
- cx: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Option<Self::Item>> {
- pin!(self).reader.poll_next_unpin(cx).map(|content| {
- content.map(|content| -> Result<Stanza> {
- let stanza = content.map(|content| Stanza::from_content(content))?;
- Ok(stanza?)
- })
- })
- }
-}
-
-impl<S> JabberStream<S>
-where
- S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug,
- JabberStream<S>: std::fmt::Debug,
-{
- #[instrument]
- pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> {
- let sasl = SASLClient::new(sasl_config);
- let mut offered_mechs: Vec<&Mechname> = Vec::new();
- for mechanism in &mechanisms.mechanisms {
- offered_mechs.push(Mechname::parse(mechanism.as_bytes())?)
- }
- debug!("{:?}", offered_mechs);
- let mut session = sasl.start_suggested(&offered_mechs)?;
- let selected_mechanism = session.get_mechname().as_str().to_owned();
- debug!("selected mech: {:?}", selected_mechanism);
- let mut data: Option<Vec<u8>> = None;
-
- if !session.are_we_first() {
- // if not first mention the mechanism then get challenge data
- // mention mechanism
- let auth = Auth {
- mechanism: selected_mechanism,
- sasl_data: "=".to_string(),
- };
- self.writer.write_full(&auth).await?;
- // get challenge data
- let challenge: Challenge = self.reader.read().await?;
- debug!("challenge: {:?}", challenge);
- data = Some((*challenge).as_bytes().to_vec());
- debug!("we didn't go first");
- } else {
- // if first, mention mechanism and send data
- let mut sasl_data = Vec::new();
- session.step64(None, &mut sasl_data).unwrap();
- let auth = Auth {
- mechanism: selected_mechanism,
- sasl_data: str::from_utf8(&sasl_data)?.to_string(),
- };
- debug!("{:?}", auth);
- self.writer.write_full(&auth).await?;
-
- let server_response: ServerResponse = self.reader.read().await?;
- debug!("server_response: {:#?}", server_response);
- match server_response {
- ServerResponse::Challenge(challenge) => {
- data = Some((*challenge).as_bytes().to_vec())
- }
- ServerResponse::Success(success) => {
- data = success.clone().map(|success| success.as_bytes().to_vec())
- }
- ServerResponse::Failure(failure) => return Err(Error::Authentication(failure)),
- }
- debug!("we went first");
- }
-
- // stepping the authentication exchange to completion
- if data != None {
- debug!("data: {:?}", data);
- let mut sasl_data = Vec::new();
- while {
- // decide if need to send more data over
- let state = session
- .step64(data.as_deref(), &mut sasl_data)
- .expect("step errored!");
- state.is_running()
- } {
- // While we aren't finished, receive more data from the other party
- let response = Response::new(str::from_utf8(&sasl_data)?.to_string());
- debug!("response: {:?}", response);
- let stdout = tokio::io::stdout();
- let mut writer = Writer::new(stdout);
- writer.write_full(&response).await?;
- self.writer.write_full(&response).await?;
- debug!("response written");
-
- let server_response: ServerResponse = self.reader.read().await?;
- debug!("server_response: {:#?}", server_response);
- match server_response {
- ServerResponse::Challenge(challenge) => {
- data = Some((*challenge).as_bytes().to_vec())
- }
- ServerResponse::Success(success) => {
- data = success.clone().map(|success| success.as_bytes().to_vec())
- }
- ServerResponse::Failure(failure) => return Err(Error::Authentication(failure)),
- }
- }
- }
- let writer = self.writer.into_inner();
- let reader = self.reader.into_inner();
- let stream = reader.unsplit(writer);
- Ok(stream)
- }
-
- #[instrument]
- pub async fn bind(mut self, jid: &mut JID) -> Result<Self> {
- let iq_id = nanoid::nanoid!();
- if let Some(resource) = &jid.resourcepart {
- let iq = Iq {
- from: None,
- id: iq_id.clone(),
- to: None,
- r#type: IqType::Set,
- lang: None,
- query: Some(Query::Bind(Bind {
- r#type: Some(BindType::Resource(ResourceType(resource.to_string()))),
- })),
- errors: Vec::new(),
- };
- self.writer.write_full(&iq).await?;
- let result: Iq = self.reader.read().await?;
- match result {
- Iq {
- from: _,
- id,
- to: _,
- r#type: IqType::Result,
- lang: _,
- query:
- Some(Query::Bind(Bind {
- r#type: Some(BindType::Jid(FullJidType(new_jid))),
- })),
- errors: _,
- } if id == iq_id => {
- *jid = new_jid;
- return Ok(self);
- }
- Iq {
- from: _,
- id,
- to: _,
- r#type: IqType::Error,
- lang: _,
- query: None,
- errors,
- } if id == iq_id => {
- return Err(Error::ClientError(
- errors.first().ok_or(Error::MissingError)?.clone(),
- ))
- }
- _ => return Err(Error::UnexpectedElement(result.into_element())),
- }
- } else {
- let iq = Iq {
- from: None,
- id: iq_id.clone(),
- to: None,
- r#type: IqType::Set,
- lang: None,
- query: Some(Query::Bind(Bind { r#type: None })),
- errors: Vec::new(),
- };
- self.writer.write_full(&iq).await?;
- let result: Iq = self.reader.read().await?;
- match result {
- Iq {
- from: _,
- id,
- to: _,
- r#type: IqType::Result,
- lang: _,
- query:
- Some(Query::Bind(Bind {
- r#type: Some(BindType::Jid(FullJidType(new_jid))),
- })),
- errors: _,
- } if id == iq_id => {
- *jid = new_jid;
- return Ok(self);
- }
- Iq {
- from: _,
- id,
- to: _,
- r#type: IqType::Error,
- lang: _,
- query: None,
- errors,
- } if id == iq_id => {
- return Err(Error::ClientError(
- errors.first().ok_or(Error::MissingError)?.clone(),
- ))
- }
- _ => return Err(Error::UnexpectedElement(result.into_element())),
- }
- }
- }
-
- #[instrument]
- pub async fn start_stream(connection: S, server: &mut String) -> Result<Self> {
- // client to server
- let (reader, writer) = tokio::io::split(connection);
- let mut reader = Reader::new(reader);
- let mut writer = Writer::new(writer);
-
- // declaration
- writer.write_declaration(XML_VERSION).await?;
-
- // opening stream element
- let stream = Stream::new_client(
- None,
- JID::from_str(server.as_ref())?,
- None,
- "en".to_string(),
- );
- writer.write_start(&stream).await?;
-
- // server to client
-
- // may or may not send a declaration
- let _decl = reader.read_prolog().await?;
-
- // receive stream element and validate
- let stream: Stream = reader.read_start().await?;
- debug!("got stream: {:?}", stream);
- if let Some(from) = stream.from {
- *server = from.to_string();
- }
-
- Ok(Self { reader, writer })
- }
-
- #[instrument]
- pub async fn get_features(mut self) -> Result<(Features, Self)> {
- debug!("getting features");
- let features: Features = self.reader.read().await?;
- debug!("got features: {:?}", features);
- Ok((features, self))
- }
-
- pub fn into_inner(self) -> S {
- 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> {
- #[instrument]
- pub async fn starttls(mut self, domain: impl AsRef<str> + std::fmt::Debug) -> Result<Tls> {
- self.writer
- .write_full(&StartTls { required: false })
- .await?;
- let proceed: Proceed = self.reader.read().await?;
- debug!("got proceed: {:?}", proceed);
- let connector = TlsConnector::new().unwrap();
- let stream = self.reader.into_inner().unsplit(self.writer.into_inner());
- if let Ok(tls_stream) = tokio_native_tls::TlsConnector::from(connector)
- .connect(domain.as_ref(), stream)
- .await
- {
- return Ok(tls_stream);
- } else {
- return Err(Error::Connection);
- }
- }
-}
-
-impl std::fmt::Debug for JabberStream<Tls> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Jabber")
- .field("connection", &"tls")
- .finish()
- }
-}
-
-impl std::fmt::Debug for JabberStream<Unencrypted> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Jabber")
- .field("connection", &"unencrypted")
- .finish()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::time::Duration;
-
- use super::*;
- use crate::connection::Connection;
- use test_log::test;
- use tokio::time::sleep;
-
- #[test(tokio::test)]
- async fn start_stream() {
- // let connection = Connection::connect("blos.sm", None, None).await.unwrap();
- // match connection {
- // Connection::Encrypted(mut c) => c.start_stream().await.unwrap(),
- // Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(),
- // }
- }
-
- #[test(tokio::test)]
- async fn sasl() {
- // let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
- // .await
- // .unwrap()
- // .ensure_tls()
- // .await
- // .unwrap();
- // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
- // println!("data: {}", text);
- // jabber.start_stream().await.unwrap();
-
- // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
- // println!("data: {}", text);
- // jabber.reader.read_buf().await.unwrap();
- // let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
- // println!("data: {}", text);
-
- // let features = jabber.get_features().await.unwrap();
- // let (sasl_config, feature) = (
- // jabber.auth.clone().unwrap(),
- // features
- // .features
- // .iter()
- // .find(|feature| matches!(feature, Feature::Sasl(_)))
- // .unwrap(),
- // );
- // match feature {
- // Feature::StartTls(_start_tls) => todo!(),
- // Feature::Sasl(mechanisms) => {
- // jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap();
- // }
- // Feature::Bind => todo!(),
- // Feature::Unknown => todo!(),
- // }
- }
-
- #[tokio::test]
- async fn negotiate() {
- // let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
- // .await
- // .unwrap()
- // .ensure_tls()
- // .await
- // .unwrap()
- // .negotiate()
- // .await
- // .unwrap();
- // sleep(Duration::from_secs(5)).await
- }
-}
diff --git a/src/jid.rs b/src/jid.rs
deleted file mode 100644
index 233227a..0000000
--- a/src/jid.rs
+++ /dev/null
@@ -1,179 +0,0 @@
-use std::str::FromStr;
-
-#[derive(PartialEq, Debug, Clone)]
-pub struct JID {
- // TODO: validate localpart (length, char]
- pub localpart: Option<String>,
- pub domainpart: String,
- pub resourcepart: Option<String>,
-}
-
-pub enum JIDError {
- NoResourcePart,
- ParseError(ParseError),
-}
-
-#[derive(Debug)]
-pub enum ParseError {
- Empty,
- 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 {
- pub fn new(
- localpart: Option<String>,
- domainpart: String,
- resourcepart: Option<String>,
- ) -> Self {
- Self {
- localpart,
- domainpart: domainpart.parse().unwrap(),
- resourcepart,
- }
- }
-
- pub fn as_bare(&self) -> Self {
- Self {
- localpart: self.localpart.clone(),
- domainpart: self.domainpart.clone(),
- resourcepart: None,
- }
- }
-
- pub fn as_full(&self) -> Result<&Self, JIDError> {
- if let Some(_) = self.resourcepart {
- Ok(&self)
- } else {
- Err(JIDError::NoResourcePart)
- }
- }
-}
-
-impl FromStr for JID {
- type Err = ParseError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let split: Vec<&str> = s.split('@').collect();
- match split.len() {
- 0 => Err(ParseError::Empty),
- 1 => {
- let split: Vec<&str> = split[0].split('/').collect();
- match split.len() {
- 1 => Ok(JID::new(None, split[0].to_string(), None)),
- 2 => Ok(JID::new(
- None,
- split[0].to_string(),
- Some(split[1].to_string()),
- )),
- _ => Err(ParseError::Malformed(s.to_string())),
- }
- }
- 2 => {
- let split2: Vec<&str> = split[1].split('/').collect();
- match split2.len() {
- 1 => Ok(JID::new(
- Some(split[0].to_string()),
- split2[0].to_string(),
- None,
- )),
- 2 => Ok(JID::new(
- Some(split[0].to_string()),
- split2[0].to_string(),
- Some(split2[1].to_string()),
- )),
- _ => Err(ParseError::Malformed(s.to_string())),
- }
- }
- _ => Err(ParseError::Malformed(s.to_string())),
- }
- }
-}
-
-impl TryFrom<String> for JID {
- type Error = ParseError;
-
- fn try_from(value: String) -> Result<Self, Self::Error> {
- value.parse()
- }
-}
-
-impl TryFrom<&str> for JID {
- type Error = ParseError;
-
- fn try_from(value: &str) -> Result<Self, Self::Error> {
- value.parse()
- }
-}
-
-impl std::fmt::Display for JID {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{}{}{}",
- self.localpart.clone().map(|l| l + "@").unwrap_or_default(),
- self.domainpart,
- self.resourcepart
- .clone()
- .map(|r| "/".to_owned() + &r)
- .unwrap_or_default()
- )
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn jid_to_string() {
- assert_eq!(
- JID::new(Some("cel".into()), "blos.sm".into(), None).to_string(),
- "cel@blos.sm".to_owned()
- );
- }
-
- #[test]
- fn parse_full_jid() {
- assert_eq!(
- "cel@blos.sm/greenhouse".parse::<JID>().unwrap(),
- JID::new(
- Some("cel".into()),
- "blos.sm".into(),
- Some("greenhouse".into())
- )
- )
- }
-
- #[test]
- fn parse_bare_jid() {
- assert_eq!(
- "cel@blos.sm".parse::<JID>().unwrap(),
- JID::new(Some("cel".into()), "blos.sm".into(), None)
- )
- }
-
- #[test]
- fn parse_domain_jid() {
- assert_eq!(
- "component.blos.sm".parse::<JID>().unwrap(),
- JID::new(None, "component.blos.sm".into(), None)
- )
- }
-
- #[test]
- fn parse_full_domain_jid() {
- assert_eq!(
- "component.blos.sm/bot".parse::<JID>().unwrap(),
- JID::new(None, "component.blos.sm".into(), Some("bot".into()))
- )
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 43aa581..0000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-#![allow(unused_must_use)]
-// #![feature(let_chains)]
-
-// TODO: logging (dropped errors)
-pub mod client;
-pub mod connection;
-pub mod error;
-pub mod jabber_stream;
-pub mod jid;
-pub mod stanza;
-
-pub use connection::Connection;
-use connection::Tls;
-pub use error::Error;
-pub use jabber_stream::JabberStream;
-pub use jid::JID;
-
-pub type Result<T> = std::result::Result<T, Error>;
-
-pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<JabberStream<Tls>> {
- todo!()
- // Ok(Connection::connect_user(jid, password.as_ref().to_string())
- // .await?
- // .ensure_tls()
- // .await?
- // .negotiate()
- // .await?)
-}
-
-#[cfg(test)]
-mod tests {
- // #[tokio::test]
- // async fn test_login() {
- // crate::login("test@blos.sm/clown", "slayed").await.unwrap();
- // }
-}
diff --git a/src/stanza/bind.rs b/src/stanza/bind.rs
deleted file mode 100644
index 0e67a83..0000000
--- a/src/stanza/bind.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-use peanuts::{
- element::{FromElement, IntoElement},
- Element,
-};
-
-use crate::JID;
-
-pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind";
-
-#[derive(Clone)]
-pub struct Bind {
- pub r#type: Option<BindType>,
-}
-
-impl FromElement for Bind {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("bind");
- element.check_name(XMLNS);
-
- let r#type = element.pop_child_opt()?;
-
- Ok(Bind { r#type })
- }
-}
-
-impl IntoElement for Bind {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("bind", Some(XMLNS)).push_child_opt(self.r#type.clone())
- }
-}
-
-#[derive(Clone)]
-pub enum BindType {
- Resource(ResourceType),
- Jid(FullJidType),
-}
-
-impl FromElement for BindType {
- fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- match element.identify() {
- (Some(XMLNS), "resource") => {
- Ok(BindType::Resource(ResourceType::from_element(element)?))
- }
- (Some(XMLNS), "jid") => Ok(BindType::Jid(FullJidType::from_element(element)?)),
- _ => Err(peanuts::DeserializeError::UnexpectedElement(element)),
- }
- }
-}
-
-impl IntoElement for BindType {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- match self {
- BindType::Resource(resource_type) => resource_type.builder(),
- BindType::Jid(full_jid_type) => full_jid_type.builder(),
- }
- }
-}
-
-// minLength 8 maxLength 3071
-#[derive(Clone)]
-pub struct FullJidType(pub JID);
-
-impl FromElement for FullJidType {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("jid");
- element.check_namespace(XMLNS);
-
- let jid = element.pop_value()?;
-
- Ok(FullJidType(jid))
- }
-}
-
-impl IntoElement for FullJidType {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("jid", Some(XMLNS)).push_text(self.0.clone())
- }
-}
-
-// minLength 1 maxLength 1023
-#[derive(Clone)]
-pub struct ResourceType(pub String);
-
-impl FromElement for ResourceType {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("resource")?;
- element.check_namespace(XMLNS)?;
-
- let resource = element.pop_value()?;
-
- Ok(ResourceType(resource))
- }
-}
-
-impl IntoElement for ResourceType {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("resource", Some(XMLNS)).push_text(self.0.clone())
- }
-}
diff --git a/src/stanza/client/error.rs b/src/stanza/client/error.rs
deleted file mode 100644
index 545b9a7..0000000
--- a/src/stanza/client/error.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-use std::str::FromStr;
-
-use peanuts::element::{FromElement, IntoElement};
-use peanuts::{DeserializeError, Element};
-
-use crate::stanza::stanza_error::Error as StanzaError;
-use crate::stanza::stanza_error::Text;
-
-use super::XMLNS;
-
-#[derive(Clone, Debug)]
-pub struct Error {
- by: Option<String>,
- r#type: ErrorType,
- // children (sequence)
- error: StanzaError,
- text: Option<Text>,
-}
-
-impl FromElement for Error {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("error")?;
- element.check_name(XMLNS)?;
-
- let by = element.attribute_opt("by")?;
- let r#type = element.attribute("type")?;
- let error = element.pop_child_one()?;
- let text = element.pop_child_opt()?;
-
- Ok(Error {
- by,
- r#type,
- error,
- text,
- })
- }
-}
-
-impl IntoElement for Error {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("error", Some(XMLNS))
- .push_attribute_opt("by", self.by.clone())
- .push_attribute("type", self.r#type)
- .push_child(self.error.clone())
- .push_child_opt(self.text.clone())
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub enum ErrorType {
- Auth,
- Cancel,
- Continue,
- Modify,
- Wait,
-}
-
-impl FromStr for ErrorType {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "auth" => Ok(ErrorType::Auth),
- "cancel" => Ok(ErrorType::Cancel),
- "continue" => Ok(ErrorType::Continue),
- "modify" => Ok(ErrorType::Modify),
- "wait" => Ok(ErrorType::Wait),
- _ => Err(DeserializeError::FromStr(s.to_string())),
- }
- }
-}
-
-impl ToString for ErrorType {
- fn to_string(&self) -> String {
- match self {
- ErrorType::Auth => "auth".to_string(),
- ErrorType::Cancel => "cancel".to_string(),
- ErrorType::Continue => "continue".to_string(),
- ErrorType::Modify => "modify".to_string(),
- ErrorType::Wait => "wait".to_string(),
- }
- }
-}
diff --git a/src/stanza/client/iq.rs b/src/stanza/client/iq.rs
deleted file mode 100644
index b23f8b7..0000000
--- a/src/stanza/client/iq.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-use std::str::FromStr;
-
-use peanuts::{
- element::{FromElement, IntoElement},
- DeserializeError, Element, XML_NS,
-};
-
-use crate::{
- stanza::{
- bind::{self, Bind},
- client::error::Error,
- },
- JID,
-};
-
-use super::XMLNS;
-
-pub struct Iq {
- pub from: Option<JID>,
- pub id: String,
- pub to: Option<JID>,
- pub r#type: IqType,
- pub lang: Option<String>,
- // children
- // ##other
- pub query: Option<Query>,
- pub errors: Vec<Error>,
-}
-
-#[derive(Clone)]
-pub enum Query {
- Bind(Bind),
- Unsupported,
-}
-
-impl FromElement for Query {
- fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- match element.identify() {
- (Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)),
- _ => Ok(Query::Unsupported),
- }
- }
-}
-
-impl IntoElement for Query {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- match self {
- Query::Bind(bind) => bind.builder(),
- // TODO: consider what to do if attempt to serialize unsupported
- Query::Unsupported => todo!(),
- }
- }
-}
-
-impl FromElement for Iq {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("iq")?;
- element.check_namespace(XMLNS)?;
-
- let from = element.attribute_opt("from")?;
- let id = element.attribute("id")?;
- let to = element.attribute_opt("to")?;
- let r#type = element.attribute("type")?;
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let query = element.pop_child_opt()?;
- let errors = element.pop_children()?;
-
- Ok(Iq {
- from,
- id,
- to,
- r#type,
- lang,
- query,
- errors,
- })
- }
-}
-
-impl IntoElement for Iq {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("iq", Some(XMLNS))
- .push_attribute_opt("from", self.from.clone())
- .push_attribute("id", self.id.clone())
- .push_attribute_opt("to", self.to.clone())
- .push_attribute("type", self.r#type)
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_child_opt(self.query.clone())
- .push_children(self.errors.clone())
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-pub enum IqType {
- Error,
- Get,
- Result,
- Set,
-}
-
-impl FromStr for IqType {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "error" => Ok(IqType::Error),
- "get" => Ok(IqType::Get),
- "result" => Ok(IqType::Result),
- "set" => Ok(IqType::Set),
- _ => Err(DeserializeError::FromStr(s.to_string())),
- }
- }
-}
-
-impl ToString for IqType {
- fn to_string(&self) -> String {
- match self {
- IqType::Error => "error".to_string(),
- IqType::Get => "get".to_string(),
- IqType::Result => "result".to_string(),
- IqType::Set => "set".to_string(),
- }
- }
-}
diff --git a/src/stanza/client/message.rs b/src/stanza/client/message.rs
deleted file mode 100644
index 626d781..0000000
--- a/src/stanza/client/message.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use std::str::FromStr;
-
-use peanuts::{
- element::{FromElement, IntoElement},
- DeserializeError, Element, XML_NS,
-};
-
-use crate::JID;
-
-use super::XMLNS;
-
-pub struct Message {
- from: Option<JID>,
- id: Option<String>,
- to: Option<JID>,
- // can be omitted, if so default to normal
- r#type: MessageType,
- lang: Option<String>,
- // children
- subject: Option<Subject>,
- body: Option<Body>,
- thread: Option<Thread>,
-}
-
-impl FromElement for Message {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("message")?;
- element.check_namespace(XMLNS)?;
-
- let from = element.attribute_opt("from")?;
- let id = element.attribute_opt("id")?;
- let to = element.attribute_opt("to")?;
- let r#type = element.attribute_opt("type")?.unwrap_or_default();
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
-
- let subject = element.child_opt()?;
- let body = element.child_opt()?;
- let thread = element.child_opt()?;
-
- Ok(Message {
- from,
- id,
- to,
- r#type,
- lang,
- subject,
- body,
- thread,
- })
- }
-}
-
-impl IntoElement for Message {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("message", Some(XMLNS))
- .push_attribute_opt("from", self.from.clone())
- .push_attribute_opt("id", self.id.clone())
- .push_attribute_opt("to", self.to.clone())
- .push_attribute_opt("type", {
- if self.r#type == MessageType::Normal {
- None
- } else {
- Some(self.r#type)
- }
- })
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_child_opt(self.subject.clone())
- .push_child_opt(self.body.clone())
- .push_child_opt(self.thread.clone())
- }
-}
-
-#[derive(Default, PartialEq, Eq, Copy, Clone)]
-pub enum MessageType {
- Chat,
- Error,
- Groupchat,
- Headline,
- #[default]
- Normal,
-}
-
-impl FromStr for MessageType {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "chat" => Ok(MessageType::Chat),
- "error" => Ok(MessageType::Error),
- "groupchat" => Ok(MessageType::Groupchat),
- "headline" => Ok(MessageType::Headline),
- "normal" => Ok(MessageType::Normal),
- _ => Err(DeserializeError::FromStr(s.to_string())),
- }
- }
-}
-
-impl ToString for MessageType {
- fn to_string(&self) -> String {
- match self {
- MessageType::Chat => "chat".to_string(),
- MessageType::Error => "error".to_string(),
- MessageType::Groupchat => "groupchat".to_string(),
- MessageType::Headline => "headline".to_string(),
- MessageType::Normal => "normal".to_string(),
- }
- }
-}
-
-#[derive(Clone)]
-pub struct Body {
- lang: Option<String>,
- body: Option<String>,
-}
-
-impl FromElement for Body {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("body")?;
- element.check_namespace(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let body = element.pop_value_opt()?;
-
- Ok(Body { lang, body })
- }
-}
-
-impl IntoElement for Body {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("body", Some(XMLNS))
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_text_opt(self.body.clone())
- }
-}
-
-#[derive(Clone)]
-pub struct Subject {
- lang: Option<String>,
- subject: Option<String>,
-}
-
-impl FromElement for Subject {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("subject")?;
- element.check_namespace(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let subject = element.pop_value_opt()?;
-
- Ok(Subject { lang, subject })
- }
-}
-
-impl IntoElement for Subject {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("subject", Some(XMLNS))
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_text_opt(self.subject.clone())
- }
-}
-
-#[derive(Clone)]
-pub struct Thread {
- parent: Option<String>,
- thread: Option<String>,
-}
-
-impl FromElement for Thread {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("thread")?;
- element.check_namespace(XMLNS)?;
-
- let parent = element.attribute_opt("parent")?;
- let thread = element.pop_value_opt()?;
-
- Ok(Thread { parent, thread })
- }
-}
-
-impl IntoElement for Thread {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("thread", Some(XMLNS))
- .push_attribute_opt("parent", self.parent.clone())
- .push_text_opt(self.thread.clone())
- }
-}
diff --git a/src/stanza/client/mod.rs b/src/stanza/client/mod.rs
deleted file mode 100644
index 2b063d6..0000000
--- a/src/stanza/client/mod.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use iq::Iq;
-use message::Message;
-use peanuts::{
- element::{Content, ContentBuilder, FromContent, FromElement, IntoContent, IntoElement},
- DeserializeError,
-};
-use presence::Presence;
-
-use super::stream::{self, Error as StreamError};
-
-pub mod error;
-pub mod iq;
-pub mod message;
-pub mod presence;
-
-pub const XMLNS: &str = "jabber:client";
-
-pub enum Stanza {
- Message(Message),
- Presence(Presence),
- Iq(Iq),
- Error(StreamError),
- OtherContent(Content),
-}
-
-impl FromContent for Stanza {
- fn from_content(content: Content) -> peanuts::element::DeserializeResult<Self> {
- match content {
- Content::Element(element) => Ok(Stanza::from_element(element)?),
- Content::Text(_) => Ok(Stanza::OtherContent(content)),
- Content::PI => Ok(Stanza::OtherContent(content)),
- Content::Comment(_) => Ok(Stanza::OtherContent(content)),
- }
- }
-}
-
-impl FromElement for Stanza {
- fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- match element.identify() {
- (Some(XMLNS), "message") => Ok(Stanza::Message(Message::from_element(element)?)),
- (Some(XMLNS), "presence") => Ok(Stanza::Presence(Presence::from_element(element)?)),
- (Some(XMLNS), "iq") => Ok(Stanza::Iq(Iq::from_element(element)?)),
- (Some(stream::XMLNS), "error") => {
- Ok(Stanza::Error(StreamError::from_element(element)?))
- }
- _ => Err(DeserializeError::UnexpectedElement(element)),
- }
- }
-}
-
-impl IntoContent for Stanza {
- fn builder(&self) -> peanuts::element::ContentBuilder {
- match self {
- Stanza::Message(message) => <Message as IntoContent>::builder(message),
- Stanza::Presence(presence) => <Presence as IntoContent>::builder(presence),
- Stanza::Iq(iq) => <Iq as IntoContent>::builder(iq),
- Stanza::Error(error) => <StreamError as IntoContent>::builder(error),
- Stanza::OtherContent(_content) => ContentBuilder::Comment("other-content".to_string()),
- }
- }
-}
diff --git a/src/stanza/client/presence.rs b/src/stanza/client/presence.rs
deleted file mode 100644
index bcb04d4..0000000
--- a/src/stanza/client/presence.rs
+++ /dev/null
@@ -1,226 +0,0 @@
-use std::str::FromStr;
-
-use peanuts::{
- element::{FromElement, IntoElement},
- DeserializeError, Element, XML_NS,
-};
-
-use crate::JID;
-
-use super::{error::Error, XMLNS};
-
-pub struct Presence {
- from: Option<JID>,
- id: Option<String>,
- to: Option<JID>,
- r#type: Option<PresenceType>,
- lang: Option<String>,
- // children
- show: Option<Show>,
- status: Option<Status>,
- priority: Option<Priority>,
- // TODO: ##other
- // other: Vec<Other>,
- errors: Vec<Error>,
-}
-
-impl FromElement for Presence {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("presence")?;
- element.check_namespace(XMLNS)?;
-
- let from = element.attribute_opt("from")?;
- let id = element.attribute_opt("id")?;
- let to = element.attribute_opt("to")?;
- let r#type = element.attribute_opt("type")?;
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
-
- let show = element.child_opt()?;
- let status = element.child_opt()?;
- let priority = element.child_opt()?;
- let errors = element.children()?;
-
- Ok(Presence {
- from,
- id,
- to,
- r#type,
- lang,
- show,
- status,
- priority,
- errors,
- })
- }
-}
-
-impl IntoElement for Presence {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("presence", Some(XMLNS))
- .push_attribute_opt("from", self.from.clone())
- .push_attribute_opt("id", self.id.clone())
- .push_attribute_opt("to", self.to.clone())
- .push_attribute_opt("type", self.r#type)
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_child_opt(self.show)
- .push_child_opt(self.status.clone())
- .push_child_opt(self.priority)
- .push_children(self.errors.clone())
- }
-}
-
-pub enum Other {}
-
-#[derive(Copy, Clone)]
-pub enum PresenceType {
- Error,
- Probe,
- Subscribe,
- Subscribed,
- Unavailable,
- Unsubscribe,
- Unsubscribed,
-}
-
-impl FromStr for PresenceType {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "error" => Ok(PresenceType::Error),
- "probe" => Ok(PresenceType::Probe),
- "subscribe" => Ok(PresenceType::Subscribe),
- "subscribed" => Ok(PresenceType::Subscribed),
- "unavailable" => Ok(PresenceType::Unavailable),
- "unsubscribe" => Ok(PresenceType::Unsubscribe),
- "unsubscribed" => Ok(PresenceType::Unsubscribed),
- s => Err(DeserializeError::FromStr(s.to_string())),
- }
- }
-}
-
-impl ToString for PresenceType {
- fn to_string(&self) -> String {
- match self {
- PresenceType::Error => "error".to_string(),
- PresenceType::Probe => "probe".to_string(),
- PresenceType::Subscribe => "subscribe".to_string(),
- PresenceType::Subscribed => "subscribed".to_string(),
- PresenceType::Unavailable => "unavailable".to_string(),
- PresenceType::Unsubscribe => "unsubscribe".to_string(),
- PresenceType::Unsubscribed => "unsubscribed".to_string(),
- }
- }
-}
-
-#[derive(Copy, Clone)]
-pub enum Show {
- Away,
- Chat,
- Dnd,
- Xa,
-}
-
-impl FromElement for Show {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("show")?;
- element.check_namespace(XMLNS)?;
-
- Ok(element.pop_value()?)
- }
-}
-
-impl FromStr for Show {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "away" => Ok(Show::Away),
- "chat" => Ok(Show::Chat),
- "dnd" => Ok(Show::Dnd),
- "xa" => Ok(Show::Xa),
- s => Err(DeserializeError::FromStr(s.to_string())),
- }
- }
-}
-
-impl IntoElement for Show {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("show", Some(XMLNS)).push_text(*self)
- }
-}
-
-impl ToString for Show {
- fn to_string(&self) -> String {
- match self {
- Show::Away => "away".to_string(),
- Show::Chat => "chat".to_string(),
- Show::Dnd => "dnd".to_string(),
- Show::Xa => "xa".to_string(),
- }
- }
-}
-
-#[derive(Clone)]
-pub struct Status {
- lang: Option<String>,
- status: String1024,
-}
-
-impl FromElement for Status {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("status")?;
- element.check_namespace(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let status = element.pop_value()?;
-
- Ok(Status { lang, status })
- }
-}
-
-impl IntoElement for Status {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("status", Some(XMLNS))
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_text(self.status.clone())
- }
-}
-
-// TODO: enforce?
-/// minLength 1 maxLength 1024
-#[derive(Clone)]
-pub struct String1024(pub String);
-
-impl FromStr for String1024 {
- type Err = DeserializeError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(String1024(s.to_string()))
- }
-}
-
-impl ToString for String1024 {
- fn to_string(&self) -> String {
- self.0.clone()
- }
-}
-
-// xs:byte
-#[derive(Clone, Copy)]
-pub struct Priority(pub i8);
-
-impl FromElement for Priority {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("priority")?;
- element.check_namespace(XMLNS)?;
-
- Ok(Priority(element.pop_value()?))
- }
-}
-
-impl IntoElement for Priority {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("priority", Some(XMLNS)).push_text(self.0)
- }
-}
diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs
deleted file mode 100644
index 32716d3..0000000
--- a/src/stanza/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use peanuts::declaration::VersionInfo;
-
-pub mod bind;
-pub mod client;
-pub mod sasl;
-pub mod stanza_error;
-pub mod starttls;
-pub mod stream;
-pub mod stream_error;
-
-pub static XML_VERSION: VersionInfo = VersionInfo::One;
diff --git a/src/stanza/sasl.rs b/src/stanza/sasl.rs
deleted file mode 100644
index ec6f63c..0000000
--- a/src/stanza/sasl.rs
+++ /dev/null
@@ -1,246 +0,0 @@
-use std::ops::Deref;
-
-use peanuts::{
- element::{FromElement, IntoElement},
- DeserializeError, Element,
-};
-use tracing::debug;
-
-pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
-
-#[derive(Debug, Clone)]
-pub struct Mechanisms {
- pub mechanisms: Vec<String>,
-}
-
-impl FromElement for Mechanisms {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("mechanisms")?;
- element.check_namespace(XMLNS)?;
- debug!("getting mechanisms");
- let mechanisms: Vec<Mechanism> = element.pop_children()?;
- debug!("gottting mechanisms");
- let mechanisms = mechanisms
- .into_iter()
- .map(|Mechanism(mechanism)| mechanism)
- .collect();
- debug!("gottting mechanisms");
-
- Ok(Mechanisms { mechanisms })
- }
-}
-
-impl IntoElement for Mechanisms {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("mechanisms", Some(XMLNS)).push_children(
- self.mechanisms
- .iter()
- .map(|mechanism| Mechanism(mechanism.to_string()))
- .collect(),
- )
- }
-}
-
-pub struct Mechanism(String);
-
-impl FromElement for Mechanism {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("mechanism")?;
- element.check_namespace(XMLNS)?;
-
- let mechanism = element.pop_value()?;
-
- Ok(Mechanism(mechanism))
- }
-}
-
-impl IntoElement for Mechanism {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("mechanism", Some(XMLNS)).push_text(self.0.clone())
- }
-}
-
-impl Deref for Mechanism {
- type Target = str;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-#[derive(Debug)]
-pub struct Auth {
- pub mechanism: String,
- pub sasl_data: String,
-}
-
-impl IntoElement for Auth {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("auth", Some(XMLNS))
- .push_attribute("mechanism", self.mechanism.clone())
- .push_text(self.sasl_data.clone())
- }
-}
-
-#[derive(Debug)]
-pub struct Challenge(String);
-
-impl Deref for Challenge {
- type Target = str;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl FromElement for Challenge {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("challenge")?;
- element.check_namespace(XMLNS)?;
-
- let sasl_data = element.value()?;
-
- Ok(Challenge(sasl_data))
- }
-}
-
-#[derive(Debug)]
-pub struct Success(Option<String>);
-
-impl Deref for Success {
- type Target = Option<String>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl FromElement for Success {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("success")?;
- element.check_namespace(XMLNS)?;
-
- let sasl_data = element.value_opt()?;
-
- Ok(Success(sasl_data))
- }
-}
-
-#[derive(Debug)]
-pub enum ServerResponse {
- Challenge(Challenge),
- Success(Success),
- Failure(Failure),
-}
-
-impl FromElement for ServerResponse {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- debug!("identification: {:?}", element.identify());
- match element.identify() {
- (Some(XMLNS), "challenge") => {
- Ok(ServerResponse::Challenge(Challenge::from_element(element)?))
- }
- (Some(XMLNS), "success") => {
- Ok(ServerResponse::Success(Success::from_element(element)?))
- }
- (Some(XMLNS), "failure") => {
- Ok(ServerResponse::Failure(Failure::from_element(element)?))
- }
- _ => Err(DeserializeError::UnexpectedElement(element)),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Response(String);
-
-impl Response {
- pub fn new(response: String) -> Self {
- Self(response)
- }
-}
-
-impl Deref for Response {
- type Target = str;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl IntoElement for Response {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("response", Some(XMLNS)).push_text(self.0.clone())
- }
-}
-
-#[derive(Debug)]
-pub struct Failure {
- r#type: Option<FailureType>,
- text: Option<Text>,
-}
-
-impl FromElement for Failure {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("failure")?;
- element.check_namespace(XMLNS)?;
-
- let r#type = element.pop_child_opt()?;
- let text = element.pop_child_opt()?;
-
- Ok(Failure { r#type, text })
- }
-}
-
-#[derive(Debug)]
-pub enum FailureType {
- Aborted,
- AccountDisabled,
- CredentialsExpired,
- EncryptionRequired,
- IncorrectEncoding,
- InvalidAuthzid,
- InvalidMechanism,
- MalformedRequest,
- MechanismTooWeak,
- NotAuthorized,
- TemporaryAuthFailure,
-}
-
-impl FromElement for FailureType {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- match element.identify() {
- (Some(XMLNS), "aborted") => Ok(FailureType::Aborted),
- (Some(XMLNS), "account-disabled") => Ok(FailureType::AccountDisabled),
- (Some(XMLNS), "credentials-expired") => Ok(FailureType::CredentialsExpired),
- (Some(XMLNS), "encryption-required") => Ok(FailureType::EncryptionRequired),
- (Some(XMLNS), "incorrect-encoding") => Ok(FailureType::IncorrectEncoding),
- (Some(XMLNS), "invalid-authzid") => Ok(FailureType::InvalidAuthzid),
- (Some(XMLNS), "invalid-mechanism") => Ok(FailureType::InvalidMechanism),
- (Some(XMLNS), "malformed-request") => Ok(FailureType::MalformedRequest),
- (Some(XMLNS), "mechanism-too-weak") => Ok(FailureType::MechanismTooWeak),
- (Some(XMLNS), "not-authorized") => Ok(FailureType::NotAuthorized),
- (Some(XMLNS), "temporary-auth-failure") => Ok(FailureType::TemporaryAuthFailure),
- _ => Err(DeserializeError::UnexpectedElement(element)),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Text {
- lang: Option<String>,
- text: Option<String>,
-}
-
-impl FromElement for Text {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("text")?;
- element.check_namespace(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", peanuts::XML_NS)?;
-
- let text = element.pop_value_opt()?;
-
- Ok(Text { lang, text })
- }
-}
diff --git a/src/stanza/stanza_error.rs b/src/stanza/stanza_error.rs
deleted file mode 100644
index 99c1f15..0000000
--- a/src/stanza/stanza_error.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-// https://datatracker.ietf.org/doc/html/rfc6120#appendix-A.8
-
-use peanuts::{
- element::{FromElement, IntoElement},
- Element, XML_NS,
-};
-
-pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas";
-
-#[derive(Clone, Debug)]
-pub enum Error {
- BadRequest,
- Conflict,
- FeatureNotImplemented,
- Forbidden,
- Gone(Option<String>),
- InternalServerError,
- ItemNotFound,
- JidMalformed,
- NotAcceptable,
- NotAllowed,
- NotAuthorized,
- PolicyViolation,
- RecipientUnavailable,
- Redirect(Option<String>),
- RegistrationRequired,
- RemoteServerNotFound,
- RemoteServerTimeout,
- ResourceConstraint,
- ServiceUnavailable,
- SubscriptionRequired,
- UndefinedCondition,
- UnexpectedRequest,
-}
-
-impl FromElement for Error {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- let error;
- match element.identify() {
- (Some(XMLNS), "bad-request") => error = Error::BadRequest,
- (Some(XMLNS), "conflict") => error = Error::Conflict,
- (Some(XMLNS), "feature-not-implemented") => error = Error::FeatureNotImplemented,
- (Some(XMLNS), "forbidden") => error = Error::Forbidden,
- (Some(XMLNS), "gone") => return Ok(Error::Gone(element.pop_value_opt()?)),
- (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError,
- (Some(XMLNS), "item-not-found") => error = Error::ItemNotFound,
- (Some(XMLNS), "jid-malformed") => error = Error::JidMalformed,
- (Some(XMLNS), "not-acceptable") => error = Error::NotAcceptable,
- (Some(XMLNS), "not-allowed") => error = Error::NotAllowed,
- (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized,
- (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation,
- (Some(XMLNS), "recipient-unavailable") => error = Error::RecipientUnavailable,
- (Some(XMLNS), "redirect") => return Ok(Error::Redirect(element.pop_value_opt()?)),
- (Some(XMLNS), "registration-required") => error = Error::RegistrationRequired,
- (Some(XMLNS), "remote-server-not-found") => error = Error::RemoteServerNotFound,
- (Some(XMLNS), "remote-server-timeout") => error = Error::RemoteServerTimeout,
- (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint,
- (Some(XMLNS), "service-unavailable") => error = Error::ServiceUnavailable,
- (Some(XMLNS), "subscription-required") => error = Error::SubscriptionRequired,
- (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition,
- (Some(XMLNS), "unexpected-request") => error = Error::UnexpectedRequest,
- _ => return Err(peanuts::DeserializeError::UnexpectedElement(element)),
- }
- element.no_more_content()?;
- return Ok(error);
- }
-}
-
-impl IntoElement for Error {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- match self {
- Error::BadRequest => Element::builder("bad-request", Some(XMLNS)),
- Error::Conflict => Element::builder("conflict", Some(XMLNS)),
- Error::FeatureNotImplemented => {
- Element::builder("feature-not-implemented", Some(XMLNS))
- }
- Error::Forbidden => Element::builder("forbidden", Some(XMLNS)),
- Error::Gone(r) => Element::builder("gone", Some(XMLNS)).push_text_opt(r.clone()),
- Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)),
- Error::ItemNotFound => Element::builder("item-not-found", Some(XMLNS)),
- Error::JidMalformed => Element::builder("jid-malformed", Some(XMLNS)),
- Error::NotAcceptable => Element::builder("not-acceptable", Some(XMLNS)),
- Error::NotAllowed => Element::builder("not-allowed", Some(XMLNS)),
- Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)),
- Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)),
- Error::RecipientUnavailable => Element::builder("recipient-unavailable", Some(XMLNS)),
- Error::Redirect(r) => {
- Element::builder("redirect", Some(XMLNS)).push_text_opt(r.clone())
- }
- Error::RegistrationRequired => Element::builder("registration-required", Some(XMLNS)),
- Error::RemoteServerNotFound => Element::builder("remote-server-not-found", Some(XMLNS)),
- Error::RemoteServerTimeout => Element::builder("remote-server-timeout", Some(XMLNS)),
- Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)),
- Error::ServiceUnavailable => Element::builder("service-unavailable", Some(XMLNS)),
- Error::SubscriptionRequired => Element::builder("subscription-required", Some(XMLNS)),
- Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)),
- Error::UnexpectedRequest => Element::builder("unexpected-request", Some(XMLNS)),
- }
- }
-}
-
-#[derive(Clone, Debug)]
-pub struct Text {
- lang: Option<String>,
- text: Option<String>,
-}
-
-impl FromElement for Text {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("text")?;
- element.check_name(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let text = element.pop_value_opt()?;
-
- Ok(Text { lang, text })
- }
-}
-
-impl IntoElement for Text {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("text", Some(XMLNS))
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_text_opt(self.text.clone())
- }
-}
diff --git a/src/stanza/starttls.rs b/src/stanza/starttls.rs
deleted file mode 100644
index fb66711..0000000
--- a/src/stanza/starttls.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-use std::collections::{HashMap, HashSet};
-
-use peanuts::{
- element::{Content, FromElement, IntoElement, Name, NamespaceDeclaration},
- Element,
-};
-
-pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-tls";
-
-#[derive(Debug, Clone)]
-pub struct StartTls {
- pub required: bool,
-}
-
-impl IntoElement for StartTls {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- let mut builder = Element::builder("starttls", Some(XMLNS));
-
- if self.required {
- builder = builder.push_child(Required)
- }
-
- builder
- }
-}
-
-impl FromElement for StartTls {
- fn from_element(
- mut element: peanuts::Element,
- ) -> std::result::Result<StartTls, peanuts::DeserializeError> {
- element.check_name("starttls")?;
- element.check_namespace(XMLNS)?;
-
- let mut required = false;
- if let Some(_) = element.child_opt::<Required>()? {
- required = true;
- }
-
- Ok(StartTls { required })
- }
-}
-
-#[derive(Debug)]
-pub struct Required;
-
-impl FromElement for Required {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("required")?;
- element.check_namespace(XMLNS)?;
-
- Ok(Required)
- }
-}
-
-impl IntoElement for Required {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("required", Some(XMLNS))
- }
-}
-
-#[derive(Debug)]
-pub struct Proceed;
-
-impl IntoElement for Proceed {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("proceed", Some(XMLNS))
- }
-}
-
-impl FromElement for Proceed {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("proceed")?;
- element.check_namespace(XMLNS)?;
-
- Ok(Proceed)
- }
-}
-
-pub struct Failure;
-
-impl IntoElement for Failure {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("failure", Some(XMLNS))
- }
-}
-
-impl FromElement for Failure {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("failure")?;
- element.check_namespace(XMLNS)?;
-
- Ok(Failure)
- }
-}
diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs
deleted file mode 100644
index 84d62d9..0000000
--- a/src/stanza/stream.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-use std::collections::{HashMap, HashSet};
-
-use peanuts::element::{Content, ElementBuilder, FromElement, IntoElement, NamespaceDeclaration};
-use peanuts::XML_NS;
-use peanuts::{element::Name, Element};
-use tracing::debug;
-
-use crate::stanza::bind;
-use crate::JID;
-
-use super::sasl::{self, Mechanisms};
-use super::starttls::{self, StartTls};
-use super::stream_error::{Error as StreamError, Text};
-use super::{client, stream_error};
-
-pub const XMLNS: &str = "http://etherx.jabber.org/streams";
-
-// MUST be qualified by stream namespace
-// #[derive(XmlSerialize, XmlDeserialize)]
-// #[peanuts(xmlns = XMLNS)]
-#[derive(Debug)]
-pub struct Stream {
- pub from: Option<JID>,
- to: Option<JID>,
- id: Option<String>,
- version: Option<String>,
- // TODO: lang enum
- lang: Option<String>,
- // #[peanuts(content)]
- // content: Message,
-}
-
-impl FromElement for Stream {
- fn from_element(mut element: Element) -> std::result::Result<Self, peanuts::DeserializeError> {
- element.check_namespace(XMLNS)?;
- element.check_name("stream")?;
-
- let from = element.attribute_opt("from")?;
- let to = element.attribute_opt("to")?;
- let id = element.attribute_opt("id")?;
- let version = element.attribute_opt("version")?;
- let lang = element.attribute_opt_namespaced("lang", peanuts::XML_NS)?;
-
- Ok(Stream {
- from,
- to,
- id,
- version,
- lang,
- })
- }
-}
-
-impl IntoElement for Stream {
- fn builder(&self) -> ElementBuilder {
- Element::builder("stream", Some(XMLNS.to_string()))
- .push_namespace_declaration_override(Some("stream"), XMLNS)
- .push_namespace_declaration_override(None::<&str>, client::XMLNS)
- .push_attribute_opt("to", self.to.clone())
- .push_attribute_opt("from", self.from.clone())
- .push_attribute_opt("id", self.id.clone())
- .push_attribute_opt("version", self.version.clone())
- .push_attribute_opt_namespaced(peanuts::XML_NS, "to", self.lang.clone())
- }
-}
-
-impl<'s> Stream {
- pub fn new(
- from: Option<JID>,
- to: Option<JID>,
- id: Option<String>,
- version: Option<String>,
- lang: Option<String>,
- ) -> Self {
- Self {
- from,
- to,
- id,
- version,
- lang,
- }
- }
-
- /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute.
- /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection.
- pub fn new_client(from: Option<JID>, to: JID, id: Option<String>, lang: String) -> Self {
- Self {
- from,
- to: Some(to),
- id,
- version: Some("1.0".to_string()),
- lang: Some(lang),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Features {
- pub features: Vec<Feature>,
-}
-
-impl IntoElement for Features {
- fn builder(&self) -> ElementBuilder {
- Element::builder("features", Some(XMLNS)).push_children(self.features.clone())
- }
-}
-
-impl FromElement for Features {
- fn from_element(
- mut element: Element,
- ) -> std::result::Result<Features, peanuts::DeserializeError> {
- element.check_namespace(XMLNS)?;
- element.check_name("features")?;
-
- debug!("got features stanza");
- let features = element.children()?;
- debug!("got features period");
-
- Ok(Features { features })
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum Feature {
- StartTls(StartTls),
- Sasl(Mechanisms),
- Bind,
- Unknown,
-}
-
-impl IntoElement for Feature {
- fn builder(&self) -> ElementBuilder {
- match self {
- Feature::StartTls(start_tls) => start_tls.builder(),
- Feature::Sasl(mechanisms) => mechanisms.builder(),
- Feature::Bind => todo!(),
- Feature::Unknown => todo!(),
- }
- }
-}
-
-impl FromElement for Feature {
- fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
- let identity = element.identify();
- debug!("identity: {:?}", identity);
- match element.identify() {
- (Some(starttls::XMLNS), "starttls") => {
- debug!("identified starttls");
- Ok(Feature::StartTls(StartTls::from_element(element)?))
- }
- (Some(sasl::XMLNS), "mechanisms") => {
- debug!("identified mechanisms");
- Ok(Feature::Sasl(Mechanisms::from_element(element)?))
- }
- (Some(bind::XMLNS), "bind") => {
- debug!("identified bind");
- Ok(Feature::Bind)
- }
- _ => {
- debug!("identified unknown feature");
- Ok(Feature::Unknown)
- }
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Error {
- error: StreamError,
- text: Option<Text>,
-}
-
-impl FromElement for Error {
- fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("error")?;
- element.check_namespace(XMLNS)?;
-
- let error = element.pop_child_one()?;
- let text = element.pop_child_opt()?;
-
- Ok(Error { error, text })
- }
-}
-
-impl IntoElement for Error {
- fn builder(&self) -> ElementBuilder {
- Element::builder("error", Some(XMLNS))
- .push_child(self.error.clone())
- .push_child_opt(self.text.clone())
- }
-}
diff --git a/src/stanza/stream_error.rs b/src/stanza/stream_error.rs
deleted file mode 100644
index 5ae04a6..0000000
--- a/src/stanza/stream_error.rs
+++ /dev/null
@@ -1,137 +0,0 @@
-use peanuts::{
- element::{FromElement, IntoElement},
- DeserializeError, Element, XML_NS,
-};
-
-pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-streams";
-
-#[derive(Clone, Debug)]
-pub enum Error {
- BadFormat,
- BadNamespacePrefix,
- Conflict,
- ConnectionTimeout,
- HostGone,
- HostUnknown,
- ImproperAddressing,
- InternalServerError,
- InvalidFrom,
- InvalidId,
- InvalidNamespace,
- InvalidXml,
- NotAuthorized,
- NotWellFormed,
- PolicyViolation,
- RemoteConnectionFailed,
- Reset,
- ResourceConstraint,
- RestrictedXml,
- SeeOtherHost(Option<String>),
- SystemShutdown,
- UndefinedCondition,
- UnsupportedEncoding,
- UnsupportedStanzaType,
- UnsupportedVersion,
-}
-
-impl FromElement for Error {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- let error;
- match element.identify() {
- (Some(XMLNS), "bad-format") => error = Error::BadFormat,
- (Some(XMLNS), "bad-namespace-prefix") => error = Error::BadNamespacePrefix,
- (Some(XMLNS), "conflict") => error = Error::Conflict,
- (Some(XMLNS), "connection-timeout") => error = Error::ConnectionTimeout,
- (Some(XMLNS), "host-gone") => error = Error::HostGone,
- (Some(XMLNS), "host-unknown") => error = Error::HostUnknown,
- (Some(XMLNS), "improper-addressing") => error = Error::ImproperAddressing,
- (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError,
- (Some(XMLNS), "invalid-from") => error = Error::InvalidFrom,
- (Some(XMLNS), "invalid-id") => error = Error::InvalidId,
- (Some(XMLNS), "invalid-namespace") => error = Error::InvalidNamespace,
- (Some(XMLNS), "invalid-xml") => error = Error::InvalidXml,
- (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized,
- (Some(XMLNS), "not-well-formed") => error = Error::NotWellFormed,
- (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation,
- (Some(XMLNS), "remote-connection-failed") => error = Error::RemoteConnectionFailed,
- (Some(XMLNS), "reset") => error = Error::Reset,
- (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint,
- (Some(XMLNS), "restricted-xml") => error = Error::RestrictedXml,
- (Some(XMLNS), "see-other-host") => {
- return Ok(Error::SeeOtherHost(element.pop_value_opt()?))
- }
- (Some(XMLNS), "system-shutdown") => error = Error::SystemShutdown,
- (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition,
- (Some(XMLNS), "unsupported-encoding") => error = Error::UnsupportedEncoding,
- (Some(XMLNS), "unsupported-stanza-type") => error = Error::UnsupportedStanzaType,
- (Some(XMLNS), "unsupported-version") => error = Error::UnsupportedVersion,
- _ => return Err(DeserializeError::UnexpectedElement(element)),
- }
- element.no_more_content()?;
- return Ok(error);
- }
-}
-
-impl IntoElement for Error {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- match self {
- Error::BadFormat => Element::builder("bad-format", Some(XMLNS)),
- Error::BadNamespacePrefix => Element::builder("bad-namespace-prefix", Some(XMLNS)),
- Error::Conflict => Element::builder("conflict", Some(XMLNS)),
- Error::ConnectionTimeout => Element::builder("connection-timeout", Some(XMLNS)),
- Error::HostGone => Element::builder("host-gone", Some(XMLNS)),
- Error::HostUnknown => Element::builder("host-unknown", Some(XMLNS)),
- Error::ImproperAddressing => Element::builder("improper-addressing", Some(XMLNS)),
- Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)),
- Error::InvalidFrom => Element::builder("invalid-from", Some(XMLNS)),
- Error::InvalidId => Element::builder("invalid-id", Some(XMLNS)),
- Error::InvalidNamespace => Element::builder("invalid-namespace", Some(XMLNS)),
- Error::InvalidXml => Element::builder("invalid-xml", Some(XMLNS)),
- Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)),
- Error::NotWellFormed => Element::builder("not-well-formed", Some(XMLNS)),
- Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)),
- Error::RemoteConnectionFailed => {
- Element::builder("remote-connection-failed", Some(XMLNS))
- }
- Error::Reset => Element::builder("reset", Some(XMLNS)),
- Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)),
- Error::RestrictedXml => Element::builder("restricted-xml", Some(XMLNS)),
- Error::SeeOtherHost(h) => {
- Element::builder("see-other-host", Some(XMLNS)).push_text_opt(h.clone())
- }
- Error::SystemShutdown => Element::builder("system-shutdown", Some(XMLNS)),
- Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)),
- Error::UnsupportedEncoding => Element::builder("unsupported-encoding", Some(XMLNS)),
- Error::UnsupportedStanzaType => {
- Element::builder("unsupported-stanza-type", Some(XMLNS))
- }
- Error::UnsupportedVersion => Element::builder("unsupported-version", Some(XMLNS)),
- }
- }
-}
-
-#[derive(Clone, Debug)]
-pub struct Text {
- text: Option<String>,
- lang: Option<String>,
-}
-
-impl FromElement for Text {
- fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
- element.check_name("text")?;
- element.check_name(XMLNS)?;
-
- let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
- let text = element.pop_value_opt()?;
-
- Ok(Text { lang, text })
- }
-}
-
-impl IntoElement for Text {
- fn builder(&self) -> peanuts::element::ElementBuilder {
- Element::builder("text", Some(XMLNS))
- .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
- .push_text_opt(self.text.clone())
- }
-}