stun_rs/attributes/stun/
user_hash.rs

1use crate::attributes::{stunt_attribute, DecodeAttributeValue, EncodeAttributeValue};
2use crate::common::{check_buffer_boundaries, sha256};
3use crate::context::{AttributeDecoderContext, AttributeEncoderContext};
4use crate::error::{StunError, StunErrorType};
5use crate::strings;
6use std::convert::TryInto;
7use std::ops::Deref;
8use std::sync::Arc;
9
10const USER_HASH: u16 = 0x001E;
11const USER_HASH_LEN: usize = 32;
12
13/// The USER-HASH attribute is used as a replacement for the USER-NAME
14/// attribute when user name anonymity is supported.
15/// # Examples
16///```rust
17/// # use stun_rs::attributes::stun::{UserHash, UserName, Realm};
18/// # use std::convert::TryFrom;
19/// # use std::error::Error;
20/// #
21/// # fn main() -> Result<(), Box<dyn Error>> {
22/// let username = UserName::try_from("username")?;
23/// let realm = Realm::try_from("example.org")?;
24/// let user_hash = UserHash::new(username, realm)?;
25/// let hash = [
26///   0x38, 0x15, 0x10, 0x9f, 0xa3, 0x11, 0x3e, 0xdd,
27///   0x39, 0x5a, 0x30, 0x20, 0x0b, 0x4f, 0xbe, 0xf8,
28///   0x92, 0x4f, 0x50, 0x50, 0xf3, 0x40, 0xcc, 0x28,
29///   0x77, 0x99, 0x65, 0x5f, 0xec, 0xd4, 0x08, 0xaa,
30/// ];
31/// assert_eq!(user_hash.hash(), hash);
32/// #   Ok(())
33/// # }
34///```
35
36#[derive(Debug, PartialEq, Eq, Clone)]
37pub struct UserHash(Arc<[u8; USER_HASH_LEN]>);
38
39impl UserHash {
40    /// Creates a new [`UserHash`] attribute.
41    /// # Arguments
42    /// - `name`: The user name
43    /// - `realm`: The realm
44    /// # Returns
45    /// The [`UserHash`] attribute or an error if either the `name` or
46    /// the `realm` can not be processed using the `OpaqueString` profile
47    pub fn new<A, B>(name: A, realm: B) -> Result<Self, StunError>
48    where
49        A: AsRef<str>,
50        B: AsRef<str>,
51    {
52        let vec = do_sha256(name.as_ref(), realm.as_ref())?;
53        Ok(Self(Arc::new(vec.try_into().map_err(|_v| {
54            StunError::new(StunErrorType::InvalidParam, "Can not create user hash")
55        })?)))
56    }
57
58    /// Returns the value of the [`UserHash`] attribute
59    pub fn hash(&self) -> &[u8] {
60        self.0.as_slice()
61    }
62}
63
64impl Deref for UserHash {
65    type Target = [u8];
66
67    fn deref(&self) -> &[u8] {
68        self.0.as_slice()
69    }
70}
71
72impl DecodeAttributeValue for UserHash {
73    fn decode(ctx: AttributeDecoderContext) -> Result<(Self, usize), StunError> {
74        let raw_value = ctx.raw_value();
75        (raw_value.len() == USER_HASH_LEN)
76            .then(|| {
77                let mut vec: [u8; USER_HASH_LEN] = [0x0; USER_HASH_LEN];
78                vec.clone_from_slice(raw_value);
79                (Self(Arc::new(vec)), raw_value.len())
80            })
81            .ok_or_else(|| {
82                StunError::new(
83                    StunErrorType::InvalidParam,
84                    format!(
85                        "Unexpected buffer size: {}, user hash legnth {}",
86                        raw_value.len(),
87                        USER_HASH_LEN
88                    ),
89                )
90            })
91    }
92}
93
94impl EncodeAttributeValue for UserHash {
95    fn encode(&self, mut ctx: AttributeEncoderContext) -> Result<usize, StunError> {
96        let len = self.0.len();
97        let raw_value = ctx.raw_value_mut();
98        check_buffer_boundaries(raw_value, len)?;
99        raw_value[..len].clone_from_slice(self.0.as_slice());
100
101        Ok(len)
102    }
103}
104
105fn do_sha256(name: &str, realm: &str) -> Result<Vec<u8>, StunError> {
106    let name = strings::opaque_string_prepapre(name)?;
107    let realm = strings::opaque_string_prepapre(realm)?;
108    let val = format!("{}:{}", name, realm);
109    let val = sha256(val.as_str());
110    let val_len = val.len();
111
112    (val_len == USER_HASH_LEN).then_some(val).ok_or_else(|| {
113        StunError::new(
114            StunErrorType::InvalidParam,
115            format!(
116                "Unexpected buffer size: {}, user hash legnth {}",
117                val_len, USER_HASH_LEN
118            ),
119        )
120    })
121}
122
123impl crate::attributes::AsVerifiable for UserHash {}
124
125stunt_attribute!(UserHash, USER_HASH);
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::attributes::stun::{Realm, UserName};
131    use crate::error::StunErrorType;
132    use crate::StunAttribute;
133    use std::convert::TryFrom;
134
135    #[test]
136    fn constructor() {
137        let username = UserName::try_from("username").unwrap();
138        let realm = Realm::try_from("realm").unwrap();
139        let attr = UserHash::new(username, realm).expect("Can not create UserHas attribute");
140
141        // Check deref
142        let slice: &[u8] = &attr;
143        assert_eq!(slice, attr.hash());
144
145        // Control characters like TAB `U+0009` are disallowed by OpaqueString profile
146        let error = UserHash::new("user\u{0009}name", "realm").expect_err("Error expected");
147        assert_eq!(error, StunErrorType::InvalidParam);
148    }
149
150    #[test]
151    fn decode_user_hash() {
152        let dummy_msg = [];
153        // `Username`: "<U+30DE><U+30C8><U+30EA><U+30C3><U+30AF><U+30B9>" (without quotes) unaffected by `OpaqueString` [RFC8265] processing
154        // Realm: "example.org" (without quotes)
155        let buffer = [
156            0x4a, 0x3c, 0xf3, 0x8f, 0xef, 0x69, 0x92, 0xbd, 0xa9, 0x52, 0xc6, 0x78, 0x04, 0x17,
157            0xda, 0x0f, 0x24, 0x81, 0x94, 0x15, 0x56, 0x9e, 0x60, 0xb2, 0x05, 0xc4, 0x6e, 0x41,
158            0x40, 0x7f, 0x17, 0x04,
159        ];
160        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &buffer);
161
162        let (user_hash, size) = UserHash::decode(ctx).expect("Can not decode USER-HASH");
163        assert_eq!(size, 32);
164        assert_eq!(&buffer[..], user_hash.hash());
165    }
166
167    #[test]
168    fn decode_user_hash_error() {
169        let dummy_msg = [];
170        let buffer = [];
171        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &buffer);
172        let result = UserHash::decode(ctx);
173        assert_eq!(
174            result.expect_err("Error expected"),
175            StunErrorType::InvalidParam
176        );
177
178        let buffer = [
179            0x4a, 0x3c, 0xf3, 0x8f, 0xef, 0x69, 0x92, 0xbd, 0xa9, 0x52, 0xc6, 0x78, 0x04, 0x17,
180            0xda, 0x0f, 0x24,
181        ];
182        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &buffer);
183        let result = UserHash::decode(ctx);
184        assert_eq!(
185            result.expect_err("Error expected"),
186            StunErrorType::InvalidParam
187        );
188
189        let buffer = [
190            0x4a, 0x3c, 0xf3, 0x8f, 0xef, 0x69, 0x92, 0xbd, 0xa9, 0x52, 0xc6, 0x78, 0x04, 0x17,
191            0xda, 0x0f, 0x24, 0x81, 0x94, 0x15, 0x56, 0x9e, 0x60, 0xb2, 0x05, 0xc4, 0x6e, 0x41,
192            0x40, 0x7f, 0x17, 0x04, 0x03,
193        ];
194        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &buffer);
195        let result = UserHash::decode(ctx);
196        assert_eq!(
197            result.expect_err("Error expected"),
198            StunErrorType::InvalidParam
199        );
200    }
201
202    #[test]
203    fn encode_user_hash() {
204        let dummy_msg: [u8; 0] = [0x0; 0];
205        let username =
206            UserName::try_from("\u{30de}\u{30c8}\u{30ea}\u{30c3}\u{30af}\u{30b9}").unwrap();
207        let realm = Realm::try_from("example.org").unwrap();
208        let result = UserHash::new(username, realm);
209        assert!(result.is_ok());
210        let user_hash = result.unwrap();
211
212        let mut buffer: [u8; 32] = [0x0; 32];
213        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut buffer);
214        let result = user_hash.encode(ctx);
215        assert_eq!(result, Ok(32));
216
217        // `Username`: "<U+30DE><U+30C8><U+30EA><U+30C3><U+30AF><U+30B9>" (without quotes) unaffected by `OpaqueString` [RFC8265] processing
218        // Realm: "example.org" (without quotes)
219        let expected_buffer = [
220            0x4a, 0x3c, 0xf3, 0x8f, 0xef, 0x69, 0x92, 0xbd, 0xa9, 0x52, 0xc6, 0x78, 0x04, 0x17,
221            0xda, 0x0f, 0x24, 0x81, 0x94, 0x15, 0x56, 0x9e, 0x60, 0xb2, 0x05, 0xc4, 0x6e, 0x41,
222            0x40, 0x7f, 0x17, 0x04,
223        ];
224        assert_eq!(&buffer[..], &expected_buffer[..]);
225    }
226
227    #[test]
228    fn encode_user_hash_error() {
229        let dummy_msg: [u8; 0] = [0x0; 0];
230        let username = UserName::try_from("username").unwrap();
231        let realm = Realm::try_from("realm").unwrap();
232        let result = UserHash::new(username, realm);
233        assert!(result.is_ok());
234        let user_hash = result.unwrap();
235
236        let mut buffer = [];
237        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut buffer);
238        let result = user_hash.encode(ctx);
239        assert_eq!(
240            result.expect_err("Error expected"),
241            StunErrorType::SmallBuffer
242        );
243
244        let mut buffer: [u8; 31] = [0x0; 31];
245        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut buffer);
246        let result = user_hash.encode(ctx);
247        assert_eq!(
248            result.expect_err("Error expected"),
249            StunErrorType::SmallBuffer
250        );
251    }
252
253    #[test]
254    fn user_hash_stunt_attribute() {
255        let user_hash = UserHash::new("a", "b").expect("Can not create user hash");
256        let attr = StunAttribute::UserHash(user_hash);
257        assert!(attr.is_user_hash());
258        assert!(attr.as_user_hash().is_ok());
259        assert!(attr.as_unknown().is_err());
260
261        assert!(attr.attribute_type().is_comprehension_required());
262        assert!(!attr.attribute_type().is_comprehension_optional());
263
264        let dbg_fmt = format!("{:?}", attr);
265        assert_eq!("UserHash(UserHash([103, 131, 163, 30, 171, 246, 140, 204, 6, 96, 249, 53, 192, 130, 98, 130, 189, 210, 36, 31, 58, 128, 169, 242, 209, 13, 89, 174, 169, 235, 181, 216]))", dbg_fmt);
266    }
267}