// Copyright (c) 2017 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use try_from::TryFrom; use disco::{Feature, Identity, DiscoInfoResult, DiscoInfoQuery}; use data_forms::DataForm; use hashes::{Hash, Algo}; use minidom::Element; use error::Error; use ns; use base64; use digest::Digest; use sha_1::Sha1; use sha2::{Sha256, Sha512}; use sha3::{Sha3_256, Sha3_512}; //use blake2::Blake2b; //use digest::{Digest, VariableOutput}; #[derive(Debug, Clone)] pub struct Caps { pub ext: Option, pub node: String, pub hash: Hash, } impl TryFrom for Caps { type Err = Error; fn try_from(elem: Element) -> Result { if !elem.is("c", ns::CAPS) { return Err(Error::ParseError("This is not a caps element.")); } for _ in elem.children() { return Err(Error::ParseError("Unknown child in caps element.")); } let hash = get_attr!(elem, "hash", required); let ver: String = get_attr!(elem, "ver", required); let hash = Hash { algo: hash, hash: base64::decode(&ver)?, }; Ok(Caps { ext: get_attr!(elem, "ext", optional), node: get_attr!(elem, "node", required), hash: hash, }) } } impl From for Element { fn from(caps: Caps) -> Element { Element::builder("c") .ns(ns::CAPS) .attr("ext", caps.ext) .attr("hash", caps.hash.algo) .attr("node", caps.node) .attr("ver", base64::encode(&caps.hash.hash)) .build() } } fn compute_item(field: &str) -> Vec { let mut bytes = field.as_bytes().to_vec(); bytes.push(b'<'); bytes } fn compute_items Vec>(things: &[T], encode: F) -> Vec { let mut string: Vec = vec!(); let mut accumulator: Vec> = vec!(); for thing in things { let bytes = encode(thing); accumulator.push(bytes); } // This works using the expected i;octet collation. accumulator.sort(); for mut bytes in accumulator { string.append(&mut bytes); } string } fn compute_features(features: &[Feature]) -> Vec { compute_items(features, |feature| compute_item(&feature.var)) } fn compute_identities(identities: &[Identity]) -> Vec { compute_items(identities, |identity| { let lang = identity.lang.clone().unwrap_or_default(); let name = identity.name.clone().unwrap_or_default(); let string = format!("{}/{}/{}/{}", identity.category, identity.type_, lang, name); let bytes = string.as_bytes(); let mut vec = Vec::with_capacity(bytes.len()); vec.extend_from_slice(bytes); vec.push(b'<'); vec }) } fn compute_extensions(extensions: &[DataForm]) -> Vec { compute_items(extensions, |extension| { let mut bytes = vec!(); // TODO: maybe handle the error case? if let Some(ref form_type) = extension.form_type { bytes.extend_from_slice(form_type.as_bytes()); } bytes.push(b'<'); for field in extension.fields.clone() { if field.var == "FORM_TYPE" { continue; } bytes.append(&mut compute_item(&field.var)); bytes.append(&mut compute_items(&field.values, |value| compute_item(value))); } bytes }) } pub fn compute_disco(disco: &DiscoInfoResult) -> Vec { let identities_string = compute_identities(&disco.identities); let features_string = compute_features(&disco.features); let extensions_string = compute_extensions(&disco.extensions); let mut final_string = vec!(); final_string.extend(identities_string); final_string.extend(features_string); final_string.extend(extensions_string); final_string } fn get_hash_vec(hash: &[u8]) -> Vec { let mut vec = Vec::with_capacity(hash.len()); vec.extend_from_slice(hash); vec } pub fn hash_caps(data: &[u8], algo: Algo) -> Result { Ok(Hash { hash: match algo { Algo::Sha_1 => { let mut hasher = Sha1::default(); hasher.input(data); let hash = hasher.result(); get_hash_vec(hash.as_slice()) }, Algo::Sha_256 => { let mut hasher = Sha256::default(); hasher.input(data); let hash = hasher.result(); get_hash_vec(hash.as_slice()) }, Algo::Sha_512 => { let mut hasher = Sha512::default(); hasher.input(data); let hash = hasher.result(); get_hash_vec(hash.as_slice()) }, Algo::Sha3_256 => { let mut hasher = Sha3_256::default(); hasher.input(data); let hash = hasher.result(); get_hash_vec(hash.as_slice()) }, Algo::Sha3_512 => { let mut hasher = Sha3_512::default(); hasher.input(data); let hash = hasher.result(); get_hash_vec(hash.as_slice()) }, Algo::Blake2b_256 | Algo::Blake2b_512 => panic!("See https://github.com/RustCrypto/hashes/issues/34"), /* Algo::Blake2b_256 => { let mut hasher = Blake2b::default(); hasher.input(data); let mut buf: [u8; 32] = [0; 32]; let hash = hasher.variable_result(&mut buf).unwrap(); get_hash_vec(hash) }, Algo::Blake2b_512 => { let mut hasher = Blake2b::default(); hasher.input(data); let mut buf: [u8; 64] = [0; 64]; let hash = hasher.variable_result(&mut buf).unwrap(); get_hash_vec(hash) }, */ Algo::Unknown(algo) => return Err(format!("Unknown algorithm: {}.", algo)), }, algo: algo, }) } pub fn query_caps(caps: Caps) -> DiscoInfoQuery { DiscoInfoQuery { node: Some(format!("{}#{}", caps.node, base64::encode(&caps.hash.hash))), } } #[cfg(test)] mod tests { use super::*; use caps; use base64; #[test] fn test_parse() { let elem: Element = "".parse().unwrap(); let caps = Caps::try_from(elem).unwrap(); assert_eq!(caps.node, String::from("coucou")); assert_eq!(caps.hash.algo, Algo::Sha_256); assert_eq!(caps.hash.hash, base64::decode("K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=").unwrap()); } #[test] fn test_invalid_child() { let elem: Element = "K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=".parse().unwrap(); let error = Caps::try_from(elem).unwrap_err(); let message = match error { Error::ParseError(string) => string, _ => panic!(), }; assert_eq!(message, "Unknown child in caps element."); } #[test] fn test_simple() { let elem: Element = "".parse().unwrap(); let disco = DiscoInfoResult::try_from(elem).unwrap(); let caps = caps::compute_disco(&disco); assert_eq!(caps.len(), 50); } #[test] fn test_xep_5_2() { let elem: Element = r#" "#.parse().unwrap(); let data = b"client/pc//Exodus 0.9.1 urn:xmpp:dataforms:softwareinfo ipv4 ipv6 Mac 10.5.1 Psi 0.11 "#.parse().unwrap(); let data = b"client/pc/el/\xce\xa8 0.11