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}