stun_types/attribute/
user.rs

1// Copyright (C) 2020 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use alloc::borrow::ToOwned;
10use alloc::string::{String, ToString};
11use core::convert::TryFrom;
12
13use crate::message::{StunParseError, StunWriteError};
14
15use super::{
16    Attribute, AttributeExt, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite,
17    AttributeWriteExt, RawAttribute,
18};
19
20/// The username [`Attribute`]
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Username {
23    user: String,
24}
25
26impl AttributeStaticType for Username {
27    const TYPE: AttributeType = AttributeType(0x0006);
28}
29
30impl Attribute for Username {
31    fn get_type(&self) -> AttributeType {
32        Self::TYPE
33    }
34
35    fn length(&self) -> u16 {
36        self.user.len() as u16
37    }
38}
39
40impl AttributeWrite for Username {
41    fn to_raw(&self) -> RawAttribute<'_> {
42        RawAttribute::new(Username::TYPE, self.user.as_bytes())
43    }
44    fn write_into_unchecked(&self, dest: &mut [u8]) {
45        let len = self.padded_len();
46        self.write_header_unchecked(dest);
47        let offset = 4 + self.user.len();
48        dest[4..offset].copy_from_slice(self.user.as_bytes());
49        if len > offset {
50            dest[offset..len].fill(0);
51        }
52    }
53}
54
55impl AttributeFromRaw<'_> for Username {
56    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
57    where
58        Self: Sized,
59    {
60        Self::try_from(raw)
61    }
62}
63
64impl TryFrom<&RawAttribute<'_>> for Username {
65    type Error = StunParseError;
66
67    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
68        raw.check_type_and_len(Self::TYPE, ..=513)?;
69        Ok(Self {
70            user: core::str::from_utf8(&raw.value)
71                .map_err(|_| StunParseError::InvalidAttributeData)?
72                .to_owned(),
73        })
74    }
75}
76
77impl Username {
78    /// Create a new [`Username`] [`Attribute`]
79    ///
80    /// # Errors
81    ///
82    /// - When the length of the username is longer than allowed in a STUN
83    ///   [`Message`](crate::message::Message)
84    /// - TODO: If converting through SASLPrep fails
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// # use stun_types::attribute::*;
90    /// let username = Username::new ("user").unwrap();
91    /// assert_eq!(username.username(), "user");
92    /// ```
93    pub fn new(user: &str) -> Result<Self, StunWriteError> {
94        if user.len() > 513 {
95            return Err(StunWriteError::TooLarge {
96                expected: 513,
97                actual: user.len(),
98            });
99        }
100        // TODO: SASLPrep RFC4013 requirements
101        Ok(Self {
102            user: user.to_owned(),
103        })
104    }
105
106    /// The username stored in a [`Username`] [`Attribute`]
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// # use stun_types::attribute::*;
112    /// let username = Username::new ("user").unwrap();
113    /// assert_eq!(username.username(), "user");
114    /// ```
115    pub fn username(&self) -> &str {
116        &self.user
117    }
118}
119
120impl core::fmt::Display for Username {
121    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122        write!(f, "{}: '{}'", Self::TYPE, self.user)
123    }
124}
125
126/// The Userhash [`Attribute`]
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct Userhash {
129    hash: [u8; 32],
130}
131
132impl AttributeStaticType for Userhash {
133    const TYPE: AttributeType = AttributeType(0x001E);
134}
135
136impl Attribute for Userhash {
137    fn get_type(&self) -> AttributeType {
138        Self::TYPE
139    }
140
141    fn length(&self) -> u16 {
142        32
143    }
144}
145
146impl AttributeWrite for Userhash {
147    fn to_raw(&self) -> RawAttribute<'_> {
148        RawAttribute::new(Userhash::TYPE, &self.hash)
149    }
150
151    fn write_into_unchecked(&self, dest: &mut [u8]) {
152        self.write_header_unchecked(dest);
153        dest[4..4 + self.hash.len()].copy_from_slice(&self.hash);
154    }
155}
156
157impl AttributeFromRaw<'_> for Userhash {
158    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
159    where
160        Self: Sized,
161    {
162        Self::try_from(raw)
163    }
164}
165
166impl TryFrom<&RawAttribute<'_>> for Userhash {
167    type Error = StunParseError;
168
169    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
170        raw.check_type_and_len(Self::TYPE, 32..=32)?;
171        // sized checked earlier
172        let hash: [u8; 32] = raw.value[..32].try_into().unwrap();
173        Ok(Self { hash })
174    }
175}
176
177impl Userhash {
178    /// Create a new Userhash [`Attribute`]
179    ///
180    /// # Examples
181    ///
182    /// ```
183    /// # use stun_types::attribute::*;
184    /// let value = [0;32];
185    /// let user = Userhash::new(value);
186    /// assert_eq!(user.hash(), &value);
187    /// ```
188    pub fn new(hash: [u8; 32]) -> Self {
189        Self { hash }
190    }
191
192    /// Retrieve the hash value
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// # use stun_types::attribute::*;
198    /// let value = [0;32];
199    /// let user = Userhash::new(value);
200    /// assert_eq!(user.hash(), &value);
201    /// ```
202    pub fn hash(&self) -> &[u8; 32] {
203        &self.hash
204    }
205
206    /// Compute the hash of a specified block of data as required by STUN
207    ///
208    /// # Examples
209    /// ```
210    /// # use stun_types::attribute::*;
211    /// assert_eq!(Userhash::compute("user", "realm"), [106, 48, 41, 17, 107, 71, 170, 152, 188, 170, 50, 83, 153, 115, 61, 193, 162, 60, 213, 126, 38, 184, 27, 239, 63, 246, 83, 28, 230, 36, 226, 218]);
212    /// ```
213    pub fn compute(user: &str, realm: &str) -> [u8; 32] {
214        let data = user.to_string() + ":" + realm;
215        use sha2::{Digest, Sha256};
216        let ret = Sha256::digest(data);
217        #[allow(deprecated)]
218        {
219            ret.as_slice().try_into().unwrap()
220        }
221    }
222}
223
224impl core::fmt::Display for Userhash {
225    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
226        write!(f, "{}: 0x", Self::TYPE)?;
227        for val in self.hash.iter() {
228            write!(f, "{val:02x}")?;
229        }
230        Ok(())
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use alloc::vec;
238    use alloc::vec::Vec;
239    use byteorder::{BigEndian, ByteOrder};
240    use tracing::trace;
241
242    #[test]
243    fn username() {
244        let _log = crate::tests::test_init_log();
245        let s = "woohoo!";
246        let user = Username::new(s).unwrap();
247        trace!("{user}");
248        assert_eq!(user.username(), s);
249        assert_eq!(user.length() as usize, s.len());
250    }
251
252    #[test]
253    fn username_raw() {
254        let _log = crate::tests::test_init_log();
255        let s = "woohoo!";
256        let user = Username::new(s).unwrap();
257        let raw = RawAttribute::from(&user);
258        trace!("{raw}");
259        assert_eq!(raw.get_type(), Username::TYPE);
260        let user2 = Username::try_from(&raw).unwrap();
261        assert_eq!(user2.username(), s);
262    }
263
264    #[test]
265    fn username_raw_wrong_type() {
266        let _log = crate::tests::test_init_log();
267        let s = "woohoo!";
268        let user = Username::new(s).unwrap();
269        let raw = RawAttribute::from(&user);
270        // provide incorrectly typed data
271        let mut data: Vec<_> = raw.into();
272        BigEndian::write_u16(&mut data[0..2], 0);
273        assert!(matches!(
274            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
275            Err(StunParseError::WrongAttributeImplementation)
276        ));
277    }
278
279    #[test]
280    fn username_write_into() {
281        let _log = crate::tests::test_init_log();
282        let s = "woohoo!";
283        let user = Username::new(s).unwrap();
284        let raw = RawAttribute::from(&user);
285
286        let mut dest = vec![0; raw.padded_len()];
287        user.write_into(&mut dest).unwrap();
288        let raw = RawAttribute::from_bytes(&dest).unwrap();
289        let user2 = Username::try_from(&raw).unwrap();
290        assert_eq!(user2.username(), s);
291    }
292
293    #[test]
294    #[should_panic(expected = "out of range")]
295    fn username_write_into_unchecked() {
296        let _log = crate::tests::test_init_log();
297        let s = "woohoo!";
298        let user = Username::new(s).unwrap();
299        let raw = RawAttribute::from(&user);
300
301        let mut dest = vec![0; raw.padded_len() - 1];
302        user.write_into_unchecked(&mut dest);
303    }
304
305    #[test]
306    fn username_not_utf8() {
307        let _log = crate::tests::test_init_log();
308        let attr = Username::new("user").unwrap();
309        let raw = RawAttribute::from(&attr);
310        let mut data = raw.to_bytes();
311        data[6] = 0x88;
312        assert!(matches!(
313            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
314            Err(StunParseError::InvalidAttributeData)
315        ));
316    }
317
318    #[test]
319    fn username_new_too_large() {
320        let _log = crate::tests::test_init_log();
321        let mut large = String::new();
322        for _i in 0..64 {
323            large.push_str("abcdefgh");
324        }
325        large.push_str("ab");
326        assert!(matches!(
327            Username::new(&large),
328            Err(StunWriteError::TooLarge {
329                expected: 513,
330                actual: 514
331            })
332        ));
333    }
334
335    #[test]
336    fn userhash() {
337        let _log = crate::tests::test_init_log();
338        let hash = Userhash::compute("username", "realm1");
339        let attr = Userhash::new(hash);
340        trace!("{attr}");
341        assert_eq!(attr.hash(), &hash);
342        assert_eq!(attr.length(), 32);
343    }
344
345    #[test]
346    fn userhash_raw() {
347        let _log = crate::tests::test_init_log();
348        let hash = Userhash::compute("username", "realm1");
349        let attr = Userhash::new(hash);
350        let raw = RawAttribute::from(&attr);
351        trace!("{raw}");
352        assert_eq!(raw.get_type(), Userhash::TYPE);
353        let mapped2 = Userhash::try_from(&raw).unwrap();
354        assert_eq!(mapped2.hash(), &hash);
355    }
356
357    #[test]
358    fn userhash_raw_short() {
359        let _log = crate::tests::test_init_log();
360        let hash = Userhash::compute("username", "realm1");
361        let attr = Userhash::new(hash);
362        let raw = RawAttribute::from(&attr);
363        // truncate by one byte
364        let mut data: Vec<_> = raw.clone().into();
365        let len = data.len();
366        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
367        assert!(matches!(
368            Userhash::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()),
369            Err(StunParseError::Truncated {
370                expected: 32,
371                actual: 31
372            })
373        ));
374    }
375
376    #[test]
377    fn userhash_raw_wrong_type() {
378        let _log = crate::tests::test_init_log();
379        let hash = Userhash::compute("username", "realm1");
380        let attr = Userhash::new(hash);
381        let raw = RawAttribute::from(&attr);
382        // provide incorrectly typed data
383        let mut data: Vec<_> = raw.into();
384        BigEndian::write_u16(&mut data[0..2], 0);
385        assert!(matches!(
386            Userhash::from_raw_ref(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
387            Err(StunParseError::WrongAttributeImplementation)
388        ));
389    }
390
391    #[test]
392    fn userhash_write_into() {
393        let _log = crate::tests::test_init_log();
394        let hash = Userhash::compute("username", "realm1");
395        let attr = Userhash::new(hash);
396        let raw = RawAttribute::from(&attr);
397
398        let mut dest = vec![0; raw.padded_len()];
399        attr.write_into(&mut dest).unwrap();
400        let raw = RawAttribute::from_bytes(&dest).unwrap();
401        let hash2 = Userhash::try_from(&raw).unwrap();
402        assert_eq!(hash2.hash(), &hash);
403    }
404
405    #[test]
406    #[should_panic(expected = "out of range")]
407    fn userhash_write_into_unchecked() {
408        let _log = crate::tests::test_init_log();
409        let hash = Userhash::compute("username", "realm1");
410        let attr = Userhash::new(hash);
411        let raw = RawAttribute::from(&attr);
412
413        let mut dest = vec![0; raw.padded_len() - 1];
414        attr.write_into_unchecked(&mut dest);
415    }
416}