diff options
Diffstat (limited to 'filamento/src/caps.rs')
-rw-r--r-- | filamento/src/caps.rs | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/filamento/src/caps.rs b/filamento/src/caps.rs new file mode 100644 index 0000000..c87e48a --- /dev/null +++ b/filamento/src/caps.rs @@ -0,0 +1,184 @@ +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<Info, CapsDecodeError> { + 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<Hash, HashNodeConversionError> { + 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![], +}; |