domain/base/name/
builder.rs

1//! Building a domain name.
2//!
3//! This is a private module for tidiness. `DnameBuilder` and `PushError`
4//! are re-exported by the parent module.
5
6use super::super::scan::{Symbol, SymbolCharsError, Symbols};
7use super::dname::Dname;
8use super::relative::{RelativeDname, RelativeDnameError};
9use super::traits::{ToDname, ToRelativeDname};
10use super::Label;
11#[cfg(feature = "bytes")]
12use bytes::BytesMut;
13use core::fmt;
14use octseq::builder::{EmptyBuilder, FreezeBuilder, OctetsBuilder, ShortBuf};
15#[cfg(feature = "std")]
16use std::vec::Vec;
17
18//------------ DnameBuilder --------------------------------------------------
19
20/// Builds a domain name step by step by appending data.
21///
22/// The domain name builder is the most fundamental way to construct a new
23/// domain name. It wraps an octets builder that assembles the name step by
24/// step.
25///
26/// The methods [`push`][Self::push] and [`append_slice`][Self::append_slice]
27/// to add the octets of a label to end of the builder. Once a label is
28/// complete, [`end_label`][Self::end_label] finishes the current label and
29/// starts a new one.
30///
31/// The method [`append_label`][Self::append_label] combines this process
32/// and appends the given octets as a label.
33///
34/// The name builder currently is not aware of internationalized domain
35/// names. The octets passed to it are used as is and are not converted.
36#[derive(Clone)]
37pub struct DnameBuilder<Builder> {
38    /// The buffer to build the name in.
39    builder: Builder,
40
41    /// The position in `octets` where the current label started.
42    ///
43    /// If this is `None` we currently do not have a label.
44    head: Option<usize>,
45}
46
47impl<Builder> DnameBuilder<Builder> {
48    /// Creates a new domain name builder from an octets builder.
49    ///
50    /// Whatever is in the buffer already is considered to be a relative
51    /// domain name. Since that may not be the case, this function is
52    /// unsafe.
53    pub(super) unsafe fn from_builder_unchecked(builder: Builder) -> Self {
54        DnameBuilder {
55            builder,
56            head: None,
57        }
58    }
59
60    /// Creates a new, empty name builder.
61    #[must_use]
62    pub fn new() -> Self
63    where
64        Builder: EmptyBuilder,
65    {
66        unsafe { DnameBuilder::from_builder_unchecked(Builder::empty()) }
67    }
68
69    /// Creates a new, empty builder with a given capacity.
70    #[must_use]
71    pub fn with_capacity(capacity: usize) -> Self
72    where
73        Builder: EmptyBuilder,
74    {
75        unsafe {
76            DnameBuilder::from_builder_unchecked(Builder::with_capacity(
77                capacity,
78            ))
79        }
80    }
81
82    /// Creates a new domain name builder atop an existing octets builder.
83    ///
84    /// The function checks that whatever is in the builder already
85    /// consititutes a correctly encoded relative domain name.
86    pub fn from_builder(builder: Builder) -> Result<Self, RelativeDnameError>
87    where
88        Builder: OctetsBuilder + AsRef<[u8]>,
89    {
90        RelativeDname::check_slice(builder.as_ref())?;
91        Ok(unsafe { DnameBuilder::from_builder_unchecked(builder) })
92    }
93}
94
95#[cfg(feature = "std")]
96impl DnameBuilder<Vec<u8>> {
97    /// Creates an empty domain name builder atop a `Vec<u8>`.
98    #[must_use]
99    pub fn new_vec() -> Self {
100        Self::new()
101    }
102
103    /// Creates an empty builder atop a `Vec<u8>` with given capacity.
104    ///
105    /// Names are limited to a length of 255 octets, but you can provide any
106    /// capacity you like here.
107    #[must_use]
108    pub fn vec_with_capacity(capacity: usize) -> Self {
109        Self::with_capacity(capacity)
110    }
111}
112
113#[cfg(feature = "bytes")]
114impl DnameBuilder<BytesMut> {
115    /// Creates an empty domain name bulider atop a bytes value.
116    pub fn new_bytes() -> Self {
117        Self::new()
118    }
119
120    /// Creates an empty bulider atop a bytes value with given capacity.
121    ///
122    /// Names are limited to a length of 255 octets, but you can provide any
123    /// capacity you like here.
124    pub fn bytes_with_capacity(capacity: usize) -> Self {
125        Self::with_capacity(capacity)
126    }
127}
128
129impl<Builder: AsRef<[u8]>> DnameBuilder<Builder> {
130    /// Returns the already assembled domain name as an octets slice.
131    pub fn as_slice(&self) -> &[u8] {
132        self.builder.as_ref()
133    }
134
135    /// Returns the length of the already assembled domain name.
136    pub fn len(&self) -> usize {
137        self.builder.as_ref().len()
138    }
139
140    /// Returns whether the name is still empty.
141    pub fn is_empty(&self) -> bool {
142        self.builder.as_ref().is_empty()
143    }
144}
145
146impl<Builder> DnameBuilder<Builder>
147where
148    Builder: OctetsBuilder + AsRef<[u8]> + AsMut<[u8]>,
149{
150    /// Returns whether there currently is a label under construction.
151    ///
152    /// This returns `false` if the name is still empty or if the last thing
153    /// that happend was a call to [`end_label`].
154    ///
155    /// [`end_label`]: #method.end_label
156    pub fn in_label(&self) -> bool {
157        self.head.is_some()
158    }
159
160    /// Attempts to append a slice to the underlying builder.
161    ///
162    /// This method doesn’t perform any checks but only does the necessary
163    /// error conversion.
164    fn _append_slice(&mut self, slice: &[u8]) -> Result<(), PushError> {
165        self.builder
166            .append_slice(slice)
167            .map_err(|_| PushError::ShortBuf)
168    }
169
170    /// Pushes an octet to the end of the domain name.
171    ///
172    /// Starts a new label if necessary. Returns an error if pushing the
173    /// octet would exceed the size limits for labels or domain names.
174    pub fn push(&mut self, ch: u8) -> Result<(), PushError> {
175        let len = self.len();
176        if len >= 254 {
177            return Err(PushError::LongName);
178        }
179        if let Some(head) = self.head {
180            if len - head > Label::MAX_LEN {
181                return Err(PushError::LongLabel);
182            }
183            self._append_slice(&[ch])?;
184        } else {
185            self.head = Some(len);
186            self._append_slice(&[0, ch])?;
187        }
188        Ok(())
189    }
190
191    /// Pushes a symbol to the end of the domain name.
192    ///
193    /// The symbol is iterpreted as part of the presentation format of a
194    /// domain name, i.e., an unescaped dot is considered a label separator.
195    pub fn push_symbol(&mut self, sym: Symbol) -> Result<(), FromStrError> {
196        if matches!(sym, Symbol::Char('.')) {
197            if !self.in_label() {
198                return Err(FromStrError::EmptyLabel);
199            }
200            self.end_label();
201            Ok(())
202        } else if matches!(sym, Symbol::SimpleEscape(b'['))
203            && !self.in_label()
204        {
205            Err(LabelFromStrError::BinaryLabel.into())
206        } else if let Ok(ch) = sym.into_octet() {
207            self.push(ch).map_err(Into::into)
208        } else {
209            return Err(match sym {
210                Symbol::Char(ch) => FromStrError::IllegalCharacter(ch),
211                _ => FromStrError::IllegalEscape,
212            });
213        }
214    }
215
216    /// Appends the content of an octets slice to the end of the domain name.
217    ///
218    /// Starts a new label if necessary. Returns an error if pushing
219    /// would exceed the size limits for labels or domain names.
220    ///
221    /// If `slice` is empty, does absolutely nothing.
222    pub fn append_slice(&mut self, slice: &[u8]) -> Result<(), PushError> {
223        if slice.is_empty() {
224            return Ok(());
225        }
226        if let Some(head) = self.head {
227            if slice.len() > Label::MAX_LEN - (self.len() - head) {
228                return Err(PushError::LongLabel);
229            }
230        } else {
231            if slice.len() > Label::MAX_LEN {
232                return Err(PushError::LongLabel);
233            }
234            if self.len() + slice.len() > 254 {
235                return Err(PushError::LongName);
236            }
237            self.head = Some(self.len());
238            self._append_slice(&[0])?;
239        }
240        self._append_slice(slice)?;
241        Ok(())
242    }
243
244    /// Ends the current label.
245    ///
246    /// If there isn’t a current label, does nothing.
247    pub fn end_label(&mut self) {
248        if let Some(head) = self.head {
249            let len = self.len() - head - 1;
250            self.builder.as_mut()[head] = len as u8;
251            self.head = None;
252        }
253    }
254
255    /// Appends an octets slice as a complete label.
256    ///
257    /// If there currently is a label under construction, it will be ended
258    /// before appending `label`.
259    ///
260    /// Returns an error if `label` exceeds the label size limit of 63 bytes
261    /// or appending the label would exceed the domain name size limit of
262    /// 255 bytes.
263    pub fn append_label(&mut self, label: &[u8]) -> Result<(), PushError> {
264        let head = self.head;
265        self.end_label();
266        if let Err(err) = self.append_slice(label) {
267            self.head = head;
268            return Err(err);
269        }
270        self.end_label();
271        Ok(())
272    }
273
274    /// Appends a relative domain name.
275    ///
276    /// If there currently is a label under construction, it will be ended
277    /// before appending `name`.
278    ///
279    /// Returns an error if appending would result in a name longer than 254
280    /// bytes.
281    //
282    //  XXX NEEDS TESTS
283    pub fn append_name<N: ToRelativeDname>(
284        &mut self,
285        name: &N,
286    ) -> Result<(), PushNameError> {
287        let head = self.head.take();
288        self.end_label();
289        if self.len() + usize::from(name.compose_len()) > 254 {
290            self.head = head;
291            return Err(PushNameError::LongName);
292        }
293        for label in name.iter_labels() {
294            label
295                .compose(&mut self.builder)
296                .map_err(|_| PushNameError::ShortBuf)?;
297        }
298        Ok(())
299    }
300
301    /// Appends a name from a sequence of symbols.
302    ///
303    /// If there currently is a label under construction, it will be ended
304    /// before appending `chars`.
305    ///
306    /// The character sequence must result in a domain name in representation
307    /// format. That is, its labels should be separated by dots,
308    /// actual dots, white space, backslashes  and byte values that are not
309    /// printable ASCII characters should be escaped.
310    ///
311    /// The last label will only be ended if the last character was a dot.
312    /// Thus, you can determine if that was the case via
313    /// [`in_label`][Self::in_label].
314    pub fn append_symbols<Sym: IntoIterator<Item = Symbol>>(
315        &mut self,
316        symbols: Sym,
317    ) -> Result<(), FromStrError> {
318        symbols
319            .into_iter()
320            .try_for_each(|symbol| self.push_symbol(symbol))
321    }
322
323    /// Appends a name from a sequence of characters.
324    ///
325    /// If there currently is a label under construction, it will be ended
326    /// before appending `chars`.
327    ///
328    /// The character sequence must result in a domain name in representation
329    /// format. That is, its labels should be separated by dots,
330    /// actual dots, white space and backslashes should be escaped by a
331    /// preceeding backslash, and any byte value that is not a printable
332    /// ASCII character should be encoded by a backslash followed by its
333    /// three digit decimal value.
334    ///
335    /// The last label will only be ended if the last character was a dot.
336    /// Thus, you can determine if that was the case via
337    /// [`in_label`][Self::in_label].
338    pub fn append_chars<C: IntoIterator<Item = char>>(
339        &mut self,
340        chars: C,
341    ) -> Result<(), FromStrError> {
342        Symbols::with(chars.into_iter(), |symbols| {
343            self.append_symbols(symbols)
344        })
345    }
346
347    /// Finishes building the name and returns the resulting relative name.
348    ///
349    /// If there currently is a label being built, ends the label first
350    /// before returning the name. I.e., you don’t have to call [`end_label`]
351    /// explicitely.
352    ///
353    /// This method converts the builder into a relative name. If you would
354    /// like to turn it into an absolute name, use [`into_dname`] which
355    /// appends the root label before finishing.
356    ///
357    /// [`end_label`]: #method.end_label
358    /// [`into_dname`]: #method.into_dname
359    pub fn finish(mut self) -> RelativeDname<Builder::Octets>
360    where
361        Builder: FreezeBuilder,
362    {
363        self.end_label();
364        unsafe { RelativeDname::from_octets_unchecked(self.builder.freeze()) }
365    }
366
367    /// Appends the root label to the name and returns it as a `Dname`.
368    ///
369    /// If there currently is a label under construction, ends the label.
370    /// Then adds the empty root label and transforms the name into a
371    /// `Dname`.
372    pub fn into_dname(mut self) -> Result<Dname<Builder::Octets>, PushError>
373    where
374        Builder: FreezeBuilder,
375    {
376        self.end_label();
377        self._append_slice(&[0])?;
378        Ok(unsafe { Dname::from_octets_unchecked(self.builder.freeze()) })
379    }
380
381    /// Appends an origin and returns the resulting `Dname`.
382    /// If there currently is a label under construction, ends the label.
383    /// Then adds the `origin` and transforms the name into a
384    /// `Dname`.
385    //
386    //  XXX NEEDS TESTS
387    pub fn append_origin<N: ToDname>(
388        mut self,
389        origin: &N,
390    ) -> Result<Dname<Builder::Octets>, PushNameError>
391    where
392        Builder: FreezeBuilder,
393    {
394        self.end_label();
395        if self.len() + usize::from(origin.compose_len()) > Dname::MAX_LEN {
396            return Err(PushNameError::LongName);
397        }
398        for label in origin.iter_labels() {
399            label
400                .compose(&mut self.builder)
401                .map_err(|_| PushNameError::ShortBuf)?;
402        }
403        Ok(unsafe { Dname::from_octets_unchecked(self.builder.freeze()) })
404    }
405}
406
407//--- Default
408
409impl<Builder: EmptyBuilder> Default for DnameBuilder<Builder> {
410    fn default() -> Self {
411        Self::new()
412    }
413}
414
415//--- AsRef
416
417impl<Builder: AsRef<[u8]>> AsRef<[u8]> for DnameBuilder<Builder> {
418    fn as_ref(&self) -> &[u8] {
419        self.builder.as_ref()
420    }
421}
422
423//------------ Santa’s Little Helpers ----------------------------------------
424
425/// Parses the contents of an escape sequence from `chars`.
426///
427/// The backslash should already have been taken out of `chars`.
428pub(super) fn parse_escape<C>(
429    chars: &mut C,
430    in_label: bool,
431) -> Result<u8, LabelFromStrError>
432where
433    C: Iterator<Item = char>,
434{
435    let ch = chars.next().ok_or(LabelFromStrError::UnexpectedEnd)?;
436    if ch.is_ascii_digit() {
437        let v = ch.to_digit(10).unwrap() * 100
438            + chars
439                .next()
440                .ok_or(LabelFromStrError::UnexpectedEnd)
441                .and_then(|c| {
442                    c.to_digit(10).ok_or(LabelFromStrError::IllegalEscape)
443                })?
444                * 10
445            + chars
446                .next()
447                .ok_or(LabelFromStrError::UnexpectedEnd)
448                .and_then(|c| {
449                    c.to_digit(10).ok_or(LabelFromStrError::IllegalEscape)
450                })?;
451        if v > 255 {
452            return Err(LabelFromStrError::IllegalEscape);
453        }
454        Ok(v as u8)
455    } else if ch == '[' {
456        // `\[` at the start of a label marks a binary label which we don’t
457        // support. Within a label, the sequence is fine.
458        if in_label {
459            Ok(b'[')
460        } else {
461            Err(LabelFromStrError::BinaryLabel)
462        }
463    } else {
464        Ok(ch as u8)
465    }
466}
467
468//============ Error Types ===================================================
469
470//------------ PushError -----------------------------------------------------
471
472/// An error happened while trying to push data to a domain name builder.
473#[derive(Clone, Copy, Debug, Eq, PartialEq)]
474pub enum PushError {
475    /// The current label would exceed the limit of 63 bytes.
476    LongLabel,
477
478    /// The name would exceed the limit of 255 bytes.
479    LongName,
480
481    /// The buffer is too short to contain the name.
482    ShortBuf,
483}
484
485//--- From
486
487impl From<ShortBuf> for PushError {
488    fn from(_: ShortBuf) -> PushError {
489        PushError::ShortBuf
490    }
491}
492
493//--- Display and Error
494
495impl fmt::Display for PushError {
496    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497        match *self {
498            PushError::LongLabel => f.write_str("long label"),
499            PushError::LongName => f.write_str("long domain name"),
500            PushError::ShortBuf => ShortBuf.fmt(f),
501        }
502    }
503}
504
505#[cfg(feature = "std")]
506impl std::error::Error for PushError {}
507
508//------------ PushNameError -------------------------------------------------
509
510/// An error happened while trying to push a name to a domain name builder.
511#[derive(Clone, Copy, Debug, Eq, PartialEq)]
512pub enum PushNameError {
513    /// The name would exceed the limit of 255 bytes.
514    LongName,
515
516    /// The buffer is too short to contain the name.
517    ShortBuf,
518}
519
520//--- From
521
522impl From<ShortBuf> for PushNameError {
523    fn from(_: ShortBuf) -> Self {
524        PushNameError::ShortBuf
525    }
526}
527
528//--- Display and Error
529
530impl fmt::Display for PushNameError {
531    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
532        match *self {
533            PushNameError::LongName => f.write_str("long domain name"),
534            PushNameError::ShortBuf => ShortBuf.fmt(f),
535        }
536    }
537}
538
539#[cfg(feature = "std")]
540impl std::error::Error for PushNameError {}
541
542//------------ LabelFromStrError ---------------------------------------------
543
544/// An error occured while reading a label from a string.
545#[derive(Clone, Copy, Debug, Eq, PartialEq)]
546pub enum LabelFromStrError {
547    /// The string ended when there should have been more characters.
548    ///
549    /// This most likely happens inside escape sequences and quoting.
550    UnexpectedEnd,
551
552    /// A binary label was encountered.
553    BinaryLabel,
554
555    /// The label would exceed the limit of 63 bytes.
556    LongLabel,
557
558    /// An illegal escape sequence was encountered.
559    ///
560    /// Escape sequences are a backslash character followed by either a
561    /// three decimal digit sequence encoding a byte value or a single
562    /// other printable ASCII character.
563    IllegalEscape,
564
565    /// An illegal character was encountered.
566    ///
567    /// Only printable ASCII characters are allowed.
568    IllegalCharacter(char),
569}
570
571//--- Display and Error
572
573impl fmt::Display for LabelFromStrError {
574    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
575        match *self {
576            LabelFromStrError::UnexpectedEnd => {
577                f.write_str("unexpected end of input")
578            }
579            LabelFromStrError::BinaryLabel => {
580                f.write_str("a binary label was encountered")
581            }
582            LabelFromStrError::LongLabel => {
583                f.write_str("label length limit exceeded")
584            }
585            LabelFromStrError::IllegalEscape => {
586                f.write_str("illegal escape sequence")
587            }
588            LabelFromStrError::IllegalCharacter(char) => {
589                write!(f, "illegal character '{}'", char)
590            }
591        }
592    }
593}
594
595#[cfg(feature = "std")]
596impl std::error::Error for LabelFromStrError {}
597
598//------------ FromStrError --------------------------------------------------
599
600#[derive(Clone, Copy, Debug, Eq, PartialEq)]
601#[non_exhaustive]
602pub enum FromStrError {
603    /// The string ended when there should have been more characters.
604    ///
605    /// This most likely happens inside escape sequences and quoting.
606    UnexpectedEnd,
607
608    /// An empty label was encountered.
609    EmptyLabel,
610
611    /// A binary label was encountered.
612    BinaryLabel,
613
614    /// A domain name label has more than 63 octets.
615    LongLabel,
616
617    /// An illegal escape sequence was encountered.
618    ///
619    /// Escape sequences are a backslash character followed by either a
620    /// three decimal digit sequence encoding a byte value or a single
621    /// other printable ASCII character.
622    IllegalEscape,
623
624    /// An illegal character was encountered.
625    ///
626    /// Only printable ASCII characters are allowed.
627    IllegalCharacter(char),
628
629    /// The name has more than 255 characters.
630    LongName,
631
632    /// The buffer is too short to contain the name.
633    ShortBuf,
634}
635
636//--- From
637
638impl From<PushError> for FromStrError {
639    fn from(err: PushError) -> FromStrError {
640        match err {
641            PushError::LongLabel => FromStrError::LongLabel,
642            PushError::LongName => FromStrError::LongName,
643            PushError::ShortBuf => FromStrError::ShortBuf,
644        }
645    }
646}
647
648impl From<PushNameError> for FromStrError {
649    fn from(err: PushNameError) -> FromStrError {
650        match err {
651            PushNameError::LongName => FromStrError::LongName,
652            PushNameError::ShortBuf => FromStrError::ShortBuf,
653        }
654    }
655}
656
657impl From<LabelFromStrError> for FromStrError {
658    fn from(err: LabelFromStrError) -> FromStrError {
659        match err {
660            LabelFromStrError::UnexpectedEnd => FromStrError::UnexpectedEnd,
661            LabelFromStrError::BinaryLabel => FromStrError::BinaryLabel,
662            LabelFromStrError::LongLabel => FromStrError::LongLabel,
663            LabelFromStrError::IllegalEscape => FromStrError::IllegalEscape,
664            LabelFromStrError::IllegalCharacter(ch) => {
665                FromStrError::IllegalCharacter(ch)
666            }
667        }
668    }
669}
670
671impl From<SymbolCharsError> for FromStrError {
672    fn from(err: SymbolCharsError) -> FromStrError {
673        use crate::base::scan::SymbolCharsEnum;
674
675        match err.0 {
676            SymbolCharsEnum::BadEscape => Self::IllegalEscape,
677            SymbolCharsEnum::ShortInput => Self::UnexpectedEnd,
678        }
679    }
680}
681
682//--- Display and Error
683
684impl fmt::Display for FromStrError {
685    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
686        match *self {
687            FromStrError::UnexpectedEnd => {
688                f.write_str("unexpected end of input")
689            }
690            FromStrError::EmptyLabel => {
691                f.write_str("an empty label was encountered")
692            }
693            FromStrError::BinaryLabel => {
694                f.write_str("a binary label was encountered")
695            }
696            FromStrError::LongLabel => {
697                f.write_str("label length limit exceeded")
698            }
699            FromStrError::IllegalEscape => {
700                f.write_str("illegal escape sequence")
701            }
702            FromStrError::IllegalCharacter(char) => {
703                write!(f, "illegal character '{}'", char)
704            }
705            FromStrError::LongName => f.write_str("long domain name"),
706            FromStrError::ShortBuf => ShortBuf.fmt(f),
707        }
708    }
709}
710
711#[cfg(feature = "std")]
712impl std::error::Error for FromStrError {}
713
714//============ Testing =======================================================
715
716#[cfg(test)]
717#[cfg(feature = "std")]
718mod test {
719    use super::*;
720
721    #[test]
722    fn compose() {
723        let mut builder = DnameBuilder::new_vec();
724        builder.push(b'w').unwrap();
725        builder.append_slice(b"ww").unwrap();
726        builder.end_label();
727        builder.append_slice(b"exa").unwrap();
728        builder.push(b'm').unwrap();
729        builder.push(b'p').unwrap();
730        builder.append_slice(b"le").unwrap();
731        builder.end_label();
732        builder.append_slice(b"com").unwrap();
733        assert_eq!(builder.finish().as_slice(), b"\x03www\x07example\x03com");
734    }
735
736    #[test]
737    fn build_by_label() {
738        let mut builder = DnameBuilder::new_vec();
739        builder.append_label(b"www").unwrap();
740        builder.append_label(b"example").unwrap();
741        builder.append_label(b"com").unwrap();
742        assert_eq!(builder.finish().as_slice(), b"\x03www\x07example\x03com");
743    }
744
745    #[test]
746    fn build_mixed() {
747        let mut builder = DnameBuilder::new_vec();
748        builder.push(b'w').unwrap();
749        builder.append_slice(b"ww").unwrap();
750        builder.append_label(b"example").unwrap();
751        builder.append_slice(b"com").unwrap();
752        assert_eq!(builder.finish().as_slice(), b"\x03www\x07example\x03com");
753    }
754
755    #[test]
756    fn name_limit() {
757        let mut builder = DnameBuilder::new_vec();
758        for _ in 0..25 {
759            // 9 bytes label is 10 bytes in total
760            builder.append_label(b"123456789").unwrap();
761        }
762
763        assert_eq!(builder.append_label(b"12345"), Err(PushError::LongName));
764        assert_eq!(builder.clone().append_label(b"1234"), Ok(()));
765
766        assert_eq!(builder.append_slice(b"12345"), Err(PushError::LongName));
767        assert_eq!(builder.clone().append_slice(b"1234"), Ok(()));
768
769        assert_eq!(builder.append_slice(b"12"), Ok(()));
770        assert_eq!(builder.push(b'3'), Ok(()));
771        assert_eq!(builder.push(b'4'), Err(PushError::LongName))
772    }
773
774    #[test]
775    fn label_limit() {
776        let mut builder = DnameBuilder::new_vec();
777        builder.append_label(&[0u8; 63][..]).unwrap();
778        assert_eq!(
779            builder.append_label(&[0u8; 64][..]),
780            Err(PushError::LongLabel)
781        );
782        assert_eq!(
783            builder.append_label(&[0u8; 164][..]),
784            Err(PushError::LongLabel)
785        );
786
787        builder.append_slice(&[0u8; 60][..]).unwrap();
788        builder.clone().append_label(b"123").unwrap();
789        assert_eq!(builder.append_slice(b"1234"), Err(PushError::LongLabel));
790        builder.append_slice(b"12").unwrap();
791        builder.push(b'3').unwrap();
792        assert_eq!(builder.push(b'4'), Err(PushError::LongLabel));
793    }
794
795    #[test]
796    fn finish() {
797        let mut builder = DnameBuilder::new_vec();
798        builder.append_label(b"www").unwrap();
799        builder.append_label(b"example").unwrap();
800        builder.append_slice(b"com").unwrap();
801        assert_eq!(builder.finish().as_slice(), b"\x03www\x07example\x03com");
802    }
803
804    #[test]
805    fn into_dname() {
806        let mut builder = DnameBuilder::new_vec();
807        builder.append_label(b"www").unwrap();
808        builder.append_label(b"example").unwrap();
809        builder.append_slice(b"com").unwrap();
810        assert_eq!(
811            builder.into_dname().unwrap().as_slice(),
812            b"\x03www\x07example\x03com\x00"
813        );
814    }
815}