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![],
};