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