Skip to main content

stun_types/attribute/
alternate.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, ToString};
13use core::convert::TryFrom;
14use core::net::SocketAddr;
15
16use crate::message::StunParseError;
17
18use super::{
19    Attribute, AttributeExt, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite,
20    AttributeWriteExt, MappedSocketAddr, RawAttribute,
21};
22
23/// The AlternateServer [`Attribute`]
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct AlternateServer {
26    addr: MappedSocketAddr,
27}
28
29impl AttributeStaticType for AlternateServer {
30    const TYPE: AttributeType = AttributeType(0x8023);
31}
32impl Attribute for AlternateServer {
33    fn get_type(&self) -> AttributeType {
34        Self::TYPE
35    }
36
37    fn length(&self) -> u16 {
38        self.addr.length()
39    }
40}
41
42impl AttributeWrite for AlternateServer {
43    fn to_raw(&self) -> RawAttribute<'_> {
44        self.addr.to_raw(AlternateServer::TYPE)
45    }
46    fn write_into_unchecked(&self, dest: &mut [u8]) {
47        self.write_header_unchecked(dest);
48        self.addr.write_into_unchecked(&mut dest[4..]);
49    }
50}
51
52impl AttributeFromRaw<'_> for AlternateServer {
53    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError> {
54        Self::try_from(raw)
55    }
56}
57
58impl TryFrom<&RawAttribute<'_>> for AlternateServer {
59    type Error = StunParseError;
60
61    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
62        raw.check_type_and_len(Self::TYPE, ..)?;
63        let addr = MappedSocketAddr::from_raw(raw)?;
64        Ok(Self { addr })
65    }
66}
67
68impl AlternateServer {
69    /// Create a new AlternateServer [`Attribute`]
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// # use stun_types::attribute::*;
75    /// let addr = "127.0.0.1:12345".parse().unwrap();
76    /// let server = AlternateServer::new(addr);
77    /// assert_eq!(server.server(), addr);
78    /// ```
79    pub fn new(addr: SocketAddr) -> Self {
80        Self {
81            addr: MappedSocketAddr::new(addr),
82        }
83    }
84
85    /// Retrieve the server value
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// # use stun_types::attribute::*;
91    /// let addr = "127.0.0.1:12345".parse().unwrap();
92    /// let server = AlternateServer::new(addr);
93    /// assert_eq!(server.server(), addr);
94    /// ```
95    pub fn server(&self) -> SocketAddr {
96        self.addr.addr()
97    }
98}
99
100impl core::fmt::Display for AlternateServer {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        write!(f, "{}: {}", AlternateServer::TYPE, self.addr)
103    }
104}
105
106/// The AlternateDomain [`Attribute`]
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct AlternateDomain {
109    domain: String,
110}
111
112impl AttributeStaticType for AlternateDomain {
113    const TYPE: AttributeType = AttributeType(0x8003);
114}
115
116impl Attribute for AlternateDomain {
117    fn get_type(&self) -> AttributeType {
118        Self::TYPE
119    }
120    fn length(&self) -> u16 {
121        self.domain.len() as u16
122    }
123}
124impl AttributeFromRaw<'_> for AlternateDomain {
125    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
126    where
127        Self: Sized,
128    {
129        Self::try_from(raw)
130    }
131}
132impl TryFrom<&RawAttribute<'_>> for AlternateDomain {
133    type Error = StunParseError;
134
135    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
136        raw.check_type_and_len(Self::TYPE, ..)?;
137        // FIXME: should be ascii-only
138        Ok(Self {
139            domain: core::str::from_utf8(&raw.value)
140                .map_err(|_| StunParseError::InvalidAttributeData)?
141                .to_owned(),
142        })
143    }
144}
145impl AttributeWrite for AlternateDomain {
146    fn to_raw(&self) -> RawAttribute<'_> {
147        RawAttribute::new(AlternateDomain::TYPE, self.domain.as_bytes())
148    }
149    fn write_into_unchecked(&self, dest: &mut [u8]) {
150        let len = self.padded_len();
151        self.write_header_unchecked(dest);
152        dest[4..4 + self.domain.len()].copy_from_slice(self.domain.as_bytes());
153        let offset = 4 + self.domain.len();
154        if len > offset {
155            dest[offset..len].fill(0);
156        }
157    }
158}
159
160impl AlternateDomain {
161    /// Create a new AlternateDomain [`Attribute`]
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// # use stun_types::attribute::*;
167    /// let dns = "example.com";
168    /// let domain = AlternateDomain::new(dns);
169    /// assert_eq!(domain.domain(), dns);
170    /// ```
171    pub fn new(domain: &str) -> Self {
172        Self {
173            domain: domain.to_string(),
174        }
175    }
176
177    /// Retrieve the domain value
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use stun_types::attribute::*;
183    /// let dns = "example.com";
184    /// let domain = AlternateDomain::new(dns);
185    /// assert_eq!(domain.domain(), dns);
186    /// ```
187    pub fn domain(&self) -> &str {
188        &self.domain
189    }
190}
191
192impl core::fmt::Display for AlternateDomain {
193    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
194        write!(f, "{}: {}", AlternateDomain::TYPE, self.domain)
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
201
202    use super::*;
203    use alloc::vec;
204    use alloc::vec::Vec;
205    use byteorder::{BigEndian, ByteOrder};
206    use tracing::trace;
207
208    const ADDRS: [SocketAddr; 2] = [
209        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), 40000),
210        SocketAddr::new(
211            IpAddr::V6(Ipv6Addr::new(
212                0xfd2, 0x3456, 0x789a, 0x01, 0x0, 0x0, 0x0, 0x1,
213            )),
214            41000,
215        ),
216    ];
217
218    #[test]
219    fn alternate_server() {
220        let _log = crate::tests::test_init_log();
221        for addr in ADDRS {
222            let mapped = AlternateServer::new(addr);
223            trace!("{mapped}");
224            assert_eq!(mapped.server(), addr);
225            match addr {
226                SocketAddr::V4(_) => assert_eq!(mapped.length(), 8),
227                SocketAddr::V6(_) => assert_eq!(mapped.length(), 20),
228            }
229        }
230    }
231
232    #[test]
233    fn alternate_server_raw() {
234        let _log = crate::tests::test_init_log();
235        for addr in ADDRS {
236            let mapped = AlternateServer::new(addr);
237            let raw = RawAttribute::from(&mapped);
238            match addr {
239                SocketAddr::V4(_) => assert_eq!(raw.length(), 8),
240                SocketAddr::V6(_) => assert_eq!(raw.length(), 20),
241            }
242            trace!("{raw}");
243            assert_eq!(raw.get_type(), AlternateServer::TYPE);
244            let mapped2 = AlternateServer::try_from(&raw).unwrap();
245            assert_eq!(mapped2.server(), addr);
246        }
247    }
248
249    #[test]
250    fn alternate_server_raw_short() {
251        let _log = crate::tests::test_init_log();
252        for addr in ADDRS {
253            let mapped = AlternateServer::new(addr);
254            let raw = RawAttribute::from(&mapped);
255            // truncate by one byte
256            let mut data: Vec<_> = raw.clone().into();
257            let len = data.len();
258            BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
259            assert!(matches!(
260                AlternateServer::try_from(
261                    &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
262                ),
263                Err(StunParseError::Truncated {
264                    expected: _,
265                    actual: _
266                })
267            ));
268        }
269    }
270
271    #[test]
272    fn alternate_server_raw_wrong_type() {
273        let _log = crate::tests::test_init_log();
274        for addr in ADDRS {
275            let mapped = AlternateServer::new(addr);
276            let raw = RawAttribute::from(&mapped);
277            // provide incorrectly typed data
278            let mut data: Vec<_> = raw.clone().into();
279            BigEndian::write_u16(&mut data[0..2], 0);
280            assert!(matches!(
281                AlternateServer::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
282                Err(StunParseError::WrongAttributeImplementation)
283            ));
284        }
285    }
286
287    #[test]
288    fn alternate_server_write_into() {
289        let _log = crate::tests::test_init_log();
290        for addr in ADDRS {
291            let mapped = AlternateServer::new(addr);
292            let raw = RawAttribute::from(&mapped);
293
294            let mut dest = vec![0; raw.padded_len()];
295            mapped.write_into(&mut dest).unwrap();
296            let raw = RawAttribute::from_bytes(&dest).unwrap();
297            let mapped2 = AlternateServer::try_from(&raw).unwrap();
298            assert_eq!(mapped2.server(), addr);
299        }
300    }
301
302    #[test]
303    #[should_panic(expected = "out of range")]
304    fn alternate_server_write_into_unchecked() {
305        let _log = crate::tests::test_init_log();
306        let mapped = AlternateServer::new(ADDRS[0]);
307        let raw = RawAttribute::from(&mapped);
308
309        let mut dest = vec![0; raw.padded_len() - 1];
310        mapped.write_into_unchecked(&mut dest);
311    }
312
313    const DOMAIN: &str = "example.com";
314
315    #[test]
316    fn alternative_domain() {
317        let _log = crate::tests::test_init_log();
318        let attr = AlternateDomain::new(DOMAIN);
319        trace!("{attr}");
320        assert_eq!(attr.domain(), DOMAIN);
321        assert_eq!(attr.length() as usize, DOMAIN.len());
322    }
323
324    #[test]
325    fn alternative_domain_raw() {
326        let _log = crate::tests::test_init_log();
327        let attr = AlternateDomain::new(DOMAIN);
328        let raw = RawAttribute::from(&attr);
329        trace!("{raw}");
330        assert_eq!(raw.get_type(), AlternateDomain::TYPE);
331        let mapped2 = AlternateDomain::try_from(&raw).unwrap();
332        assert_eq!(mapped2.domain(), DOMAIN);
333    }
334
335    #[test]
336    fn alternative_domain_raw_wrong_type() {
337        let _log = crate::tests::test_init_log();
338        let attr = AlternateDomain::new(DOMAIN);
339        let raw = RawAttribute::from(&attr);
340        let mut data: Vec<_> = raw.clone().into();
341        // provide incorrectly typed data
342        BigEndian::write_u16(&mut data[0..2], 0);
343        assert!(matches!(
344            AlternateDomain::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
345            Err(StunParseError::WrongAttributeImplementation)
346        ));
347    }
348
349    #[test]
350    fn alternative_domain_raw_invalid_utf8() {
351        let _log = crate::tests::test_init_log();
352        let attr = AlternateDomain::new(DOMAIN);
353        let raw = RawAttribute::from(&attr);
354
355        // invalid utf-8 data
356        let mut data: Vec<_> = raw.clone().into();
357        data[8] = 0x88;
358        assert!(matches!(
359            AlternateDomain::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
360            Err(StunParseError::InvalidAttributeData)
361        ));
362    }
363
364    #[test]
365    fn alternative_domain_write_into() {
366        let _log = crate::tests::test_init_log();
367        let attr = AlternateDomain::new(DOMAIN);
368        let raw = RawAttribute::from(&attr);
369
370        let mut dest = vec![0; raw.padded_len()];
371        attr.write_into(&mut dest).unwrap();
372        let raw = RawAttribute::from_bytes(&dest).unwrap();
373        let mapped2 = AlternateDomain::try_from(&raw).unwrap();
374        assert_eq!(mapped2.domain(), DOMAIN);
375    }
376
377    #[test]
378    #[should_panic(expected = "out of range")]
379    fn alternative_domain_write_into_unchecked() {
380        let _log = crate::tests::test_init_log();
381        let attr = AlternateDomain::new(DOMAIN);
382        let raw = RawAttribute::from(&attr);
383
384        let mut dest = vec![0; raw.padded_len() - 1];
385        attr.write_into_unchecked(&mut dest);
386    }
387}