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