aboutsummaryrefslogtreecommitdiffstats
path: root/filamento/src/caps.rs
diff options
context:
space:
mode:
Diffstat (limited to 'filamento/src/caps.rs')
-rw-r--r--filamento/src/caps.rs184
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![],
+};