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}