Skip to main content

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