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())),) } }