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