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;
8
9use disco::{Feature, Identity, DiscoInfoResult, DiscoInfoQuery};
10use data_forms::DataForm;
11use hashes::{Hash, Algo};
12
13use minidom::Element;
14use error::Error;
15use ns;
16use base64;
17
18use digest::Digest;
19use sha_1::Sha1;
20use sha2::{Sha256, Sha512};
21use sha3::{Sha3_256, Sha3_512};
22//use blake2::Blake2b;
23//use digest::{Digest, VariableOutput};
24
25#[derive(Debug, Clone)]
26pub struct Caps {
27 pub ext: Option<String>,
28 pub node: String,
29 pub hash: Hash,
30}
31
32impl TryFrom<Element> for Caps {
33 type Err = Error;
34
35 fn try_from(elem: Element) -> Result<Caps, Error> {
36 if !elem.is("c", ns::CAPS) {
37 return Err(Error::ParseError("This is not a caps element."));
38 }
39 for _ in elem.children() {
40 return Err(Error::ParseError("Unknown child in caps element."));
41 }
42 let hash = get_attr!(elem, "hash", required);
43 let ver: String = get_attr!(elem, "ver", required);
44 let hash = Hash {
45 algo: hash,
46 hash: base64::decode(&ver)?,
47 };
48 Ok(Caps {
49 ext: get_attr!(elem, "ext", optional),
50 node: get_attr!(elem, "node", required),
51 hash: hash,
52 })
53 }
54}
55
56impl From<Caps> for Element {
57 fn from(caps: Caps) -> Element {
58 Element::builder("c")
59 .ns(ns::CAPS)
60 .attr("ext", caps.ext)
61 .attr("hash", caps.hash.algo)
62 .attr("node", caps.node)
63 .attr("ver", base64::encode(&caps.hash.hash))
64 .build()
65 }
66}
67
68fn compute_item(field: &str) -> Vec<u8> {
69 let mut bytes = field.as_bytes().to_vec();
70 bytes.push(b'<');
71 bytes
72}
73
74fn compute_items<T, F: Fn(&T) -> Vec<u8>>(things: &[T], encode: F) -> Vec<u8> {
75 let mut string: Vec<u8> = vec!();
76 let mut accumulator: Vec<Vec<u8>> = vec!();
77 for thing in things {
78 let bytes = encode(thing);
79 accumulator.push(bytes);
80 }
81 // This works using the expected i;octet collation.
82 accumulator.sort();
83 for mut bytes in accumulator {
84 string.append(&mut bytes);
85 }
86 string
87}
88
89fn compute_features(features: &[Feature]) -> Vec<u8> {
90 compute_items(features, |feature| compute_item(&feature.var))
91}
92
93fn compute_identities(identities: &[Identity]) -> Vec<u8> {
94 compute_items(identities, |identity| {
95 let lang = identity.lang.clone().unwrap_or_default();
96 let name = identity.name.clone().unwrap_or_default();
97 let string = format!("{}/{}/{}/{}", identity.category, identity.type_, lang, name);
98 let bytes = string.as_bytes();
99 let mut vec = Vec::with_capacity(bytes.len());
100 vec.extend_from_slice(bytes);
101 vec.push(b'<');
102 vec
103 })
104}
105
106fn compute_extensions(extensions: &[DataForm]) -> Vec<u8> {
107 compute_items(extensions, |extension| {
108 let mut bytes = vec!();
109 // TODO: maybe handle the error case?
110 if let Some(ref form_type) = extension.form_type {
111 bytes.extend_from_slice(form_type.as_bytes());
112 }
113 bytes.push(b'<');
114 for field in extension.fields.clone() {
115 if field.var == "FORM_TYPE" {
116 continue;
117 }
118 bytes.append(&mut compute_item(&field.var));
119 bytes.append(&mut compute_items(&field.values,
120 |value| compute_item(value)));
121 }
122 bytes
123 })
124}
125
126pub fn compute_disco(disco: &DiscoInfoResult) -> Vec<u8> {
127 let identities_string = compute_identities(&disco.identities);
128 let features_string = compute_features(&disco.features);
129 let extensions_string = compute_extensions(&disco.extensions);
130
131 let mut final_string = vec!();
132 final_string.extend(identities_string);
133 final_string.extend(features_string);
134 final_string.extend(extensions_string);
135 final_string
136}
137
138fn get_hash_vec(hash: &[u8]) -> Vec<u8> {
139 let mut vec = Vec::with_capacity(hash.len());
140 vec.extend_from_slice(hash);
141 vec
142}
143
144pub fn hash_caps(data: &[u8], algo: Algo) -> Result<Hash, String> {
145 Ok(Hash {
146 hash: match algo {
147 Algo::Sha_1 => {
148 let mut hasher = Sha1::default();
149 hasher.input(data);
150 let hash = hasher.result();
151 get_hash_vec(hash.as_slice())
152 },
153 Algo::Sha_256 => {
154 let mut hasher = Sha256::default();
155 hasher.input(data);
156 let hash = hasher.result();
157 get_hash_vec(hash.as_slice())
158 },
159 Algo::Sha_512 => {
160 let mut hasher = Sha512::default();
161 hasher.input(data);
162 let hash = hasher.result();
163 get_hash_vec(hash.as_slice())
164 },
165 Algo::Sha3_256 => {
166 let mut hasher = Sha3_256::default();
167 hasher.input(data);
168 let hash = hasher.result();
169 get_hash_vec(hash.as_slice())
170 },
171 Algo::Sha3_512 => {
172 let mut hasher = Sha3_512::default();
173 hasher.input(data);
174 let hash = hasher.result();
175 get_hash_vec(hash.as_slice())
176 },
177 Algo::Blake2b_256
178 | Algo::Blake2b_512 => panic!("See https://github.com/RustCrypto/hashes/issues/34"),
179 /*
180 Algo::Blake2b_256 => {
181 let mut hasher = Blake2b::default();
182 hasher.input(data);
183 let mut buf: [u8; 32] = [0; 32];
184 let hash = hasher.variable_result(&mut buf).unwrap();
185 get_hash_vec(hash)
186 },
187 Algo::Blake2b_512 => {
188 let mut hasher = Blake2b::default();
189 hasher.input(data);
190 let mut buf: [u8; 64] = [0; 64];
191 let hash = hasher.variable_result(&mut buf).unwrap();
192 get_hash_vec(hash)
193 },
194 */
195 Algo::Unknown(algo) => return Err(format!("Unknown algorithm: {}.", algo)),
196 },
197 algo: algo,
198 })
199}
200
201pub fn query_caps(caps: Caps) -> DiscoInfoQuery {
202 DiscoInfoQuery {
203 node: Some(format!("{}#{}", caps.node, base64::encode(&caps.hash.hash))),
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use caps;
211 use base64;
212
213 #[test]
214 fn test_parse() {
215 let elem: Element = "<c xmlns='http://jabber.org/protocol/caps' hash='sha-256' node='coucou' ver='K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4='/>".parse().unwrap();
216 let caps = Caps::try_from(elem).unwrap();
217 assert_eq!(caps.node, String::from("coucou"));
218 assert_eq!(caps.hash.algo, Algo::Sha_256);
219 assert_eq!(caps.hash.hash, base64::decode("K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=").unwrap());
220 }
221
222 #[test]
223 fn test_invalid_child() {
224 let elem: Element = "<c xmlns='http://jabber.org/protocol/caps'><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash></c>".parse().unwrap();
225 let error = Caps::try_from(elem).unwrap_err();
226 let message = match error {
227 Error::ParseError(string) => string,
228 _ => panic!(),
229 };
230 assert_eq!(message, "Unknown child in caps element.");
231 }
232
233 #[test]
234 fn test_simple() {
235 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/><feature var='http://jabber.org/protocol/disco#info'/></query>".parse().unwrap();
236 let disco = DiscoInfoResult::try_from(elem).unwrap();
237 let caps = caps::compute_disco(&disco);
238 assert_eq!(caps.len(), 50);
239 }
240
241 #[test]
242 fn test_xep_5_2() {
243 let elem: Element = r#"
244<query xmlns='http://jabber.org/protocol/disco#info'
245 node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>
246 <identity category='client' name='Exodus 0.9.1' type='pc'/>
247 <feature var='http://jabber.org/protocol/caps'/>
248 <feature var='http://jabber.org/protocol/disco#info'/>
249 <feature var='http://jabber.org/protocol/disco#items'/>
250 <feature var='http://jabber.org/protocol/muc'/>
251</query>
252"#.parse().unwrap();
253
254 let data = b"client/pc//Exodus 0.9.1<http://jabber.org/protocol/caps<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<";
255 let mut expected = Vec::with_capacity(data.len());
256 expected.extend_from_slice(data);
257 let disco = DiscoInfoResult::try_from(elem).unwrap();
258 let caps = caps::compute_disco(&disco);
259 assert_eq!(caps, expected);
260
261 let sha_1 = caps::hash_caps(&caps, Algo::Sha_1).unwrap();
262 assert_eq!(sha_1.hash, base64::decode("QgayPKawpkPSDYmwT/WM94uAlu0=").unwrap());
263 }
264
265 #[test]
266 fn test_xep_5_3() {
267 let elem: Element = r#"
268<query xmlns='http://jabber.org/protocol/disco#info'
269 node='http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w='>
270 <identity xml:lang='en' category='client' name='Psi 0.11' type='pc'/>
271 <identity xml:lang='el' category='client' name='Ψ 0.11' type='pc'/>
272 <feature var='http://jabber.org/protocol/caps'/>
273 <feature var='http://jabber.org/protocol/disco#info'/>
274 <feature var='http://jabber.org/protocol/disco#items'/>
275 <feature var='http://jabber.org/protocol/muc'/>
276 <x xmlns='jabber:x:data' type='result'>
277 <field var='FORM_TYPE' type='hidden'>
278 <value>urn:xmpp:dataforms:softwareinfo</value>
279 </field>
280 <field var='ip_version'>
281 <value>ipv4</value>
282 <value>ipv6</value>
283 </field>
284 <field var='os'>
285 <value>Mac</value>
286 </field>
287 <field var='os_version'>
288 <value>10.5.1</value>
289 </field>
290 <field var='software'>
291 <value>Psi</value>
292 </field>
293 <field var='software_version'>
294 <value>0.11</value>
295 </field>
296 </x>
297</query>
298"#.parse().unwrap();
299 let data = b"client/pc/el/\xce\xa8 0.11<client/pc/en/Psi 0.11<http://jabber.org/protocol/caps<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<urn:xmpp:dataforms:softwareinfo<ip_version<ipv4<ipv6<os<Mac<os_version<10.5.1<software<Psi<software_version<0.11<";
300 let mut expected = Vec::with_capacity(data.len());
301 expected.extend_from_slice(data);
302 let disco = DiscoInfoResult::try_from(elem).unwrap();
303 let caps = caps::compute_disco(&disco);
304 assert_eq!(caps, expected);
305
306 let sha_1 = caps::hash_caps(&caps, Algo::Sha_1).unwrap();
307 assert_eq!(sha_1.hash, base64::decode("q07IKJEyjvHSyhy//CH0CxmKi8w=").unwrap());
308 }
309}