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    #[cfg(not(feature = "disable-validation"))]
 81    use xso::error::{Error, FromElementError};
 82
 83    #[cfg(target_pointer_width = "32")]
 84    #[test]
 85    fn test_size() {
 86        assert_size!(BindFeature, 1);
 87        assert_size!(Required, 0);
 88        assert_size!(BindQuery, 12);
 89        assert_size!(BindResponse, 16);
 90    }
 91
 92    #[cfg(target_pointer_width = "64")]
 93    #[test]
 94    fn test_size() {
 95        assert_size!(BindFeature, 1);
 96        assert_size!(Required, 0);
 97        assert_size!(BindQuery, 24);
 98        assert_size!(BindResponse, 32);
 99    }
100
101    #[test]
102    fn test_simple() {
103        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
104            .parse()
105            .unwrap();
106        let bind = BindQuery::try_from(elem).unwrap();
107        assert_eq!(bind.resource, None);
108
109        let elem: Element =
110            "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
111                .parse()
112                .unwrap();
113        let bind = BindQuery::try_from(elem).unwrap();
114        // FIXME: “™” should be resourceprep’d into “TM” here…
115        //assert_eq!(bind.resource.unwrap(), "HelloTM");
116        assert_eq!(bind.resource.unwrap(), "Hello™");
117
118        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>coucou@linkmauve.fr/Hello™</jid></bind>"
119            .parse()
120            .unwrap();
121        let bind = BindResponse::try_from(elem).unwrap();
122        assert_eq!(
123            bind.jid,
124            FullJid::new("coucou@linkmauve.fr/HelloTM").unwrap()
125        );
126    }
127
128    #[cfg(not(feature = "disable-validation"))]
129    #[test]
130    fn test_invalid_resource() {
131        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
132            .parse()
133            .unwrap();
134        let error = BindQuery::try_from(elem).unwrap_err();
135        let message = match error {
136            FromElementError::Invalid(Error::Other(string)) => string,
137            _ => panic!(),
138        };
139        assert_eq!(
140            message,
141            "Unknown attribute in extraction for field 'resource' in BindQuery element."
142        );
143
144        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
145            .parse()
146            .unwrap();
147        let error = BindQuery::try_from(elem).unwrap_err();
148        let message = match error {
149            FromElementError::Invalid(Error::Other(string)) => string,
150            _ => panic!(),
151        };
152        assert_eq!(
153            message,
154            "Unknown child in extraction for field 'resource' in BindQuery element."
155        );
156    }
157}