From d444fc407b1cc9267486d8d3c74cd3ec34313c14 Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Wed, 8 Mar 2023 19:56:53 +0100 Subject: [PATCH] feat(xmpp): iq parsing --- docs/cheatsheet.md | 5 ++ src/protos/xmpp/client.rs | 113 ++++++++++++++++++++++++++++++++++++++ src/util/xml.rs | 8 +++ 3 files changed, 126 insertions(+) diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 03c1de7..5e15ee2 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -23,3 +23,8 @@ Make sure `xmpp.key` starts and ends with: -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- ``` + + +## Protocol Specs + +XMPP XSDs - [https://xmpp.org/schemas/index.shtml] diff --git a/src/protos/xmpp/client.rs b/src/protos/xmpp/client.rs index 34349a4..4fdd7ce 100644 --- a/src/protos/xmpp/client.rs +++ b/src/protos/xmpp/client.rs @@ -152,6 +152,119 @@ impl MessageType { } } +pub struct Iq { + pub from: Option, + pub id: String, + pub to: Option, + pub r#type: IqType, + pub body: T, +} + +impl FromXml for Iq { + type P = IqParser; + + fn parse() -> Self::P { + IqParser(IqParserInner::Init) + } +} + +pub struct IqParser(IqParserInner); + +enum IqParserInner { + Init, + ParsingBody(IqParserState, T::P), + Final(IqParserState), +} +struct IqParserState { + pub from: Option, + pub id: Option, + pub to: Option, + pub r#type: Option, + pub body: Option, +} + +impl Parser for IqParser { + type Output = Result>; + + fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation { + match self.0 { + IqParserInner::Init => { + if let Event::Start(ref bytes) = event { + let mut state: IqParserState = IqParserState { from: None, id: None, to: None, r#type: None, body: None }; + for attr in bytes.attributes() { + let attr = fail_fast!(attr); + if attr.key.0 == b"from" { + let value = fail_fast!(std::str::from_utf8(&*attr.value)); + state.from = Some(value.to_string()) + } else if attr.key.0 == b"id" { + let value = fail_fast!(std::str::from_utf8(&*attr.value)); + state.id = Some(value.to_string()) + } else if attr.key.0 == b"to" { + let value = fail_fast!(std::str::from_utf8(&*attr.value)); + state.to = Some(value.to_string()) + } else if attr.key.0 == b"type" { + let value = fail_fast!(IqType::from_str(&*attr.value)); + state.r#type = Some(value); + } + } + Continuation::Continue(IqParser(IqParserInner::ParsingBody(state, T::parse()))) + } else { + Continuation::Final(Err(ffail!("Expected start"))) + } + } + IqParserInner::ParsingBody(mut state, parser) => { + match parser.consume(event) { + Continuation::Final(f) => { + let body = fail_fast!(f); + state.body = Some(body); + Continuation::Continue(IqParser(IqParserInner::Final(state))) + } + Continuation::Continue(parser) => { + Continuation::Continue(IqParser(IqParserInner::ParsingBody(state, parser))) + } + } + } + IqParserInner::Final(state) => { + if let Event::End(ref bytes) = event { + let id = fail_fast!(state.id.ok_or_else(|| fail("No id provided"))); + let r#type = fail_fast!(state.r#type.ok_or_else(|| fail("No type provided"))); + let body = fail_fast!(state.body.ok_or_else(|| fail("No body provided"))); + Continuation::Final(Ok(Iq { + from: state.from, + id, + to: state.to, + r#type, + body, + })) + } else { + Continuation::Final(Err(ffail!("Unexpected event: {event:?}"))) + } + } + } + } +} + +pub enum IqType { + Error, + Get, + Result, + Set, +} + +impl IqType { + pub fn from_str(s: &[u8]) -> Result { + use IqType::*; + let s = std::str::from_utf8(s)?; + match s { + "error" => Ok(Error), + "get" => Ok(Get), + "result" => Ok(Result), + "set" => Ok(Set), + t => Err(ffail!("Unknown iq type: {t}")), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/util/xml.rs b/src/util/xml.rs index 2a9dadd..e04b0cb 100644 --- a/src/util/xml.rs +++ b/src/util/xml.rs @@ -1,5 +1,13 @@ use quick_xml::events::Event; +use crate::prelude::Result; + +pub trait FromXml: Sized { + type P: Parser>; + + fn parse() -> Self::P; +} + pub trait Parser: Sized { type Output;