1use 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#[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 pub(crate) const RTYPE: Rtype = Rtype::NAPTR;
62}
63
64impl<Octs, Name> Naptr<Octs, Name> {
65 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 pub fn order(&self) -> u16 {
88 self.order
89 }
90
91 pub fn preference(&self) -> u16 {
93 self.preference
94 }
95
96 pub fn flags(&self) -> &CharStr<Octs> {
99 &self.flags
100 }
101
102 pub fn services(&self) -> &CharStr<Octs> {
105 &self.services
106 }
107
108 pub fn regexp(&self) -> &CharStr<Octs> {
112 &self.regexp
113 }
114
115 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
184impl<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
208impl<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
223impl<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
245impl<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
349impl<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
366impl<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
444impl<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
465impl<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
484impl<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#[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)] 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}