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