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 {