stun_types/attribute/
alternate.rs1use std::convert::TryFrom;
10use std::net::SocketAddr;
11
12use crate::message::StunParseError;
13
14use super::{
15 Attribute, AttributeExt, AttributeStaticType, AttributeType, AttributeWrite, AttributeWriteExt,
16 MappedSocketAddr, RawAttribute,
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct AlternateServer {
22 addr: MappedSocketAddr,
23}
24
25impl AttributeStaticType for AlternateServer {
26 const TYPE: AttributeType = AttributeType(0x8023);
27}
28impl Attribute for AlternateServer {
29 fn get_type(&self) -> AttributeType {
30 Self::TYPE
31 }
32
33 fn length(&self) -> u16 {
34 self.addr.length()
35 }
36}
37
38impl AttributeWrite for AlternateServer {
39 fn to_raw(&self) -> RawAttribute {
40 self.addr.to_raw(AlternateServer::TYPE)
41 }
42 fn write_into_unchecked(&self, dest: &mut [u8]) {
43 self.write_header_unchecked(dest);
44 self.addr.write_into_unchecked(&mut dest[4..]);
45 }
46}
47
48impl<'a> TryFrom<&RawAttribute<'a>> for AlternateServer {
49 type Error = StunParseError;
50
51 fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
52 raw.check_type_and_len(Self::TYPE, ..)?;
53 let addr = MappedSocketAddr::from_raw(raw)?;
54 Ok(Self { addr })
55 }
56}
57
58impl AlternateServer {
59 pub fn new(addr: SocketAddr) -> Self {
70 Self {
71 addr: MappedSocketAddr::new(addr),
72 }
73 }
74
75 pub fn server(&self) -> SocketAddr {
86 self.addr.addr()
87 }
88}
89
90impl std::fmt::Display for AlternateServer {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(f, "{}: {}", AlternateServer::TYPE, self.addr)
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct AlternateDomain {
99 domain: String,
100}
101
102impl AttributeStaticType for AlternateDomain {
103 const TYPE: AttributeType = AttributeType(0x8003);
104}
105
106impl Attribute for AlternateDomain {
107 fn get_type(&self) -> AttributeType {
108 Self::TYPE
109 }
110 fn length(&self) -> u16 {
111 self.domain.len() as u16
112 }
113}
114impl<'a> TryFrom<&RawAttribute<'a>> for AlternateDomain {
115 type Error = StunParseError;
116
117 fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
118 raw.check_type_and_len(Self::TYPE, ..)?;
119 Ok(Self {
121 domain: std::str::from_utf8(&raw.value)
122 .map_err(|_| StunParseError::InvalidAttributeData)?
123 .to_owned(),
124 })
125 }
126}
127impl AttributeWrite for AlternateDomain {
128 fn to_raw(&self) -> RawAttribute {
129 RawAttribute::new(AlternateDomain::TYPE, self.domain.as_bytes())
130 }
131 fn write_into_unchecked(&self, dest: &mut [u8]) {
132 let len = self.padded_len();
133 self.write_header_unchecked(dest);
134 dest[4..4 + self.domain.len()].copy_from_slice(self.domain.as_bytes());
135 let offset = 4 + self.domain.len();
136 if len > offset {
137 dest[offset..len].fill(0);
138 }
139 }
140}
141
142impl AlternateDomain {
143 pub fn new(domain: &str) -> Self {
154 Self {
155 domain: domain.to_string(),
156 }
157 }
158
159 pub fn domain(&self) -> &str {
170 &self.domain
171 }
172}
173
174impl std::fmt::Display for AlternateDomain {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 write!(f, "{}: {}", AlternateDomain::TYPE, self.domain)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use byteorder::{BigEndian, ByteOrder};
184 use tracing::trace;
185
186 #[test]
187 fn alternate_server() {
188 let _log = crate::tests::test_init_log();
189 let addrs = &[
190 "192.168.0.1:40000".parse().unwrap(),
191 "[fd12:3456:789a:1::1]:41000".parse().unwrap(),
192 ];
193 for addr in addrs {
194 let mapped = AlternateServer::new(*addr);
195 trace!("{mapped}");
196 assert_eq!(mapped.server(), *addr);
197 match addr {
198 SocketAddr::V4(_) => assert_eq!(mapped.length(), 8),
199 SocketAddr::V6(_) => assert_eq!(mapped.length(), 20),
200 }
201 let raw = RawAttribute::from(&mapped);
202 trace!("{raw}");
203 assert_eq!(raw.get_type(), AlternateServer::TYPE);
204 let mapped2 = AlternateServer::try_from(&raw).unwrap();
205 assert_eq!(mapped2.server(), *addr);
206 let mut data: Vec<_> = raw.clone().into();
208 let len = data.len();
209 BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
210 assert!(matches!(
211 AlternateServer::try_from(
212 &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
213 ),
214 Err(StunParseError::Truncated {
215 expected: _,
216 actual: _
217 })
218 ));
219 let mut data: Vec<_> = raw.clone().into();
221 BigEndian::write_u16(&mut data[0..2], 0);
222 assert!(matches!(
223 AlternateServer::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
224 Err(StunParseError::WrongAttributeImplementation)
225 ));
226
227 let mut dest = vec![0; raw.padded_len()];
228 mapped.write_into(&mut dest).unwrap();
229 let raw = RawAttribute::from_bytes(&dest).unwrap();
230 let mapped2 = AlternateServer::try_from(&raw).unwrap();
231 assert_eq!(mapped2.server(), *addr);
232 }
233 }
234
235 #[test]
236 fn alternative_domain() {
237 let _log = crate::tests::test_init_log();
238 let dns = "example.com";
239 let attr = AlternateDomain::new(dns);
240 trace!("{attr}");
241 assert_eq!(attr.domain(), dns);
242 assert_eq!(attr.length() as usize, dns.len());
243 let raw = RawAttribute::from(&attr);
244 trace!("{raw}");
245 assert_eq!(raw.get_type(), AlternateDomain::TYPE);
246 let mapped2 = AlternateDomain::try_from(&raw).unwrap();
247 assert_eq!(mapped2.domain(), dns);
248 let mut data: Vec<_> = raw.clone().into();
250 BigEndian::write_u16(&mut data[0..2], 0);
251 assert!(matches!(
252 AlternateDomain::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
253 Err(StunParseError::WrongAttributeImplementation)
254 ));
255 let mut data: Vec<_> = raw.clone().into();
256 data[8] = 0x88;
258 assert!(matches!(
259 AlternateDomain::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
260 Err(StunParseError::InvalidAttributeData)
261 ));
262
263 let mut dest = vec![0; raw.padded_len()];
264 attr.write_into(&mut dest).unwrap();
265 let raw = RawAttribute::from_bytes(&dest).unwrap();
266 let mapped2 = AlternateDomain::try_from(&raw).unwrap();
267 assert_eq!(mapped2.domain(), dns);
268 }
269}