Compare commits

...

3 Commits

Author SHA1 Message Date
cel 🌸 1b91ff6904 use cargo workspace 2024-12-04 18:18:37 +00:00
cel 🌸 03764f8ced rename jabber to jabber_stream 2024-12-04 17:40:56 +00:00
cel 🌸 21f10a0b43 implement send_stanza 2024-12-04 17:38:36 +00:00
26 changed files with 2094 additions and 244 deletions

View File

@ -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"]}

10
README.md Normal file
View File

@ -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

1935
jabber/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

29
jabber/Cargo.toml Normal file
View File

@ -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"]}

View File

@ -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;

View File

@ -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)]

View File

@ -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);

View File

@ -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>;

6
jid/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "jid"
version = "0.1.0"
edition = "2021"
[dependencies]

View File

@ -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>,

6
luz/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "luz"
version = "0.1.0"
edition = "2021"
[dependencies]

3
luz/src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

8
stanza/Cargo.toml Normal file
View File

@ -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" }

View File

@ -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)]

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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 {

View File

@ -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)?))

View File

@ -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>,

View File

@ -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>,