aboutsummaryrefslogtreecommitdiffstats
path: root/stanza/src/stream.rs
diff options
context:
space:
mode:
Diffstat (limited to 'stanza/src/stream.rs')
-rw-r--r--stanza/src/stream.rs205
1 files changed, 205 insertions, 0 deletions
diff --git a/stanza/src/stream.rs b/stanza/src/stream.rs
new file mode 100644
index 0000000..89b03d8
--- /dev/null
+++ b/stanza/src/stream.rs
@@ -0,0 +1,205 @@
+use std::collections::{HashMap, HashSet};
+
+use jid::JID;
+use peanuts::element::{Content, ElementBuilder, FromElement, IntoElement, NamespaceDeclaration};
+use peanuts::{element::Name, Element};
+
+use crate::bind;
+
+use super::sasl::{self, Mechanisms};
+use super::starttls::{self, StartTls};
+use super::stream_error::{Error as StreamError, Text};
+use super::{client, stream_error};
+
+pub const XMLNS: &str = "http://etherx.jabber.org/streams";
+
+// MUST be qualified by stream namespace
+// #[derive(XmlSerialize, XmlDeserialize)]
+// #[peanuts(xmlns = XMLNS)]
+#[derive(Debug)]
+pub struct Stream {
+ pub from: Option<JID>,
+ to: Option<JID>,
+ id: Option<String>,
+ version: Option<String>,
+ // TODO: lang enum
+ lang: Option<String>,
+ // #[peanuts(content)]
+ // content: Message,
+}
+
+impl FromElement for Stream {
+ fn from_element(mut element: Element) -> std::result::Result<Self, peanuts::DeserializeError> {
+ element.check_namespace(XMLNS)?;
+ element.check_name("stream")?;
+
+ let from = element.attribute_opt("from")?;
+ let to = element.attribute_opt("to")?;
+ let id = element.attribute_opt("id")?;
+ let version = element.attribute_opt("version")?;
+ let lang = element.attribute_opt_namespaced("lang", peanuts::XML_NS)?;
+
+ Ok(Stream {
+ from,
+ to,
+ id,
+ version,
+ lang,
+ })
+ }
+}
+
+impl IntoElement for Stream {
+ fn builder(&self) -> ElementBuilder {
+ Element::builder("stream", Some(XMLNS.to_string()))
+ .push_namespace_declaration_override(Some("stream"), XMLNS)
+ .push_namespace_declaration_override(None::<&str>, client::XMLNS)
+ .push_attribute_opt("to", self.to.clone())
+ .push_attribute_opt("from", self.from.clone())
+ .push_attribute_opt("id", self.id.clone())
+ .push_attribute_opt("version", self.version.clone())
+ .push_attribute_opt_namespaced(peanuts::XML_NS, "to", self.lang.clone())
+ }
+}
+
+impl<'s> Stream {
+ pub fn new(
+ from: Option<JID>,
+ to: Option<JID>,
+ id: Option<String>,
+ version: Option<String>,
+ lang: Option<String>,
+ ) -> Self {
+ Self {
+ from,
+ to,
+ id,
+ version,
+ lang,
+ }
+ }
+
+ /// For initial stream headers, the initiating entity SHOULD include the 'xml:lang' attribute.
+ /// For privacy, it is better to not set `from` when sending a client stanza over an unencrypted connection.
+ pub fn new_client(from: Option<JID>, to: JID, id: Option<String>, lang: String) -> Self {
+ Self {
+ from,
+ to: Some(to),
+ id,
+ version: Some("1.0".to_string()),
+ lang: Some(lang),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Features {
+ pub features: Vec<Feature>,
+}
+
+impl Features {
+ pub fn negotiate(self) -> Option<Feature> {
+ if let Some(Feature::StartTls(s)) = self
+ .features
+ .iter()
+ .find(|feature| matches!(feature, Feature::StartTls(_s)))
+ {
+ // TODO: avoid clone
+ return Some(Feature::StartTls(s.clone()));
+ } else if let Some(Feature::Sasl(mechanisms)) = self
+ .features
+ .iter()
+ .find(|feature| matches!(feature, Feature::Sasl(_)))
+ {
+ // TODO: avoid clone
+ return Some(Feature::Sasl(mechanisms.clone()));
+ } else if let Some(Feature::Bind) = self
+ .features
+ .into_iter()
+ .find(|feature| matches!(feature, Feature::Bind))
+ {
+ Some(Feature::Bind)
+ } else {
+ return None;
+ }
+ }
+}
+
+impl IntoElement for Features {
+ fn builder(&self) -> ElementBuilder {
+ Element::builder("features", Some(XMLNS)).push_children(self.features.clone())
+ }
+}
+
+impl FromElement for Features {
+ fn from_element(
+ mut element: Element,
+ ) -> std::result::Result<Features, peanuts::DeserializeError> {
+ element.check_namespace(XMLNS)?;
+ element.check_name("features")?;
+
+ let features = element.children()?;
+
+ Ok(Features { features })
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Feature {
+ StartTls(StartTls),
+ Sasl(Mechanisms),
+ Bind,
+ Unknown,
+}
+
+impl IntoElement for Feature {
+ fn builder(&self) -> ElementBuilder {
+ match self {
+ Feature::StartTls(start_tls) => start_tls.builder(),
+ Feature::Sasl(mechanisms) => mechanisms.builder(),
+ Feature::Bind => todo!(),
+ Feature::Unknown => todo!(),
+ }
+ }
+}
+
+impl FromElement for Feature {
+ fn from_element(element: Element) -> peanuts::element::DeserializeResult<Self> {
+ match element.identify() {
+ (Some(starttls::XMLNS), "starttls") => {
+ Ok(Feature::StartTls(StartTls::from_element(element)?))
+ }
+ (Some(sasl::XMLNS), "mechanisms") => {
+ Ok(Feature::Sasl(Mechanisms::from_element(element)?))
+ }
+ (Some(bind::XMLNS), "bind") => Ok(Feature::Bind),
+ _ => Ok(Feature::Unknown),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Error {
+ error: StreamError,
+ text: Option<Text>,
+}
+
+impl FromElement for Error {
+ fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+ element.check_name("error")?;
+ element.check_namespace(XMLNS)?;
+
+ let error = element.pop_child_one()?;
+ let text = element.pop_child_opt()?;
+
+ Ok(Error { error, text })
+ }
+}
+
+impl IntoElement for Error {
+ fn builder(&self) -> ElementBuilder {
+ Element::builder("error", Some(XMLNS))
+ .push_child(self.error.clone())
+ .push_child_opt(self.text.clone())
+ }
+}