stun_types/attribute/
software.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;
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 Software [`Attribute`]
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Software {
23    software: String,
24}
25
26impl AttributeStaticType for Software {
27    const TYPE: AttributeType = AttributeType(0x8022);
28}
29
30impl Attribute for Software {
31    fn get_type(&self) -> AttributeType {
32        Self::TYPE
33    }
34
35    fn length(&self) -> u16 {
36        self.software.len() as u16
37    }
38}
39
40impl AttributeWrite for Software {
41    fn to_raw(&self) -> RawAttribute<'_> {
42        RawAttribute::new(Software::TYPE, self.software.as_bytes())
43    }
44
45    fn write_into_unchecked(&self, dest: &mut [u8]) {
46        let len = self.padded_len();
47        let offset = self.write_header_unchecked(dest);
48        dest[offset..offset + self.software.len()].copy_from_slice(self.software.as_bytes());
49        let offset = offset + self.software.len();
50        if len > offset {
51            dest[offset..len].fill(0);
52        }
53    }
54}
55
56impl AttributeFromRaw<'_> for Software {
57    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
58    where
59        Self: Sized,
60    {
61        Self::try_from(raw)
62    }
63}
64
65impl TryFrom<&RawAttribute<'_>> for Software {
66    type Error = StunParseError;
67
68    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
69        raw.check_type_and_len(Self::TYPE, ..=763)?;
70        Ok(Self {
71            software: core::str::from_utf8(&raw.value)
72                .map_err(|_| StunParseError::InvalidAttributeData)?
73                .to_owned(),
74        })
75    }
76}
77
78impl Software {
79    /// Create a new unknown attributes [`Attribute`]
80    ///
81    /// # Errors
82    ///
83    /// If the length of the provided string is too long for the [`Attribute`]
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// # use stun_types::attribute::*;
89    /// let software = Software::new("stun-types 0.1").unwrap();
90    /// assert_eq!(software.software(), "stun-types 0.1");
91    /// ```
92    pub fn new(software: &str) -> Result<Self, StunWriteError> {
93        // TODO: should only allow 128 characters
94        if software.len() > 763 {
95            return Err(StunWriteError::TooLarge {
96                expected: 763,
97                actual: software.len(),
98            });
99        }
100        Ok(Self {
101            software: software.to_owned(),
102        })
103    }
104
105    /// The value of the software field
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// # use stun_types::attribute::*;
111    /// let software = Software::new("stun-types 0.1").unwrap();
112    /// assert_eq!(software.software(), "stun-types 0.1");
113    /// ```
114    pub fn software(&self) -> &str {
115        &self.software
116    }
117}
118
119impl core::fmt::Display for Software {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        write!(f, "{}: '{}'", Software::TYPE, self.software)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use alloc::vec;
129    use alloc::vec::Vec;
130    use byteorder::{BigEndian, ByteOrder};
131    use tracing::trace;
132
133    #[test]
134    fn software() {
135        let _log = crate::tests::test_init_log();
136        let software = Software::new("software").unwrap();
137        trace!("{software}");
138        assert_eq!(software.software(), "software");
139        assert_eq!(software.length() as usize, "software".len());
140    }
141
142    #[test]
143    fn software_raw() {
144        let _log = crate::tests::test_init_log();
145        let software = Software::new("software").unwrap();
146        let raw = RawAttribute::from(&software);
147        trace!("{raw}");
148        assert_eq!(raw.get_type(), Software::TYPE);
149        let software2 = Software::try_from(&raw).unwrap();
150        assert_eq!(software2.software(), "software");
151    }
152
153    #[test]
154    fn software_raw_wrong_type() {
155        let _log = crate::tests::test_init_log();
156        let software = Software::new("software").unwrap();
157        let raw = RawAttribute::from(&software);
158
159        // provide incorrectly typed data
160        let mut data: Vec<_> = raw.into();
161        BigEndian::write_u16(&mut data[0..2], 0);
162        assert!(matches!(
163            Software::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
164            Err(StunParseError::WrongAttributeImplementation)
165        ));
166    }
167
168    #[test]
169    fn software_write_into() {
170        let _log = crate::tests::test_init_log();
171        let software = Software::new("software").unwrap();
172        let raw = RawAttribute::from(&software);
173
174        let mut dest = vec![0; raw.padded_len()];
175        software.write_into(&mut dest).unwrap();
176        let raw = RawAttribute::from_bytes(&dest).unwrap();
177        let software2 = Software::try_from(&raw).unwrap();
178        assert_eq!(software2.software(), "software");
179    }
180
181    #[test]
182    #[should_panic(expected = "out of range")]
183    fn software_write_into_unchcked() {
184        let _log = crate::tests::test_init_log();
185        let software = Software::new("software").unwrap();
186        let raw = RawAttribute::from(&software);
187
188        let mut dest = vec![0; raw.padded_len() - 1];
189        software.write_into_unchecked(&mut dest);
190    }
191
192    #[test]
193    fn software_not_utf8() {
194        let _log = crate::tests::test_init_log();
195        let attr = Software::new("software").unwrap();
196        let raw = RawAttribute::from(&attr);
197        let mut data = raw.to_bytes();
198        data[6] = 0x88;
199        assert!(matches!(
200            Software::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
201            Err(StunParseError::InvalidAttributeData)
202        ));
203    }
204
205    #[test]
206    fn software_new_too_large() {
207        let _log = crate::tests::test_init_log();
208        let mut large = String::new();
209        for _i in 0..95 {
210            large.push_str("abcdefgh");
211        }
212        large.push_str("abcd");
213        assert!(matches!(
214            Software::new(&large),
215            Err(StunWriteError::TooLarge {
216                expected: 763,
217                actual: 764
218            })
219        ));
220    }
221}