From ecee3e9ee8a55179a8895eab3778a7926977e37e Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 30 Jul 2019 21:25:27 +0200 Subject: [PATCH] bind: Split Bind into request/response. --- src/bind.rs | 131 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 40 deletions(-) diff --git a/src/bind.rs b/src/bind.rs index 408c08bb..35d22045 100644 --- a/src/bind.rs +++ b/src/bind.rs @@ -17,76 +17,111 @@ use std::convert::TryFrom; /// /// See https://xmpp.org/rfcs/rfc6120.html#bind #[derive(Debug, Clone, PartialEq)] -pub enum Bind { - /// Requests no particular resource, a random one will be affected by the - /// server. - None, - +pub struct BindRequest { /// Requests this resource, the server may associate another one though. - Resource(String), - - /// The full JID returned by the server for this client. - Jid(FullJid), + /// + /// If this is None, we request no particular resource, and a random one + /// will be affected by the server. + resource: Option, } -impl Bind { +impl BindRequest { /// Creates a resource binding request. - pub fn new(resource: Option) -> Bind { - match resource { - None => Bind::None, - Some(resource) => Bind::Resource(resource), - } + pub fn new(resource: Option) -> BindRequest { + BindRequest { resource } } } -impl IqSetPayload for Bind {} -impl IqResultPayload for Bind {} +impl IqSetPayload for BindRequest {} -impl TryFrom for Bind { +impl TryFrom for BindRequest { type Error = Error; - fn try_from(elem: Element) -> Result { + fn try_from(elem: Element) -> Result { check_self!(elem, "bind", BIND); check_no_attributes!(elem, "bind"); - let mut bind = Bind::None; + let mut resource = None; for child in elem.children() { - if bind != Bind::None { + if resource.is_some() { return Err(Error::ParseError("Bind can only have one child.")); } if child.is("resource", ns::BIND) { check_no_attributes!(child, "resource"); check_no_children!(child, "resource"); - bind = Bind::Resource(child.text()); - } else if child.is("jid", ns::BIND) { - check_no_attributes!(child, "jid"); - check_no_children!(child, "jid"); - bind = Bind::Jid(FullJid::from_str(&child.text())?); + resource = Some(child.text()); } else { - return Err(Error::ParseError("Unknown element in bind.")); + return Err(Error::ParseError("Unknown element in bind request.")); } } - Ok(bind) + Ok(BindRequest { resource }) } } -impl From for Element { - fn from(bind: Bind) -> Element { +impl From for Element { + fn from(bind: BindRequest) -> Element { Element::builder("bind") .ns(ns::BIND) - .append(match bind { - Bind::None => vec![], - Bind::Resource(resource) => vec![Element::builder("resource") + .append(match bind.resource { + None => vec![], + Some(resource) => vec![Element::builder("resource") .ns(ns::BIND) .append(resource) .build()], - Bind::Jid(jid) => vec![Element::builder("jid").ns(ns::BIND).append(jid).build()], }) .build() } } +/// The response for resource binding, containing the client’s full JID. +/// +/// See https://xmpp.org/rfcs/rfc6120.html#bind +#[derive(Debug, Clone, PartialEq)] +pub struct BindResponse { + /// The full JID returned by the server for this client. + jid: FullJid, +} + +impl IqResultPayload for BindResponse {} + +impl TryFrom for BindResponse { + type Error = Error; + + fn try_from(elem: Element) -> Result { + check_self!(elem, "bind", BIND); + check_no_attributes!(elem, "bind"); + + let mut jid = None; + for child in elem.children() { + if jid.is_some() { + return Err(Error::ParseError("Bind can only have one child.")); + } + if child.is("jid", ns::BIND) { + check_no_attributes!(child, "jid"); + check_no_children!(child, "jid"); + jid = Some(FullJid::from_str(&child.text())?); + } else { + return Err(Error::ParseError("Unknown element in bind response.")); + } + } + + Ok(BindResponse { jid: match jid { + None => return Err(Error::ParseError("Bind response must contain a jid element.")), + Some(jid) => jid, + } }) + } +} + +impl From for Element { + fn from(bind: BindResponse) -> Element { + Element::builder("bind") + .ns(ns::BIND) + .append(Element::builder("jid").ns(ns::BIND).append(bind.jid).build()) + .build() + } +} + #[cfg(test)] mod tests { use super::*; @@ -94,13 +129,15 @@ mod tests { #[cfg(target_pointer_width = "32")] #[test] fn test_size() { - assert_size!(Bind, 40); + assert_size!(BindRequest, 12); + assert_size!(BindResponse, 36); } #[cfg(target_pointer_width = "64")] #[test] fn test_size() { - assert_size!(Bind, 80); + assert_size!(BindRequest, 24); + assert_size!(BindResponse, 72); } #[test] @@ -108,8 +145,22 @@ mod tests { let elem: Element = "" .parse() .unwrap(); - let bind = Bind::try_from(elem).unwrap(); - assert_eq!(bind, Bind::None); + let bind = BindRequest::try_from(elem).unwrap(); + assert_eq!(bind.resource, None); + + let elem: Element = "Hello™" + .parse() + .unwrap(); + let bind = BindRequest::try_from(elem).unwrap(); + // FIXME: “™” should be resourceprep’d into “TM” here… + //assert_eq!(bind.resource.unwrap(), "HelloTM"); + assert_eq!(bind.resource.unwrap(), "Hello™"); + + let elem: Element = "coucou@linkmauve.fr/HelloTM" + .parse() + .unwrap(); + let bind = BindResponse::try_from(elem).unwrap(); + assert_eq!(bind.jid, FullJid::new("coucou", "linkmauve.fr", "HelloTM")); } #[cfg(not(feature = "disable-validation"))] @@ -118,7 +169,7 @@ mod tests { let elem: Element = "resource" .parse() .unwrap(); - let error = Bind::try_from(elem).unwrap_err(); + let error = BindRequest::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), @@ -128,7 +179,7 @@ mod tests { let elem: Element = "resource" .parse() .unwrap(); - let error = Bind::try_from(elem).unwrap_err(); + let error = BindRequest::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(),