use std::str::FromStr; use base64::{Engine, prelude::BASE64_STANDARD}; use sha2::{Digest, Sha256}; use sha3::Sha3_256; use stanza::{ xep_0030::info, xep_0300::{self, Algo, Hash}, xep_0390::C, }; use crate::{ disco::{Identity, Info, identity::Category}, error::{CapsDecodeError, HashNodeConversionError}, }; pub fn caps(query: info::Query) -> C { let mut string = String::new(); // features string let mut features = Vec::new(); for feature in query.features { let mut string = String::new(); string.push_str(&feature.var); string.push('\x1f'); features.push(string); } features.sort(); let features_string = features.concat(); string.push_str(&features_string); string.push('\x1c'); // identities string let mut identities = Vec::new(); for identity in query.identities { let mut string = String::new(); string.push_str(&identity.category); string.push('\x1f'); string.push_str(&identity.r#type); string.push('\x1f'); string.push_str(&identity.lang.unwrap_or_default()); string.push('\x1f'); string.push_str(&identity.name.unwrap_or_default()); string.push('\x1f'); string.push('\x1e'); identities.push(string); } identities.sort(); let identities_string = identities.concat(); string.push_str(&identities_string); string.push('\x1c'); // extensions string let mut extensions = Vec::new(); for extension in query.extensions { let mut string = String::new(); let mut fields = Vec::new(); for field in extension.fields { let mut string = String::new(); string.push_str(&field.var.unwrap_or_default()); string.push('\x1f'); let mut values = Vec::new(); for value in field.values { let mut string = String::new(); string.push_str(&value.0); string.push('\x1f'); values.push(string); } values.sort(); let values_string = values.concat(); string.push_str(&values_string); string.push('\x1e'); fields.push(string); } fields.sort(); let fields_string = fields.concat(); string.push_str(&fields_string); string.push('\x1d'); extensions.push(string); } extensions.sort(); let extensions_string = extensions.concat(); string.push_str(&extensions_string); string.push('\x1c'); let mut sha256 = Sha256::new(); sha256.update(&string); let result = sha256.finalize(); let sha256_result = BASE64_STANDARD.encode(result); let mut sha3_256 = Sha3_256::new(); sha3_256.update(string); let result = sha3_256.finalize(); let sha3_256_result = BASE64_STANDARD.encode(result); C(vec![ Hash { algo: Algo::SHA256, hash: sha256_result, }, Hash { algo: Algo::SHA3256, hash: sha3_256_result, }, ]) } /// takes a base64 encoded cached caps string and converts it into a disco info result pub fn info(info: String) -> Result { let info = String::from_utf8(BASE64_STANDARD.decode(info)?)?; let mut strings = info.split_terminator('\x1c'); let features_string = strings.next().ok_or(CapsDecodeError::MissingFeatures)?; let mut features = Vec::new(); for feature in features_string.split_terminator('\x1f') { features.push(feature.to_owned()); } let identities_string = strings.next().ok_or(CapsDecodeError::MissingIdentities)?; let mut identities = Vec::new(); for identity in identities_string.split_terminator('\x1e') { let mut identity_string = identity.split_terminator('\x1f'); let category = identity_string .next() .ok_or(CapsDecodeError::MissingIdentityCategory)?; let r#type = identity_string .next() .ok_or(CapsDecodeError::MissingIdentityType)?; let _ = identity_string .next() .ok_or(CapsDecodeError::MissingIdentityLang)?; let name = identity_string .next() .ok_or(CapsDecodeError::MissingIdentityName)?; let name = if name.is_empty() { None } else { Some(name.to_string()) }; let category = Category::from_category_and_type(category, r#type); identities.push(Identity { name, category }) } // TODO: service discovery extensions Ok(Info { node: None, features, identities, }) } pub fn hash_to_node(hash: xep_0300::Hash) -> String { let mut string = String::from("urn:xmpp:caps#"); string.push_str(&hash.algo.to_string()); string.push('.'); string.push_str(&hash.hash); string } pub fn node_to_hash(node: String) -> Result { let string = node .strip_prefix("urn:xmpp:caps#") .ok_or(HashNodeConversionError::NoPrefix)?; let (algo, hash) = string .rsplit_once('.') .ok_or(HashNodeConversionError::MissingPeriod)?; Ok(Hash { algo: Algo::from_str(algo).unwrap(), hash: hash.to_string(), }) } static CLIENT_INFO: Info = Info { node: None, features: vec![], identities: vec![], };