1use alloc::{string::String, vec::Vec};
13use core::borrow::Borrow;
14use core::cmp::{Ordering, PartialEq};
15use core::fmt::{self, Debug, Display, Formatter, Write};
16use core::hash::{Hash, Hasher};
17
18use idna::uts46::{AsciiDenyList, DnsLength, Hyphens, Uts46};
19use tinyvec::TinyVec;
20use tracing::debug;
21
22use crate::error::*;
23use crate::serialize::binary::DecodeError;
24
25const WILDCARD: &[u8] = b"*";
26const IDNA_PREFIX: &[u8] = b"xn--";
27
28#[derive(Clone, Eq)]
30pub struct Label(TinyVec<[u8; 24]>);
31
32impl Label {
33 pub fn from_raw_bytes(bytes: &[u8]) -> ProtoResult<Self> {
38 if bytes.is_empty() {
42 return Err("Label requires a minimum length of 1".into());
43 }
44 if bytes.len() > 63 {
45 return Err(DecodeError::LabelBytesTooLong(bytes.len()).into());
46 };
47 Ok(Self(TinyVec::from(bytes)))
48 }
49
50 pub fn from_utf8(s: &str) -> ProtoResult<Self> {
52 if s.as_bytes() == WILDCARD {
53 return Ok(Self::wildcard());
54 }
55
56 if s.starts_with('_') {
58 return Self::from_ascii(s);
59 }
60
61 match Uts46::new().to_ascii(
65 s.as_bytes(),
66 AsciiDenyList::STD3,
67 Hyphens::Allow,
68 DnsLength::Ignore,
69 ) {
70 Ok(puny) => Self::from_ascii(&puny),
71 e => Err(format!("Label contains invalid characters: {e:?}").into()),
72 }
73 }
74
75 pub fn from_ascii(s: &str) -> ProtoResult<Self> {
79 if s.len() > 63 {
80 return Err(DecodeError::LabelBytesTooLong(s.len()).into());
81 }
82
83 if s.as_bytes() == WILDCARD {
84 return Ok(Self::wildcard());
85 }
86
87 if !s.is_empty()
88 && s.is_ascii()
89 && s.chars().take(1).all(|c| is_safe_ascii(c, true, false))
90 && s.chars().skip(1).all(|c| is_safe_ascii(c, false, false))
91 {
92 Self::from_raw_bytes(s.as_bytes())
93 } else {
94 Err(format!("Malformed label: {s}").into())
95 }
96 }
97
98 pub fn wildcard() -> Self {
100 Self(TinyVec::from(WILDCARD))
101 }
102
103 pub fn to_lowercase(&self) -> Self {
105 if let Some((idx, _)) = self
107 .0
108 .iter()
109 .enumerate()
110 .find(|&(_, c)| *c != c.to_ascii_lowercase())
111 {
112 let mut lower_label: Vec<u8> = self.0.to_vec();
113 lower_label[idx..].make_ascii_lowercase();
114 Self(TinyVec::from(lower_label.as_slice()))
115 } else {
116 self.clone()
117 }
118 }
119
120 pub fn is_wildcard(&self) -> bool {
122 self.as_bytes() == WILDCARD
123 }
124
125 pub fn len(&self) -> usize {
127 self.0.len()
128 }
129
130 pub fn is_empty(&self) -> bool {
132 self.0.is_empty()
133 }
134
135 pub fn as_bytes(&self) -> &[u8] {
139 &self.0
140 }
141
142 pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
144 self.0.eq_ignore_ascii_case(&other.0)
145 }
146
147 pub fn cmp_with_f<F: LabelCmp>(&self, other: &Self) -> Ordering {
149 let s = self.0.iter();
150 let o = other.0.iter();
151
152 for (s, o) in s.zip(o) {
153 match F::cmp_u8(*s, *o) {
154 Ordering::Equal => continue,
155 not_eq => return not_eq,
156 }
157 }
158
159 self.0.len().cmp(&other.0.len())
160 }
161
162 pub fn to_utf8(&self) -> String {
164 format!("{self}")
165 }
166
167 pub fn to_ascii(&self) -> String {
171 let mut ascii = String::with_capacity(self.as_bytes().len());
172
173 self.write_ascii(&mut ascii)
174 .expect("should never fail to write a new string");
175 ascii
176 }
177
178 pub fn write_ascii<W: Write>(&self, f: &mut W) -> Result<(), fmt::Error> {
180 fn escape_non_ascii<W: Write>(
182 byte: u8,
183 f: &mut W,
184 is_first: bool,
185 ) -> Result<(), fmt::Error> {
186 let to_triple_escape = |ch: u8| format!("\\{ch:03o}");
187 let to_single_escape = |ch: char| format!("\\{ch}");
188
189 match char::from(byte) {
190 c if is_safe_ascii(c, is_first, true) => f.write_char(c)?,
191 c if byte > b'\x20' && byte < b'\x7f' => f.write_str(&to_single_escape(c))?,
193 _ => f.write_str(&to_triple_escape(byte))?,
194 }
195
196 Ok(())
197 }
198
199 let mut chars = self.as_bytes().iter();
201 if let Some(ch) = chars.next() {
202 escape_non_ascii(*ch, f, true)?;
203 }
204
205 for ch in chars {
206 escape_non_ascii(*ch, f, false)?;
207 }
208
209 Ok(())
210 }
211}
212
213impl AsRef<[u8]> for Label {
214 fn as_ref(&self) -> &[u8] {
215 self.as_bytes()
216 }
217}
218
219impl Borrow<[u8]> for Label {
220 fn borrow(&self) -> &[u8] {
221 &self.0
222 }
223}
224
225fn is_safe_ascii(c: char, is_first: bool, for_encoding: bool) -> bool {
226 match c {
227 c if !c.is_ascii() => false,
228 c if c.is_alphanumeric() => true,
229 '-' if !is_first => true, '_' => true, '*' if is_first => true, '.' if !for_encoding => true, _ => false,
234 }
235}
236
237impl Display for Label {
238 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
243 if self.as_bytes().starts_with(IDNA_PREFIX) {
244 let label = String::from_utf8_lossy(self.borrow());
246 let (label, e) =
247 Uts46::new().to_unicode(label.as_bytes(), AsciiDenyList::EMPTY, Hyphens::Allow);
248
249 if e.is_ok() {
250 return f.write_str(&label);
251 } else {
252 debug!(
253 "xn-- prefixed string did not translate via IDNA properly: {:?}",
254 e
255 )
256 }
257 }
258
259 self.write_ascii(f)
261 }
262}
263
264impl Debug for Label {
265 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
266 let label = String::from_utf8_lossy(self.borrow());
267 f.write_str(&label)
268 }
269}
270
271impl PartialEq<Self> for Label {
272 fn eq(&self, other: &Self) -> bool {
273 self.eq_ignore_ascii_case(other)
274 }
275}
276
277impl PartialOrd<Self> for Label {
278 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
279 Some(self.cmp(other))
280 }
281}
282
283impl Ord for Label {
284 fn cmp(&self, other: &Self) -> Ordering {
285 self.cmp_with_f::<CaseInsensitive>(other)
286 }
287}
288
289impl Hash for Label {
290 fn hash<H>(&self, state: &mut H)
291 where
292 H: Hasher,
293 {
294 let vec = vec![(); self.len()];
300 vec.hash(state);
301 for b in self.borrow() as &[u8] {
303 state.write_u8(b.to_ascii_lowercase());
304 }
305 }
306}
307
308pub trait LabelCmp {
310 fn cmp_u8(l: u8, r: u8) -> Ordering;
312}
313
314pub(super) struct CaseSensitive;
316
317impl LabelCmp for CaseSensitive {
318 fn cmp_u8(l: u8, r: u8) -> Ordering {
319 l.cmp(&r)
320 }
321}
322
323pub(super) struct CaseInsensitive;
325
326impl LabelCmp for CaseInsensitive {
327 fn cmp_u8(l: u8, r: u8) -> Ordering {
328 l.to_ascii_lowercase().cmp(&r.to_ascii_lowercase())
329 }
330}
331
332pub trait IntoLabel: Sized {
334 fn into_label(self) -> ProtoResult<Label>;
336}
337
338impl IntoLabel for &Label {
339 fn into_label(self) -> ProtoResult<Label> {
340 Ok(self.clone())
341 }
342}
343
344impl IntoLabel for Label {
345 fn into_label(self) -> ProtoResult<Label> {
346 Ok(self)
347 }
348}
349
350impl IntoLabel for &str {
351 fn into_label(self) -> ProtoResult<Label> {
352 Label::from_utf8(self)
353 }
354}
355
356impl IntoLabel for String {
357 fn into_label(self) -> ProtoResult<Label> {
358 Label::from_utf8(&self)
359 }
360}
361
362impl IntoLabel for &[u8] {
363 fn into_label(self) -> ProtoResult<Label> {
364 Label::from_raw_bytes(self)
365 }
366}
367
368impl IntoLabel for Vec<u8> {
369 fn into_label(self) -> ProtoResult<Label> {
370 Label::from_raw_bytes(&self)
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 #![allow(clippy::dbg_macro, clippy::print_stdout)]
377
378 use alloc::string::ToString;
379 #[cfg(feature = "std")]
380 use std::{eprintln, println};
381
382 use super::*;
383
384 #[test]
385 fn test_encoding() {
386 assert_eq!(
387 Label::from_utf8("abc").unwrap(),
388 Label::from_raw_bytes(b"abc").unwrap()
389 );
390 assert_eq!(
392 Label::from_utf8("ABC").unwrap(),
393 Label::from_raw_bytes(b"ABC").unwrap()
394 );
395 assert_eq!(
396 Label::from_utf8("🦀").unwrap(),
397 Label::from_raw_bytes(b"xn--zs9h").unwrap()
398 );
399 assert_eq!(
400 Label::from_utf8("rust-🦀-icon").unwrap(),
401 Label::from_raw_bytes(b"xn--rust--icon-9447i").unwrap()
402 );
403 assert_eq!(
404 Label::from_ascii("ben.fry").unwrap(),
405 Label::from_raw_bytes(b"ben.fry").unwrap()
406 );
407 assert_eq!(Label::from_utf8("🦀").unwrap().to_utf8(), "🦀");
408 assert_eq!(Label::from_utf8("🦀").unwrap().to_ascii(), "xn--zs9h");
409 }
410
411 fn assert_panic_label_too_long(error: ProtoResult<Label>, len: usize) {
412 #[cfg(feature = "std")]
414 eprintln!("{error:?}");
415 assert!(error.is_err());
416 match error.unwrap_err() {
417 ProtoError::Decode(DecodeError::LabelBytesTooLong(n)) if n == len => (),
418 ProtoError::Decode(DecodeError::LabelBytesTooLong(e)) => {
419 panic!(
420 "LabelTooLongError error don't report expected size {} of the label provided.",
421 e
422 )
423 }
424 _ => panic!("Should have returned a LabelTooLongError"),
425 }
426 }
427
428 #[test]
429 fn test_label_too_long_ascii_with_utf8() {
430 let label_too_long = "alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
431 let error = Label::from_utf8(label_too_long);
432 assert_panic_label_too_long(error, label_too_long.len());
433 }
434
435 #[test]
436 fn test_label_too_long_utf8_puny_emoji() {
437 let emoji_case = "💜🦀🏖️🖥️😨🚀✨🤖💚🦾🦿😱😨✉️👺📚💻🗓️🤡🦀😈🚀💀⚡🦄";
439 let error = Label::from_utf8(emoji_case);
440 assert_panic_label_too_long(error, 64);
441 }
442
443 #[test]
444 fn test_label_too_long_utf8_puny_emoji_mixed() {
445 let emoji_case = "こんにちは-I-mögen-jesień-café-🦀-intéressant";
449 let error = Label::from_utf8(emoji_case);
450 assert_panic_label_too_long(error, 65);
451 }
452
453 #[test]
454 fn test_label_too_long_utf8_puny_mixed() {
455 let edge_case = "🦀testwithalonglabelinutf8tofitin63octetsisagoodhabit🦀";
458 let error = Label::from_utf8(edge_case);
459 assert_panic_label_too_long(error, 64);
460 }
461
462 #[test]
463 fn test_label_too_long_raw() {
464 let label_too_long = b"alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
465 let error = Label::from_raw_bytes(label_too_long);
466 assert_panic_label_too_long(error, label_too_long.len());
467 }
468
469 #[test]
470 fn test_label_too_long_ascii() {
471 let label_too_long = "alwaystestingcodewithatoolonglabeltoolongtofitin63bytesisagoodhabit";
472 let error = Label::from_ascii(label_too_long);
473 assert_panic_label_too_long(error, label_too_long.len());
474 }
475
476 #[test]
477 fn test_decoding() {
478 assert_eq!(Label::from_raw_bytes(b"abc").unwrap().to_string(), "abc");
479 assert_eq!(
480 Label::from_raw_bytes(b"xn--zs9h").unwrap().to_string(),
481 "🦀"
482 );
483 assert_eq!(
484 Label::from_raw_bytes(b"xn--rust--icon-9447i")
485 .unwrap()
486 .to_string(),
487 "rust-🦀-icon"
488 );
489 }
490
491 #[test]
492 fn test_from_ascii_adversial_utf8() {
493 let expect_err = Label::from_ascii("🦀");
494 assert!(expect_err.is_err());
495 }
496
497 #[test]
498 fn test_to_lowercase() {
499 assert_ne!(Label::from_ascii("ABC").unwrap().to_string(), "abc");
500 assert_ne!(Label::from_ascii("abcDEF").unwrap().to_string(), "abcdef");
501 assert_eq!(
502 Label::from_ascii("ABC").unwrap().to_lowercase().to_string(),
503 "abc"
504 );
505 assert_eq!(
506 Label::from_ascii("abcDEF")
507 .unwrap()
508 .to_lowercase()
509 .to_string(),
510 "abcdef"
511 );
512 }
513
514 #[test]
515 fn test_to_cmp_f() {
516 assert_eq!(
517 Label::from_ascii("ABC")
518 .unwrap()
519 .cmp_with_f::<CaseInsensitive>(&Label::from_ascii("abc").unwrap()),
520 Ordering::Equal
521 );
522 assert_eq!(
523 Label::from_ascii("abcDEF")
524 .unwrap()
525 .cmp_with_f::<CaseInsensitive>(&Label::from_ascii("abcdef").unwrap()),
526 Ordering::Equal
527 );
528 assert_eq!(
529 Label::from_ascii("ABC")
530 .unwrap()
531 .cmp_with_f::<CaseSensitive>(&Label::from_ascii("abc").unwrap()),
532 Ordering::Less
533 );
534 assert_eq!(
535 Label::from_ascii("abcDEF")
536 .unwrap()
537 .cmp_with_f::<CaseSensitive>(&Label::from_ascii("abcdef").unwrap()),
538 Ordering::Less
539 );
540 }
541
542 #[test]
543 fn test_partial_cmp() {
544 let comparisons: Vec<(Label, Label)> = vec![
545 (
546 Label::from_raw_bytes(b"yljkjljk").unwrap(),
547 Label::from_raw_bytes(b"Z").unwrap(),
548 ),
549 (
550 Label::from_raw_bytes(b"Z").unwrap(),
551 Label::from_raw_bytes(b"zABC").unwrap(),
552 ),
553 (
554 Label::from_raw_bytes(&[1]).unwrap(),
555 Label::from_raw_bytes(b"*").unwrap(),
556 ),
557 (
558 Label::from_raw_bytes(b"*").unwrap(),
559 Label::from_raw_bytes(&[200]).unwrap(),
560 ),
561 ];
562
563 for (left, right) in comparisons {
564 #[cfg(feature = "std")]
565 println!("left: {left}, right: {right}");
566 assert_eq!(left.cmp(&right), Ordering::Less);
567 }
568 }
569
570 #[test]
571 fn test_is_wildcard() {
572 assert!(Label::from_raw_bytes(b"*").unwrap().is_wildcard());
573 assert!(Label::from_ascii("*").unwrap().is_wildcard());
574 assert!(Label::from_utf8("*").unwrap().is_wildcard());
575 assert!(!Label::from_raw_bytes(b"abc").unwrap().is_wildcard());
576 }
577
578 #[test]
579 fn test_ascii_escape() {
580 assert_eq!(
581 Label::from_raw_bytes(&[0o200]).unwrap().to_string(),
582 "\\200"
583 );
584 assert_eq!(
585 Label::from_raw_bytes(&[0o001]).unwrap().to_string(),
586 "\\001"
587 );
588 assert_eq!(Label::from_ascii(".").unwrap().to_ascii(), "\\.");
589 assert_eq!(
590 Label::from_ascii("ben.fry").unwrap().to_string(),
591 "ben\\.fry"
592 );
593 assert_eq!(Label::from_raw_bytes(&[0o200]).unwrap().to_ascii(), "\\200");
594 }
595}