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