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