1use crate::{read_u8, DnsError};
2use core::convert::TryFrom;
3use core::fmt::{Display, Formatter};
4use fixed_buffer::FixedBuf;
5
6#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
66pub struct DnsName(String);
67impl DnsName {
68 fn is_letter(b: u8) -> bool {
69 b.is_ascii_lowercase() || b.is_ascii_uppercase()
70 }
71
72 fn is_letter_digit(b: u8) -> bool {
73 Self::is_letter(b) || b.is_ascii_digit()
74 }
75
76 fn is_letter_digit_hyphen(b: u8) -> bool {
77 Self::is_letter_digit(b) || b == b'-'
78 }
79
80 fn is_valid_label(label: &str) -> bool {
81 if label.is_empty() || label.len() > 63 {
82 return false;
83 }
84 let bytes = label.as_bytes();
85 Self::is_letter(bytes[0])
86 && bytes.iter().copied().all(Self::is_letter_digit_hyphen)
87 && Self::is_letter_digit(*bytes.last().unwrap())
88 }
89
90 fn is_valid_name(value: &str) -> bool {
91 if !value.is_ascii() {
92 return false;
93 }
94 value.split('.').all(Self::is_valid_label)
95 }
96
97 pub fn new(value: &str) -> Result<Self, String> {
100 let trimmed = value.strip_suffix('.').unwrap_or(value);
101 if trimmed.len() > 255 || !Self::is_valid_name(trimmed) {
102 return Err(format!("not a valid DNS name: {value:?}"));
103 }
104 Ok(Self(trimmed.to_string()))
105 }
106
107 pub fn read<const N: usize>(buf: &mut FixedBuf<N>) -> Result<DnsName, DnsError> {
110 let mut value = String::new();
111 for _ in 0..63 {
112 let len = read_u8(buf)? as usize;
113 if len == 0 {
114 if value.len() > 255 {
115 return Err(DnsError::NameTooLong);
116 }
117 return Ok(Self(value));
118 }
119 if buf.readable().len() < len {
120 return Err(DnsError::Truncated);
121 }
122 let label_bytes = buf.read_bytes(len);
123 let label = std::str::from_utf8(label_bytes).map_err(|_| DnsError::InvalidLabel)?;
124 if !Self::is_valid_label(label) {
125 return Err(DnsError::InvalidLabel);
126 }
127 if !value.is_empty() {
128 value.push('.');
129 }
130 value.push_str(label);
131 }
132 Err(DnsError::TooManyLabels)
133 }
134
135 pub fn write<const N: usize>(&self, out: &mut FixedBuf<N>) -> Result<(), DnsError> {
138 for label in self.0.split('.') {
139 if label.len() > 63 {
140 return Err(DnsError::Unreachable(file!(), line!()));
141 }
142 let len =
143 u8::try_from(label.len()).map_err(|_| DnsError::Unreachable(file!(), line!()))?;
144 out.write_bytes(&[len])
145 .map_err(|_| DnsError::ResponseBufferFull)?;
146 out.write_bytes(label.as_bytes())
147 .map_err(|_| DnsError::ResponseBufferFull)?;
148 }
149 out.write_bytes(&[0])
150 .map_err(|_| DnsError::ResponseBufferFull)?;
151 Ok(())
152 }
153
154 pub fn as_bytes(&self) -> Result<FixedBuf<256>, DnsError> {
157 let mut buf: FixedBuf<256> = FixedBuf::new();
158 self.write(&mut buf)?;
159 Ok(buf)
160 }
161
162 #[must_use]
163 pub fn inner(&self) -> &str {
164 &self.0
165 }
166}
167impl Display for DnsName {
168 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
169 write!(f, "{}", self.0)
170 }
171}
172impl TryFrom<&'static str> for DnsName {
173 type Error = String;
174
175 fn try_from(value: &'static str) -> Result<Self, Self::Error> {
176 DnsName::new(value)
177 }
178}
179
180#[cfg(test)]
181#[test]
182fn test_err() {
183 assert_eq!(
184 <Result<DnsName, String>>::Err("not a valid DNS name: \"abc!\"".to_string()),
185 DnsName::new("abc!")
186 );
187}
188
189#[cfg(test)]
190#[test]
191fn test_new_label_separators() {
192 DnsName::new(".").unwrap_err();
193 assert_eq!("a", DnsName::new("a.").unwrap().inner());
194 DnsName::new("a..").unwrap_err();
195 DnsName::new(".a").unwrap_err();
196 DnsName::new("b..a").unwrap_err();
197 DnsName::new(".b.a").unwrap_err();
198}
199
200#[cfg(test)]
201#[test]
202fn test_new_label_charset() {
203 const ALLOWED: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.";
204 for c in ALLOWED.chars() {
205 let value = format!("a{c}a");
206 DnsName::new(&value).expect(&value);
207 }
208 for b in 0..=255_u8 {
209 let c = char::from(b);
210 if !ALLOWED.contains(c) {
211 let value = format!("a{c}a");
212 assert_eq!(
213 <Result<DnsName, String>>::Err(format!("not a valid DNS name: {value:?}")),
214 DnsName::new(&value)
215 );
216 }
217 }
218 assert_eq!(
219 <Result<DnsName, String>>::Err("not a valid DNS name: \"a\u{263A}\"".to_string()),
220 DnsName::new("a\u{263A}")
221 );
222}
223
224#[cfg(test)]
225#[test]
226fn test_new_label() {
227 assert_eq!(
228 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789",
229 DnsName::new("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789")
230 .unwrap()
231 .inner()
232 );
233 assert_eq!("aB-Cd.eFg", DnsName::new("aB-Cd.eFg").unwrap().inner());
234}
235
236#[cfg(test)]
237#[test]
238fn test_new_label_format() {
239 DnsName::new("a").unwrap();
240 DnsName::new("1").unwrap_err();
241 DnsName::new("1a").unwrap_err();
242 DnsName::new("a1").unwrap();
243 DnsName::new("a9876543210").unwrap();
244 DnsName::new("-").unwrap_err();
245 DnsName::new("a-").unwrap_err();
246 DnsName::new("-a").unwrap_err();
247 DnsName::new("a-.b").unwrap_err();
248 DnsName::new("a.-b").unwrap_err();
249 DnsName::new("a-b").unwrap();
250 DnsName::new("a-0").unwrap();
251 DnsName::new("a---b").unwrap();
252}
253
254#[cfg(test)]
255#[test]
256fn test_new_label_length() {
257 DnsName::new("").unwrap_err();
258 DnsName::new("a").unwrap();
259 DnsName::new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap();
260 DnsName::new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap_err();
261}
262
263#[cfg(test)]
264#[test]
265fn test_new_name_length() {
266 DnsName::new(concat!(
267 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
268 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
269 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
270 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
271 ))
272 .unwrap();
273 DnsName::new(concat!(
274 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
275 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
276 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
277 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
278 ))
279 .unwrap();
280 DnsName::new(concat!(
281 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
282 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
283 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.",
284 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a"
285 ))
286 .unwrap_err();
287 DnsName::new(concat!(
288 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
289 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
290 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
291 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
292 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
293 "a.a.a",
294 ))
295 .unwrap();
296 DnsName::new(concat!(
297 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
298 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
299 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
300 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
301 "a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.",
302 "a.a.aa",
303 ))
304 .unwrap_err();
305}
306
307#[cfg(test)]
311#[test]
312fn test_inner() {
313 assert_eq!("abc", DnsName::new("abc").unwrap().inner());
314}
315
316#[cfg(test)]
317#[test]
318fn test_display() {
319 assert_eq!(
320 "example.com",
321 format!("{}", DnsName::new("example.com").unwrap())
322 );
323}
324
325