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        ret.as_slice().try_into().unwrap()
218    }
219}
220
221impl core::fmt::Display for Userhash {
222    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
223        write!(f, "{}: 0x", Self::TYPE)?;
224        for val in self.hash.iter() {
225            write!(f, "{val:02x}")?;
226        }
227        Ok(())
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use alloc::vec::Vec;
235    use byteorder::{BigEndian, ByteOrder};
236    use tracing::trace;
237
238    #[test]
239    fn username() {
240        let _log = crate::tests::test_init_log();
241        let s = "woohoo!";
242        let user = Username::new(s).unwrap();
243        trace!("{user}");
244        assert_eq!(user.username(), s);
245        assert_eq!(user.length() as usize, s.len());
246        let raw = RawAttribute::from(&user);
247        trace!("{raw}");
248        assert_eq!(raw.get_type(), Username::TYPE);
249        let user2 = Username::try_from(&raw).unwrap();
250        assert_eq!(user2.username(), s);
251        // provide incorrectly typed data
252        let mut data: Vec<_> = raw.into();
253        BigEndian::write_u16(&mut data[0..2], 0);
254        assert!(matches!(
255            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
256            Err(StunParseError::WrongAttributeImplementation)
257        ));
258    }
259
260    #[test]
261    fn username_not_utf8() {
262        let _log = crate::tests::test_init_log();
263        let attr = Username::new("user").unwrap();
264        let raw = RawAttribute::from(&attr);
265        let mut data = raw.to_bytes();
266        data[6] = 0x88;
267        assert!(matches!(
268            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
269            Err(StunParseError::InvalidAttributeData)
270        ));
271    }
272
273    #[test]
274    fn username_new_too_large() {
275        let _log = crate::tests::test_init_log();
276        let mut large = String::new();
277        for _i in 0..64 {
278            large.push_str("abcdefgh");
279        }
280        large.push_str("ab");
281        assert!(matches!(
282            Username::new(&large),
283            Err(StunWriteError::TooLarge {
284                expected: 513,
285                actual: 514
286            })
287        ));
288    }
289
290    #[test]
291    fn userhash() {
292        let _log = crate::tests::test_init_log();
293        let hash = Userhash::compute("username", "realm1");
294        let attr = Userhash::new(hash);
295        trace!("{attr}");
296        assert_eq!(attr.hash(), &hash);
297        assert_eq!(attr.length(), 32);
298        let raw = RawAttribute::from(&attr);
299        trace!("{raw}");
300        assert_eq!(raw.get_type(), Userhash::TYPE);
301        let mapped2 = Userhash::try_from(&raw).unwrap();
302        assert_eq!(mapped2.hash(), &hash);
303        // truncate by one byte
304        let mut data: Vec<_> = raw.clone().into();
305        let len = data.len();
306        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
307        assert!(matches!(
308            Userhash::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()),
309            Err(StunParseError::Truncated {
310                expected: 32,
311                actual: 31
312            })
313        ));
314        // provide incorrectly typed data
315        let mut data: Vec<_> = raw.into();
316        BigEndian::write_u16(&mut data[0..2], 0);
317        assert!(matches!(
318            Userhash::from_raw_ref(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
319            Err(StunParseError::WrongAttributeImplementation)
320        ));
321    }
322}