Skip to main content

simple_dns/dns/
name.rs

1use crate::{
2    bytes_buffer::BytesBuffer,
3    lib::{
4        fmt::{Debug, Display, Formatter, Result as FmtResult},
5        format, Hash, Hasher, Iter, Seek, TryFrom, Vec, Write,
6    },
7};
8
9use super::{
10    label::{ExactLabels, Label, LabelsIter},
11    WireFormat, MAX_LABEL_LENGTH, MAX_NAME_LENGTH,
12};
13
14const POINTER_MASK: u8 = 0b1100_0000;
15const POINTER_MASK_U16: u16 = 0b1100_0000_0000_0000;
16const MAX_COMPRESSION_OFFSET: u64 = !POINTER_MASK_U16 as u64;
17
18// NOTE: there are no extend labels implemented today
19// const EXTENDED_LABEL: u8 = 0b0100_0000;
20// const EXTENDED_LABEL_U16: u16 = 0b0100_0000_0000_0000;
21
22/// A Name represents a domain-name, which consists of character strings separated by dots.  
23/// Each section of a name is called label  
24/// ex: `google.com` consists of two labels `google` and `com`
25///
26/// A name is at most 255 octets long in the wire format, and each label at most 63 octets.
27/// [`Name::new`] checks these limits, but not the content of the labels. Use the `is_valid_*`
28/// methods to check a name against the rules of a given role:
29/// - [`Name::is_valid_hostname`]
30/// - [`Name::is_valid_service_name`]
31/// - [`Name::is_valid_instance_name`]
32/// - [`Name::is_valid_sub_type_name`]
33///
34/// # Text format
35///
36/// [`Name::new`] reads the presentation format of RFC 1035 section 5.1 and RFC 4343
37/// section 2.1:
38/// - an unescaped `.` separates labels
39/// - `\.` is a dot inside a label and `\\` is a backslash
40/// - `\DDD`, three decimal digits, is the octet with that value, and `\X` for any other
41///   non-digit X is the character X
42/// - a trailing dot marks the name as absolute and does not add a label. A name made only of
43///   dots is the root and has no labels
44/// - empty labels, as in `a..b`, are skipped
45///
46/// [`Name::new_unchecked`] splits the text at unescaped dots in the same way, but it does not
47/// interpret the escapes: the bytes of each label are kept as written, so `a\.b` becomes the
48/// four byte label `a\.b`. To build a name from raw label content, use
49/// [`Name::new_with_labels`].
50///
51/// [`Display`](core::fmt::Display) writes the same format, escaping `.` and `\` inside labels
52/// (RFC 6763 section 4.3), so a name parsed back with [`Name::new`] is equal to the original.
53///
54/// The escapes exist only in text. In the wire format, labels are length-prefixed and hold
55/// any octet.
56///
57/// ```
58/// # use simple_dns::Name;
59/// let name = Name::new(r"My\.Device._googlecast._tcp.local").unwrap();
60/// assert_eq!(4, name.get_labels().len());
61/// assert_eq!(b"My.Device", name.get_labels()[0].as_ref());
62/// assert_eq!(r"My\.Device._googlecast._tcp.local", name.to_string());
63/// ```
64///
65/// Microsoft implementation allows unicode characters in the name content.
66/// To create a name with unicode characters, use [`Name::new_unchecked`] or
67/// [`Name::new_with_labels`]
68#[derive(Eq, Clone)]
69pub struct Name<'a> {
70    labels: Vec<Label<'a>>,
71}
72
73impl<'a> Name<'a> {
74    /// Creates a new Name from text in the format described in the [type docs](Name#text-format).
75    ///
76    /// Returns an error if an escape sequence is malformed, if a label is longer than 63 octets
77    /// after unescaping, or if the encoded name is longer than 255 octets.
78    /// The content of the labels is not checked against a naming rule, use the `is_valid_*`
79    /// methods for that.
80    pub fn new(name: &'a str) -> crate::Result<Self> {
81        let labels = LabelsIter::new(name.as_bytes())
82            .map(Label::new)
83            .collect::<Result<Vec<Label>, _>>()?;
84
85        let name = Self { labels };
86
87        if name.len() > MAX_NAME_LENGTH {
88            Err(crate::SimpleDnsError::InvalidServiceName)
89        } else {
90            Ok(name)
91        }
92    }
93
94    /// Create a new Name without checking for size limits or contents.
95    ///
96    /// The text is split at unescaped dots, but escape sequences are not interpreted: the bytes
97    /// of each label are kept as written (see the [type docs](Name#text-format)). Use
98    /// [`Name::new`] to unescape, or [`Name::new_with_labels`] to set the label content directly.
99    pub fn new_unchecked(name: &'a str) -> Self {
100        let labels = LabelsIter::new(name.as_bytes())
101            .map(Label::new_unchecked)
102            .collect();
103
104        Self { labels }
105    }
106
107    /// Creates a new Name with given labels
108    ///
109    /// Allows construction of labels with `.` in them.
110    pub fn new_with_labels(labels: &[Label<'a>]) -> Self {
111        Self {
112            labels: labels.to_vec(),
113        }
114    }
115
116    /// Verify if name ends with .local.
117    pub fn is_link_local(&self) -> bool {
118        match self.iter().last() {
119            Some(label) => b"local".eq_ignore_ascii_case(&label.data),
120            None => false,
121        }
122    }
123
124    /// Returns an Iter of this Name Labels
125    pub fn iter(&'a self) -> Iter<'a, Label<'a>> {
126        self.labels.iter()
127    }
128
129    /// Returns true if self is a subdomain of other
130    pub fn is_subdomain_of(&self, other: &Name) -> bool {
131        self.labels.len() > other.labels.len()
132            && other
133                .iter()
134                .rev()
135                .zip(self.iter().rev())
136                .all(|(o, s)| *o == *s)
137    }
138
139    /// Transforms the inner data into its owned type
140    pub fn into_owned<'b>(self) -> Name<'b> {
141        Name {
142            labels: self.labels.into_iter().map(|l| l.into_owned()).collect(),
143        }
144    }
145
146    /// Returns the subdomain part of self, based on `domain`.
147    /// If self is not a subdomain of `domain`, returns None
148    ///
149    /// Example:
150    /// ```
151    /// # use simple_dns::Name;
152    /// let name = Name::new_unchecked("sub.domain.local");
153    /// let domain = Name::new_unchecked("domain.local");
154    ///
155    /// assert!(domain.without(&name).is_none());
156    ///
157    /// let sub = name.without(&domain).unwrap();
158    /// assert_eq!(sub.to_string(), "sub")
159    /// ```
160    pub fn without(&'_ self, domain: &Name) -> Option<Name<'_>> {
161        if self.is_subdomain_of(domain) {
162            let labels = self.labels[..self.labels.len() - domain.labels.len()].to_vec();
163
164            Some(Name { labels })
165        } else {
166            None
167        }
168    }
169
170    /// Get the labels that compose this name
171    pub fn get_labels(&'_ self) -> &'_ [Label<'a>] {
172        &self.labels[..]
173    }
174
175    fn plain_append<T: Write>(&self, out: &mut T) -> crate::Result<()> {
176        for label in self.iter() {
177            out.write_all(&[label.len() as u8])?;
178            out.write_all(&label.data)?;
179        }
180
181        out.write_all(&[0])?;
182        Ok(())
183    }
184
185    fn compress_append<T: Write + Seek>(
186        &'a self,
187        out: &mut T,
188        name_refs: &mut crate::lib::BTreeMap<crate::dns::label::ExactLabels<'a, '_>, u16>,
189    ) -> crate::Result<()> {
190        for (i, label) in self.iter().enumerate() {
191            match name_refs.entry(ExactLabels::new(&self.labels[i..])) {
192                crate::lib::BTreeEntry::Occupied(e) => {
193                    let p = *e.get();
194                    out.write_all(&(p | POINTER_MASK_U16).to_be_bytes())?;
195
196                    return Ok(());
197                }
198                crate::lib::BTreeEntry::Vacant(e) => {
199                    let pos = out.stream_position()?;
200                    if pos <= MAX_COMPRESSION_OFFSET {
201                        e.insert(pos as u16);
202                    }
203                    out.write_all(&[label.len() as u8])?;
204                    out.write_all(&label.data)?;
205                }
206            }
207        }
208
209        out.write_all(&[0])?;
210        Ok(())
211    }
212
213    /// Returns `true` if the name has at least one label and every label is a valid host name
214    /// label, see [`Label::is_valid_hostname`].
215    ///
216    /// ```
217    /// # use simple_dns::Name;
218    /// assert!(Name::new_unchecked("my-host.example.com").is_valid_hostname());
219    /// assert!(!Name::new_unchecked("_srv.example.com").is_valid_hostname());
220    /// assert!(!Name::new_unchecked(".").is_valid_hostname());
221    /// ```
222    pub fn is_valid_hostname(&self) -> bool {
223        !self.labels.is_empty() && self.labels.iter().all(|label| label.is_valid_hostname())
224    }
225
226    /// Returns `true` if the name is a DNS-SD subtype name:
227    /// `<Subtype>._sub.<sn>._tcp|_udp.<domain>` (RFC 6763 section 7.1).
228    ///
229    /// The subtype can be any label (see [`Label::is_valid_instance_label`]) and does not need to
230    /// start with an underscore. The rest of the name must be a valid service name, see
231    /// [`Name::is_valid_service_name`].
232    ///
233    /// ```
234    /// # use simple_dns::Name;
235    /// assert!(Name::new_unchecked("_printer._sub._http._tcp.local").is_valid_sub_type_name());
236    /// assert!(Name::new_unchecked("The Music Store._sub._http._tcp.local").is_valid_sub_type_name());
237    /// assert!(!Name::new_unchecked("_printer._http._tcp.local").is_valid_sub_type_name());
238    /// ```
239    pub fn is_valid_sub_type_name(&self) -> bool {
240        // <sub>._sub.<sn>._tcp.<servicedomain>.<parentdomain>.
241        if self.labels.is_empty() {
242            return false;
243        }
244
245        let mut labels = self.labels.iter();
246        let (Some(subtype), Some(marker)) = (labels.next(), labels.next()) else {
247            return false;
248        };
249
250        let sub = Label::new_unchecked(&b"_sub"[..]);
251        subtype.is_valid_instance_label() && *marker == sub && Self::validate_service_name(labels)
252    }
253
254    /// Returns `true` if the name is a DNS-SD service instance name:
255    /// `<Instance>.<sn>._tcp|_udp.<domain>` (RFC 6763 section 4.3).
256    ///
257    /// The instance can be any label (see [`Label::is_valid_instance_label`]), including one that
258    /// contains dots. The rest of the name must be a valid service name, see
259    /// [`Name::is_valid_service_name`].
260    ///
261    /// ```
262    /// # use simple_dns::Name;
263    /// assert!(Name::new_unchecked("My Printer._ipp._tcp.local").is_valid_instance_name());
264    /// // the escaped dot keeps `My. Printer` as a single label
265    /// assert!(Name::new(r"My\. Printer._ipp._tcp.local").unwrap().is_valid_instance_name());
266    /// assert!(!Name::new_unchecked("My Printer._ipp._ftp.local").is_valid_instance_name());
267    /// ```
268    pub fn is_valid_instance_name(&self) -> bool {
269        // <Instance>.<sn>._tcp.<servicedomain>.<parentdomain>.
270
271        if self.labels.is_empty() {
272            return false;
273        }
274
275        let mut labels = self.labels.iter();
276        let Some(instance_name) = labels.next() else {
277            return false;
278        };
279
280        instance_name.is_valid_instance_label() && Self::validate_service_name(labels)
281    }
282
283    /// Returns `true` if the name is a DNS-SD service name: `<sn>._tcp|_udp.<domain>`
284    /// (RFC 6763 sections 4.1.2 and 7).
285    ///
286    /// - the first label must pass [`Label::is_valid_service_label`]
287    /// - the second label must be `_tcp` or `_udp`, ignoring case
288    /// - the remaining domain labels must pass [`Label::is_valid_hostname`]
289    ///
290    /// This is a syntax check. It does not check that the service is registered.
291    ///
292    /// ```
293    /// # use simple_dns::Name;
294    /// assert!(Name::new_unchecked("_http._tcp.local").is_valid_service_name());
295    /// assert!(!Name::new_unchecked("http._tcp.local").is_valid_service_name());
296    /// assert!(!Name::new_unchecked("_http._ftp.local").is_valid_service_name());
297    /// ```
298    pub fn is_valid_service_name(&self) -> bool {
299        if self.labels.is_empty() {
300            return false;
301        }
302
303        // <sn>._tcp . <servicedomain> . <parentdomain>.
304        Self::validate_service_name(self.labels.iter())
305    }
306
307    fn validate_service_name(mut labels: Iter<'_, Label<'_>>) -> bool {
308        let (Some(sn), Some(protocol)) = (labels.next(), labels.next()) else {
309            return false;
310        };
311
312        let tcp = Label::new_unchecked(&b"_tcp"[..]);
313        let udp = Label::new_unchecked(&b"_udp"[..]);
314
315        sn.is_valid_service_label()
316            && (*protocol == tcp || *protocol == udp)
317            && labels.all(|l| l.is_valid_hostname())
318    }
319
320    /// Returns the bytes of each of the labels that compose this Name
321    pub fn as_bytes(&self) -> impl Iterator<Item = &[u8]> {
322        self.labels.iter().map(|label| label.as_ref())
323    }
324}
325
326impl<'a> WireFormat<'a> for Name<'a> {
327    const MINIMUM_LEN: usize = 1;
328
329    fn parse(data: &mut BytesBuffer<'a>) -> crate::Result<Self>
330    where
331        Self: Sized,
332    {
333        // Parse labels will extract labels until it finds a 0 len label
334        // or a pointer  to another label
335        fn parse_labels<'a>(
336            data: &mut BytesBuffer<'a>,
337            name_len: &mut usize,
338            labels: &mut Vec<Label<'a>>,
339        ) -> crate::Result<Option<usize>> {
340            loop {
341                match data.get_u8()? {
342                    0 => break Ok(None),
343                    len if len & POINTER_MASK == POINTER_MASK => {
344                        let mut pointer = len as u16;
345                        pointer <<= 8;
346                        pointer += data.get_u8()? as u16;
347                        pointer &= !POINTER_MASK_U16;
348
349                        break Ok(Some(pointer as usize));
350                    }
351                    len => {
352                        *name_len += 1 + len as usize;
353
354                        // Checking the full name len to avoid circular pointers
355                        if *name_len >= MAX_NAME_LENGTH {
356                            return Err(crate::SimpleDnsError::InvalidDnsPacket);
357                        }
358
359                        if len as usize > MAX_LABEL_LENGTH {
360                            return Err(crate::SimpleDnsError::InvalidServiceLabel);
361                        }
362
363                        // Parsing allow invalid characters in the label.
364                        // However, the length of the label must be validated (above)
365                        labels.push(Label::new_unchecked(data.get_slice(len as usize)?));
366                    }
367                }
368            }
369        }
370
371        let mut labels = Vec::new();
372        let mut name_len = 0usize;
373
374        let mut pointer = parse_labels(data, &mut name_len, &mut labels)?;
375
376        let mut data = data.clone();
377        while let Some(p) = pointer {
378            // By creating a new buffer, it is possible to simplify the parse routine since
379            // the original buffer position will remain intact when iterating throught the
380            // pointers
381            data = data.new_at(p)?;
382            pointer = parse_labels(&mut data, &mut name_len, &mut labels)?;
383        }
384
385        Ok(Self { labels })
386    }
387
388    fn write_to<T: Write>(&self, out: &mut T) -> crate::Result<()> {
389        self.plain_append(out)
390    }
391
392    fn write_compressed_to<T: Write + Seek>(
393        &'a self,
394        out: &mut T,
395        name_refs: &mut crate::lib::BTreeMap<crate::dns::label::ExactLabels<'a, '_>, u16>,
396    ) -> crate::Result<()> {
397        self.compress_append(out, name_refs)
398    }
399
400    fn len(&self) -> usize {
401        self.labels
402            .iter()
403            .map(|label| label.len() + 1)
404            .sum::<usize>()
405            + Self::MINIMUM_LEN
406    }
407}
408
409impl<'a> TryFrom<&'a str> for Name<'a> {
410    type Error = crate::SimpleDnsError;
411
412    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
413        Name::new(value)
414    }
415}
416
417impl<'a> From<&'a [Label<'a>]> for Name<'a> {
418    fn from(labels: &'a [Label<'a>]) -> Self {
419        Name::new_with_labels(labels)
420    }
421}
422
423impl<'a, const N: usize> From<[Label<'a>; N]> for Name<'a> {
424    fn from(labels: [Label<'a>; N]) -> Self {
425        Name::new_with_labels(&labels)
426    }
427}
428
429impl Display for Name<'_> {
430    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
431        let mut labels = self.labels.iter();
432
433        if let Some(label) = labels.next() {
434            f.write_fmt(format_args!("{label}"))?;
435        }
436
437        for label in labels {
438            f.write_fmt(format_args!(".{label}"))?;
439        }
440
441        Ok(())
442    }
443}
444
445impl Debug for Name<'_> {
446    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
447        f.debug_tuple("Name")
448            .field(&format!("{self}"))
449            .field(&format!("{}", self.len()))
450            .finish()
451    }
452}
453
454impl PartialEq for Name<'_> {
455    fn eq(&self, other: &Self) -> bool {
456        self.labels == other.labels
457    }
458}
459
460impl Hash for Name<'_> {
461    fn hash<H: Hasher>(&self, state: &mut H) {
462        self.labels.hash(state);
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469    use crate::lib::Cursor;
470    use crate::{lib::Vec, SimpleDnsError};
471
472    #[test]
473    fn construct_valid_names() {
474        assert!(Name::new("some").is_ok());
475        assert!(Name::new("some.local").is_ok());
476        assert!(Name::new("some.local.").is_ok());
477        assert!(Name::new("some-dash.local.").is_ok());
478        assert!(Name::new("_sync_miss._tcp.local").is_ok());
479        assert!(Name::new("1sync_miss._tcp.local").is_ok());
480
481        assert_eq!(Name::new_unchecked("\u{1F600}.local.").labels.len(), 2);
482    }
483
484    #[test]
485    fn is_link_local() {
486        assert!(!Name::new("some.example.com").unwrap().is_link_local());
487        assert!(Name::new("some.example.local.").unwrap().is_link_local());
488    }
489
490    #[test]
491    fn parse_without_compression() {
492        let mut data = BytesBuffer::new(
493            b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\x01F\x03ISI\x04ARPA\x00\x04ARPA\x00",
494        );
495        data.advance(3).unwrap();
496        let name = Name::parse(&mut data).unwrap();
497        assert_eq!("F.ISI.ARPA", name.to_string());
498
499        let name = Name::parse(&mut data).unwrap();
500        assert_eq!("FOO.F.ISI.ARPA", name.to_string());
501    }
502
503    #[test]
504    fn parse_with_compression() {
505        let mut data = BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03\x07INVALID\xc0\x1b" );
506        data.advance(3).unwrap();
507
508        let name = Name::parse(&mut data).unwrap();
509        assert_eq!("F.ISI.ARPA", name.to_string());
510
511        let name = Name::parse(&mut data).unwrap();
512        assert_eq!("FOO.F.ISI.ARPA", name.to_string());
513
514        let name = Name::parse(&mut data).unwrap();
515        assert_eq!("BAR.F.ISI.ARPA", name.to_string());
516
517        assert!(Name::parse(&mut data).is_err());
518    }
519
520    #[test]
521    fn parse_handle_circular_pointers() {
522        let mut data = BytesBuffer::new(&[249, 0, 37, 1, 1, 139, 192, 6, 1, 1, 1, 139, 192, 6]);
523        data.advance(12).unwrap();
524
525        assert_eq!(
526            Name::parse(&mut data),
527            Err(SimpleDnsError::InvalidDnsPacket)
528        );
529    }
530
531    #[test]
532    fn test_write() {
533        let mut bytes = Vec::with_capacity(30);
534        Name::new_unchecked("_srv._udp.local")
535            .write_to(&mut bytes)
536            .unwrap();
537
538        assert_eq!(b"\x04_srv\x04_udp\x05local\x00", &bytes[..]);
539
540        let mut bytes = Vec::with_capacity(30);
541        Name::new_unchecked("_srv._udp.local2.")
542            .write_to(&mut bytes)
543            .unwrap();
544
545        assert_eq!(b"\x04_srv\x04_udp\x06local2\x00", &bytes[..]);
546    }
547
548    #[test]
549    fn root_name_should_generate_no_labels() {
550        assert_eq!(Name::new_unchecked("").labels.len(), 0);
551        assert_eq!(Name::new_unchecked(".").labels.len(), 0);
552    }
553
554    #[test]
555    fn dot_sequence_should_generate_no_labels() {
556        assert_eq!(Name::new_unchecked(".....").labels.len(), 0);
557        assert_eq!(Name::new_unchecked("example.....com").labels.len(), 2);
558    }
559
560    #[test]
561    fn root_name_should_write_zero() {
562        let mut bytes = Vec::with_capacity(30);
563        Name::new_unchecked(".").write_to(&mut bytes).unwrap();
564
565        assert_eq!(b"\x00", &bytes[..]);
566    }
567
568    #[test]
569    fn append_to_vec_with_compression() {
570        let mut buf = Cursor::new(crate::lib::vec![0, 0, 0]);
571        buf.set_position(3);
572
573        let mut name_refs = Default::default();
574
575        let f_isi_arpa = Name::new_unchecked("F.ISI.ARPA");
576        f_isi_arpa
577            .write_compressed_to(&mut buf, &mut name_refs)
578            .expect("failed to add F.ISI.ARPA");
579        let foo_f_isi_arpa = Name::new_unchecked("FOO.F.ISI.ARPA");
580        foo_f_isi_arpa
581            .write_compressed_to(&mut buf, &mut name_refs)
582            .expect("failed to add FOO.F.ISI.ARPA");
583
584        Name::new_unchecked("BAR.F.ISI.ARPA")
585            .write_compressed_to(&mut buf, &mut name_refs)
586            .expect("failed to add FOO.F.ISI.ARPA");
587
588        let data = b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03";
589        assert_eq!(data[..], buf.get_ref()[..]);
590    }
591
592    #[test]
593    fn append_to_vec_with_compression_mult_names() {
594        let mut buf = Cursor::new(Vec::new());
595        let mut name_refs = Default::default();
596
597        let isi_arpa = Name::new_unchecked("ISI.ARPA");
598        isi_arpa
599            .write_compressed_to(&mut buf, &mut name_refs)
600            .expect("failed to add ISI.ARPA");
601
602        let f_isi_arpa = Name::new_unchecked("F.ISI.ARPA");
603        f_isi_arpa
604            .write_compressed_to(&mut buf, &mut name_refs)
605            .expect("failed to add F.ISI.ARPA");
606        let foo_f_isi_arpa = Name::new_unchecked("FOO.F.ISI.ARPA");
607        foo_f_isi_arpa
608            .write_compressed_to(&mut buf, &mut name_refs)
609            .expect("failed to add F.ISI.ARPA");
610        Name::new_unchecked("BAR.F.ISI.ARPA")
611            .write_compressed_to(&mut buf, &mut name_refs)
612            .expect("failed to add F.ISI.ARPA");
613
614        let expected = b"\x03ISI\x04ARPA\x00\x01F\xc0\x00\x03FOO\xc0\x0a\x03BAR\xc0\x0a";
615        assert_eq!(expected[..], buf.get_ref()[..]);
616
617        let mut data = BytesBuffer::new(buf.get_ref());
618
619        let first = Name::parse(&mut data).unwrap();
620        assert_eq!("ISI.ARPA", first.to_string());
621        let second = Name::parse(&mut data).unwrap();
622        assert_eq!("F.ISI.ARPA", second.to_string());
623        let third = Name::parse(&mut data).unwrap();
624        assert_eq!("FOO.F.ISI.ARPA", third.to_string());
625        let fourth = Name::parse(&mut data).unwrap();
626        assert_eq!("BAR.F.ISI.ARPA", fourth.to_string());
627    }
628
629    #[test]
630    fn names_differing_only_in_case_are_not_compressed_together() {
631        let mut buf = Cursor::new(Vec::new());
632        let mut name_refs = Default::default();
633
634        let lower = Name::new_unchecked("foo.local");
635        let upper = Name::new_unchecked("FOO.LOCAL");
636        assert_eq!(lower, upper);
637
638        lower
639            .write_compressed_to(&mut buf, &mut name_refs)
640            .expect("failed to add foo.local");
641        upper
642            .write_compressed_to(&mut buf, &mut name_refs)
643            .expect("failed to add FOO.LOCAL");
644        // an exact repeat is still compressed
645        upper
646            .write_compressed_to(&mut buf, &mut name_refs)
647            .expect("failed to add FOO.LOCAL again");
648
649        let expected = b"\x03foo\x05local\x00\x03FOO\x05LOCAL\x00\xc0\x0b";
650        assert_eq!(expected[..], buf.get_ref()[..]);
651
652        // casing survives a parse of the compressed bytes
653        let mut data = BytesBuffer::new(buf.get_ref());
654        let first = Name::parse(&mut data).unwrap();
655        let second = Name::parse(&mut data).unwrap();
656        assert_eq!(b"FOO", second.get_labels()[0].as_ref());
657        assert_eq!(b"foo", first.get_labels()[0].as_ref());
658    }
659
660    #[test]
661    fn ensure_different_domains_are_not_compressed() {
662        let mut buf = Cursor::new(Vec::new());
663        let mut name_refs = Default::default();
664
665        let foo_bar_baz = Name::new_unchecked("FOO.BAR.BAZ");
666        foo_bar_baz
667            .write_compressed_to(&mut buf, &mut name_refs)
668            .expect("failed to add FOO.BAR.BAZ");
669
670        let foo_bar_buz = Name::new_unchecked("FOO.BAR.BUZ");
671        foo_bar_buz
672            .write_compressed_to(&mut buf, &mut name_refs)
673            .expect("failed to add FOO.BAR.BUZ");
674
675        Name::new_unchecked("FOO.BAR")
676            .write_compressed_to(&mut buf, &mut name_refs)
677            .expect("failed to add FOO.BAR");
678
679        let expected = b"\x03FOO\x03BAR\x03BAZ\x00\x03FOO\x03BAR\x03BUZ\x00\x03FOO\x03BAR\x00";
680        assert_eq!(expected[..], buf.get_ref()[..]);
681    }
682
683    #[test]
684    fn eq_other_name() -> Result<(), SimpleDnsError> {
685        assert_eq!(Name::new("example.com")?, Name::new("example.com")?);
686        assert_ne!(Name::new("some.example.com")?, Name::new("example.com")?);
687        assert_ne!(Name::new("example.co")?, Name::new("example.com")?);
688        assert_ne!(Name::new("example.com.org")?, Name::new("example.com")?);
689
690        let mut data =
691            BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03");
692        data.advance(3)?;
693        assert_eq!(Name::new("F.ISI.ARPA")?, Name::parse(&mut data)?);
694        assert_eq!(Name::new("FOO.F.ISI.ARPA")?, Name::parse(&mut data)?);
695        Ok(())
696    }
697
698    #[test]
699    fn len() -> crate::Result<()> {
700        let mut bytes = Vec::new();
701        let name_one = Name::new_unchecked("ex.com.");
702        name_one.write_to(&mut bytes)?;
703
704        assert_eq!(8, bytes.len());
705        assert_eq!(bytes.len(), name_one.len());
706        assert_eq!(8, Name::parse(&mut BytesBuffer::new(&bytes))?.len());
707
708        Ok(())
709    }
710
711    #[test]
712    fn len_compressed() -> crate::Result<()> {
713        let name_one = Name::new_unchecked("ex.com.");
714        let mut name_refs = Default::default();
715        let mut bytes = Cursor::new(Vec::new());
716        name_one.write_compressed_to(&mut bytes, &mut name_refs)?;
717        name_one.write_compressed_to(&mut bytes, &mut name_refs)?;
718
719        assert_eq!(10, bytes.get_ref().len());
720        Ok(())
721    }
722
723    #[test]
724    #[cfg(feature = "std")]
725    fn hash() -> crate::Result<()> {
726        fn get_hash(name: &Name) -> u64 {
727            let mut hasher = std::hash::DefaultHasher::default();
728            name.hash(&mut hasher);
729            hasher.finish()
730        }
731
732        let mut data =
733            BytesBuffer::new(b"\x00\x00\x00\x01F\x03ISI\x04ARPA\x00\x03FOO\xc0\x03\x03BAR\xc0\x03");
734        data.advance(3)?;
735
736        assert_eq!(
737            get_hash(&Name::new("F.ISI.ARPA")?),
738            get_hash(&Name::parse(&mut data)?)
739        );
740
741        assert_eq!(
742            get_hash(&Name::new("FOO.F.ISI.ARPA")?),
743            get_hash(&Name::parse(&mut data)?)
744        );
745
746        Ok(())
747    }
748
749    #[test]
750    fn is_subdomain_of() {
751        assert!(Name::new_unchecked("sub.example.com")
752            .is_subdomain_of(&Name::new_unchecked("example.com")));
753
754        assert!(!Name::new_unchecked("example.com")
755            .is_subdomain_of(&Name::new_unchecked("example.com")));
756
757        assert!(Name::new_unchecked("foo.sub.example.com")
758            .is_subdomain_of(&Name::new_unchecked("example.com")));
759
760        assert!(!Name::new_unchecked("example.com")
761            .is_subdomain_of(&Name::new_unchecked("example.xom")));
762
763        assert!(!Name::new_unchecked("domain.com")
764            .is_subdomain_of(&Name::new_unchecked("other.domain")));
765
766        assert!(!Name::new_unchecked("domain.com")
767            .is_subdomain_of(&Name::new_unchecked("domain.com.br")));
768    }
769
770    #[test]
771    fn subtract_domain() {
772        let domain = Name::new_unchecked("_srv3._tcp.local");
773        assert_eq!(
774            Name::new_unchecked("a._srv3._tcp.local")
775                .without(&domain)
776                .unwrap()
777                .to_string(),
778            "a"
779        );
780
781        assert!(Name::new_unchecked("unrelated").without(&domain).is_none(),);
782
783        assert_eq!(
784            Name::new_unchecked("some.longer.domain._srv3._tcp.local")
785                .without(&domain)
786                .unwrap()
787                .to_string(),
788            "some.longer.domain"
789        );
790    }
791
792    #[test]
793    fn test_compress_append_near_boundary() -> crate::Result<()> {
794        let mut buf = Cursor::new(Vec::new());
795        let mut name_refs = Default::default();
796
797        let before_boundary_pos = (MAX_COMPRESSION_OFFSET - 5) as usize;
798        let padding = vec![0u8; before_boundary_pos];
799        buf.write_all(&padding)?;
800
801        let name1 = Name::new_unchecked("foo.example.com");
802        let name2 = Name::new_unchecked("bar.test.net");
803
804        // before the boundary
805        let old_pos = buf.position();
806        name1.write_compressed_to(&mut buf, &mut name_refs)?;
807        assert_eq!(buf.position() - old_pos, name1.len() as u64);
808
809        // after the boundary
810        let old_pos = buf.position();
811        name2.write_compressed_to(&mut buf, &mut name_refs)?;
812        assert_eq!(buf.position() - old_pos, name2.len() as u64);
813
814        // should be compressed
815        let old_pos = buf.position();
816        name1.write_compressed_to(&mut buf, &mut name_refs)?;
817        assert_eq!(buf.position() - old_pos, 2);
818
819        // should not be compressed
820        let old_pos = buf.position();
821        name2.write_compressed_to(&mut buf, &mut name_refs)?;
822        assert_eq!(buf.position() - old_pos, name2.len() as u64);
823
824        Ok(())
825    }
826
827    #[test]
828    fn case_insensitive_label_and_name() {
829        let lower = Name::new("_googlecast._tcp.local").unwrap();
830        let upper = Name::new("_googlecast._TCP.LOCAL").unwrap();
831
832        assert_eq!(lower, upper);
833        assert!(Name::new("device._googlecast._TCP.LOCAL")
834            .unwrap()
835            .is_subdomain_of(&lower));
836
837        let full = Name::new("device._googlecast._TCP.LOCAL").unwrap();
838        let sub = full.without(&lower).unwrap();
839        assert_eq!("device", sub.to_string());
840
841        let mut map = std::collections::HashMap::new();
842        map.insert(lower.clone(), 123);
843        assert_eq!(Some(&123), map.get(&upper));
844
845        let l1 = Label::new("Example".as_bytes()).unwrap();
846        let l2 = Label::new("example".as_bytes()).unwrap();
847        assert_eq!(l1, l2);
848        assert_eq!(core::cmp::Ordering::Equal, l1.cmp(&l2));
849    }
850
851    #[test]
852    fn escaped_dots_and_slashes() {
853        let name = Name::new(r"My\.Device._googlecast._tcp.local").expect("failed to build name");
854        assert_eq!(4, name.get_labels().len());
855        assert_eq!(b"My.Device", name.get_labels()[0].as_ref());
856        assert_eq!(r"My\.Device._googlecast._tcp.local", name.to_string());
857
858        let name2 = Name::new(r"My\.Device._googlecast._tcp.local").expect("failed to build name");
859        assert_eq!(4, name2.get_labels().len());
860        assert_eq!(b"My.Device", name2.get_labels()[0].as_ref());
861        assert_eq!(r"My\.Device._googlecast._tcp.local", name2.to_string());
862
863        let formatted = name2.to_string();
864        let roundtrip = Name::new(&formatted).expect("failed to build name");
865        assert_eq!(name2, roundtrip);
866
867        let name_slash = Name::new(r"My\\Device._tcp.local").expect("failed to build name");
868        assert_eq!(3, name_slash.get_labels().len());
869        assert_eq!(b"My\\Device", name_slash.get_labels()[0].as_ref());
870        assert_eq!(r"My\\Device._tcp.local", name_slash.to_string());
871    }
872
873    #[test]
874    fn escaped_dot_is_a_single_label_on_the_wire() {
875        let name = Name::new(r"a\.b.local").expect("failed to build name");
876        let mut bytes = Vec::new();
877        name.write_to(&mut bytes).unwrap();
878
879        // the dot is plain data on the wire, no escaping
880        assert_eq!(b"\x03a.b\x05local\x00", &bytes[..]);
881
882        let parsed = Name::parse(&mut BytesBuffer::new(&bytes)).unwrap();
883        assert_eq!(name, parsed);
884        assert_eq!(r"a\.b.local", parsed.to_string());
885    }
886
887    #[test]
888    fn backslash_is_escaped_in_display() {
889        let name = Name::new_with_labels(&[
890            Label::new_unchecked(b"a\\b".as_slice()),
891            Label::new_unchecked(b"local".as_slice()),
892        ]);
893        assert_eq!(r"a\\b.local", name.to_string());
894    }
895
896    #[test]
897    fn escaped_backslash_followed_by_separator_dot() {
898        // `\\` is a literal backslash, so the following `.` is a separator
899        let name = Name::new(r"a\\.local").expect("failed to build name");
900        assert_eq!(2, name.get_labels().len());
901        assert_eq!(b"a\\", name.get_labels()[0].as_ref());
902        assert_eq!(b"local", name.get_labels()[1].as_ref());
903    }
904
905    #[test]
906    fn escaped_dot_after_escaped_backslash() {
907        // `\\\.` is a literal backslash followed by a literal dot
908        let name = Name::new(r"a\\\.b.local").expect("failed to build name");
909        assert_eq!(2, name.get_labels().len());
910        assert_eq!(b"a\\.b", name.get_labels()[0].as_ref());
911    }
912
913    #[test]
914    fn dns_sd_instance_name_roundtrip() {
915        // RFC 6763 section 4.3: dots inside the instance label are escaped
916        let labels = [
917            Label::new_unchecked(b"My Printer. Floor 2".as_slice()),
918            Label::new_unchecked(b"_ipp".as_slice()),
919            Label::new_unchecked(b"_tcp".as_slice()),
920            Label::new_unchecked(b"local".as_slice()),
921        ];
922        let name = Name::new_with_labels(&labels);
923        let text = name.to_string();
924        assert_eq!(r"My Printer\. Floor 2._ipp._tcp.local", text);
925
926        let parsed = Name::new(&text).unwrap();
927        assert_eq!(name, parsed);
928        assert_eq!(4, parsed.get_labels().len());
929    }
930
931    #[test]
932    fn display_parse_roundtrip_with_special_bytes() {
933        let name = Name::new_with_labels(&[
934            Label::new_unchecked(b"a.b\\c.d".as_slice()),
935            Label::new_unchecked(b"local".as_slice()),
936        ]);
937        let text = name.to_string();
938        assert_eq!(r"a\.b\\c\.d.local", text);
939        assert_eq!(name, Name::new(&text).unwrap());
940    }
941
942    #[test]
943    fn decimal_escape_is_parsed_as_byte() {
944        // RFC 1035 section 5.1: \DDD is the octet with that decimal value
945        let name = Name::new(r"My\032Printer.local").expect("failed to build name");
946        assert_eq!(2, name.get_labels().len());
947        assert_eq!(b"My Printer", name.get_labels()[0].as_ref());
948
949        let name = Name::new(r"a\046b.local").expect("failed to build name");
950        assert_eq!(2, name.get_labels().len());
951        assert_eq!(b"a.b", name.get_labels()[0].as_ref());
952    }
953
954    #[test]
955    fn escaped_non_special_char_is_the_char() {
956        // RFC 1035 section 5.1: \X quotes X
957        let name =
958            Name::new(r"Stuart\'s\ Test\ Server._http._tcp.local").expect("failed to build name");
959        assert_eq!(4, name.get_labels().len());
960        assert_eq!(b"Stuart's Test Server", name.get_labels()[0].as_ref());
961    }
962
963    #[test]
964    fn label_length_limit_counts_unescaped_octets() {
965        // 63 octets once unescaped, even though the text form is longer
966        let at_limit = format!("{}.local", r"a\.".repeat(31) + "a");
967        let name = Name::new(&at_limit).unwrap();
968        assert_eq!(63, name.get_labels()[0].len());
969
970        // 64 octets once unescaped
971        let over_limit = format!("{}.local", r"a\.".repeat(31) + "aa");
972        assert!(Name::new(&over_limit).is_err());
973    }
974
975    #[test]
976    fn malformed_decimal_escapes_are_rejected() {
977        // RFC 4343 section 2.1: one or two digits are undefined
978        assert!(Name::new(r"a\1.local").is_err());
979        assert!(Name::new(r"a\12.local").is_err());
980        // value does not fit in an octet
981        assert!(Name::new(r"a\256b.local").is_err());
982    }
983
984    #[test]
985    fn trailing_lone_backslash_is_rejected() {
986        assert!(Name::new(r"local\").is_err());
987        assert!(Name::new(r"a.local\").is_err());
988    }
989
990    #[test]
991    fn decimal_escape_for_dot_is_data_not_separator() {
992        // \046 is the octet 0x2E, but must not split the label
993        let name = Name::new(r"a\046b.local").expect("failed to build name");
994        assert_eq!(2, name.get_labels().len());
995        assert_eq!(b"a.b", name.get_labels()[0].as_ref());
996        assert_eq!(r"a\.b.local", name.to_string());
997    }
998
999    #[test]
1000    fn decimal_escape_followed_by_digit() {
1001        // four digits: first three are the escape, the fourth is a literal digit
1002        let name = Name::new(r"a\0321b.local").expect("failed to build name");
1003        assert_eq!(2, name.get_labels().len());
1004        assert_eq!(b"a 1b", name.get_labels()[0].as_ref());
1005    }
1006
1007    #[test]
1008    fn decimal_escape_boundaries() {
1009        let name = Name::new(r"a\000\255z.local").expect("failed to build name");
1010        assert_eq!(b"a\x00\xffz", name.get_labels()[0].as_ref());
1011
1012        let mut bytes = Vec::new();
1013        name.write_to(&mut bytes).unwrap();
1014        assert_eq!(b"\x04a\x00\xffz\x05local\x00", &bytes[..]);
1015    }
1016
1017    #[test]
1018    fn rfc4343_examples() {
1019        let name =
1020            Name::new(r"Donald\032E\.\032Eastlake\0323rd.example.").expect("failed to build name");
1021        assert_eq!(2, name.get_labels().len());
1022        assert_eq!(b"Donald E. Eastlake 3rd", name.get_labels()[0].as_ref());
1023
1024        let name = Name::new(r"a\000\\\255z.example.").expect("failed to build name");
1025        assert_eq!(2, name.get_labels().len());
1026        assert_eq!(b"a\x00\\\xffz", name.get_labels()[0].as_ref());
1027    }
1028
1029    #[test]
1030    fn escaped_dot_before_trailing_dot_is_not_a_separator() {
1031        // the trailing unescaped dot only marks the name as absolute
1032        let name = Name::new(r"a\.").expect("failed to build name");
1033        assert_eq!(1, name.get_labels().len());
1034        assert_eq!(b"a.", name.get_labels()[0].as_ref());
1035
1036        let absolute = Name::new(r"a\..").expect("failed to build name");
1037        assert_eq!(name, absolute);
1038    }
1039
1040    #[test]
1041    fn root_name_with_escapes_elsewhere() {
1042        assert_eq!(0, Name::new_unchecked(".").get_labels().len());
1043        assert_eq!(1, Name::new_unchecked(r"\.").get_labels().len());
1044    }
1045
1046    #[test]
1047    fn total_name_length_counts_unescaped_octets() {
1048        let label63 = r"a\.".repeat(31) + "a";
1049        let label60 = r"a\.".repeat(29) + "aa";
1050
1051        // 3 * 64 + 61 + 1 = 254 octets
1052        let fits = format!("{label63}.{label63}.{label63}.{label60}");
1053        let name = Name::new(&fits).unwrap();
1054        assert_eq!(254, name.len());
1055
1056        // 4 * 64 + 1 = 257 octets
1057        let too_long = format!("{label63}.{label63}.{label63}.{label63}");
1058        assert!(Name::new(&too_long).is_err());
1059    }
1060
1061    #[test]
1062    fn hostname_valid() {
1063        assert!(Name::new_unchecked("example.com").is_valid_hostname());
1064        assert!(Name::new_unchecked("example.com.").is_valid_hostname());
1065        assert!(Name::new_unchecked("my-host.sub.example.com").is_valid_hostname());
1066        assert!(Name::new_unchecked("1host.local").is_valid_hostname());
1067        assert!(Name::new_unchecked("localhost").is_valid_hostname());
1068    }
1069
1070    #[test]
1071    fn hostname_invalid() {
1072        assert!(!Name::new_unchecked("").is_valid_hostname());
1073        assert!(!Name::new_unchecked(".").is_valid_hostname());
1074        assert!(!Name::new_unchecked("-a.com").is_valid_hostname());
1075        assert!(!Name::new_unchecked("a-.com").is_valid_hostname());
1076        assert!(!Name::new_unchecked("_srv.local").is_valid_hostname());
1077        assert!(!Name::new_unchecked("a b.com").is_valid_hostname());
1078        assert!(!Name::new_unchecked("\u{1F600}.local").is_valid_hostname());
1079        // an escaped dot is data in the label, not a valid hostname character
1080        assert!(!Name::new_unchecked(r"a\.b.com").is_valid_hostname());
1081        // 64 octet label
1082        assert!(!Name::new_unchecked(&format!("{}.com", "a".repeat(64))).is_valid_hostname());
1083    }
1084
1085    #[test]
1086    fn service_name_valid() {
1087        // RFC 6763 section 4.1.2: <sn>._tcp|_udp.<domain>
1088        assert!(Name::new_unchecked("_http._tcp.local").is_valid_service_name());
1089        assert!(Name::new_unchecked("_http._udp.local").is_valid_service_name());
1090        assert!(Name::new_unchecked("_http._tcp.local.").is_valid_service_name());
1091        assert!(Name::new_unchecked("_ipp-tls._tcp.example.com").is_valid_service_name());
1092        assert!(Name::new_unchecked("_9p._tcp.local").is_valid_service_name());
1093    }
1094
1095    #[test]
1096    fn service_name_transport_is_case_insensitive() {
1097        // RFC 1035 section 2.3.3 / RFC 4343: comparison is case-insensitive
1098        assert!(Name::new_unchecked("_HTTP._TCP.local").is_valid_service_name());
1099        assert!(Name::new_unchecked("_http._Udp.local").is_valid_service_name());
1100    }
1101
1102    #[test]
1103    fn service_name_invalid() {
1104        assert!(!Name::new_unchecked("").is_valid_service_name());
1105        // missing underscore on the service label
1106        assert!(!Name::new_unchecked("http._tcp.local").is_valid_service_name());
1107        // transport label must be _tcp or _udp
1108        assert!(!Name::new_unchecked("_http._ftp.local").is_valid_service_name());
1109        assert!(!Name::new_unchecked("_http.tcp.local").is_valid_service_name());
1110        assert!(!Name::new_unchecked("_http.local").is_valid_service_name());
1111        // the <Service> portion is a pair of labels
1112        assert!(!Name::new_unchecked("_http").is_valid_service_name());
1113        // service name syntax (RFC 6335)
1114        assert!(!Name::new_unchecked("_123._tcp.local").is_valid_service_name());
1115        assert!(!Name::new_unchecked("_a--b._tcp.local").is_valid_service_name());
1116        assert!(!Name::new_unchecked("_-a._tcp.local").is_valid_service_name());
1117        assert!(!Name::new_unchecked("_abcdefghijklmnop._tcp.local").is_valid_service_name());
1118        // invalid domain label
1119        assert!(!Name::new_unchecked("_http._tcp.-bad.local").is_valid_service_name());
1120    }
1121
1122    #[test]
1123    fn instance_name_valid() {
1124        // RFC 6763 section 4.3: <Instance>.<sn>._tcp|_udp.<domain>
1125        assert!(Name::new_unchecked("printer._ipp._tcp.local").is_valid_instance_name());
1126        assert!(Name::new_unchecked("My Printer._ipp._tcp.local").is_valid_instance_name());
1127        assert!(Name::new_unchecked("My Printer._ipp._tcp.local.").is_valid_instance_name());
1128        assert!(
1129            Name::new_unchecked("Stuart's Test Server._http._tcp.local").is_valid_instance_name()
1130        );
1131        assert!(Name::new_unchecked("\u{1F5A8} Printer._ipp._udp.local").is_valid_instance_name());
1132        // dot inside the instance label
1133        assert!(Name::new_unchecked(r"My\. Printer._ipp._tcp.local").is_valid_instance_name());
1134        assert!(Name::new_with_labels(&[
1135            Label::new_unchecked(b"My. Printer".as_slice()),
1136            Label::new_unchecked(b"_ipp".as_slice()),
1137            Label::new_unchecked(b"_tcp".as_slice()),
1138            Label::new_unchecked(b"local".as_slice()),
1139        ])
1140        .is_valid_instance_name());
1141    }
1142
1143    #[test]
1144    fn instance_name_escaped_dot_keeps_label_count() {
1145        let name = Name::new_unchecked(r"a\.b._ipp._tcp.local");
1146        assert_eq!(4, name.get_labels().len());
1147        assert!(name.is_valid_instance_name());
1148
1149        // unescaped, the same text is five labels and the service pair is misplaced
1150        let name = Name::new_unchecked("a.b._ipp._tcp.local");
1151        assert_eq!(5, name.get_labels().len());
1152        assert!(!name.is_valid_instance_name());
1153    }
1154
1155    #[test]
1156    fn instance_name_invalid() {
1157        assert!(!Name::new_unchecked("").is_valid_instance_name());
1158        // missing instance label: `_ipp` is taken as the instance, so the pair is wrong
1159        assert!(!Name::new_unchecked("_ipp._tcp.local").is_valid_instance_name());
1160        assert!(!Name::new_unchecked("My Printer._ipp._ftp.local").is_valid_instance_name());
1161        assert!(!Name::new_unchecked("My Printer.ipp._tcp.local").is_valid_instance_name());
1162        assert!(!Name::new_unchecked("My Printer._123._tcp.local").is_valid_instance_name());
1163        // the <Service> pair is required
1164        assert!(!Name::new_unchecked("My Printer").is_valid_instance_name());
1165        assert!(!Name::new_unchecked("My Printer._ipp").is_valid_instance_name());
1166        // instance label longer than 63 octets
1167        let long = format!("{}._ipp._tcp.local", "a".repeat(64));
1168        assert!(!Name::new_unchecked(&long).is_valid_instance_name());
1169    }
1170
1171    #[test]
1172    fn sub_type_name_valid() {
1173        // RFC 6763 section 7.1: <Subtype>._sub.<Service>.<Domain>
1174        assert!(Name::new_unchecked("_printer._sub._http._tcp.local").is_valid_sub_type_name());
1175        assert!(Name::new_unchecked("_printer._sub._http._udp.local.").is_valid_sub_type_name());
1176    }
1177
1178    #[test]
1179    fn sub_type_name_allows_arbitrary_label() {
1180        // subtype strings are not required to begin with an underscore and
1181        // may be constructed using arbitrary 8-bit data values
1182        assert!(Name::new_unchecked("printer._sub._http._tcp.local").is_valid_sub_type_name());
1183        assert!(
1184            Name::new_unchecked("The Music Store._sub._http._tcp.local").is_valid_sub_type_name()
1185        );
1186        assert!(Name::new_with_labels(&[
1187            Label::new_unchecked(b"a.b\xff".as_slice()),
1188            Label::new_unchecked(b"_sub".as_slice()),
1189            Label::new_unchecked(b"_http".as_slice()),
1190            Label::new_unchecked(b"_tcp".as_slice()),
1191            Label::new_unchecked(b"local".as_slice()),
1192        ])
1193        .is_valid_sub_type_name());
1194    }
1195
1196    #[test]
1197    fn sub_type_name_invalid() {
1198        assert!(!Name::new_unchecked("").is_valid_sub_type_name());
1199        // `_sub` marker is required
1200        assert!(!Name::new_unchecked("_printer._http._tcp.local").is_valid_sub_type_name());
1201        assert!(!Name::new_unchecked("_printer._subx._http._tcp.local").is_valid_sub_type_name());
1202        // the service must still be valid
1203        assert!(!Name::new_unchecked("_printer._sub.http._tcp.local").is_valid_sub_type_name());
1204        assert!(!Name::new_unchecked("_printer._sub._http._ftp.local").is_valid_sub_type_name());
1205        assert!(!Name::new_unchecked("_printer._sub._123._tcp.local").is_valid_sub_type_name());
1206        // missing service pair
1207        assert!(!Name::new_unchecked("_printer._sub").is_valid_sub_type_name());
1208        assert!(!Name::new_unchecked("_printer").is_valid_sub_type_name());
1209    }
1210}