domain/rdata/rfc1035/
soa.rs

1//! Record data for the SOA record.
2//!
3//! This is a private module. It’s content is re-exported by the parent.
4
5use crate::base::cmp::CanonicalOrd;
6use crate::base::iana::Rtype;
7use crate::base::name::{FlattenInto, ParsedName, ToName};
8use crate::base::rdata::{ComposeRecordData, ParseRecordData, RecordData};
9use crate::base::record::Ttl;
10use crate::base::scan::{Scan, Scanner};
11use crate::base::serial::Serial;
12use crate::base::wire::{Compose, Composer, ParseError};
13use crate::base::zonefile_fmt::{
14    self, Formatter, ZonefileFmt,
15};
16use core::cmp::Ordering;
17use core::fmt;
18use octseq::octets::{Octets, OctetsFrom, OctetsInto};
19use octseq::parse::Parser;
20
21//------------ Soa ----------------------------------------------------------
22
23/// Soa record data.
24///
25/// Soa records mark the top of a zone and contain information pertinent to
26/// name server maintenance operations.
27///
28/// The Soa record type is defined in [RFC 1035, section 3.3.13][1].
29///
30/// [1]: https://tools.ietf.org/html/rfc1035#section-3.3.13
31#[derive(Clone, Debug, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Soa<N> {
34    mname: N,
35    rname: N,
36    serial: Serial,
37    refresh: Ttl,
38    retry: Ttl,
39    expire: Ttl,
40    minimum: Ttl,
41}
42
43impl Soa<()> {
44    /// The rtype of this record data type.
45    pub(crate) const RTYPE: Rtype = Rtype::SOA;
46}
47
48impl<N> Soa<N> {
49    /// Creates new Soa record data from content.
50    pub fn new(
51        mname: N,
52        rname: N,
53        serial: Serial,
54        refresh: Ttl,
55        retry: Ttl,
56        expire: Ttl,
57        minimum: Ttl,
58    ) -> Self {
59        Soa {
60            mname,
61            rname,
62            serial,
63            refresh,
64            retry,
65            expire,
66            minimum,
67        }
68    }
69
70    /// The primary name server for the zone.
71    pub fn mname(&self) -> &N {
72        &self.mname
73    }
74
75    /// The mailbox for the person responsible for this zone.
76    pub fn rname(&self) -> &N {
77        &self.rname
78    }
79
80    /// The serial number of the original copy of the zone.
81    pub fn serial(&self) -> Serial {
82        self.serial
83    }
84
85    /// The time interval before the zone should be refreshed.
86    pub fn refresh(&self) -> Ttl {
87        self.refresh
88    }
89
90    /// The time before a failed refresh is retried.
91    pub fn retry(&self) -> Ttl {
92        self.retry
93    }
94
95    /// The upper limit of time the zone is authoritative.
96    pub fn expire(&self) -> Ttl {
97        self.expire
98    }
99
100    /// The minimum TTL to be exported with any RR from this zone.
101    pub fn minimum(&self) -> Ttl {
102        self.minimum
103    }
104
105    pub(in crate::rdata) fn convert_octets<Target: OctetsFrom<N>>(
106        self,
107    ) -> Result<Soa<Target>, Target::Error> {
108        Ok(Soa::new(
109            self.mname.try_octets_into()?,
110            self.rname.try_octets_into()?,
111            self.serial,
112            self.refresh,
113            self.retry,
114            self.expire,
115            self.minimum,
116        ))
117    }
118
119    pub(in crate::rdata) fn flatten<TargetName>(
120        self,
121    ) -> Result<Soa<TargetName>, N::AppendError>
122    where
123        N: FlattenInto<TargetName>,
124    {
125        Ok(Soa::new(
126            self.mname.try_flatten_into()?,
127            self.rname.try_flatten_into()?,
128            self.serial,
129            self.refresh,
130            self.retry,
131            self.expire,
132            self.minimum,
133        ))
134    }
135
136    pub fn scan<S: Scanner<Name = N>>(
137        scanner: &mut S,
138    ) -> Result<Self, S::Error> {
139        Ok(Self::new(
140            scanner.scan_name()?,
141            scanner.scan_name()?,
142            Serial::scan(scanner)?,
143            Ttl::scan(scanner)?,
144            Ttl::scan(scanner)?,
145            Ttl::scan(scanner)?,
146            Ttl::scan(scanner)?,
147        ))
148    }
149}
150
151impl<Octs> Soa<ParsedName<Octs>> {
152    pub fn parse<'a, Src: Octets<Range<'a> = Octs> + ?Sized + 'a>(
153        parser: &mut Parser<'a, Src>,
154    ) -> Result<Self, ParseError> {
155        Ok(Self::new(
156            ParsedName::parse(parser)?,
157            ParsedName::parse(parser)?,
158            Serial::parse(parser)?,
159            Ttl::parse(parser)?,
160            Ttl::parse(parser)?,
161            Ttl::parse(parser)?,
162            Ttl::parse(parser)?,
163        ))
164    }
165}
166
167//--- OctetsFrom and FlattenInto
168
169impl<Name, SrcName> OctetsFrom<Soa<SrcName>> for Soa<Name>
170where
171    Name: OctetsFrom<SrcName>,
172{
173    type Error = Name::Error;
174
175    fn try_octets_from(source: Soa<SrcName>) -> Result<Self, Self::Error> {
176        Ok(Soa::new(
177            Name::try_octets_from(source.mname)?,
178            Name::try_octets_from(source.rname)?,
179            source.serial,
180            source.refresh,
181            source.retry,
182            source.expire,
183            source.minimum,
184        ))
185    }
186}
187
188impl<Name, TName> FlattenInto<Soa<TName>> for Soa<Name>
189where
190    Name: FlattenInto<TName>,
191{
192    type AppendError = Name::AppendError;
193
194    fn try_flatten_into(self) -> Result<Soa<TName>, Name::AppendError> {
195        self.flatten()
196    }
197}
198
199//--- PartialEq and Eq
200
201impl<N, NN> PartialEq<Soa<NN>> for Soa<N>
202where
203    N: ToName,
204    NN: ToName,
205{
206    fn eq(&self, other: &Soa<NN>) -> bool {
207        self.mname.name_eq(&other.mname)
208            && self.rname.name_eq(&other.rname)
209            && self.serial == other.serial
210            && self.refresh == other.refresh
211            && self.retry == other.retry
212            && self.expire == other.expire
213            && self.minimum == other.minimum
214    }
215}
216
217impl<N: ToName> Eq for Soa<N> {}
218
219//--- PartialOrd, Ord, and CanonicalOrd
220
221impl<N, NN> PartialOrd<Soa<NN>> for Soa<N>
222where
223    N: ToName,
224    NN: ToName,
225{
226    fn partial_cmp(&self, other: &Soa<NN>) -> Option<Ordering> {
227        match self.mname.name_cmp(&other.mname) {
228            Ordering::Equal => {}
229            other => return Some(other),
230        }
231        match self.rname.name_cmp(&other.rname) {
232            Ordering::Equal => {}
233            other => return Some(other),
234        }
235        match u32::from(self.serial).partial_cmp(&u32::from(other.serial)) {
236            Some(Ordering::Equal) => {}
237            other => return other,
238        }
239        match self.refresh.partial_cmp(&other.refresh) {
240            Some(Ordering::Equal) => {}
241            other => return other,
242        }
243        match self.retry.partial_cmp(&other.retry) {
244            Some(Ordering::Equal) => {}
245            other => return other,
246        }
247        match self.expire.partial_cmp(&other.expire) {
248            Some(Ordering::Equal) => {}
249            other => return other,
250        }
251        self.minimum.partial_cmp(&other.minimum)
252    }
253}
254
255impl<N: ToName> Ord for Soa<N> {
256    fn cmp(&self, other: &Self) -> Ordering {
257        match self.mname.name_cmp(&other.mname) {
258            Ordering::Equal => {}
259            other => return other,
260        }
261        match self.rname.name_cmp(&other.rname) {
262            Ordering::Equal => {}
263            other => return other,
264        }
265        match u32::from(self.serial).cmp(&u32::from(other.serial)) {
266            Ordering::Equal => {}
267            other => return other,
268        }
269        match self.refresh.cmp(&other.refresh) {
270            Ordering::Equal => {}
271            other => return other,
272        }
273        match self.retry.cmp(&other.retry) {
274            Ordering::Equal => {}
275            other => return other,
276        }
277        match self.expire.cmp(&other.expire) {
278            Ordering::Equal => {}
279            other => return other,
280        }
281        self.minimum.cmp(&other.minimum)
282    }
283}
284
285impl<N: ToName, NN: ToName> CanonicalOrd<Soa<NN>> for Soa<N> {
286    fn canonical_cmp(&self, other: &Soa<NN>) -> Ordering {
287        match self.mname.lowercase_composed_cmp(&other.mname) {
288            Ordering::Equal => {}
289            other => return other,
290        }
291        match self.rname.lowercase_composed_cmp(&other.rname) {
292            Ordering::Equal => {}
293            other => return other,
294        }
295        match self.serial.canonical_cmp(&other.serial) {
296            Ordering::Equal => {}
297            other => return other,
298        }
299        match self.refresh.cmp(&other.refresh) {
300            Ordering::Equal => {}
301            other => return other,
302        }
303        match self.retry.cmp(&other.retry) {
304            Ordering::Equal => {}
305            other => return other,
306        }
307        match self.expire.cmp(&other.expire) {
308            Ordering::Equal => {}
309            other => return other,
310        }
311        self.minimum.cmp(&other.minimum)
312    }
313}
314
315//--- RecordData, ParseRecordData, ComposeRecordData
316
317impl<N> RecordData for Soa<N> {
318    fn rtype(&self) -> Rtype {
319        Soa::RTYPE
320    }
321}
322
323impl<'a, Octs: Octets + ?Sized> ParseRecordData<'a, Octs>
324    for Soa<ParsedName<Octs::Range<'a>>>
325{
326    fn parse_rdata(
327        rtype: Rtype,
328        parser: &mut Parser<'a, Octs>,
329    ) -> Result<Option<Self>, ParseError> {
330        if rtype == Soa::RTYPE {
331            Self::parse(parser).map(Some)
332        } else {
333            Ok(None)
334        }
335    }
336}
337
338impl<Name: ToName> ComposeRecordData for Soa<Name> {
339    fn rdlen(&self, compress: bool) -> Option<u16> {
340        if compress {
341            None
342        } else {
343            Some(
344                self.mname.compose_len()
345                    + self.rname.compose_len()
346                    + Serial::COMPOSE_LEN
347                    + 4 * u32::COMPOSE_LEN,
348            )
349        }
350    }
351
352    fn compose_rdata<Target: Composer + ?Sized>(
353        &self,
354        target: &mut Target,
355    ) -> Result<(), Target::AppendError> {
356        if target.can_compress() {
357            target.append_compressed_name(&self.mname)?;
358            target.append_compressed_name(&self.rname)?;
359        } else {
360            self.mname.compose(target)?;
361            self.rname.compose(target)?;
362        }
363        self.compose_fixed(target)
364    }
365
366    fn compose_canonical_rdata<Target: Composer + ?Sized>(
367        &self,
368        target: &mut Target,
369    ) -> Result<(), Target::AppendError> {
370        self.mname.compose_canonical(target)?;
371        self.rname.compose_canonical(target)?;
372        self.compose_fixed(target)
373    }
374}
375
376impl<Name: ToName> Soa<Name> {
377    fn compose_fixed<Target: Composer + ?Sized>(
378        &self,
379        target: &mut Target,
380    ) -> Result<(), Target::AppendError> {
381        self.serial.compose(target)?;
382        self.refresh.compose(target)?;
383        self.retry.compose(target)?;
384        self.expire.compose(target)?;
385        self.minimum.compose(target)
386    }
387}
388
389//--- Display
390
391impl<N: fmt::Display> fmt::Display for Soa<N> {
392    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
393        write!(
394            f,
395            "{}. {}. {} {} {} {} {}",
396            self.mname,
397            self.rname,
398            self.serial,
399            self.refresh.as_secs(),
400            self.retry.as_secs(),
401            self.expire.as_secs(),
402            self.minimum.as_secs()
403        )
404    }
405}
406
407impl<N: ToName> ZonefileFmt for Soa<N> {
408    fn fmt(&self, p: &mut impl Formatter) -> zonefile_fmt::Result {
409        p.block(|p| {
410            p.write_token(self.mname.fmt_with_dot())?;
411            p.write_comment("mname")?;
412            p.write_token(self.rname.fmt_with_dot())?;
413            p.write_comment("rname")?;
414            p.write_token(self.serial)?;
415            p.write_comment("serial")?;
416            p.write_show(self.refresh)?;
417            p.write_comment(format_args!(
418                "refresh ({})",
419                self.refresh.pretty(),
420            ))?;
421            p.write_show(self.retry)?;
422            p.write_comment(
423                format_args!("retry ({})", self.retry.pretty(),),
424            )?;
425            p.write_show(self.expire)?;
426            p.write_comment(format_args!(
427                "expire ({})",
428                self.expire.pretty(),
429            ))?;
430            p.write_show(self.minimum)?;
431            p.write_comment(format_args!(
432                "minumum ({})",
433                self.minimum.pretty(),
434            ))
435        })
436    }
437}
438
439//============ Testing =======================================================
440
441#[cfg(test)]
442#[cfg(all(feature = "std", feature = "bytes"))]
443mod test {
444    use super::*;
445    use crate::base::name::Name;
446    use crate::base::rdata::test::{
447        test_compose_parse, test_rdlen, test_scan,
448    };
449    use core::str::FromStr;
450    use std::vec::Vec;
451
452    #[test]
453    #[allow(clippy::redundant_closure)] // lifetimes ...
454    fn soa_compose_parse_scan() {
455        let rdata = Soa::<Name<Vec<u8>>>::new(
456            Name::from_str("m.example.com").unwrap(),
457            Name::from_str("r.example.com").unwrap(),
458            Serial(11),
459            Ttl::from_secs(12),
460            Ttl::from_secs(13),
461            Ttl::from_secs(14),
462            Ttl::from_secs(15),
463        );
464        test_rdlen(&rdata);
465        test_compose_parse(&rdata, |parser| Soa::parse(parser));
466        test_scan(
467            &[
468                "m.example.com",
469                "r.example.com",
470                "11",
471                "12",
472                "13",
473                "14",
474                "15",
475            ],
476            Soa::scan,
477            &rdata,
478        );
479    }
480}