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