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