caps.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;
  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}