From f131454cb2e7f5d22585d92f1cc3f752295fe820 Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Sat, 11 Mar 2023 16:07:02 +0100 Subject: [PATCH] feat(xmpp): parsing of bind request --- docs/cheatsheet.md | 2 + src/protos/xmpp/bind.rs | 153 ++++++++++++++++++++++++++++++++++++++ src/protos/xmpp/client.rs | 22 ++++-- src/protos/xmpp/mod.rs | 1 + src/protos/xmpp/stream.rs | 5 +- src/util/xml.rs | 7 +- 6 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 src/protos/xmpp/bind.rs diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 5e15ee2..2097d9c 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -28,3 +28,5 @@ Make sure `xmpp.key` starts and ends with: ## Protocol Specs XMPP XSDs - [https://xmpp.org/schemas/index.shtml] + +IRC modern spec - [https://modern.ircdocs.horse/] diff --git a/src/protos/xmpp/bind.rs b/src/protos/xmpp/bind.rs new file mode 100644 index 0000000..3d53121 --- /dev/null +++ b/src/protos/xmpp/bind.rs @@ -0,0 +1,153 @@ +use nom::AsBytes; +use quick_xml::events::Event; +use quick_xml::name::{Namespace, ResolveResult}; + +use crate::prelude::*; +use crate::util::xml::{Continuation, FromXml, Parser}; + +pub const XMLNS: &'static str = "urn:ietf:params:xml:ns:xmpp-bind"; + +#[derive(PartialEq, Eq, Debug)] +pub struct Name(String); + +#[derive(PartialEq, Eq, Debug)] +pub struct Server(String); + +#[derive(PartialEq, Eq, Debug)] +pub struct Resource(String); + +#[derive(PartialEq, Eq, Debug)] +pub struct Jid { + pub name: Name, + pub server: Server, + pub resource: Resource, +} + +/// Request to bind to a resource. +/// +/// Example: +/// ```xml +/// +/// mobile +/// +/// ``` +/// +#[derive(PartialEq, Eq, Debug)] +pub struct BindRequest(Resource); + +pub struct BindRequestParser(BindRequestParserInner); + +enum BindRequestParserInner { + Initial, + /// Consumed start and expects + InBind(Option), + /// Consumed start + InBindResourceInitial, + /// Consumer start and inner text + InBindResourceEnd(String), +} + +impl FromXml for BindRequest { + type P = BindRequestParser; + + fn parse() -> Self::P { + BindRequestParser(BindRequestParserInner::Initial) + } +} + +impl Parser for BindRequestParser { + type Output = Result; + + fn consume<'a>( + self: Self, + namespace: ResolveResult, + event: &Event<'a>, + ) -> Continuation { + // TODO validate tag names and namespaces + use BindRequestParserInner::*; + match self.0 { + Initial => { + let Event::Start(bytes) = event else { + return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))); + }; + if bytes.name().0 != b"bind" { + return Continuation::Final(Err(ffail!( + "Unexpected XML tag: {:?}", + bytes.name() + ))); + } + let ResolveResult::Bound(Namespace(ns)) = namespace else { + return Continuation::Final(Err(ffail!("No namespace provided"))); + }; + if ns != XMLNS.as_bytes() { + return Continuation::Final(Err(ffail!("Incorrect namespace"))); + } + Continuation::Continue(BindRequestParser(InBind(None))) + } + InBind(resource) => match event { + Event::Start(bytes) => { + Continuation::Continue(BindRequestParser(InBindResourceInitial)) + } + Event::End(bytes) => { + let Some(resource) = resource else { + return Continuation::Final(Err(ffail!("No resource was provided"))); + }; + Continuation::Final(Ok(BindRequest(Resource(resource)))) + } + _ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))), + }, + InBindResourceInitial => { + let Event::Text(text) = event else { + return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))); + }; + let resource = match std::str::from_utf8(text.as_bytes()) { + Ok(e) => e.to_string(), + Err(err) => return Continuation::Final(Err(err.into())), + }; + Continuation::Continue(BindRequestParser(InBindResourceEnd(resource))) + } + InBindResourceEnd(resource) => { + let Event::End(bytes) = event else { + return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))); + }; + Continuation::Continue(BindRequestParser(InBind(Some(resource)))) + } + } + } +} + +pub struct BindResponse(Jid); + +#[cfg(test)] +mod tests { + use quick_xml::NsReader; + + use super::*; + + #[tokio::test] + async fn parse_message() { + let input = + r#"mobile"#; + let mut reader = NsReader::from_reader(input.as_bytes()); + let mut buf = vec![]; + let (ns, event) = reader + .read_resolved_event_into_async(&mut buf) + .await + .unwrap(); + let mut parser = BindRequest::parse().consume(ns, &event); + let result = loop { + match parser { + Continuation::Final(res) => break res, + Continuation::Continue(next) => { + let (ns, event) = reader + .read_resolved_event_into_async(&mut buf) + .await + .unwrap(); + parser = next.consume(ns, &event); + } + } + } + .unwrap(); + assert_eq!(result, BindRequest(Resource("mobile".to_string())),) + } +} diff --git a/src/protos/xmpp/client.rs b/src/protos/xmpp/client.rs index 4fdd7ce..616cd98 100644 --- a/src/protos/xmpp/client.rs +++ b/src/protos/xmpp/client.rs @@ -1,4 +1,5 @@ use quick_xml::events::Event; +use quick_xml::name::ResolveResult; use crate::prelude::*; use crate::util::xml::*; @@ -44,7 +45,11 @@ struct MessageParserState { impl Parser for MessageParser { type Output = Result; - fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation { + fn consume<'a>( + self: Self, + namespace: ResolveResult, + event: &Event<'a>, + ) -> Continuation { // TODO validate tag name and namespace at each stage match self { MessageParser::Init => { @@ -186,7 +191,11 @@ struct IqParserState { impl Parser for IqParser { type Output = Result>; - fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation { + fn consume<'a>( + self: Self, + namespace: ResolveResult, + event: &Event<'a>, + ) -> Continuation { match self.0 { IqParserInner::Init => { if let Event::Start(ref bytes) = event { @@ -213,7 +222,7 @@ impl Parser for IqParser { } } IqParserInner::ParsingBody(mut state, parser) => { - match parser.consume(event) { + match parser.consume(namespace, event) { Continuation::Final(f) => { let body = fail_fast!(f); state.body = Some(body); @@ -275,13 +284,14 @@ mod tests { let input = r#"daabbb"#; let mut reader = NsReader::from_reader(input.as_bytes()); let mut buf = vec![]; - let event = reader.read_event_into_async(&mut buf).await.unwrap(); - let mut parser = Message::parse().consume(&event); + let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap(); + let mut parser = Message::parse().consume(ns, &event); let result = loop { match parser { Continuation::Final(res) => break res, Continuation::Continue(next) => { - parser = next.consume(&reader.read_event_into_async(&mut buf).await.unwrap()) + let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap(); + parser = next.consume(ns, &event); } } } diff --git a/src/protos/xmpp/mod.rs b/src/protos/xmpp/mod.rs index 37088f6..92ba9be 100644 --- a/src/protos/xmpp/mod.rs +++ b/src/protos/xmpp/mod.rs @@ -1,3 +1,4 @@ +pub mod bind; pub mod client; pub mod sasl; pub mod stanzaerror; diff --git a/src/protos/xmpp/stream.rs b/src/protos/xmpp/stream.rs index da6465a..b4a9818 100644 --- a/src/protos/xmpp/stream.rs +++ b/src/protos/xmpp/stream.rs @@ -179,12 +179,13 @@ impl FromClient { }; let (ns, name) = reader.resolve_element(start.name()); if name.as_ref() == b"message" { - let mut parser = Message::parse().consume(&incoming); + let mut parser = Message::parse().consume(ns, &incoming); let result = loop { match parser { Continuation::Final(res) => break res, Continuation::Continue(next) => { - parser = next.consume(&reader.read_event_into_async(buf).await.unwrap()) + let (ns, event) = reader.read_resolved_event_into_async(buf).await.unwrap(); + parser = next.consume(ns, &event); } } }?; diff --git a/src/util/xml.rs b/src/util/xml.rs index e04b0cb..ad6ea3a 100644 --- a/src/util/xml.rs +++ b/src/util/xml.rs @@ -1,4 +1,5 @@ use quick_xml::events::Event; +use quick_xml::name::ResolveResult; use crate::prelude::Result; @@ -11,7 +12,11 @@ pub trait FromXml: Sized { pub trait Parser: Sized { type Output; - fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation; + fn consume<'a>( + self: Self, + namespace: ResolveResult, + event: &Event<'a>, + ) -> Continuation; } pub enum Continuation {