diff options
| author | 2023-08-02 00:56:38 +0100 | |
|---|---|---|
| committer | 2023-08-02 00:56:38 +0100 | |
| commit | cd7bb95c0a31d187bfe25bad15043f0b33b111cf (patch) | |
| tree | c5be0c651198abf736f8867a36906f9345f3a0ac /src | |
| parent | 322b2a3b46348ec1c5acbc538de93310c9030b96 (diff) | |
| download | luz-cd7bb95c0a31d187bfe25bad15043f0b33b111cf.tar.gz luz-cd7bb95c0a31d187bfe25bad15043f0b33b111cf.tar.bz2 luz-cd7bb95c0a31d187bfe25bad15043f0b33b111cf.zip | |
implement resource binding
Diffstat (limited to '')
| -rw-r--r-- | src/client/encrypted.rs | 55 | ||||
| -rw-r--r-- | src/error.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 14 | ||||
| -rw-r--r-- | src/stanza/bind.rs | 111 | ||||
| -rw-r--r-- | src/stanza/iq.rs | 171 | ||||
| -rw-r--r-- | src/stanza/mod.rs | 46 | ||||
| -rw-r--r-- | src/stanza/sasl.rs | 27 | ||||
| -rw-r--r-- | src/stanza/stream.rs | 6 | 
8 files changed, 384 insertions, 52 deletions
| diff --git a/src/client/encrypted.rs b/src/client/encrypted.rs index e8b7271..86aba13 100644 --- a/src/client/encrypted.rs +++ b/src/client/encrypted.rs @@ -11,19 +11,22 @@ use tokio::net::TcpStream;  use tokio_native_tls::TlsStream;  use crate::stanza::{ -    sasl::{Auth, Response}, -    stream::{Stream, StreamFeature}, -}; -use crate::stanza::{ +    bind::Bind, +    iq::IQ,      sasl::{Challenge, Success},      Element,  }; +use crate::stanza::{ +    sasl::{Auth, Response}, +    stream::{Stream, StreamFeature}, +};  use crate::Jabber; +use crate::JabberError;  use crate::Result;  pub struct JabberClient<'j> { -    reader: Reader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, -    writer: Writer<WriteHalf<TlsStream<TcpStream>>>, +    pub reader: Reader<BufReader<ReadHalf<TlsStream<TcpStream>>>>, +    pub writer: Writer<WriteHalf<TlsStream<TcpStream>>>,      jabber: &'j mut Jabber<'j>,  } @@ -64,15 +67,19 @@ impl<'j> JabberClient<'j> {      pub async fn negotiate(&mut self) -> Result<()> {          loop { -            println!("loop"); +            println!("negotiate loop");              let features = self.get_features().await?;              println!("features: {:?}", features); +              match &features[0] {                  StreamFeature::Sasl(sasl) => {                      println!("sasl?");                      self.sasl(&sasl).await?;                  } -                StreamFeature::Bind => todo!(), +                StreamFeature::Bind => { +                    self.bind().await?; +                    return Ok(()); +                }                  x => println!("{:?}", x),              }          } @@ -165,4 +172,36 @@ impl<'j> JabberClient<'j> {          self.start_stream().await?;          Ok(())      } + +    pub async fn bind(&mut self) -> Result<()> { +        match &self.jabber.jid.resourcepart { +            Some(resource) => { +                println!("setting resource"); +                let bind = Bind { +                    resource: Some(resource.clone()), +                    jid: None, +                }; +                let result: Bind = IQ::set(self, None, None, bind).await?.try_into()?; +                if let Some(jid) = result.jid { +                    println!("{}", jid); +                    self.jabber.jid = jid; +                    return Ok(()); +                } +            } +            None => { +                println!("not setting resource"); +                let bind = Bind { +                    resource: None, +                    jid: None, +                }; +                let result: Bind = IQ::set(self, None, None, bind).await?.try_into()?; +                if let Some(jid) = result.jid { +                    println!("{}", jid); +                    self.jabber.jid = jid; +                    return Ok(()); +                } +            } +        } +        Err(JabberError::BindError) +    }  } diff --git a/src/error.rs b/src/error.rs index 17bfbef..a912840 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,8 +17,14 @@ pub enum JabberError {      Utf8Decode,      NoFeatures,      UnknownNamespace, +    UnknownAttribute, +    NoID, +    NoType, +    IDMismatch, +    BindError,      ParseError,      UnexpectedEnd, +    UnexpectedElement,      XML(quick_xml::Error),      SASL(SASLError),      Element(ElementError<'static>), @@ -1,4 +1,5 @@  #![allow(unused_must_use)] +#![feature(let_chains)]  // TODO: logging (dropped errors)  pub mod client; @@ -44,10 +45,13 @@ mod tests {      #[tokio::test]      async fn login() { -        Jabber::new(JID::from_str("test@blos.sm").unwrap(), "slayed".to_owned()) -            .unwrap() -            .login() -            .await -            .unwrap(); +        Jabber::new( +            JID::from_str("test@blos.sm/clown").unwrap(), +            "slayed".to_owned(), +        ) +        .unwrap() +        .login() +        .await +        .unwrap();      }  } diff --git a/src/stanza/bind.rs b/src/stanza/bind.rs new file mode 100644 index 0000000..f1bdc2d --- /dev/null +++ b/src/stanza/bind.rs @@ -0,0 +1,111 @@ +use quick_xml::{ +    events::{BytesStart, BytesText, Event}, +    name::QName, +    Reader, +}; + +use super::{Element, IntoElement}; +use crate::{JabberError, JID}; + +const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; + +pub struct Bind { +    pub resource: Option<String>, +    pub jid: Option<JID>, +} + +impl<'e> IntoElement<'e> for Bind { +    fn event(&self) -> quick_xml::events::Event<'static> { +        let mut bind_event = BytesStart::new("bind"); +        bind_event.push_attribute(("xmlns", XMLNS)); +        if self.resource.is_none() && self.jid.is_none() { +            return Event::Empty(bind_event); +        } else { +            return Event::Start(bind_event); +        } +    } + +    fn children(&self) -> Option<Vec<Element<'static>>> { +        if let Some(resource) = &self.resource { +            let resource_event: BytesStart<'static> = BytesStart::new("resource"); +            let resource_child: BytesText<'static> = BytesText::new(resource).into_owned(); +            let resource_child: Element<'static> = Element { +                event: Event::Text(resource_child), +                children: None, +            }; +            let resource_element: Element<'static> = Element { +                event: Event::Start(resource_event), +                children: Some(vec![resource_child]), +            }; +            return Some(vec![resource_element]); +        } else if let Some(jid) = &self.jid { +            let jid_event = BytesStart::new("jid"); +            let jid_child = BytesText::new(&jid.to_string()).into_owned(); +            let jid_child = Element { +                event: Event::Text(jid_child), +                children: None, +            }; +            let jid_element = Element { +                event: Event::Start(jid_event), +                children: Some(vec![jid_child]), +            }; +            return Some(vec![jid_element]); +        } +        None +    } +} + +impl TryFrom<Element<'static>> for Bind { +    type Error = JabberError; + +    fn try_from(element: Element<'static>) -> Result<Self, Self::Error> { +        if let Event::Start(start) = &element.event { +            let buf: Vec<u8> = Vec::new(); +            let reader = Reader::from_reader(buf); +            if start.name() == QName(b"bind") +                && start.try_get_attribute("xmlns")?.is_some_and(|attribute| { +                    attribute.decode_and_unescape_value(&reader).unwrap() == XMLNS +                }) +            { +                let child: Element<'static> = element.child()?.clone(); +                if let Event::Start(start) = &child.event { +                    match start.name() { +                        QName(b"resource") => { +                            let resource_text = child.child()?; +                            if let Event::Text(text) = &resource_text.event { +                                return Ok(Self { +                                    resource: Some(text.unescape()?.into_owned()), +                                    jid: None, +                                }); +                            } +                        } +                        QName(b"jid") => { +                            let jid_text = child.child()?; +                            if let Event::Text(text) = &jid_text.event { +                                return Ok(Self { +                                    jid: Some(text.unescape()?.into_owned().try_into()?), +                                    resource: None, +                                }); +                            } +                        } +                        _ => return Err(JabberError::UnexpectedElement), +                    } +                } +            } +        } else if let Event::Empty(start) = &element.event { +            let buf: Vec<u8> = Vec::new(); +            let reader = Reader::from_reader(buf); +            if start.name() == QName(b"bind") +                && start.try_get_attribute("xmlns")?.is_some_and(|attribute| { +                    attribute.decode_and_unescape_value(&reader).unwrap() == XMLNS +                }) +            { +                return Ok(Bind { +                    resource: None, +                    jid: None, +                }); +            } +        } +        Err(JabberError::UnexpectedElement) +    } +} diff --git a/src/stanza/iq.rs b/src/stanza/iq.rs new file mode 100644 index 0000000..8a373b2 --- /dev/null +++ b/src/stanza/iq.rs @@ -0,0 +1,171 @@ +use nanoid::nanoid; +use quick_xml::{ +    events::{BytesStart, Event}, +    name::QName, +    Reader, Writer, +}; + +use crate::{JabberClient, JabberError, JID}; + +use super::{Element, IntoElement}; +use crate::Result; + +#[derive(Debug)] +pub struct IQ { +    to: Option<JID>, +    from: Option<JID>, +    id: String, +    r#type: IQType, +    lang: Option<String>, +    child: Element<'static>, +} + +#[derive(Debug)] +enum IQType { +    Get, +    Set, +    Result, +    Error, +} + +impl IQ { +    pub async fn set<'j, R: IntoElement<'static>>( +        client: &mut JabberClient<'j>, +        to: Option<JID>, +        from: Option<JID>, +        element: R, +    ) -> Result<Element<'static>> { +        let id = nanoid!(); +        let iq = IQ { +            to, +            from, +            id: id.clone(), +            r#type: IQType::Set, +            lang: None, +            child: Element::from(element), +        }; +        println!("{:?}", iq); +        let iq = Element::from(iq); +        println!("{:?}", iq); +        iq.write(&mut client.writer).await?; +        let result = Element::read(&mut client.reader).await?; +        let iq = IQ::try_from(result)?; +        if iq.id == id { +            return Ok(iq.child); +        } +        Err(JabberError::IDMismatch) +    } +} + +impl<'e> IntoElement<'e> for IQ { +    fn event(&self) -> quick_xml::events::Event<'e> { +        let mut start = BytesStart::new("iq"); +        if let Some(to) = &self.to { +            start.push_attribute(("to", to.to_string().as_str())); +        } +        if let Some(from) = &self.from { +            start.push_attribute(("from", from.to_string().as_str())); +        } +        start.push_attribute(("id", self.id.as_str())); +        match self.r#type { +            IQType::Get => start.push_attribute(("type", "get")), +            IQType::Set => start.push_attribute(("type", "set")), +            IQType::Result => start.push_attribute(("type", "result")), +            IQType::Error => start.push_attribute(("type", "error")), +        } +        if let Some(lang) = &self.lang { +            start.push_attribute(("from", lang.to_string().as_str())); +        } + +        quick_xml::events::Event::Start(start) +    } + +    fn children(&self) -> Option<Vec<Element<'e>>> { +        Some(vec![self.child.clone()]) +    } +} + +impl TryFrom<Element<'static>> for IQ { +    type Error = JabberError; + +    fn try_from(element: Element<'static>) -> std::result::Result<Self, Self::Error> { +        if let Event::Start(start) = &element.event { +            if start.name() == QName(b"iq") { +                let mut to: Option<JID> = None; +                let mut from: Option<JID> = None; +                let mut id = None; +                let mut r#type = None; +                let mut lang = None; +                start +                    .attributes() +                    .into_iter() +                    .try_for_each(|attribute| -> Result<()> { +                        if let Ok(attribute) = attribute { +                            let buf: Vec<u8> = Vec::new(); +                            let reader = Reader::from_reader(buf); +                            match attribute.key { +                                QName(b"to") => { +                                    to = Some( +                                        attribute +                                            .decode_and_unescape_value(&reader) +                                            .or(Err(JabberError::Utf8Decode))? +                                            .into_owned() +                                            .try_into()?, +                                    ) +                                } +                                QName(b"from") => { +                                    from = Some( +                                        attribute +                                            .decode_and_unescape_value(&reader) +                                            .or(Err(JabberError::Utf8Decode))? +                                            .into_owned() +                                            .try_into()?, +                                    ) +                                } +                                QName(b"id") => { +                                    id = Some( +                                        attribute +                                            .decode_and_unescape_value(&reader) +                                            .or(Err(JabberError::Utf8Decode))? +                                            .into_owned(), +                                    ) +                                } +                                QName(b"type") => { +                                    let value = attribute +                                        .decode_and_unescape_value(&reader) +                                        .or(Err(JabberError::Utf8Decode))?; +                                    match value.as_ref() { +                                        "get" => r#type = Some(IQType::Get), +                                        "set" => r#type = Some(IQType::Set), +                                        "result" => r#type = Some(IQType::Result), +                                        "error" => r#type = Some(IQType::Error), +                                        _ => return Err(JabberError::ParseError), +                                    } +                                } +                                QName(b"lang") => { +                                    lang = Some( +                                        attribute +                                            .decode_and_unescape_value(&reader) +                                            .or(Err(JabberError::Utf8Decode))? +                                            .into_owned(), +                                    ) +                                } +                                _ => return Err(JabberError::UnknownAttribute), +                            } +                        } +                        Ok(()) +                    })?; +                let iq = IQ { +                    to, +                    from, +                    id: id.ok_or(JabberError::NoID)?, +                    r#type: r#type.ok_or(JabberError::NoType)?, +                    lang, +                    child: element.child()?.to_owned(), +                }; +                return Ok(iq); +            } +        } +        Err(JabberError::ParseError) +    } +} diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs index c29b1a2..ad9e228 100644 --- a/src/stanza/mod.rs +++ b/src/stanza/mod.rs @@ -1,5 +1,7 @@  // use quick_xml::events::BytesDecl; +pub mod bind; +pub mod iq;  pub mod sasl;  pub mod stream; @@ -128,28 +130,40 @@ impl<'e> Element<'e> {              e => Err(ElementError::NotAStart(e.into_owned()).into()),          }      } -} -/// if there is only one child in the vec of children, will return that element -pub fn child<'p, 'e>(element: &'p Element<'e>) -> Result<&'p Element<'e>, ElementError<'static>> { -    if let Some(children) = &element.children { -        if children.len() == 1 { -            return Ok(&children[0]); -        } else { -            return Err(ElementError::MultipleChildren); +    /// if there is only one child in the vec of children, will return that element +    pub fn child<'p>(&'p self) -> Result<&'p Element<'e>, ElementError<'static>> { +        if let Some(children) = &self.children { +            if children.len() == 1 { +                return Ok(&children[0]); +            } else { +                return Err(ElementError::MultipleChildren); +            } +        } +        Err(ElementError::NoChildren) +    } + +    /// returns reference to children +    pub fn children<'p>(&'p self) -> Result<&'p Vec<Element<'e>>, ElementError<'e>> { +        if let Some(children) = &self.children { +            return Ok(children);          } +        Err(ElementError::NoChildren)      } -    Err(ElementError::NoChildren)  } -/// returns reference to children -pub fn children<'p, 'e>( -    element: &'p Element<'e>, -) -> Result<&'p Vec<Element<'e>>, ElementError<'e>> { -    if let Some(children) = &element.children { -        return Ok(children); +pub trait IntoElement<'e> { +    fn event(&self) -> Event<'e>; +    fn children(&self) -> Option<Vec<Element<'e>>>; +} + +impl<'e, T: IntoElement<'e>> From<T> for Element<'e> { +    fn from(value: T) -> Self { +        Element { +            event: value.event(), +            children: value.children(), +        }      } -    Err(ElementError::NoChildren)  }  #[derive(Debug)] diff --git a/src/stanza/sasl.rs b/src/stanza/sasl.rs index bbf3f41..50ffd83 100644 --- a/src/stanza/sasl.rs +++ b/src/stanza/sasl.rs @@ -7,6 +7,7 @@ use crate::error::SASLError;  use crate::JabberError;  use super::Element; +use super::IntoElement;  const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-sasl"; @@ -16,7 +17,7 @@ pub struct Auth<'e> {      pub sasl_data: &'e str,  } -impl<'e> Auth<'e> { +impl<'e> IntoElement<'e> for Auth<'e> {      fn event(&self) -> Event<'e> {          let mut start = BytesStart::new("auth");          start.push_attribute(("xmlns", XMLNS)); @@ -34,15 +35,6 @@ impl<'e> Auth<'e> {      }  } -impl<'e> Into<Element<'e>> for Auth<'e> { -    fn into(self) -> Element<'e> { -        Element { -            event: self.event(), -            children: self.children(), -        } -    } -} -  #[derive(Debug)]  pub struct Challenge {      pub sasl_data: Vec<u8>, @@ -54,7 +46,7 @@ impl<'e> TryFrom<&Element<'e>> for Challenge {      fn try_from(element: &Element<'e>) -> Result<Challenge, Self::Error> {          if let Event::Start(start) = &element.event {              if start.name() == QName(b"challenge") { -                let sasl_data: &Element<'_> = super::child(element)?; +                let sasl_data: &Element<'_> = element.child()?;                  if let Event::Text(sasl_data) = &sasl_data.event {                      let s = sasl_data.clone();                      let s = s.into_inner(); @@ -101,7 +93,7 @@ pub struct Response<'e> {      pub sasl_data: &'e str,  } -impl<'e> Response<'e> { +impl<'e> IntoElement<'e> for Response<'e> {      fn event(&self) -> Event<'e> {          let mut start = BytesStart::new("response");          start.push_attribute(("xmlns", XMLNS)); @@ -118,15 +110,6 @@ impl<'e> Response<'e> {      }  } -impl<'e> Into<Element<'e>> for Response<'e> { -    fn into(self) -> Element<'e> { -        Element { -            event: self.event(), -            children: self.children(), -        } -    } -} -  #[derive(Debug)]  pub struct Success {      pub sasl_data: Option<Vec<u8>>, @@ -139,7 +122,7 @@ impl<'e> TryFrom<&Element<'e>> for Success {          match &element.event {              Event::Start(start) => {                  if start.name() == QName(b"success") { -                    match super::child(element) { +                    match element.child() {                          Ok(sasl_data) => {                              if let Event::Text(sasl_data) = &sasl_data.event {                                  return Ok(Success { diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs index 66741b8..f85166f 100644 --- a/src/stanza/stream.rs +++ b/src/stanza/stream.rs @@ -175,7 +175,11 @@ impl<'e> TryFrom<Element<'e>> for Vec<StreamFeature> {                              }                              features.push(StreamFeature::Sasl(mechanisms))                          } -                        _ => {} +                        _ => features.push(StreamFeature::Unknown), +                    }, +                    Event::Empty(e) => match e.name() { +                        QName(b"bind") => features.push(StreamFeature::Bind), +                        _ => features.push(StreamFeature::Unknown),                      },                      _ => features.push(StreamFeature::Unknown),                  } | 
