domain/rdata/
naptr.rs

1//! Record data from [RFC 3403]: NAPTR records.
2//!
3//! This RFC defines the NAPTR record type.
4//!
5//! [RFC 3403]: https://www.rfc-editor.org/info/rfc3403
6
7use crate::base::{
8    name::FlattenInto,
9    rdata::ComposeRecordData,
10    scan::{Scan, Scanner},
11    wire::{Compose, Parse, ParseError},
12    zonefile_fmt::{self, Formatter, ZonefileFmt},
13    CanonicalOrd, CharStr, ParseRecordData, ParsedName, RecordData, Rtype,
14    ToName,
15};
16use core::{cmp::Ordering, fmt, hash};
17#[cfg(feature = "serde")]
18use octseq::builder::{EmptyBuilder, FromBuilder, OctetsBuilder};
19use octseq::{Octets, OctetsFrom, OctetsInto, Parser};
20
21//------------ Naptr ---------------------------------------------------------
22
23/// Naptr record data.
24///
25/// The Naptr encodes DNS rules for URI delegation, allowing changes and redelegation.
26/// It uses regex for string-to-domain name conversion, chosen for compactness and
27/// expressivity in small DNS packets.
28///
29/// The Naptr record type is defined in [RFC 3403, section 4.1][1].
30///
31/// [1]: https://www.rfc-editor.org/rfc/rfc3403#section-4.1
32#[derive(Clone)]
33#[cfg_attr(
34    feature = "serde",
35    derive(serde::Serialize, serde::Deserialize),
36    serde(bound(
37        serialize = "
38            Octs: octseq::serde::SerializeOctets + AsRef<[u8]>,
39            Name: serde::Serialize,
40        ",
41        deserialize = "
42            Octs: FromBuilder + octseq::serde::DeserializeOctets<'de>,
43            <Octs as FromBuilder>::Builder:
44                OctetsBuilder + EmptyBuilder
45                + AsRef<[u8]> + AsMut<[u8]>,
46            Name: serde::Deserialize<'de>,
47        ",
48    ))
49)]
50pub struct Naptr<Octs, Name> {
51    order: u16,
52    preference: u16,
53    flags: CharStr<Octs>,
54    services: CharStr<Octs>,
55    regexp: CharStr<Octs>,
56    replacement: Name,
57}
58
59impl Naptr<(), ()> {
60    /// The rtype of this record data type.
61    pub(crate) const RTYPE: Rtype = Rtype::NAPTR;
62}
63
64impl<Octs, Name> Naptr<Octs, Name> {
65    /// Creates a new Naptr record data from content.
66    pub fn new(
67        order: u16,
68        preference: u16,
69        flags: CharStr<Octs>,
70        services: CharStr<Octs>,
71        regexp: CharStr<Octs>,
72        replacement: Name,
73    ) -> Self {
74        Naptr {
75            order,
76            preference,
77            flags,
78            services,
79            regexp,
80            replacement,
81        }
82    }
83
84    /// The order of processing the records is from lowest to highest.
85    /// If two records have the same order value, they should be processed
86    /// according to their preference value and services field.
87    pub fn order(&self) -> u16 {
88        self.order
89    }
90
91    /// The priority of the DDDS Algorithm, from lowest to highest.
92    pub fn preference(&self) -> u16 {
93        self.preference
94    }
95
96    /// The flags controls aspects of the rewriting and interpretation of
97    /// the fields in the record.
98    pub fn flags(&self) -> &CharStr<Octs> {
99        &self.flags
100    }
101
102    /// The services specify the Service Parameters applicable to
103    /// this delegation path.
104    pub fn services(&self) -> &CharStr<Octs> {
105        &self.services
106    }
107
108    /// The regexp containing a substitution expression that is
109    /// applied to the original string held by the client in order to
110    /// construct the next domain name to lookup.
111    pub fn regexp(&self) -> &CharStr<Octs> {
112        &self.regexp
113    }
114
115    /// The replacement is the next domain name to query for,
116    /// depending on the potential values found in the flags field.
117    pub fn replacement(&self) -> &Name {
118        &self.replacement
119    }
120
121    pub(in crate::rdata) fn convert_octets<TOcts, TName>(
122        self,
123    ) -> Result<Naptr<TOcts, TName>, TOcts::Error>
124    where
125        TOcts: OctetsFrom<Octs>,
126        TName: OctetsFrom<Name, Error = TOcts::Error>,
127    {
128        Ok(Naptr::new(
129            self.order,
130            self.preference,
131            self.flags.try_octets_into()?,
132            self.services.try_octets_into()?,
133            self.regexp.try_octets_into()?,
134            self.replacement.try_octets_into()?,
135        ))
136    }
137
138    pub(in crate::rdata) fn flatten<TOcts, TName>(
139        self,
140    ) -> Result<Naptr<TOcts, TName>, TOcts::Error>
141    where
142        TOcts: OctetsFrom<Octs>,
143        Name: FlattenInto<TName, AppendError = TOcts::Error>,
144    {
145        Ok(Naptr::new(
146            self.order,
147            self.preference,
148            CharStr::try_octets_into(self.flags)?,
149            CharStr::try_octets_into(self.services)?,
150            CharStr::try_octets_into(self.regexp)?,
151            Name::try_flatten_into(self.replacement)?,
152        ))
153    }
154
155    pub fn scan<S: Scanner<Octets = Octs, Name = Name>>(
156        scanner: &mut S,
157    ) -> Result<Self, S::Error> {
158        Ok(Self::new(
159            u16::scan(scanner)?,
160            u16::scan(scanner)?,
161            scanner.scan_charstr()?,
162            scanner.scan_charstr()?,
163            scanner.scan_charstr()?,
164            scanner.scan_name()?,
165        ))
166    }
167}
168
169impl<Octs: AsRef<[u8]>> Naptr<Octs, ParsedName<Octs>> {
170    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized>(
171        parser: &mut octseq::Parser<'a, Src>,
172    ) -> Result<Self, ParseError> {
173        Ok(Self::new(
174            u16::parse(parser)?,
175            u16::parse(parser)?,
176            CharStr::parse(parser)?,
177            CharStr::parse(parser)?,
178            CharStr::parse(parser)?,
179            ParsedName::parse(parser)?,
180        ))
181    }
182}
183
184//--- OctetsFrom
185
186impl<Octs, SrcOcts, Name, SrcName> OctetsFrom<Naptr<SrcOcts, SrcName>>
187    for Naptr<Octs, Name>
188where
189    Octs: OctetsFrom<SrcOcts>,
190    Name: OctetsFrom<SrcName, Error = Octs::Error>,
191{
192    type Error = Octs::Error;
193
194    fn try_octets_from(
195        source: Naptr<SrcOcts, SrcName>,
196    ) -> Result<Self, Self::Error> {
197        Ok(Naptr::new(
198            source.order,
199            source.preference,
200            CharStr::try_octets_from(source.flags)?,
201            CharStr::try_octets_from(source.services)?,
202            CharStr::try_octets_from(source.regexp)?,
203            Name::try_octets_from(source.replacement)?,
204        ))
205    }
206}
207
208//--- FlattenInto
209
210impl<Octs, TOcts, Name, TName> FlattenInto<Naptr<TOcts, TName>>
211    for Naptr<Octs, Name>
212where
213    TOcts: OctetsFrom<Octs>,
214    Name: FlattenInto<TName, AppendError = TOcts::Error>,
215{
216    type AppendError = TOcts::Error;
217
218    fn try_flatten_into(self) -> Result<Naptr<TOcts, TName>, TOcts::Error> {
219        self.flatten()
220    }
221}
222
223//--- PartialEq and Eq
224
225impl<Octs, OtherOcts, Name, OtherName> PartialEq<Naptr<OtherOcts, OtherName>>
226    for Naptr<Octs, Name>
227where
228    Octs: AsRef<[u8]>,
229    OtherOcts: AsRef<[u8]>,
230    Name: ToName,
231    OtherName: ToName,
232{
233    fn eq(&self, other: &Naptr<OtherOcts, OtherName>) -> bool {
234        self.order == other.order
235            && self.preference == other.preference
236            && self.flags.eq(&other.flags)
237            && self.services.eq(&other.services)
238            && self.regexp.eq(&other.regexp)
239            && self.replacement.name_eq(&other.replacement)
240    }
241}
242
243impl<Octs: AsRef<[u8]>, Name: ToName> Eq for Naptr<Octs, Name> {}
244
245//--- PartialOrd, Ord, and CanonicalOrd
246
247impl<Octs, OtherOcts, Name, OtherName> PartialOrd<Naptr<OtherOcts, OtherName>>
248    for Naptr<Octs, Name>
249where
250    Octs: AsRef<[u8]>,
251    OtherOcts: AsRef<[u8]>,
252    Name: ToName,
253    OtherName: ToName,
254{
255    fn partial_cmp(
256        &self,
257        other: &Naptr<OtherOcts, OtherName>,
258    ) -> Option<Ordering> {
259        match self.order.partial_cmp(&other.order) {
260            Some(Ordering::Equal) => {}
261            other => return other,
262        }
263        match self.preference.partial_cmp(&other.preference) {
264            Some(Ordering::Equal) => {}
265            other => return other,
266        }
267        match self.flags.partial_cmp(&other.flags) {
268            Some(Ordering::Equal) => {}
269            other => return other,
270        }
271        match self.services.partial_cmp(&other.services) {
272            Some(Ordering::Equal) => {}
273            other => return other,
274        }
275        match self.regexp.partial_cmp(&other.regexp) {
276            Some(Ordering::Equal) => {}
277            other => return other,
278        }
279
280        Some(self.replacement.name_cmp(&other.replacement))
281    }
282}
283
284impl<Octs, OtherOcts, Name, OtherName>
285    CanonicalOrd<Naptr<OtherOcts, OtherName>> for Naptr<Octs, Name>
286where
287    Octs: AsRef<[u8]>,
288    OtherOcts: AsRef<[u8]>,
289    Name: ToName,
290    OtherName: ToName,
291{
292    fn canonical_cmp(&self, other: &Naptr<OtherOcts, OtherName>) -> Ordering {
293        match self.order.cmp(&other.order) {
294            Ordering::Equal => {}
295            other => return other,
296        }
297        match self.preference.cmp(&other.preference) {
298            Ordering::Equal => {}
299            other => return other,
300        }
301        match self.flags.canonical_cmp(&other.flags) {
302            Ordering::Equal => {}
303            other => return other,
304        }
305        match self.services.canonical_cmp(&other.services) {
306            Ordering::Equal => {}
307            other => return other,
308        }
309        match self.regexp.canonical_cmp(&other.regexp) {
310            Ordering::Equal => {}
311            other => return other,
312        }
313
314        self.replacement.lowercase_composed_cmp(&other.replacement)
315    }
316}
317
318impl<Octs, Name> Ord for Naptr<Octs, Name>
319where
320    Octs: AsRef<[u8]>,
321    Name: ToName,
322{
323    fn cmp(&self, other: &Self) -> Ordering {
324        match self.order.cmp(&other.order) {
325            Ordering::Equal => {}
326            other => return other,
327        }
328        match self.preference.cmp(&other.preference) {
329            Ordering::Equal => {}
330            other => return other,
331        }
332        match self.flags.cmp(&other.flags) {
333            Ordering::Equal => {}
334            other => return other,
335        }
336        match self.services.cmp(&other.services) {
337            Ordering::Equal => {}
338            other => return other,
339        }
340        match self.regexp.cmp(&other.regexp) {
341            Ordering::Equal => {}
342            other => return other,
343        }
344
345        self.replacement.name_cmp(&other.replacement)
346    }
347}
348
349//--- Hash
350
351impl<Octs, Name> hash::Hash for Naptr<Octs, Name>
352where
353    Octs: AsRef<[u8]>,
354    Name: hash::Hash,
355{
356    fn hash<H: hash::Hasher>(&self, state: &mut H) {
357        self.order.hash(state);
358        self.preference.hash(state);
359        self.flags.hash(state);
360        self.services.hash(state);
361        self.regexp.hash(state);
362        self.replacement.hash(state);
363    }
364}
365
366//--- RecordData, ParseRecordData, ComposeRecordData
367
368impl<Octs, Name> RecordData for Naptr<Octs, Name> {
369    fn rtype(&self) -> Rtype {
370        Naptr::RTYPE
371    }
372}
373
374impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
375    for Naptr<Octs::Range<'a>, ParsedName<Octs::Range<'a>>>
376{
377    fn parse_rdata(
378        rtype: Rtype,
379        parser: &mut Parser<'a, Octs>,
380    ) -> Result<Option<Self>, ParseError> {
381        if rtype == Naptr::RTYPE {
382            Self::parse(parser).map(Some)
383        } else {
384            Ok(None)
385        }
386    }
387}
388
389impl<Octs, Name> ComposeRecordData for Naptr<Octs, Name>
390where
391    Octs: AsRef<[u8]>,
392    Name: ToName,
393{
394    fn rdlen(&self, _compress: bool) -> Option<u16> {
395        Some(
396            (u16::COMPOSE_LEN + u16::COMPOSE_LEN)
397                .checked_add(self.flags.compose_len())
398                .expect("flags too long")
399                .checked_add(self.services.compose_len())
400                .expect("services too long")
401                .checked_add(self.regexp.compose_len())
402                .expect("regexp too long")
403                .checked_add(self.replacement.compose_len())
404                .expect("replacement too long"),
405        )
406    }
407
408    fn compose_rdata<Target: crate::base::wire::Composer + ?Sized>(
409        &self,
410        target: &mut Target,
411    ) -> Result<(), Target::AppendError> {
412        self.compose_head(target)?;
413        self.replacement.compose(target)
414    }
415
416    fn compose_canonical_rdata<
417        Target: crate::base::wire::Composer + ?Sized,
418    >(
419        &self,
420        target: &mut Target,
421    ) -> Result<(), Target::AppendError> {
422        self.compose_head(target)?;
423        self.replacement.compose_canonical(target)
424    }
425}
426
427impl<Octs, Name> Naptr<Octs, Name>
428where
429    Octs: AsRef<[u8]>,
430    Name: ToName,
431{
432    fn compose_head<Target: crate::base::wire::Composer + ?Sized>(
433        &self,
434        target: &mut Target,
435    ) -> Result<(), Target::AppendError> {
436        self.order.compose(target)?;
437        self.preference.compose(target)?;
438        self.flags.compose(target)?;
439        self.services.compose(target)?;
440        self.regexp.compose(target)
441    }
442}
443
444//--- Display
445
446impl<Octs, Name> core::fmt::Display for Naptr<Octs, Name>
447where
448    Octs: AsRef<[u8]>,
449    Name: fmt::Display,
450{
451    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
452        write!(
453            f,
454            "{} {} {} {} {} {}.",
455            self.order,
456            self.preference,
457            self.flags.display_quoted(),
458            self.services.display_quoted(),
459            self.regexp.display_quoted(),
460            self.replacement
461        )
462    }
463}
464
465//--- Debug
466
467impl<Octs, Name> core::fmt::Debug for Naptr<Octs, Name>
468where
469    Octs: AsRef<[u8]>,
470    Name: fmt::Debug,
471{
472    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
473        f.debug_struct("Naptr")
474            .field("order", &self.order)
475            .field("preference", &self.preference)
476            .field("flags", &self.flags)
477            .field("services", &self.services)
478            .field("regexp", &self.regexp)
479            .field("replacement", &self.replacement)
480            .finish()
481    }
482}
483
484//--- ZonefileFmt
485
486impl<Octs, Name> ZonefileFmt for Naptr<Octs, Name>
487where
488    Octs: AsRef<[u8]>,
489    Name: ToName,
490{
491    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
492        p.block(|p| {
493            p.write_token(self.order)?;
494            p.write_comment("order")?;
495            p.write_token(self.preference)?;
496            p.write_comment("preference")?;
497            p.write_token(self.flags.display_quoted())?;
498            p.write_comment("flags")?;
499            p.write_token(self.services.display_quoted())?;
500            p.write_comment("services")?;
501            p.write_token(self.regexp.display_quoted())?;
502            p.write_comment("regexp")?;
503            p.write_token(self.replacement.fmt_with_dot())?;
504            p.write_comment("replacement")
505        })
506    }
507}
508
509//============ Testing =======================================================
510
511#[cfg(test)]
512#[cfg(all(feature = "std", feature = "bytes"))]
513mod test {
514    use bytes::Bytes;
515
516    use super::*;
517    use crate::base::{
518        rdata::test::{test_compose_parse, test_rdlen, test_scan},
519        Name,
520    };
521    use core::str::FromStr;
522    use std::vec::Vec;
523
524    #[test]
525    #[allow(clippy::redundant_closure)] // lifetimes ...
526    fn naptr_compose_parse_scan() {
527        let rdata = Naptr::new(
528            100,
529            50,
530            CharStr::from_octets("a").unwrap(),
531            CharStr::from_octets("z3950+N2L+N2C").unwrap(),
532            CharStr::from_octets("").unwrap(),
533            Name::<Vec<u8>>::from_str("cidserver.example.com.").unwrap(),
534        );
535        test_rdlen(&rdata);
536        test_compose_parse(&rdata, |parser| Naptr::parse(parser));
537        test_scan(
538            &[
539                "100",
540                "50",
541                "a",
542                "z3950+N2L+N2C",
543                "",
544                "cidserver.example.com.",
545            ],
546            Naptr::scan,
547            &rdata,
548        );
549    }
550
551    #[test]
552    fn naptr_octets_into() {
553        let naptr: Naptr<&str, Name<Vec<u8>>> = Naptr::new(
554            100,
555            50,
556            CharStr::from_octets("a").unwrap(),
557            CharStr::from_octets("z3950+N2L+N2C").unwrap(),
558            CharStr::from_octets("").unwrap(),
559            Name::<Vec<u8>>::from_str("cidserver.example.com.").unwrap(),
560        );
561        let naptr_bytes: Naptr<Bytes, Name<Bytes>> =
562            naptr.clone().octets_into();
563        assert_eq!(naptr.order(), naptr_bytes.order());
564        assert_eq!(naptr.preference(), naptr_bytes.preference());
565        assert_eq!(naptr.flags(), naptr_bytes.flags());
566        assert_eq!(naptr.services(), naptr_bytes.services());
567        assert_eq!(naptr.regexp(), naptr_bytes.regexp());
568        assert_eq!(naptr.replacement(), naptr_bytes.replacement());
569    }
570
571    #[test]
572    fn naptr_display() {
573        let naptr: Naptr<&str, Name<Vec<u8>>> = Naptr::new(
574            100,
575            50,
576            CharStr::from_octets("a").unwrap(),
577            CharStr::from_octets("z3950+N2L+N2C").unwrap(),
578            CharStr::from_octets(r#"!^urn:cid:.+@([^\.]+\.)(.*)$!\2!i"#)
579                .unwrap(),
580            Name::<Vec<u8>>::from_str("cidserver.example.com.").unwrap(),
581        );
582        assert_eq!(
583            format!("{}", naptr),
584            r#"100 50 "a" "z3950+N2L+N2C" "!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\2!i" cidserver.example.com."#
585        );
586    }
587}