implement bind
This commit is contained in:
parent
859a19820d
commit
be198ca15b
|
@ -2,6 +2,7 @@ use std::str::Utf8Error;
|
||||||
|
|
||||||
use rsasl::mechname::MechanismNameError;
|
use rsasl::mechname::MechanismNameError;
|
||||||
|
|
||||||
|
use crate::stanza::client::error::Error as ClientError;
|
||||||
use crate::{jid::ParseError, stanza::sasl::Failure};
|
use crate::{jid::ParseError, stanza::sasl::Failure};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -22,12 +23,14 @@ pub enum Error {
|
||||||
Negotiation,
|
Negotiation,
|
||||||
TlsRequired,
|
TlsRequired,
|
||||||
UnexpectedEnd,
|
UnexpectedEnd,
|
||||||
UnexpectedElement,
|
UnexpectedElement(peanuts::Element),
|
||||||
UnexpectedText,
|
UnexpectedText,
|
||||||
XML(peanuts::Error),
|
XML(peanuts::Error),
|
||||||
SASL(SASLError),
|
SASL(SASLError),
|
||||||
JID(ParseError),
|
JID(ParseError),
|
||||||
Authentication(Failure),
|
Authentication(Failure),
|
||||||
|
ClientError(ClientError),
|
||||||
|
MissingError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
111
src/jabber.rs
111
src/jabber.rs
|
@ -13,6 +13,9 @@ use trust_dns_resolver::proto::rr::domain::IntoLabel;
|
||||||
|
|
||||||
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::stanza::client::error::Error as ClientError;
|
||||||
|
use crate::stanza::client::iq::{Iq, IqType, Query};
|
||||||
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
||||||
use crate::stanza::starttls::{Proceed, StartTls};
|
use crate::stanza::starttls::{Proceed, StartTls};
|
||||||
use crate::stanza::stream::{Feature, Features, Stream};
|
use crate::stanza::stream::{Feature, Features, Stream};
|
||||||
|
@ -147,7 +150,96 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn bind(&mut self) -> Result<()> {
|
pub async fn bind(&mut self) -> Result<()> {
|
||||||
todo!()
|
let iq_id = nanoid::nanoid!();
|
||||||
|
if let Some(resource) = self.jid.clone().unwrap().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))),
|
||||||
|
})),
|
||||||
|
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(jid))),
|
||||||
|
})),
|
||||||
|
errors: _,
|
||||||
|
} if id == iq_id => {
|
||||||
|
self.jid = Some(jid);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
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(jid))),
|
||||||
|
})),
|
||||||
|
errors: _,
|
||||||
|
} if id == iq_id => {
|
||||||
|
self.jid = Some(jid);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
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]
|
#[instrument]
|
||||||
|
@ -324,9 +416,12 @@ impl std::fmt::Debug for Jabber<Unencrypted> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
#[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
async fn start_stream() {
|
async fn start_stream() {
|
||||||
|
@ -373,4 +468,18 @@ mod tests {
|
||||||
Feature::Unknown => 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,99 @@
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use peanuts::element::{FromElement, IntoElement};
|
||||||
|
use peanuts::{DeserializeError, Element};
|
||||||
|
|
||||||
|
use crate::stanza::error::Text;
|
||||||
|
use crate::stanza::Error as StanzaError;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::JID;
|
||||||
|
|
||||||
|
pub struct Message {
|
||||||
|
from: Option<JID>,
|
||||||
|
id: Option<String>,
|
||||||
|
to: Option<JID>,
|
||||||
|
r#type: Option<MessageType>,
|
||||||
|
// children
|
||||||
|
subject: Option<Subject>,
|
||||||
|
body: Option<Body>,
|
||||||
|
thread: Option<Thread>,
|
||||||
|
lang: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MessageType {
|
||||||
|
Chat,
|
||||||
|
Error,
|
||||||
|
Groupchat,
|
||||||
|
Headline,
|
||||||
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Body {
|
||||||
|
lang: Option<String>,
|
||||||
|
body: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Subject {
|
||||||
|
lang: Option<String>,
|
||||||
|
subject: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Thread {
|
||||||
|
// TODO: NOT DONE
|
||||||
|
parent: Option<String>,
|
||||||
|
thread: Option<String>,
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod error;
|
||||||
|
pub mod iq;
|
||||||
|
pub mod message;
|
||||||
|
pub mod presence;
|
||||||
|
|
||||||
|
pub const XMLNS: &str = "jabber:client";
|
|
@ -0,0 +1,48 @@
|
||||||
|
use peanuts::element::{FromElement, IntoElement};
|
||||||
|
|
||||||
|
use crate::JID;
|
||||||
|
|
||||||
|
use super::error::Error;
|
||||||
|
|
||||||
|
pub struct Presence {
|
||||||
|
from: Option<JID>,
|
||||||
|
id: Option<String>,
|
||||||
|
to: Option<JID>,
|
||||||
|
r#type: PresenceType,
|
||||||
|
lang: Option<String>,
|
||||||
|
// children
|
||||||
|
show: Option<Show>,
|
||||||
|
status: Option<Status>,
|
||||||
|
priority: Option<Priority>,
|
||||||
|
errors: Vec<Error>,
|
||||||
|
// ##other
|
||||||
|
// content: Vec<Box<dyn AsElement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PresenceType {
|
||||||
|
Error,
|
||||||
|
Probe,
|
||||||
|
Subscribe,
|
||||||
|
Subscribed,
|
||||||
|
Unavailable,
|
||||||
|
Unsubscribe,
|
||||||
|
Unsubscribed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Show {
|
||||||
|
Away,
|
||||||
|
Chat,
|
||||||
|
Dnd,
|
||||||
|
Xa,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Status {
|
||||||
|
lang: Option<String>,
|
||||||
|
status: String1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
// minLength 1 maxLength 1024
|
||||||
|
pub struct String1024(String);
|
||||||
|
|
||||||
|
// xs:byte
|
||||||
|
pub struct Priority(u8);
|
|
@ -0,0 +1,126 @@
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use peanuts::declaration::VersionInfo;
|
use peanuts::declaration::VersionInfo;
|
||||||
|
|
||||||
pub mod bind;
|
pub mod bind;
|
||||||
pub mod iq;
|
pub mod client;
|
||||||
pub mod message;
|
pub mod error;
|
||||||
pub mod presence;
|
|
||||||
pub mod sasl;
|
pub mod sasl;
|
||||||
pub mod starttls;
|
pub mod starttls;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
|
||||||
pub static XML_VERSION: VersionInfo = VersionInfo::One;
|
pub static XML_VERSION: VersionInfo = VersionInfo::One;
|
||||||
|
|
||||||
|
pub use error::Error;
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl IntoElement for StartTls {
|
||||||
let mut builder = Element::builder("starttls", Some(XMLNS));
|
let mut builder = Element::builder("starttls", Some(XMLNS));
|
||||||
|
|
||||||
if self.required {
|
if self.required {
|
||||||
builder = builder.push_child(Element::builder("required", Some(XMLNS)))
|
builder = builder.push_child(Required)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder
|
builder
|
||||||
|
@ -52,6 +52,12 @@ impl FromElement for Required {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoElement for Required {
|
||||||
|
fn builder(&self) -> peanuts::element::ElementBuilder {
|
||||||
|
Element::builder("required", Some(XMLNS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Proceed;
|
pub struct Proceed;
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,14 @@ use peanuts::XML_NS;
|
||||||
use peanuts::{element::Name, Element};
|
use peanuts::{element::Name, Element};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::stanza::bind;
|
||||||
use crate::{Error, JID};
|
use crate::{Error, JID};
|
||||||
|
|
||||||
|
use super::client;
|
||||||
use super::sasl::{self, Mechanisms};
|
use super::sasl::{self, Mechanisms};
|
||||||
use super::starttls::{self, StartTls};
|
use super::starttls::{self, StartTls};
|
||||||
|
|
||||||
pub const XMLNS: &str = "http://etherx.jabber.org/streams";
|
pub const XMLNS: &str = "http://etherx.jabber.org/streams";
|
||||||
pub const XMLNS_CLIENT: &str = "jabber:client";
|
|
||||||
|
|
||||||
// MUST be qualified by stream namespace
|
// MUST be qualified by stream namespace
|
||||||
// #[derive(XmlSerialize, XmlDeserialize)]
|
// #[derive(XmlSerialize, XmlDeserialize)]
|
||||||
|
@ -53,7 +54,7 @@ impl IntoElement for Stream {
|
||||||
fn builder(&self) -> ElementBuilder {
|
fn builder(&self) -> ElementBuilder {
|
||||||
Element::builder("stream", Some(XMLNS.to_string()))
|
Element::builder("stream", Some(XMLNS.to_string()))
|
||||||
.push_namespace_declaration_override(Some("stream"), XMLNS)
|
.push_namespace_declaration_override(Some("stream"), XMLNS)
|
||||||
.push_namespace_declaration_override(None::<&str>, XMLNS_CLIENT)
|
.push_namespace_declaration_override(None::<&str>, client::XMLNS)
|
||||||
.push_attribute_opt("to", self.to.clone())
|
.push_attribute_opt("to", self.to.clone())
|
||||||
.push_attribute_opt("from", self.from.clone())
|
.push_attribute_opt("from", self.from.clone())
|
||||||
.push_attribute_opt("id", self.id.clone())
|
.push_attribute_opt("id", self.id.clone())
|
||||||
|
@ -150,6 +151,10 @@ impl FromElement for Feature {
|
||||||
debug!("identified mechanisms");
|
debug!("identified mechanisms");
|
||||||
Ok(Feature::Sasl(Mechanisms::from_element(element)?))
|
Ok(Feature::Sasl(Mechanisms::from_element(element)?))
|
||||||
}
|
}
|
||||||
|
(Some(bind::XMLNS), "bind") => {
|
||||||
|
debug!("identified bind");
|
||||||
|
Ok(Feature::Bind)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!("identified unknown feature");
|
debug!("identified unknown feature");
|
||||||
Ok(Feature::Unknown)
|
Ok(Feature::Unknown)
|
||||||
|
|
Loading…
Reference in New Issue