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::{
14 self, Formatter, ZonefileFmt,
15};
16use core::cmp::Ordering;
17use core::fmt;
18use octseq::octets::{Octets, OctetsFrom, OctetsInto};
19use octseq::parse::Parser;
20
21#[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 pub(crate) const RTYPE: Rtype = Rtype::SOA;
46}
47
48impl<N> Soa<N> {
49 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 pub fn mname(&self) -> &N {
72 &self.mname
73 }
74
75 pub fn rname(&self) -> &N {
77 &self.rname
78 }
79
80 pub fn serial(&self) -> Serial {
82 self.serial
83 }
84
85 pub fn refresh(&self) -> Ttl {
87 self.refresh
88 }
89
90 pub fn retry(&self) -> Ttl {
92 self.retry
93 }
94
95 pub fn expire(&self) -> Ttl {
97 self.expire
98 }
99
100 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
167impl<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
199impl<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
219impl<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
315impl<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
389impl<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#[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)] 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}