1use core::hash::Hasher;
2
3use crate::{
4 dns::MAX_SERVICE_NAME_LENGTH,
5 lib::{
6 fmt::{Debug, Display, Formatter, Result as FmtResult},
7 Cow, Hash, String, ToString, Vec,
8 },
9};
10
11use super::MAX_LABEL_LENGTH;
12
13#[derive(Eq, Clone)]
34pub struct Label<'a> {
35 pub(crate) data: Cow<'a, [u8]>,
36}
37
38impl<'a> Label<'a> {
39 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> crate::Result<Self> {
60 let data = data.into();
61 let label = if let Some((i, _)) = data.iter().enumerate().find(|(_, c)| **c == b'\\') {
62 unescape_label(data, i)?
63 } else {
64 Self { data }
65 };
66
67 if label.data.is_empty() || label.data.len() > MAX_LABEL_LENGTH {
68 return Err(crate::SimpleDnsError::InvalidServiceLabel);
69 }
70
71 Ok(label)
72 }
73
74 pub fn new_unchecked<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
78 Self { data: data.into() }
79 }
80
81 pub fn len(&self) -> usize {
83 self.data.len()
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.data.is_empty()
89 }
90
91 pub fn into_owned<'b>(self) -> Label<'b> {
93 Label {
94 data: self.data.into_owned().into(),
95 }
96 }
97
98 pub fn is_valid_hostname(&self) -> bool {
110 if self.data.is_empty() || self.data.len() > MAX_LABEL_LENGTH {
111 return false;
112 }
113
114 if let Some(first) = self.data.first() {
115 if !first.is_ascii_alphanumeric() {
116 return false;
117 }
118 }
119
120 if !self
121 .data
122 .iter()
123 .skip(1)
124 .all(|c| c.is_ascii_alphanumeric() || *c == b'-')
125 {
126 return false;
127 }
128
129 if let Some(last) = self.data.last() {
130 if !last.is_ascii_alphanumeric() {
131 return false;
132 }
133 }
134
135 true
136 }
137
138 pub fn is_valid_service_label(&self) -> bool {
156 if self.data.is_empty() || self.data.len() > MAX_SERVICE_NAME_LENGTH {
157 return false;
158 }
159
160 let Some((b'_', name)) = self.data.split_first() else {
161 return false;
162 };
163
164 name.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'-')
167 && name.iter().any(|c| c.is_ascii_alphabetic())
168 && !name.starts_with(b"-")
169 && !name.ends_with(b"-")
170 && !name.windows(2).any(|w| w == b"--")
171 }
172
173 pub fn is_valid_instance_label(&self) -> bool {
185 !(self.data.is_empty() || self.data.len() > MAX_LABEL_LENGTH)
186 }
187}
188
189impl PartialEq for Label<'_> {
190 fn eq(&self, other: &Self) -> bool {
191 self.data.eq_ignore_ascii_case(&other.data)
192 }
193}
194
195impl Hash for Label<'_> {
196 fn hash<H: Hasher>(&self, state: &mut H) {
197 state.write_usize(self.data.len());
198 for &b in self.data.iter() {
199 state.write_u8(b.to_ascii_lowercase());
200 }
201 }
202}
203
204impl PartialOrd for Label<'_> {
205 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
206 Some(self.cmp(other))
207 }
208}
209
210impl Ord for Label<'_> {
211 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
212 self.data
213 .iter()
214 .map(u8::to_ascii_lowercase)
215 .cmp(other.data.iter().map(u8::to_ascii_lowercase))
216 }
217}
218
219fn unescape_label<'a>(bytes: Cow<'a, [u8]>, mut position: usize) -> crate::Result<Label<'a>> {
220 let mut out = Vec::with_capacity(bytes.len());
221 out.extend_from_slice(&bytes[..position]);
222
223 while position < bytes.len() {
224 match bytes[position] {
225 b'\\' if position + 1 == bytes.len() => {
227 return Err(crate::SimpleDnsError::InvalidServiceLabel);
228 }
229
230 b'\\' => {
231 position += 1;
232 if bytes[position].is_ascii_digit() {
233 if (bytes.len() - position) < 3
237 || !bytes[position + 1].is_ascii_digit()
238 || !bytes[position + 2].is_ascii_digit()
239 {
240 return Err(crate::SimpleDnsError::InvalidServiceLabel);
241 }
242
243 let mut digit = (bytes[position] - b'0') as u16 * 100;
244 digit += (bytes[position + 1] - b'0') as u16 * 10;
245 digit += (bytes[position + 2] - b'0') as u16;
246
247 if digit > 255 {
248 return Err(crate::SimpleDnsError::InvalidServiceLabel);
249 }
250
251 out.push(digit as u8);
252 position += 3;
253 } else {
254 out.push(bytes[position]);
255 position += 1;
256 }
257 }
258 c => {
259 out.push(c);
260 position += 1;
261 }
262 }
263 }
264
265 Ok(Label { data: out.into() })
266}
267
268impl Display for Label<'_> {
269 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
270 use core::fmt::Write;
271
272 let s = String::from_utf8_lossy(&self.data);
273 for c in s.chars() {
274 match c {
275 '.' => {
276 f.write_char('\\')?;
277 f.write_char(c)?;
278 }
279 '\\' => {
280 f.write_str(r"\\")?;
281 }
282 _ => {
283 f.write_char(c)?;
284 }
285 }
286 }
287
288 Ok(())
289 }
290}
291
292impl Debug for Label<'_> {
293 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
294 f.debug_struct("Label")
295 .field("data", &self.to_string())
296 .finish()
297 }
298}
299
300impl AsRef<[u8]> for Label<'_> {
301 fn as_ref(&self) -> &[u8] {
302 self.data.as_ref()
303 }
304}
305
306#[derive(Clone, Copy)]
311pub(crate) struct ExactLabels<'a, 'b>(&'b [Label<'a>]);
312
313impl<'a, 'b> ExactLabels<'a, 'b> {
314 pub(crate) fn new(labels: &'b [Label<'a>]) -> Self {
315 Self(labels)
316 }
317}
318
319impl PartialEq for ExactLabels<'_, '_> {
320 fn eq(&self, other: &Self) -> bool {
321 self.cmp(other).is_eq()
322 }
323}
324
325impl Eq for ExactLabels<'_, '_> {}
326
327impl PartialOrd for ExactLabels<'_, '_> {
328 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
329 Some(self.cmp(other))
330 }
331}
332
333impl Ord for ExactLabels<'_, '_> {
334 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
335 self.0
336 .iter()
337 .map(|l| &l.data[..])
338 .cmp(other.0.iter().map(|l| &l.data[..]))
339 }
340}
341
342pub(crate) struct LabelsIter<'a> {
344 bytes: &'a [u8],
345 current: usize,
346}
347
348impl<'a> LabelsIter<'a> {
349 pub fn new(bytes: &'a [u8]) -> Self {
350 Self { bytes, current: 0 }
351 }
352}
353
354impl<'a> Iterator for LabelsIter<'a> {
355 type Item = Cow<'a, [u8]>;
356
357 fn next(&mut self) -> Option<Self::Item> {
358 let mut i = self.current;
359 while i < self.bytes.len() {
363 match self.bytes[i] {
364 b'\\' if i + 1 == self.bytes.len() => {
365 break;
367 }
368 b'\\' => {
369 i += 2;
372 }
373 b'.' => {
374 let current = crate::lib::mem::replace(&mut self.current, i + 1);
375 if i - current == 0 {
376 i = self.current;
379 continue;
380 }
381
382 return Some(self.bytes[current..i].into());
383 }
384 _ => {
385 i += 1;
387 }
388 }
389 }
390
391 if self.current < self.bytes.len() {
392 let current = crate::lib::mem::replace(&mut self.current, self.bytes.len());
393 Some(self.bytes[current..].into())
395 } else {
396 None
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn display_invalid_label() {
407 let input = b"invalid\xF0\x90\x80label";
408 let label = Label::new_unchecked(input);
409
410 assert_eq!(label.to_string(), "invalid�label");
411 }
412
413 #[test]
414 fn labels_are_escaped() {
415 let l = Label::new(b"escaped.label").expect("failed to create label");
416 assert_eq!(r"escaped\.label", l.to_string());
417 }
418
419 fn label(s: &str) -> Label<'_> {
420 Label::new_unchecked(s.as_bytes())
421 }
422
423 #[test]
424 fn hostname_label_valid() {
425 assert!(label("example").is_valid_hostname());
427 assert!(label("a").is_valid_hostname());
428 assert!(label("EXAMPLE").is_valid_hostname());
429 assert!(label("a-b").is_valid_hostname());
430 assert!(label("a--b").is_valid_hostname());
431 assert!(label("1abc").is_valid_hostname());
432 assert!(label("abc1").is_valid_hostname());
433 assert!(label("123").is_valid_hostname());
434 assert!(label(&"a".repeat(63)).is_valid_hostname());
435 }
436
437 #[test]
438 fn hostname_label_invalid() {
439 assert!(!label("").is_valid_hostname());
440 assert!(!label(&"a".repeat(64)).is_valid_hostname());
441 assert!(!label("-a").is_valid_hostname());
442 assert!(!label("a-").is_valid_hostname());
443 assert!(!label("-").is_valid_hostname());
444 assert!(!label("_a").is_valid_hostname());
445 assert!(!label("a_b").is_valid_hostname());
446 assert!(!label("a.b").is_valid_hostname());
447 assert!(!label("a b").is_valid_hostname());
448 assert!(!label(r"a\b").is_valid_hostname());
449 assert!(!label("caf\u{e9}").is_valid_hostname());
450 assert!(!label("\u{1F600}").is_valid_hostname());
451 }
452
453 #[test]
454 fn service_label_valid() {
455 assert!(label("_http").is_valid_service_label());
457 assert!(label("_a").is_valid_service_label());
458 assert!(label("_HTTP").is_valid_service_label());
459 assert!(label("_ipp-tls").is_valid_service_label());
460 assert!(label("_a-b-c").is_valid_service_label());
461 assert!(label("_9p").is_valid_service_label());
463 assert!(label("_1a").is_valid_service_label());
464 assert!(label("_a1").is_valid_service_label());
465 assert!(label("_abcdefghijklmno").is_valid_service_label());
467 }
468
469 #[test]
470 fn service_label_invalid() {
471 assert!(!label("").is_valid_service_label());
472 assert!(!label("_").is_valid_service_label());
473 assert!(!label("http").is_valid_service_label());
475 assert!(!label("_abcdefghijklmnop").is_valid_service_label());
477 assert!(!label("_123").is_valid_service_label());
479 assert!(!label("_1-2").is_valid_service_label());
480 assert!(!label("_-a").is_valid_service_label());
482 assert!(!label("_a-").is_valid_service_label());
483 assert!(!label("_a--b").is_valid_service_label());
485 assert!(!label("_a_b").is_valid_service_label());
487 assert!(!label("_a.b").is_valid_service_label());
488 assert!(!label("_a b").is_valid_service_label());
489 assert!(!label("_caf\u{e9}").is_valid_service_label());
490 assert!(!label("__http").is_valid_service_label());
492 }
493
494 #[test]
495 fn instance_label_valid() {
496 assert!(label("My Printer").is_valid_instance_label());
498 assert!(label("a").is_valid_instance_label());
499 assert!(label("Stuart's Test Server").is_valid_instance_label());
500 assert!(label("My Printer. Floor 2").is_valid_instance_label());
501 assert!(label(r"back\slash").is_valid_instance_label());
502 assert!(label("-leading and trailing-").is_valid_instance_label());
503 assert!(label("_underscore").is_valid_instance_label());
504 assert!(label("caf\u{e9}").is_valid_instance_label());
505 assert!(label("\u{1F5A8}").is_valid_instance_label());
506 assert!(label(&"a".repeat(63)).is_valid_instance_label());
507 }
508
509 #[test]
510 fn instance_label_invalid() {
511 assert!(!label("").is_valid_instance_label());
512 assert!(!label(&"a".repeat(64)).is_valid_instance_label());
513 }
514
515 #[test]
516 fn instance_label_limit_counts_utf8_octets() {
517 assert!(!label(&"\u{e9}".repeat(32)).is_valid_instance_label());
519 assert!(label(&("\u{e9}".repeat(31) + "a")).is_valid_instance_label());
521 }
522}