ibb.rs

  1// Copyright (c) 2017 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 try_from::TryFrom;
  8use std::str::FromStr;
  9
 10use minidom::{Element, IntoAttributeValue};
 11use base64;
 12
 13use error::Error;
 14
 15use ns;
 16
 17generate_attribute!(Stanza, "stanza", {
 18    Iq => "iq",
 19    Message => "message",
 20}, Default = Iq);
 21
 22#[derive(Debug, Clone)]
 23pub struct Open {
 24    pub block_size: u16,
 25    pub sid: String,
 26    pub stanza: Stanza,
 27}
 28
 29impl TryFrom<Element> for Open {
 30    type Err = Error;
 31
 32    fn try_from(elem: Element) -> Result<Open, Error> {
 33        if !elem.is("open", ns::IBB) {
 34            return Err(Error::ParseError("This is not an open element."));
 35        }
 36        for _ in elem.children() {
 37            return Err(Error::ParseError("Unknown child in open element."));
 38        }
 39        Ok(Open {
 40            block_size: get_attr!(elem, "block-size", required),
 41            sid: get_attr!(elem, "sid", required),
 42            stanza: get_attr!(elem, "stanza", default),
 43        })
 44    }
 45}
 46
 47impl From<Open> for Element {
 48    fn from(open: Open) -> Element {
 49        Element::builder("open")
 50                .ns(ns::IBB)
 51                .attr("block-size", open.block_size)
 52                .attr("sid", open.sid)
 53                .attr("stanza", open.stanza)
 54                .build()
 55    }
 56}
 57
 58#[derive(Debug, Clone)]
 59pub struct Data {
 60    pub seq: u16,
 61    pub sid: String,
 62    pub data: Vec<u8>,
 63}
 64
 65impl TryFrom<Element> for Data {
 66    type Err = Error;
 67
 68    fn try_from(elem: Element) -> Result<Data, Error> {
 69        if !elem.is("data", ns::IBB) {
 70            return Err(Error::ParseError("This is not a data element."));
 71        }
 72        for _ in elem.children() {
 73            return Err(Error::ParseError("Unknown child in data element."));
 74        }
 75        Ok(Data {
 76            seq: get_attr!(elem, "seq", required),
 77            sid: get_attr!(elem, "sid", required),
 78            data: base64::decode(&elem.text())?,
 79        })
 80    }
 81}
 82
 83impl From<Data> for Element {
 84    fn from(data: Data) -> Element {
 85        Element::builder("data")
 86                .ns(ns::IBB)
 87                .attr("seq", data.seq)
 88                .attr("sid", data.sid)
 89                .append(base64::encode(&data.data))
 90                .build()
 91    }
 92}
 93
 94#[derive(Debug, Clone)]
 95pub struct Close {
 96    pub sid: String,
 97}
 98
 99impl TryFrom<Element> for Close {
100    type Err = Error;
101
102    fn try_from(elem: Element) -> Result<Close, Error> {
103        if !elem.is("close", ns::IBB) {
104            return Err(Error::ParseError("This is not a close element."));
105        }
106        for _ in elem.children() {
107            return Err(Error::ParseError("Unknown child in close element."));
108        }
109        Ok(Close {
110            sid: get_attr!(elem, "sid", required),
111        })
112    }
113}
114
115impl From<Close> for Element {
116    fn from(close: Close) -> Element {
117        Element::builder("close")
118                .ns(ns::IBB)
119                .attr("sid", close.sid)
120                .build()
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use std::error::Error as StdError;
128
129    #[test]
130    fn test_simple() {
131        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='3' sid='coucou'/>".parse().unwrap();
132        let open = Open::try_from(elem).unwrap();
133        assert_eq!(open.block_size, 3);
134        assert_eq!(open.sid, "coucou");
135        assert_eq!(open.stanza, Stanza::Iq);
136
137        let elem: Element = "<data xmlns='http://jabber.org/protocol/ibb' seq='0' sid='coucou'>AAAA</data>".parse().unwrap();
138        let data = Data::try_from(elem).unwrap();
139        assert_eq!(data.seq, 0);
140        assert_eq!(data.sid, "coucou");
141        assert_eq!(data.data, vec!(0, 0, 0));
142
143        let elem: Element = "<close xmlns='http://jabber.org/protocol/ibb' sid='coucou'/>".parse().unwrap();
144        let close = Close::try_from(elem).unwrap();
145        assert_eq!(close.sid, "coucou");
146    }
147
148    #[test]
149    fn test_invalid() {
150        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb'/>".parse().unwrap();
151        let error = Open::try_from(elem).unwrap_err();
152        let message = match error {
153            Error::ParseError(string) => string,
154            _ => panic!(),
155        };
156        assert_eq!(message, "Required attribute 'block-size' missing.");
157
158        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='-5'/>".parse().unwrap();
159        let error = Open::try_from(elem).unwrap_err();
160        let message = match error {
161            Error::ParseIntError(error) => error,
162            _ => panic!(),
163        };
164        assert_eq!(message.description(), "invalid digit found in string");
165
166        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128'/>".parse().unwrap();
167        let error = Open::try_from(elem).unwrap_err();
168        let message = match error {
169            Error::ParseError(error) => error,
170            _ => panic!(),
171        };
172        assert_eq!(message, "Required attribute 'sid' missing.");
173    }
174
175    #[test]
176    fn test_invalid_stanza() {
177        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128' sid='coucou' stanza='fdsq'/>".parse().unwrap();
178        let error = Open::try_from(elem).unwrap_err();
179        let message = match error {
180            Error::ParseError(string) => string,
181            _ => panic!(),
182        };
183        assert_eq!(message, "Unknown value for 'stanza' attribute.");
184    }
185}