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 std::convert::TryFrom;
10
11use crate::message::{StunParseError, StunWriteError};
12
13use super::{
14    Attribute, AttributeExt, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite,
15    AttributeWriteExt, RawAttribute,
16};
17
18/// The username [`Attribute`]
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Username {
21    user: String,
22}
23
24impl AttributeStaticType for Username {
25    const TYPE: AttributeType = AttributeType(0x0006);
26}
27
28impl Attribute for Username {
29    fn get_type(&self) -> AttributeType {
30        Self::TYPE
31    }
32
33    fn length(&self) -> u16 {
34        self.user.len() as u16
35    }
36}
37
38impl AttributeWrite for Username {
39    fn to_raw(&self) -> RawAttribute<'_> {
40        RawAttribute::new(Username::TYPE, self.user.as_bytes())
41    }
42    fn write_into_unchecked(&self, dest: &mut [u8]) {
43        let len = self.padded_len();
44        self.write_header_unchecked(dest);
45        let offset = 4 + self.user.len();
46        dest[4..offset].copy_from_slice(self.user.as_bytes());
47        if len > offset {
48            dest[offset..len].fill(0);
49        }
50    }
51}
52
53impl AttributeFromRaw<'_> for Username {
54    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
55    where
56        Self: Sized,
57    {
58        Self::try_from(raw)
59    }
60}
61
62impl TryFrom<&RawAttribute<'_>> for Username {
63    type Error = StunParseError;
64
65    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
66        raw.check_type_and_len(Self::TYPE, ..=513)?;
67        Ok(Self {
68            user: std::str::from_utf8(&raw.value)
69                .map_err(|_| StunParseError::InvalidAttributeData)?
70                .to_owned(),
71        })
72    }
73}
74
75impl Username {
76    /// Create a new [`Username`] [`Attribute`]
77    ///
78    /// # Errors
79    ///
80    /// - When the length of the username is longer than allowed in a STUN
81    ///   [`Message`](crate::message::Message)
82    /// - TODO: If converting through SASLPrep fails
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// # use stun_types::attribute::*;
88    /// let username = Username::new ("user").unwrap();
89    /// assert_eq!(username.username(), "user");
90    /// ```
91    pub fn new(user: &str) -> Result<Self, StunWriteError> {
92        if user.len() > 513 {
93            return Err(StunWriteError::TooLarge {
94                expected: 513,
95                actual: user.len(),
96            });
97        }
98        // TODO: SASLPrep RFC4013 requirements
99        Ok(Self {
100            user: user.to_owned(),
101        })
102    }
103
104    /// The username stored in a [`Username`] [`Attribute`]
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// # use stun_types::attribute::*;
110    /// let username = Username::new ("user").unwrap();
111    /// assert_eq!(username.username(), "user");
112    /// ```
113    pub fn username(&self) -> &str {
114        &self.user
115    }
116}
117
118impl std::fmt::Display for Username {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        write!(f, "{}: '{}'", Self::TYPE, self.user)
121    }
122}
123
124/// The Userhash [`Attribute`]
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct Userhash {
127    hash: [u8; 32],
128}
129
130impl AttributeStaticType for Userhash {
131    const TYPE: AttributeType = AttributeType(0x001E);
132}
133
134impl Attribute for Userhash {
135    fn get_type(&self) -> AttributeType {
136        Self::TYPE
137    }
138
139    fn length(&self) -> u16 {
140        32
141    }
142}
143
144impl AttributeWrite for Userhash {
145    fn to_raw(&self) -> RawAttribute<'_> {
146        RawAttribute::new(Userhash::TYPE, &self.hash)
147    }
148
149    fn write_into_unchecked(&self, dest: &mut [u8]) {
150        self.write_header_unchecked(dest);
151        dest[4..4 + self.hash.len()].copy_from_slice(&self.hash);
152    }
153}
154
155impl AttributeFromRaw<'_> for Userhash {
156    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
157    where
158        Self: Sized,
159    {
160        Self::try_from(raw)
161    }
162}
163
164impl TryFrom<&RawAttribute<'_>> for Userhash {
165    type Error = StunParseError;
166
167    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
168        raw.check_type_and_len(Self::TYPE, 32..=32)?;
169        // sized checked earlier
170        let hash: [u8; 32] = raw.value[..32].try_into().unwrap();
171        Ok(Self { hash })
172    }
173}
174
175impl Userhash {
176    /// Create a new Userhash [`Attribute`]
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// # use stun_types::attribute::*;
182    /// let value = [0;32];
183    /// let user = Userhash::new(value);
184    /// assert_eq!(user.hash(), &value);
185    /// ```
186    pub fn new(hash: [u8; 32]) -> Self {
187        Self { hash }
188    }
189
190    /// Retrieve the hash value
191    ///
192    /// # Examples
193    ///
194    /// ```
195    /// # use stun_types::attribute::*;
196    /// let value = [0;32];
197    /// let user = Userhash::new(value);
198    /// assert_eq!(user.hash(), &value);
199    /// ```
200    pub fn hash(&self) -> &[u8; 32] {
201        &self.hash
202    }
203
204    /// Compute the hash of a specified block of data as required by STUN
205    ///
206    /// # Examples
207    /// ```
208    /// # use stun_types::attribute::*;
209    /// 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]);
210    /// ```
211    pub fn compute(user: &str, realm: &str) -> [u8; 32] {
212        let data = user.to_string() + ":" + realm;
213        use sha2::{Digest, Sha256};
214        let ret = Sha256::digest(data);
215        ret.as_slice().try_into().unwrap()
216    }
217}
218
219impl std::fmt::Display for Userhash {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(f, "{}: 0x", Self::TYPE)?;
222        for val in self.hash.iter() {
223            write!(f, "{val:02x}")?;
224        }
225        Ok(())
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use byteorder::{BigEndian, ByteOrder};
233    use tracing::trace;
234
235    #[test]
236    fn username() {
237        let _log = crate::tests::test_init_log();
238        let s = "woohoo!";
239        let user = Username::new(s).unwrap();
240        trace!("{user}");
241        assert_eq!(user.username(), s);
242        assert_eq!(user.length() as usize, s.len());
243        let raw = RawAttribute::from(&user);
244        trace!("{raw}");
245        assert_eq!(raw.get_type(), Username::TYPE);
246        let user2 = Username::try_from(&raw).unwrap();
247        assert_eq!(user2.username(), s);
248        // provide incorrectly typed data
249        let mut data: Vec<_> = raw.into();
250        BigEndian::write_u16(&mut data[0..2], 0);
251        assert!(matches!(
252            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
253            Err(StunParseError::WrongAttributeImplementation)
254        ));
255    }
256
257    #[test]
258    fn username_not_utf8() {
259        let _log = crate::tests::test_init_log();
260        let attr = Username::new("user").unwrap();
261        let raw = RawAttribute::from(&attr);
262        let mut data = raw.to_bytes();
263        data[6] = 0x88;
264        assert!(matches!(
265            Username::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
266            Err(StunParseError::InvalidAttributeData)
267        ));
268    }
269
270    #[test]
271    fn username_new_too_large() {
272        let _log = crate::tests::test_init_log();
273        let mut large = String::new();
274        for _i in 0..64 {
275            large.push_str("abcdefgh");
276        }
277        large.push_str("ab");
278        assert!(matches!(
279            Username::new(&large),
280            Err(StunWriteError::TooLarge {
281                expected: 513,
282                actual: 514
283            })
284        ));
285    }
286
287    #[test]
288    fn userhash() {
289        let _log = crate::tests::test_init_log();
290        let hash = Userhash::compute("username", "realm1");
291        let attr = Userhash::new(hash);
292        trace!("{attr}");
293        assert_eq!(attr.hash(), &hash);
294        assert_eq!(attr.length(), 32);
295        let raw = RawAttribute::from(&attr);
296        trace!("{raw}");
297        assert_eq!(raw.get_type(), Userhash::TYPE);
298        let mapped2 = Userhash::try_from(&raw).unwrap();
299        assert_eq!(mapped2.hash(), &hash);
300        // truncate by one byte
301        let mut data: Vec<_> = raw.clone().into();
302        let len = data.len();
303        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
304        assert!(matches!(
305            Userhash::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()),
306            Err(StunParseError::Truncated {
307                expected: 32,
308                actual: 31
309            })
310        ));
311        // provide incorrectly typed data
312        let mut data: Vec<_> = raw.into();
313        BigEndian::write_u16(&mut data[0..2], 0);
314        assert!(matches!(
315            Userhash::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
316            Err(StunParseError::WrongAttributeImplementation)
317        ));
318    }
319}