xmpp: rewrite bind request parser as a coroutine (#12)

Reviewed-on: lavina/lavina#12
This commit is contained in:
Nikita Vilunov 2023-09-30 16:02:18 +00:00
parent 444b608e96
commit 1b3551f108
1 changed files with 38 additions and 71 deletions

View File

@ -78,89 +78,56 @@ impl Jid {
#[derive(PartialEq, Eq, Debug)]
pub struct BindRequest(pub Resource);
pub struct BindRequestParser(BindRequestParserInner);
enum BindRequestParserInner {
Initial,
/// Consumed <bind> start and expects <resource>
InBind(Option<String>),
/// Consumed <resource> start
InBindResourceInitial,
/// Consumer <resource> start and inner text
InBindResourceEnd(String),
}
impl FromXmlTag for BindRequest {
const NS: &'static str = XMLNS;
const NAME: &'static str = "bind";
}
impl FromXml for BindRequest {
type P = BindRequestParser;
type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P {
BindRequestParser(BindRequestParserInner::Initial)
}
}
// TODO rewrite as a generator
impl Parser for BindRequestParser {
type Output = Result<BindRequest>;
fn consume<'a>(
self: Self,
namespace: ResolveResult,
event: &Event<'a>,
) -> Continuation<Self, Self::Output> {
// TODO validate tag names and namespaces
use BindRequestParserInner::*;
match self.0 {
Initial => {
let Event::Start(bytes) = event else {
return Continuation::Final(Err(anyhow!("Unexpected XML event: {event:?}")));
};
if bytes.name().0 != BindRequest::NAME.as_bytes() {
return Continuation::Final(Err(anyhow!(
"Unexpected XML tag: {:?}",
bytes.name()
)));
}
let ResolveResult::Bound(Namespace(ns)) = namespace else {
return Continuation::Final(Err(anyhow!("No namespace provided")));
};
if ns != XMLNS.as_bytes() {
return Continuation::Final(Err(anyhow!("Incorrect namespace")));
}
Continuation::Continue(BindRequestParser(InBind(None)))
|(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
let mut resource: Option<Str> = None;
let Event::Start(bytes) = event else {
return Err(anyhow!("Unexpected XML event: {event:?}"));
};
if bytes.name().0 != BindRequest::NAME.as_bytes() {
return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name()));
}
InBind(resource) => match event {
Event::Start(bytes) => {
Continuation::Continue(BindRequestParser(InBindResourceInitial))
}
Event::End(bytes) => {
let Some(resource) = resource else {
return Continuation::Final(Err(anyhow!("No resource was provided")));
let ResolveResult::Bound(Namespace(ns)) = namespace else {
return Err(anyhow!("No namespace provided"));
};
if ns != XMLNS.as_bytes() {
return Err(anyhow!("Incorrect namespace"));
}
loop {
let (namespace, event) = yield;
match event {
Event::Start(bytes) if bytes.name().0 == b"resource" => {
let (namespace, event) = yield;
let Event::Text(text) = event else {
return Err(anyhow!("Unexpected XML event: {event:?}"));
};
Continuation::Final(Ok(BindRequest(Resource(resource.into()))))
resource = Some(std::str::from_utf8(&*text)?.into());
let (namespace, event) = yield;
let Event::End(bytes) = event else {
return Err(anyhow!("Unexpected XML event: {event:?}"));
};
if bytes.name().0 != b"resource" {
return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name()));
}
}
Event::End(bytes) if bytes.name().0 == BindRequest::NAME.as_bytes() => {
break;
}
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
}
_ => Continuation::Final(Err(anyhow!("Unexpected XML event: {event:?}"))),
},
InBindResourceInitial => {
let Event::Text(text) = event else {
return Continuation::Final(Err(anyhow!("Unexpected XML event: {event:?}")));
};
let resource = match std::str::from_utf8(&*text) {
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(anyhow!("Unexpected XML event: {event:?}")));
};
Continuation::Continue(BindRequestParser(InBind(Some(resource))))
}
let Some(resource) = resource else {
return Err(anyhow!("No resource was provided"));
};
Ok(BindRequest(Resource(resource)))
}
}
}