aboutsummaryrefslogblamecommitdiffstats
path: root/filamento/src/caps.rs
blob: c87e48a008a848584af0248d4a976e743b1a7e56 (plain) (tree)























































































































































































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