bind.rs

  1// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use xso::{AsXml, FromXml};
  8
  9use crate::iq::{IqResultPayload, IqSetPayload};
 10use crate::ns;
 11use jid::{FullJid, Jid};
 12
 13/// The bind feature exposed in stream:features.
 14#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 15#[xml(namespace = ns::BIND, name = "bind")]
 16pub struct BindFeature {
 17    /// Present if bind is required.
 18    #[xml(child(default))]
 19    required: Option<Required>,
 20}
 21
 22/// Notes that bind is required.
 23#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 24#[xml(namespace = ns::BIND, name = "required")]
 25pub struct Required;
 26
 27/// The request for resource binding, which is the process by which a client
 28/// can obtain a full JID and start exchanging on the XMPP network.
 29///
 30/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
 31#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 32#[xml(namespace = ns::BIND, name = "bind")]
 33pub struct BindQuery {
 34    /// Requests this resource, the server may associate another one though.
 35    ///
 36    /// If this is None, we request no particular resource, and a random one
 37    /// will be affected by the server.
 38    #[xml(extract(default, fields(text(type_ = String))))]
 39    resource: Option<String>,
 40}
 41
 42impl BindQuery {
 43    /// Creates a resource binding request.
 44    pub fn new(resource: Option<String>) -> BindQuery {
 45        BindQuery { resource }
 46    }
 47}
 48
 49impl IqSetPayload for BindQuery {}
 50
 51/// The response for resource binding, containing the client’s full JID.
 52///
 53/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
 54#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 55#[xml(namespace = ns::BIND, name = "bind")]
 56pub struct BindResponse {
 57    /// The full JID returned by the server for this client.
 58    #[xml(extract(fields(text(type_ = FullJid))))]
 59    jid: FullJid,
 60}
 61
 62impl IqResultPayload for BindResponse {}
 63
 64impl From<BindResponse> for FullJid {
 65    fn from(bind: BindResponse) -> FullJid {
 66        bind.jid
 67    }
 68}
 69
 70impl From<BindResponse> for Jid {
 71    fn from(bind: BindResponse) -> Jid {
 72        Jid::from(bind.jid)
 73    }
 74}
 75
 76#[cfg(test)]
 77mod tests {
 78    use super::*;
 79    use minidom::Element;
 80    use xso::error::{Error, FromElementError};
 81
 82    #[cfg(target_pointer_width = "32")]
 83    #[test]
 84    fn test_size() {
 85        assert_size!(BindFeature, 1);
 86        assert_size!(Required, 0);
 87        assert_size!(BindQuery, 12);
 88        assert_size!(BindResponse, 16);
 89    }
 90
 91    #[cfg(target_pointer_width = "64")]
 92    #[test]
 93    fn test_size() {
 94        assert_size!(BindFeature, 1);
 95        assert_size!(Required, 0);
 96        assert_size!(BindQuery, 24);
 97        assert_size!(BindResponse, 32);
 98    }
 99
100    #[test]
101    fn test_simple() {
102        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
103            .parse()
104            .unwrap();
105        let bind = BindQuery::try_from(elem).unwrap();
106        assert_eq!(bind.resource, None);
107
108        let elem: Element =
109            "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
110                .parse()
111                .unwrap();
112        let bind = BindQuery::try_from(elem).unwrap();
113        // FIXME: “™” should be resourceprep’d into “TM” here…
114        //assert_eq!(bind.resource.unwrap(), "HelloTM");
115        assert_eq!(bind.resource.unwrap(), "Hello™");
116
117        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>coucou@linkmauve.fr/Hello™</jid></bind>"
118            .parse()
119            .unwrap();
120        let bind = BindResponse::try_from(elem).unwrap();
121        assert_eq!(
122            bind.jid,
123            FullJid::new("coucou@linkmauve.fr/HelloTM").unwrap()
124        );
125    }
126
127    #[cfg(not(feature = "disable-validation"))]
128    #[test]
129    fn test_invalid_resource() {
130        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
131            .parse()
132            .unwrap();
133        let error = BindQuery::try_from(elem).unwrap_err();
134        let message = match error {
135            FromElementError::Invalid(Error::Other(string)) => string,
136            _ => panic!(),
137        };
138        assert_eq!(
139            message,
140            "Unknown attribute in extraction for field 'resource' in BindQuery element."
141        );
142
143        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
144            .parse()
145            .unwrap();
146        let error = BindQuery::try_from(elem).unwrap_err();
147        let message = match error {
148            FromElementError::Invalid(Error::Other(string)) => string,
149            _ => panic!(),
150        };
151        assert_eq!(
152            message,
153            "Unknown child in extraction for field 'resource' in BindQuery element."
154        );
155    }
156}