aboutsummaryrefslogtreecommitdiffstats
path: root/filamento/src/caps.rs
blob: c87e48a008a848584af0248d4a976e743b1a7e56 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
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![],
};