1use 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#[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 pub(crate) const RTYPE: Rtype = Rtype::SOA;
44}
45
46impl<N> Soa<N> {
47 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 pub fn mname(&self) -> &N {
70 &self.mname
71 }
72
73 pub fn rname(&self) -> &N {
75 &self.rname
76 }
77
78 pub fn serial(&self) -> Serial {
80 self.serial
81 }
82
83 pub fn refresh(&self) -> Ttl {
85 self.refresh
86 }
87
88 pub fn retry(&self) -> Ttl {
90 self.retry
91 }
92
93 pub fn expire(&self) -> Ttl {
95 self.expire
96 }
97
98 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
165impl<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
197impl<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
217impl<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
313impl<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
387impl<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#[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)] 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}