hickory_proto/rr/resource.rs
1// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! resource record implementation
9
10use std::{cmp::Ordering, convert::TryFrom, fmt};
11
12#[cfg(feature = "serde-config")]
13use serde::{Deserialize, Serialize};
14
15use crate::{
16 error::{ProtoError, ProtoErrorKind, ProtoResult},
17 rr::{dns_class::DNSClass, Name, RData, RecordData, RecordSet, RecordType},
18 serialize::binary::{BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict},
19};
20
21#[allow(deprecated)]
22use crate::rr::IntoRecordSet;
23
24#[cfg(feature = "mdns")]
25/// From [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
26/// ```text
27/// The cache-flush bit is the most significant bit of the second
28/// 16-bit word of a resource record in a Resource Record Section of a
29/// Multicast DNS message (the field conventionally referred to as the
30/// rrclass field), and the actual resource record class is the least
31/// significant fifteen bits of this field.
32/// ```
33const MDNS_ENABLE_CACHE_FLUSH: u16 = 1 << 15;
34/// Resource records are storage value in DNS, into which all key/value pair data is stored.
35///
36/// # Generic type
37/// * `R` - the RecordData type this resource record represents, if unknown at runtime use the `RData` abstract enum type
38///
39/// [RFC 1035](https://tools.ietf.org/html/rfc1035), DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987
40///
41/// ```text
42/// 4.1.3. Resource record format
43///
44/// The answer, authority, and additional sections all share the same
45/// format: a variable number of resource records, where the number of
46/// records is specified in the corresponding count field in the header.
47/// Each resource record has the following format:
48/// 1 1 1 1 1 1
49/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
50/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
51/// | |
52/// / /
53/// / NAME /
54/// | |
55/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
56/// | TYPE |
57/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
58/// | CLASS |
59/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
60/// | TTL |
61/// | |
62/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
63/// | RDLENGTH |
64/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
65/// / RDATA /
66/// / /
67/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
68///
69/// ```
70#[cfg_attr(feature = "serde-config", derive(Deserialize, Serialize))]
71#[derive(Eq, Debug, Clone)]
72// TODO: make Record carry a lifetime for more efficient storage options in the future
73pub struct Record<R: RecordData = RData> {
74 name_labels: Name,
75 rr_type: RecordType,
76 dns_class: DNSClass,
77 ttl: u32,
78 rdata: Option<R>,
79 #[cfg(feature = "mdns")]
80 mdns_cache_flush: bool,
81}
82
83impl<R: RecordData> Default for Record<R> {
84 fn default() -> Self {
85 Self {
86 // TODO: these really should all be Optionals, I was lazy.
87 name_labels: Name::new(),
88 rr_type: RecordType::NULL,
89 dns_class: DNSClass::IN,
90 ttl: 0,
91 rdata: None,
92 #[cfg(feature = "mdns")]
93 mdns_cache_flush: false,
94 }
95 }
96}
97
98impl<R: RecordData> Record<R> {
99 /// Creates a default record, use the setters to build a more useful object.
100 ///
101 /// There are no optional elements in this object, defaults are an empty name, type A, class IN,
102 /// ttl of 0 and the 0.0.0.0 ip address.
103 pub fn new() -> Self {
104 Self::default()
105 }
106
107 /// Create a record with the specified initial values.
108 ///
109 /// # Arguments
110 ///
111 /// * `name` - name of the resource records
112 /// * `rr_type` - the record type
113 /// * `ttl` - time-to-live is the amount of time this record should be cached before refreshing
114 pub fn with(name: Name, rr_type: RecordType, ttl: u32) -> Self {
115 Self {
116 name_labels: name,
117 rr_type,
118 dns_class: DNSClass::IN,
119 ttl,
120 rdata: None,
121 #[cfg(feature = "mdns")]
122 mdns_cache_flush: false,
123 }
124 }
125
126 /// Create a record with the specified initial values.
127 ///
128 /// # Arguments
129 ///
130 /// * `name` - name of the resource records
131 /// * `ttl` - time-to-live is the amount of time this record should be cached before refreshing
132 /// * `rdata` - record data to associate with the Record
133 pub fn from_rdata(name: Name, ttl: u32, rdata: R) -> Self {
134 Self {
135 name_labels: name,
136 rr_type: rdata.record_type(),
137 dns_class: DNSClass::IN,
138 ttl,
139 rdata: Some(rdata),
140 #[cfg(feature = "mdns")]
141 mdns_cache_flush: false,
142 }
143 }
144
145 /// Attempts to convert the generic `RData` based Record into this one with the interior `R`
146 #[allow(clippy::result_large_err)]
147 pub fn try_from(record: Record<RData>) -> Result<Self, Record<RData>> {
148 let Record {
149 name_labels,
150 rr_type,
151 dns_class,
152 ttl,
153 rdata,
154 #[cfg(feature = "mdns")]
155 mdns_cache_flush,
156 } = record;
157
158 match rdata.map(R::try_from_rdata) {
159 None => Ok(Self {
160 name_labels,
161 rr_type,
162 dns_class,
163 ttl,
164 rdata: None,
165 #[cfg(feature = "mdns")]
166 mdns_cache_flush,
167 }),
168 Some(Ok(rdata)) => Ok(Self {
169 name_labels,
170 rr_type,
171 dns_class,
172 ttl,
173 rdata: Some(rdata),
174 #[cfg(feature = "mdns")]
175 mdns_cache_flush,
176 }),
177 Some(Err(rdata)) => Err(Record {
178 name_labels,
179 rr_type,
180 dns_class,
181 ttl,
182 rdata: Some(rdata),
183 #[cfg(feature = "mdns")]
184 mdns_cache_flush,
185 }),
186 }
187 }
188
189 /// Converts this Record into a generic version of RData
190 pub fn into_record_of_rdata(self) -> Record<RData> {
191 let Self {
192 name_labels,
193 rr_type,
194 dns_class,
195 ttl,
196 rdata,
197 #[cfg(feature = "mdns")]
198 mdns_cache_flush,
199 } = self;
200
201 let rdata: Option<RData> = rdata.map(RecordData::into_rdata);
202
203 Record {
204 name_labels,
205 rr_type,
206 dns_class,
207 ttl,
208 rdata,
209 #[cfg(feature = "mdns")]
210 mdns_cache_flush,
211 }
212 }
213
214 /// ```text
215 /// NAME a domain name to which this resource record pertains.
216 /// ```
217 pub fn set_name(&mut self, name: Name) -> &mut Self {
218 self.name_labels = name;
219 self
220 }
221
222 /// ```text
223 /// TYPE two octets containing one of the RR type codes. This
224 /// field specifies the meaning of the data in the RDATA
225 /// field.
226 /// ```
227 // #[deprecated(note = "use `Record::set_record_type`")]
228 pub fn set_rr_type(&mut self, rr_type: RecordType) -> &mut Self {
229 self.rr_type = rr_type;
230 self
231 }
232
233 /// ```text
234 /// TYPE two octets containing one of the RR type codes. This
235 /// field specifies the meaning of the data in the RDATA
236 /// field.
237 /// ```
238 pub fn set_record_type(&mut self, rr_type: RecordType) -> &mut Self {
239 self.rr_type = rr_type;
240 self
241 }
242
243 /// ```text
244 /// CLASS two octets which specify the class of the data in the
245 /// RDATA field.
246 /// ```
247 pub fn set_dns_class(&mut self, dns_class: DNSClass) -> &mut Self {
248 self.dns_class = dns_class;
249 self
250 }
251
252 /// ```text
253 /// TTL a 32 bit unsigned integer that specifies the time
254 /// interval (in seconds) that the resource record may be
255 /// cached before it should be discarded. Zero values are
256 /// interpreted to mean that the RR can only be used for the
257 /// transaction in progress, and should not be cached.
258 /// ```
259 pub fn set_ttl(&mut self, ttl: u32) -> &mut Self {
260 self.ttl = ttl;
261 self
262 }
263
264 /// ```text
265 /// RDATA a variable length string of octets that describes the
266 /// resource. The format of this information varies
267 /// according to the TYPE and CLASS of the resource record.
268 /// For example, the if the TYPE is A and the CLASS is IN,
269 /// the RDATA field is a 4 octet ARPA Internet address.
270 /// ```
271 #[track_caller]
272 pub fn set_data(&mut self, rdata: Option<R>) -> &mut Self {
273 debug_assert!(
274 if let Some(rdata) = &rdata {
275 rdata.record_type() == self.record_type() || rdata.record_type() == RecordType::NULL
276 } else {
277 true
278 },
279 "record types do not match, {} <> {:?}",
280 self.record_type(),
281 rdata.map(|r| r.record_type())
282 );
283
284 self.rdata = rdata;
285 self
286 }
287
288 /// Changes mDNS cache-flush bit
289 /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
290 #[cfg(feature = "mdns")]
291 #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
292 pub fn set_mdns_cache_flush(&mut self, flag: bool) -> &mut Self {
293 self.mdns_cache_flush = flag;
294 self
295 }
296
297 /// Returns the name of the record
298 #[inline]
299 pub fn name(&self) -> &Name {
300 &self.name_labels
301 }
302
303 /// Returns the type of the RecordData in the record
304 #[inline]
305 pub fn record_type(&self) -> RecordType {
306 self.rr_type
307 }
308
309 /// Returns the DNSClass of the Record, generally IN fro internet
310 #[inline]
311 pub fn dns_class(&self) -> DNSClass {
312 self.dns_class
313 }
314
315 /// Returns the time-to-live of the record, for caching purposes
316 #[inline]
317 pub fn ttl(&self) -> u32 {
318 self.ttl
319 }
320
321 /// Returns the Record Data, i.e. the record information
322 #[inline]
323 pub fn data(&self) -> Option<&R> {
324 self.rdata.as_ref()
325 }
326
327 /// Returns a mutable reference to the Record Data
328 #[inline]
329 pub fn data_mut(&mut self) -> Option<&mut R> {
330 self.rdata.as_mut()
331 }
332
333 /// Returns the RData consuming the Record
334 #[inline]
335 pub fn into_data(self) -> Option<R> {
336 self.rdata
337 }
338
339 /// Consumes `Record` and returns its components
340 #[inline]
341 pub fn into_parts(self) -> RecordParts {
342 let this = self.into_record_of_rdata();
343 this.into()
344 }
345
346 /// Returns if the mDNS cache-flush bit is set or not
347 /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
348 #[cfg(feature = "mdns")]
349 #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
350 #[inline]
351 pub fn mdns_cache_flush(&self) -> bool {
352 self.mdns_cache_flush
353 }
354}
355
356/// Consumes `Record` giving public access to fields of `Record` so they can
357/// be destructured and taken by value
358pub struct RecordParts<R: RecordData = RData> {
359 /// label names
360 pub name_labels: Name,
361 /// record type
362 pub rr_type: RecordType,
363 /// dns class
364 pub dns_class: DNSClass,
365 /// time to live
366 pub ttl: u32,
367 /// rdata
368 pub rdata: Option<R>,
369 /// mDNS cache flush
370 #[cfg(feature = "mdns")]
371 #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
372 pub mdns_cache_flush: bool,
373}
374
375impl<R: RecordData> From<Record<R>> for RecordParts<R> {
376 fn from(record: Record<R>) -> Self {
377 cfg_if::cfg_if! {
378 if #[cfg(feature = "mdns")] {
379 let Record {
380 name_labels,
381 rr_type,
382 dns_class,
383 ttl,
384 rdata,
385 mdns_cache_flush,
386 } = record;
387 } else {
388 let Record {
389 name_labels,
390 rr_type,
391 dns_class,
392 ttl,
393 rdata,
394 } = record;
395 }
396 }
397
398 Self {
399 name_labels,
400 rr_type,
401 dns_class,
402 ttl,
403 rdata,
404 #[cfg(feature = "mdns")]
405 mdns_cache_flush,
406 }
407 }
408}
409
410#[allow(deprecated)]
411impl IntoRecordSet for Record {
412 fn into_record_set(self) -> RecordSet {
413 RecordSet::from(self)
414 }
415}
416
417impl<R: RecordData> BinEncodable for Record<R> {
418 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
419 self.name_labels.emit(encoder)?;
420 self.rr_type.emit(encoder)?;
421
422 #[cfg(not(feature = "mdns"))]
423 self.dns_class.emit(encoder)?;
424
425 #[cfg(feature = "mdns")]
426 {
427 if self.mdns_cache_flush {
428 encoder.emit_u16(u16::from(self.dns_class()) | MDNS_ENABLE_CACHE_FLUSH)?;
429 } else {
430 self.dns_class.emit(encoder)?;
431 }
432 }
433
434 encoder.emit_u32(self.ttl)?;
435
436 // place the RData length
437 let place = encoder.place::<u16>()?;
438
439 // write the RData
440 // the None case is handled below by writing `0` for the length of the RData
441 // this is in turn read as `None` during the `read` operation.
442 if let Some(rdata) = &self.rdata {
443 rdata.emit(encoder)?;
444 }
445
446 // get the length written
447 let len = encoder.len_since_place(&place);
448 assert!(len <= u16::MAX as usize);
449
450 // replace the location with the length
451 place.replace(encoder, len as u16)?;
452 Ok(())
453 }
454}
455
456impl<'r> BinDecodable<'r> for Record<RData> {
457 /// parse a resource record line example:
458 /// WARNING: the record_bytes is 100% consumed and destroyed in this parsing process
459 fn read(decoder: &mut BinDecoder<'r>) -> ProtoResult<Self> {
460 // NAME an owner name, i.e., the name of the node to which this
461 // resource record pertains.
462 let name_labels: Name = Name::read(decoder)?;
463
464 // TYPE two octets containing one of the RR TYPE codes.
465 let record_type: RecordType = RecordType::read(decoder)?;
466
467 #[cfg(feature = "mdns")]
468 let mut mdns_cache_flush = false;
469
470 // CLASS two octets containing one of the RR CLASS codes.
471 let class: DNSClass = if record_type == RecordType::OPT {
472 // verify that the OPT record is Root
473 if !name_labels.is_root() {
474 return Err(ProtoErrorKind::EdnsNameNotRoot(name_labels).into());
475 }
476
477 // DNS Class is overloaded for OPT records in EDNS - RFC 6891
478 DNSClass::for_opt(
479 decoder.read_u16()?.unverified(/*restricted to a min of 512 in for_opt*/),
480 )
481 } else {
482 #[cfg(not(feature = "mdns"))]
483 {
484 DNSClass::read(decoder)?
485 }
486
487 #[cfg(feature = "mdns")]
488 {
489 let dns_class_value =
490 decoder.read_u16()?.unverified(/*DNSClass::from_u16 will verify the value*/);
491 if dns_class_value & MDNS_ENABLE_CACHE_FLUSH > 0 {
492 mdns_cache_flush = true;
493 DNSClass::from(dns_class_value & !MDNS_ENABLE_CACHE_FLUSH)
494 } else {
495 DNSClass::from(dns_class_value)
496 }
497 }
498 };
499
500 // TTL a 32 bit signed integer that specifies the time interval
501 // that the resource record may be cached before the source
502 // of the information should again be consulted. Zero
503 // values are interpreted to mean that the RR can only be
504 // used for the transaction in progress, and should not be
505 // cached. For example, SOA records are always distributed
506 // with a zero TTL to prohibit caching. Zero values can
507 // also be used for extremely volatile data.
508 // note: u32 seems more accurate given that it can only be positive
509 let ttl: u32 = decoder.read_u32()?.unverified(/*any u32 is valid*/);
510
511 // RDLENGTH an unsigned 16 bit integer that specifies the length in
512 // octets of the RDATA field.
513 let rd_length = decoder
514 .read_u16()?
515 .verify_unwrap(|u| (*u as usize) <= decoder.len())
516 .map_err(|u| {
517 ProtoError::from(format!(
518 "rdata length too large for remaining bytes, need: {} remain: {}",
519 u,
520 decoder.len()
521 ))
522 })?;
523
524 // this is to handle updates, RFC 2136, which uses 0 to indicate certain aspects of pre-requisites
525 // Null represents any data.
526 let rdata = if rd_length == 0 {
527 None
528 } else {
529 // RDATA a variable length string of octets that describes the
530 // resource. The format of this information varies
531 // according to the TYPE and CLASS of the resource record.
532 // Adding restrict to the rdata length because it's used for many calculations later
533 // and must be validated before hand
534 Some(RData::read(decoder, record_type, Restrict::new(rd_length))?)
535 };
536
537 debug_assert!(
538 if let Some(rdata) = &rdata {
539 rdata.record_type() == record_type
540 } else {
541 true
542 },
543 "record types do not match, {} <> {:?}",
544 record_type,
545 rdata.map(|r| r.record_type())
546 );
547
548 Ok(Self {
549 name_labels,
550 rr_type: record_type,
551 dns_class: class,
552 ttl,
553 rdata,
554 #[cfg(feature = "mdns")]
555 mdns_cache_flush,
556 })
557 }
558}
559
560/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
561///
562/// ```text
563/// RESOURCE RECORDS
564///
565/// Records in the zone data files are called resource records (RRs).
566/// They are specified in RFC-883 and RFC-973. An RR has a standard
567/// format as shown:
568///
569/// <name> [<ttl>] [<class>] <type> <data>
570///
571/// The record is divided into fields which are separated by white space.
572///
573/// <name>
574///
575/// The name field defines what domain name applies to the given
576/// RR. In some cases the name field can be left blank and it will
577/// default to the name field of the previous RR.
578///
579/// <ttl>
580///
581/// TTL stands for Time To Live. It specifies how long a domain
582/// resolver should cache the RR before it throws it out and asks a
583/// domain server again. See the section on TTL's. If you leave
584/// the TTL field blank it will default to the minimum time
585/// specified in the SOA record (described later).
586///
587/// <class>
588///
589/// The class field specifies the protocol group. If left blank it
590/// will default to the last class specified.
591///
592/// <type>
593///
594/// The type field specifies what type of data is in the RR. See
595/// the section on types.
596///
597/// <data>
598///
599/// The data field is defined differently for each type and class
600/// of data. Popular RR data formats are described later.
601/// ```
602impl<R: RecordData> fmt::Display for Record<R> {
603 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
604 write!(
605 f,
606 "{name} {ttl} {class} {ty}",
607 name = self.name_labels,
608 ttl = self.ttl,
609 class = self.dns_class,
610 ty = self.rr_type,
611 )?;
612
613 if let Some(rdata) = &self.rdata {
614 write!(f, " {rdata}")?;
615 }
616
617 Ok(())
618 }
619}
620
621impl<R: RecordData> PartialEq for Record<R> {
622 /// Equality or records, as defined by
623 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
624 ///
625 /// ```text
626 /// 1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
627 /// RDLENGTH and RDATA fields are equal. Note that the time-to-live
628 /// (TTL) field is explicitly excluded from the comparison.
629 ///
630 /// 1.1.2. The rules for comparison of character strings in names are
631 /// specified in [RFC1035 2.3.3]. i.e. case insensitive
632 /// ```
633 fn eq(&self, other: &Self) -> bool {
634 // self == other && // the same pointer
635 self.name_labels == other.name_labels
636 && self.rr_type == other.rr_type
637 && self.dns_class == other.dns_class
638 && self.rdata == other.rdata
639 }
640}
641
642/// returns the value of the compare if the items are greater or lesser, but continues on equal
643macro_rules! compare_or_equal {
644 ($x:ident, $y:ident, $z:ident) => {
645 match ($x).$z.cmp(&($y).$z) {
646 o @ Ordering::Less | o @ Ordering::Greater => return o,
647 Ordering::Equal => (),
648 }
649 };
650}
651
652impl Ord for Record {
653 /// Canonical ordering as defined by
654 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
655 ///
656 /// ```text
657 /// 6.2. Canonical RR Form
658 ///
659 /// For the purposes of DNS security, the canonical form of an RR is the
660 /// wire format of the RR where:
661 ///
662 /// 1. every domain name in the RR is fully expanded (no DNS name
663 /// compression) and fully qualified;
664 ///
665 /// 2. all uppercase US-ASCII letters in the owner name of the RR are
666 /// replaced by the corresponding lowercase US-ASCII letters;
667 ///
668 /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
669 /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
670 /// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
671 /// the DNS names contained within the RDATA are replaced by the
672 /// corresponding lowercase US-ASCII letters;
673 ///
674 /// 4. if the owner name of the RR is a wildcard name, the owner name is
675 /// in its original unexpanded form, including the "*" label (no
676 /// wildcard substitution); and
677 ///
678 /// 5. the RR's TTL is set to its original value as it appears in the
679 /// originating authoritative zone or the Original TTL field of the
680 /// covering RRSIG RR.
681 /// ```
682 fn cmp(&self, other: &Self) -> Ordering {
683 // TODO: given that the ordering of Resource Records is dependent on it's binary form and this
684 // method will be used during insertion sort or similar, we should probably do this
685 // conversion once somehow and store it separately. Or should the internal storage of all
686 // resource records be maintained in binary?
687
688 compare_or_equal!(self, other, name_labels);
689 compare_or_equal!(self, other, rr_type);
690 compare_or_equal!(self, other, dns_class);
691 compare_or_equal!(self, other, ttl);
692 compare_or_equal!(self, other, rdata);
693 Ordering::Equal
694 }
695}
696
697impl PartialOrd<Self> for Record {
698 /// Canonical ordering as defined by
699 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
700 ///
701 /// ```text
702 /// 6.2. Canonical RR Form
703 ///
704 /// For the purposes of DNS security, the canonical form of an RR is the
705 /// wire format of the RR where:
706 ///
707 /// 1. every domain name in the RR is fully expanded (no DNS name
708 /// compression) and fully qualified;
709 ///
710 /// 2. all uppercase US-ASCII letters in the owner name of the RR are
711 /// replaced by the corresponding lowercase US-ASCII letters;
712 ///
713 /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
714 /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
715 /// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
716 /// the DNS names contained within the RDATA are replaced by the
717 /// corresponding lowercase US-ASCII letters;
718 ///
719 /// 4. if the owner name of the RR is a wildcard name, the owner name is
720 /// in its original unexpanded form, including the "*" label (no
721 /// wildcard substitution); and
722 ///
723 /// 5. the RR's TTL is set to its original value as it appears in the
724 /// originating authoritative zone or the Original TTL field of the
725 /// covering RRSIG RR.
726 /// ```
727 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
728 Some(self.cmp(other))
729 }
730}
731
732/// A Record where the RecordData type is already known
733pub struct RecordRef<'a, R: RecordData> {
734 name_labels: &'a Name,
735 rr_type: RecordType,
736 dns_class: DNSClass,
737 ttl: u32,
738 rdata: Option<&'a R>,
739 #[cfg(feature = "mdns")]
740 mdns_cache_flush: bool,
741}
742
743impl<R: RecordData> RecordRef<'_, R> {
744 /// Allocates space for a Record with the same fields
745 pub fn to_owned(&self) -> Record<R> {
746 Record {
747 name_labels: self.name_labels.to_owned(),
748 rr_type: self.rr_type,
749 dns_class: self.dns_class,
750 ttl: self.ttl,
751 rdata: self.rdata.cloned(),
752 #[cfg(feature = "mdns")]
753 mdns_cache_flush: self.mdns_cache_flush,
754 }
755 }
756
757 /// Returns the name of the record
758 #[inline]
759 pub fn name(&self) -> &Name {
760 self.name_labels
761 }
762
763 /// Returns the type of the RecordData in the record
764 #[inline]
765 pub fn record_type(&self) -> RecordType {
766 self.rr_type
767 }
768
769 /// Returns the DNSClass of the Record, generally IN fro internet
770 #[inline]
771 pub fn dns_class(&self) -> DNSClass {
772 self.dns_class
773 }
774
775 /// Returns the time-to-live of the record, for caching purposes
776 #[inline]
777 pub fn ttl(&self) -> u32 {
778 self.ttl
779 }
780
781 /// Returns the Record Data, i.e. the record information
782 #[inline]
783 pub fn data(&self) -> Option<&R> {
784 self.rdata
785 }
786
787 /// Returns if the mDNS cache-flush bit is set or not
788 /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
789 #[cfg(feature = "mdns")]
790 #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
791 #[inline]
792 pub fn mdns_cache_flush(&self) -> bool {
793 self.mdns_cache_flush
794 }
795}
796
797impl<'a, R: RecordData> TryFrom<&'a Record> for RecordRef<'a, R> {
798 type Error = &'a Record;
799
800 fn try_from(record: &'a Record) -> Result<Self, Self::Error> {
801 let Record {
802 name_labels,
803 rr_type,
804 dns_class,
805 ttl,
806 rdata,
807 #[cfg(feature = "mdns")]
808 mdns_cache_flush,
809 } = record;
810
811 match rdata.as_ref().and_then(R::try_borrow) {
812 None => Ok(Self {
813 name_labels,
814 rr_type: *rr_type,
815 dns_class: *dns_class,
816 ttl: *ttl,
817 rdata: None,
818 #[cfg(feature = "mdns")]
819 mdns_cache_flush: *mdns_cache_flush,
820 }),
821 Some(rdata) => Ok(Self {
822 name_labels,
823 rr_type: *rr_type,
824 dns_class: *dns_class,
825 ttl: *ttl,
826 rdata: Some(rdata),
827 #[cfg(feature = "mdns")]
828 mdns_cache_flush: *mdns_cache_flush,
829 }),
830 }
831 }
832}
833
834#[cfg(test)]
835mod tests {
836 #![allow(clippy::dbg_macro, clippy::print_stdout)]
837
838 use std::cmp::Ordering;
839 use std::str::FromStr;
840
841 use super::*;
842 use crate::rr::dns_class::DNSClass;
843 use crate::rr::rdata::A;
844 use crate::rr::record_data::RData;
845 use crate::rr::record_type::RecordType;
846 use crate::rr::Name;
847 #[allow(clippy::useless_attribute)]
848 #[allow(unused)]
849 use crate::serialize::binary::*;
850
851 #[test]
852 fn test_emit_and_read() {
853 let mut record = Record::new();
854 record
855 .set_name(Name::from_str("www.example.com").unwrap())
856 .set_rr_type(RecordType::A)
857 .set_dns_class(DNSClass::IN)
858 .set_ttl(5)
859 .set_data(Some(RData::A(A::new(192, 168, 0, 1))));
860
861 let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
862 {
863 let mut encoder = BinEncoder::new(&mut vec_bytes);
864 record.emit(&mut encoder).unwrap();
865 }
866
867 let mut decoder = BinDecoder::new(&vec_bytes);
868
869 let got = Record::read(&mut decoder).unwrap();
870
871 assert_eq!(got, record);
872 }
873
874 #[test]
875 fn test_order() {
876 let mut record = Record::new();
877 record
878 .set_name(Name::from_str("www.example.com").unwrap())
879 .set_rr_type(RecordType::A)
880 .set_dns_class(DNSClass::IN)
881 .set_ttl(5)
882 .set_data(Some(RData::A(A::new(192, 168, 0, 1))));
883
884 let mut greater_name = record.clone();
885 greater_name.set_name(Name::from_str("zzz.example.com").unwrap());
886
887 let mut greater_type = record.clone();
888 greater_type.set_rr_type(RecordType::AAAA);
889
890 let mut greater_class = record.clone();
891 greater_class.set_dns_class(DNSClass::NONE);
892
893 let mut greater_rdata = record.clone();
894 greater_rdata.set_data(Some(RData::A(A::new(192, 168, 0, 255))));
895
896 let compares = vec![
897 (&record, &greater_name),
898 (&record, &greater_type),
899 (&record, &greater_class),
900 (&record, &greater_rdata),
901 ];
902
903 assert_eq!(record.clone(), record.clone());
904 for (r, g) in compares {
905 println!("r, g: {r:?}, {g:?}");
906 assert_eq!(r.cmp(g), Ordering::Less);
907 }
908 }
909
910 #[cfg(feature = "mdns")]
911 #[test]
912 fn test_mdns_cache_flush_bit_handling() {
913 const RR_CLASS_OFFSET: usize = 1 /* empty name */ +
914 std::mem::size_of::<u16>() /* rr_type */;
915
916 let mut record = Record::<RData>::new();
917 record.set_mdns_cache_flush(true);
918
919 let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
920 {
921 let mut encoder = BinEncoder::new(&mut vec_bytes);
922 record.emit(&mut encoder).unwrap();
923
924 let rr_class_slice = encoder.slice_of(RR_CLASS_OFFSET, RR_CLASS_OFFSET + 2);
925 assert_eq!(rr_class_slice, &[0x80, 0x01]);
926 }
927
928 let mut decoder = BinDecoder::new(&vec_bytes);
929
930 let got = Record::<RData>::read(&mut decoder).unwrap();
931
932 assert_eq!(got.dns_class(), DNSClass::IN);
933 assert!(got.mdns_cache_flush());
934 }
935}