hickory_proto/rr/record.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, boxed::Box};
11use core::{cmp::Ordering, convert::TryFrom, fmt};
12
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16#[cfg(feature = "__dnssec")]
17use crate::dnssec::{Proof, Proven};
18#[cfg(test)]
19use crate::rr::rdata::A;
20use crate::{
21 error::ProtoResult,
22 rr::{Name, RData, RecordData, RecordType, dns_class::DNSClass},
23 serialize::binary::{
24 BinDecodable, BinDecoder, BinEncodable, BinEncoder, DecodeError, Restrict,
25 },
26};
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#[non_exhaustive]
75#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
76#[derive(Eq, Debug, Clone)]
77// TODO: make Record carry a lifetime for more efficient storage options in the future
78pub struct Record<R: RecordData = RData> {
79 /// ```text
80 /// NAME a domain name to which this resource record pertains.
81 /// ```
82 pub name: Name,
83 /// ```text
84 /// CLASS two octets which specify the class of the data in the
85 /// RDATA field.
86 /// ```
87 pub dns_class: DNSClass,
88 /// ```text
89 /// TTL a 32 bit unsigned integer that specifies the time
90 /// interval (in seconds) that the resource record may be
91 /// cached before it should be discarded. Zero values are
92 /// interpreted to mean that the RR can only be used for the
93 /// transaction in progress, and should not be cached.
94 /// ```
95 pub ttl: u32,
96 /// ```text
97 /// RDATA a variable length string of octets that describes the
98 /// resource. The format of this information varies
99 /// according to the TYPE and CLASS of the resource record.
100 /// For example, the if the TYPE is A and the CLASS is IN,
101 /// the RDATA field is a 4 octet ARPA Internet address.
102 /// ```
103 pub data: R,
104 /// Whether the mDNS cache-flush bit is set
105 /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
106 #[cfg(feature = "mdns")]
107 pub mdns_cache_flush: bool,
108 /// The proof of DNSSEC validation for this record.
109 ///
110 /// This is only valid if some form of validation has occurred.
111 #[cfg(feature = "__dnssec")]
112 pub proof: Proof,
113}
114
115impl Record {
116 #[cfg(test)]
117 pub(crate) fn stub() -> Self {
118 Self {
119 name: Name::from_ascii(".").unwrap(),
120 dns_class: DNSClass::IN,
121 ttl: 0,
122 data: RData::A(A::new(0, 0, 0, 0)),
123 #[cfg(feature = "mdns")]
124 mdns_cache_flush: false,
125 #[cfg(feature = "__dnssec")]
126 proof: Proof::default(),
127 }
128 }
129}
130
131impl Record {
132 /// Creates an update record with RDLENGTH=0
133 pub fn update0(name: Name, ttl: u32, rr_type: RecordType) -> Self {
134 Self {
135 name,
136 dns_class: DNSClass::IN,
137 ttl,
138 data: RData::Update0(rr_type),
139 #[cfg(feature = "mdns")]
140 mdns_cache_flush: false,
141 #[cfg(feature = "__dnssec")]
142 proof: Proof::default(),
143 }
144 }
145
146 /// Tries the borrow this record as the specific record type, T
147 pub fn try_borrow<T>(&self) -> Option<RecordRef<'_, T>>
148 where
149 T: RecordData,
150 {
151 RecordRef::try_from(self).ok()
152 }
153}
154
155impl<R: RecordData> Record<R> {
156 /// Create a record with the specified initial values.
157 ///
158 /// # Arguments
159 ///
160 /// * `name` - name of the resource records
161 /// * `ttl` - time-to-live is the amount of time this record should be cached before refreshing
162 /// * `rdata` - record data to associate with the Record
163 pub fn from_rdata(name: Name, ttl: u32, rdata: R) -> Self {
164 Self {
165 name,
166 dns_class: DNSClass::IN,
167 ttl,
168 data: rdata,
169 #[cfg(feature = "mdns")]
170 mdns_cache_flush: false,
171 #[cfg(feature = "__dnssec")]
172 proof: Proof::default(),
173 }
174 }
175
176 /// Converts this Record into a more specific version of RData
177 pub fn map<N: RecordData>(self, f: impl FnOnce(R) -> Option<N>) -> Option<Record<N>> {
178 let Self {
179 name,
180 dns_class,
181 ttl,
182 data: rdata,
183 #[cfg(feature = "mdns")]
184 mdns_cache_flush,
185 #[cfg(feature = "__dnssec")]
186 proof,
187 } = self;
188
189 Some(Record {
190 name,
191 dns_class,
192 ttl,
193 data: f(rdata)?,
194 #[cfg(feature = "mdns")]
195 mdns_cache_flush,
196 #[cfg(feature = "__dnssec")]
197 proof,
198 })
199 }
200
201 /// Converts this Record into a generic version of RData
202 pub fn into_record_of_rdata(self) -> Record<RData> {
203 let Self {
204 name,
205 dns_class,
206 ttl,
207 data: rdata,
208 #[cfg(feature = "mdns")]
209 mdns_cache_flush,
210 #[cfg(feature = "__dnssec")]
211 proof,
212 } = self;
213
214 let rdata = RecordData::into_rdata(rdata);
215
216 Record {
217 name,
218 dns_class,
219 ttl,
220 data: rdata,
221 #[cfg(feature = "mdns")]
222 mdns_cache_flush,
223 #[cfg(feature = "__dnssec")]
224 proof,
225 }
226 }
227
228 /// Decrement the record TTL by `offset` seconds. If offset is greater than the record TTL,
229 /// the record TTL will be set to 0.
230 pub fn decrement_ttl(&mut self, offset: u32) -> &mut Self {
231 self.ttl = self.ttl.saturating_sub(offset);
232 self
233 }
234
235 /// Returns the type of the RecordData in the record
236 #[inline]
237 pub fn record_type(&self) -> RecordType {
238 self.data.record_type()
239 }
240}
241
242impl<R: RecordData> BinEncodable for Record<R> {
243 fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
244 self.name.emit(encoder)?;
245 self.record_type().emit(encoder)?;
246
247 #[cfg(not(feature = "mdns"))]
248 self.dns_class.emit(encoder)?;
249
250 #[cfg(feature = "mdns")]
251 {
252 if self.mdns_cache_flush {
253 encoder.emit_u16(u16::from(self.dns_class) | MDNS_ENABLE_CACHE_FLUSH)?;
254 } else {
255 self.dns_class.emit(encoder)?;
256 }
257 }
258
259 encoder.emit_u32(self.ttl)?;
260
261 // place the RData length
262 let place = encoder.place::<u16>()?;
263
264 // write the RData
265 // the None case is handled below by writing `0` for the length of the RData
266 // this is in turn read as `None` during the `read` operation.
267 if !self.data.is_update() {
268 self.data.emit(encoder)?;
269 }
270
271 // get the length written
272 let len = encoder.len_since_place(&place);
273 assert!(len <= u16::MAX as usize);
274
275 // replace the location with the length
276 place.replace(encoder, len as u16)?;
277 Ok(())
278 }
279}
280
281impl<'r> BinDecodable<'r> for Record<RData> {
282 /// parse a resource record line example:
283 /// WARNING: the record_bytes is 100% consumed and destroyed in this parsing process
284 fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> {
285 // NAME an owner name, i.e., the name of the node to which this
286 // resource record pertains.
287 let name_labels: Name = Name::read(decoder)?;
288
289 // TYPE two octets containing one of the RR TYPE codes.
290 let record_type: RecordType = RecordType::read(decoder)?;
291
292 #[cfg(feature = "mdns")]
293 let mut mdns_cache_flush = false;
294
295 // CLASS two octets containing one of the RR CLASS codes.
296 let class: DNSClass = if record_type == RecordType::OPT {
297 // verify that the OPT record is Root
298 if !name_labels.is_root() {
299 return Err(DecodeError::EdnsNameNotRoot(Box::new(name_labels)));
300 }
301
302 // DNS Class is overloaded for OPT records in EDNS - RFC 6891
303 DNSClass::for_opt(
304 decoder.read_u16()?.unverified(/*restricted to a min of 512 in for_opt*/),
305 )
306 } else {
307 #[cfg(not(feature = "mdns"))]
308 {
309 DNSClass::read(decoder)?
310 }
311
312 #[cfg(feature = "mdns")]
313 {
314 let dns_class_value =
315 decoder.read_u16()?.unverified(/*DNSClass::from_u16 will verify the value*/);
316 if dns_class_value & MDNS_ENABLE_CACHE_FLUSH > 0 {
317 mdns_cache_flush = true;
318 DNSClass::from(dns_class_value & !MDNS_ENABLE_CACHE_FLUSH)
319 } else {
320 DNSClass::from(dns_class_value)
321 }
322 }
323 };
324
325 // TTL a 32 bit signed integer that specifies the time interval
326 // that the resource record may be cached before the source
327 // of the information should again be consulted. Zero
328 // values are interpreted to mean that the RR can only be
329 // used for the transaction in progress, and should not be
330 // cached. For example, SOA records are always distributed
331 // with a zero TTL to prohibit caching. Zero values can
332 // also be used for extremely volatile data.
333 // note: u32 seems more accurate given that it can only be positive
334 let ttl: u32 = decoder.read_u32()?.unverified(/*any u32 is valid*/);
335
336 // RDLENGTH an unsigned 16 bit integer that specifies the length in
337 // octets of the RDATA field.
338 let rd_length = decoder
339 .read_u16()?
340 .verify_unwrap(|u| (*u as usize) <= decoder.len())
341 .map_err(|u| DecodeError::IncorrectRDataLengthRead {
342 read: decoder.len(),
343 len: u as usize,
344 })?;
345
346 // this is to handle updates, RFC 2136, which uses 0 to indicate certain aspects of pre-requisites.
347 // Null represents any data. The caller should validate whether this is allowed.
348 let rdata = if rd_length == 0 {
349 RData::Update0(record_type)
350 } else {
351 // RDATA a variable length string of octets that describes the
352 // resource. The format of this information varies
353 // according to the TYPE and CLASS of the resource record.
354 // Adding restrict to the rdata length because it's used for many calculations later
355 // and must be validated before hand
356 RData::read(decoder, record_type, Restrict::new(rd_length))?
357 };
358
359 Ok(Self {
360 name: name_labels,
361 dns_class: class,
362 ttl,
363 data: rdata,
364 #[cfg(feature = "mdns")]
365 mdns_cache_flush,
366 #[cfg(feature = "__dnssec")]
367 proof: Proof::default(),
368 })
369 }
370}
371
372/// [RFC 1033](https://tools.ietf.org/html/rfc1033), DOMAIN OPERATIONS GUIDE, November 1987
373///
374/// ```text
375/// RESOURCE RECORDS
376///
377/// Records in the zone data files are called resource records (RRs).
378/// They are specified in RFC-883 and RFC-973. An RR has a standard
379/// format as shown:
380///
381/// <name> [<ttl>] [<class>] <type> <data>
382///
383/// The record is divided into fields which are separated by white space.
384///
385/// <name>
386///
387/// The name field defines what domain name applies to the given
388/// RR. In some cases the name field can be left blank and it will
389/// default to the name field of the previous RR.
390///
391/// <ttl>
392///
393/// TTL stands for Time To Live. It specifies how long a domain
394/// resolver should cache the RR before it throws it out and asks a
395/// domain server again. See the section on TTL's. If you leave
396/// the TTL field blank it will default to the minimum time
397/// specified in the SOA record (described later).
398///
399/// <class>
400///
401/// The class field specifies the protocol group. If left blank it
402/// will default to the last class specified.
403///
404/// <type>
405///
406/// The type field specifies what type of data is in the RR. See
407/// the section on types.
408///
409/// <data>
410///
411/// The data field is defined differently for each type and class
412/// of data. Popular RR data formats are described later.
413/// ```
414impl<R: RecordData> fmt::Display for Record<R> {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
416 write!(
417 f,
418 "{name} {ttl} {class} {ty} {rdata}",
419 name = self.name,
420 ttl = self.ttl,
421 class = self.dns_class,
422 ty = self.record_type(),
423 rdata = self.data,
424 )?;
425
426 Ok(())
427 }
428}
429
430impl<R: RecordData> PartialEq for Record<R> {
431 /// Equality of records, as defined by
432 /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
433 ///
434 /// ```text
435 /// 1.1.1. Two RRs are considered equal if their NAME, CLASS, TYPE,
436 /// RDLENGTH and RDATA fields are equal. Note that the time-to-live
437 /// (TTL) field is explicitly excluded from the comparison.
438 ///
439 /// 1.1.2. The rules for comparison of character strings in names are
440 /// specified in [RFC1035 2.3.3]. i.e. case insensitive
441 /// ```
442 fn eq(&self, other: &Self) -> bool {
443 // self == other && // the same pointer
444 self.name == other.name && self.dns_class == other.dns_class && self.data == other.data
445 }
446}
447
448/// returns the value of the compare if the items are greater or lesser, but continues on equal
449macro_rules! compare_or_equal {
450 ($x:ident, $y:ident, $z:ident) => {
451 match ($x).$z.cmp(&($y).$z) {
452 o @ Ordering::Less | o @ Ordering::Greater => return o,
453 Ordering::Equal => (),
454 }
455 };
456}
457
458impl Ord for Record {
459 /// Canonical ordering as defined by
460 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
461 ///
462 /// ```text
463 /// 6.2. Canonical RR Form
464 ///
465 /// For the purposes of DNS security, the canonical form of an RR is the
466 /// wire format of the RR where:
467 ///
468 /// 1. every domain name in the RR is fully expanded (no DNS name
469 /// compression) and fully qualified;
470 ///
471 /// 2. all uppercase US-ASCII letters in the owner name of the RR are
472 /// replaced by the corresponding lowercase US-ASCII letters;
473 ///
474 /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
475 /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
476 /// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
477 /// the DNS names contained within the RDATA are replaced by the
478 /// corresponding lowercase US-ASCII letters;
479 ///
480 /// 4. if the owner name of the RR is a wildcard name, the owner name is
481 /// in its original unexpanded form, including the "*" label (no
482 /// wildcard substitution); and
483 ///
484 /// 5. the RR's TTL is set to its original value as it appears in the
485 /// originating authoritative zone or the Original TTL field of the
486 /// covering RRSIG RR.
487 /// ```
488 fn cmp(&self, other: &Self) -> Ordering {
489 // TODO: given that the ordering of Resource Records is dependent on it's binary form and this
490 // method will be used during insertion sort or similar, we should probably do this
491 // conversion once somehow and store it separately. Or should the internal storage of all
492 // resource records be maintained in binary?
493
494 compare_or_equal!(self, other, name);
495 match self.record_type().cmp(&other.record_type()) {
496 o @ Ordering::Less | o @ Ordering::Greater => return o,
497 Ordering::Equal => {}
498 }
499 compare_or_equal!(self, other, dns_class);
500 compare_or_equal!(self, other, ttl);
501 compare_or_equal!(self, other, data);
502 Ordering::Equal
503 }
504}
505
506impl PartialOrd<Self> for Record {
507 /// Canonical ordering as defined by
508 /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
509 ///
510 /// ```text
511 /// 6.2. Canonical RR Form
512 ///
513 /// For the purposes of DNS security, the canonical form of an RR is the
514 /// wire format of the RR where:
515 ///
516 /// 1. every domain name in the RR is fully expanded (no DNS name
517 /// compression) and fully qualified;
518 ///
519 /// 2. all uppercase US-ASCII letters in the owner name of the RR are
520 /// replaced by the corresponding lowercase US-ASCII letters;
521 ///
522 /// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
523 /// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
524 /// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
525 /// the DNS names contained within the RDATA are replaced by the
526 /// corresponding lowercase US-ASCII letters;
527 ///
528 /// 4. if the owner name of the RR is a wildcard name, the owner name is
529 /// in its original unexpanded form, including the "*" label (no
530 /// wildcard substitution); and
531 ///
532 /// 5. the RR's TTL is set to its original value as it appears in the
533 /// originating authoritative zone or the Original TTL field of the
534 /// covering RRSIG RR.
535 /// ```
536 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
537 Some(self.cmp(other))
538 }
539}
540
541#[cfg(feature = "__dnssec")]
542impl From<Record> for Proven<Record> {
543 fn from(record: Record) -> Self {
544 let proof = record.proof;
545 Self::new(proof, record)
546 }
547}
548
549#[cfg(feature = "__dnssec")]
550impl<'a> From<&'a Record> for Proven<&'a Record> {
551 fn from(record: &'a Record) -> Self {
552 let proof = record.proof;
553 Self::new(proof, record)
554 }
555}
556
557/// A Record where the RecordData type is already known
558pub struct RecordRef<'a, R: RecordData> {
559 name: &'a Name,
560 dns_class: DNSClass,
561 ttl: u32,
562 data: &'a R,
563 #[cfg(feature = "mdns")]
564 mdns_cache_flush: bool,
565 #[cfg(feature = "__dnssec")]
566 proof: Proof,
567}
568
569impl<R: RecordData> Clone for RecordRef<'_, R> {
570 fn clone(&self) -> Self {
571 *self
572 }
573}
574
575impl<R: RecordData> Copy for RecordRef<'_, R> {}
576
577impl<R: RecordData> RecordRef<'_, R> {
578 /// Allocates space for a Record with the same fields
579 pub fn to_owned(&self) -> Record<R> {
580 Record {
581 name: self.name.to_owned(),
582 dns_class: self.dns_class,
583 ttl: self.ttl,
584 data: self.data.clone(),
585 #[cfg(feature = "mdns")]
586 mdns_cache_flush: self.mdns_cache_flush,
587 #[cfg(feature = "__dnssec")]
588 proof: self.proof,
589 }
590 }
591
592 /// Returns the name of the record
593 #[inline]
594 pub fn name(&self) -> &Name {
595 self.name
596 }
597
598 /// Returns the type of the RecordData in the record
599 #[inline]
600 pub fn record_type(&self) -> RecordType {
601 self.data.record_type()
602 }
603
604 /// Returns the DNSClass of the Record, generally IN fro internet
605 #[inline]
606 pub fn dns_class(&self) -> DNSClass {
607 self.dns_class
608 }
609
610 /// Returns the time-to-live of the record, for caching purposes
611 #[inline]
612 pub fn ttl(&self) -> u32 {
613 self.ttl
614 }
615
616 /// Returns the Record Data, i.e. the record information
617 #[inline]
618 pub fn data(&self) -> &R {
619 self.data
620 }
621
622 /// Returns if the mDNS cache-flush bit is set or not
623 /// See [RFC 6762](https://tools.ietf.org/html/rfc6762#section-10.2)
624 #[cfg(feature = "mdns")]
625 #[inline]
626 pub fn mdns_cache_flush(&self) -> bool {
627 self.mdns_cache_flush
628 }
629
630 /// The Proof of DNSSEC validation for this record, this is only valid if some form of validation has occurred
631 #[cfg(feature = "__dnssec")]
632 #[inline]
633 pub fn proof(&self) -> Proof {
634 self.proof
635 }
636}
637
638impl<'a, R: RecordData> TryFrom<&'a Record> for RecordRef<'a, R> {
639 type Error = &'a Record;
640
641 fn try_from(record: &'a Record) -> Result<Self, Self::Error> {
642 let Record {
643 name,
644 dns_class,
645 ttl,
646 data: rdata,
647 #[cfg(feature = "mdns")]
648 mdns_cache_flush,
649 #[cfg(feature = "__dnssec")]
650 proof,
651 } = record;
652
653 match R::try_borrow(rdata) {
654 None => Err(record),
655 Some(rdata) => Ok(Self {
656 name,
657 dns_class: *dns_class,
658 ttl: *ttl,
659 data: rdata,
660 #[cfg(feature = "mdns")]
661 mdns_cache_flush: *mdns_cache_flush,
662 #[cfg(feature = "__dnssec")]
663 proof: *proof,
664 }),
665 }
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 #![allow(clippy::dbg_macro, clippy::print_stdout)]
672
673 use alloc::vec::Vec;
674 use core::cmp::Ordering;
675 use core::str::FromStr;
676 #[cfg(feature = "std")]
677 use std::println;
678
679 use super::*;
680 use crate::rr::Name;
681 use crate::rr::dns_class::DNSClass;
682 use crate::rr::rdata::{A, AAAA};
683 use crate::rr::record_data::RData;
684
685 #[test]
686 fn test_emit_and_read() {
687 let record = Record::from_rdata(
688 Name::from_str("www.example.com.").unwrap(),
689 5,
690 RData::A(A::new(192, 168, 0, 1)),
691 );
692
693 let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
694 {
695 let mut encoder = BinEncoder::new(&mut vec_bytes);
696 record.emit(&mut encoder).unwrap();
697 }
698
699 let mut decoder = BinDecoder::new(&vec_bytes);
700
701 let got = Record::read(&mut decoder).unwrap();
702
703 assert_eq!(got, record);
704 }
705
706 #[test]
707 fn test_order() {
708 let record = Record::from_rdata(
709 Name::from_str("www.example.com").unwrap(),
710 5,
711 RData::A(A::new(192, 168, 0, 1)),
712 );
713
714 let mut greater_name = record.clone();
715 greater_name.name = Name::from_str("zzz.example.com").unwrap();
716
717 let mut greater_type = record.clone().into_record_of_rdata();
718 greater_type.data = RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 0));
719
720 let mut greater_class = record.clone();
721 greater_class.dns_class = DNSClass::NONE;
722
723 let mut greater_rdata = record.clone();
724 greater_rdata.data = RData::A(A::new(192, 168, 0, 255));
725
726 let compares = vec![
727 (&record, &greater_name),
728 (&record, &greater_type),
729 (&record, &greater_class),
730 (&record, &greater_rdata),
731 ];
732
733 assert_eq!(record.clone(), record.clone());
734 for (r, g) in compares {
735 #[cfg(feature = "std")]
736 println!("r, g: {r:?}, {g:?}");
737 assert_eq!(r.cmp(g), Ordering::Less);
738 }
739 }
740
741 #[cfg(feature = "mdns")]
742 #[test]
743 fn test_mdns_cache_flush_bit_handling() {
744 const RR_CLASS_OFFSET: usize = 1 /* empty name */ +
745 size_of::<u16>() /* rr_type */;
746
747 let mut record = Record::<RData>::stub();
748 record.mdns_cache_flush = true;
749
750 let mut vec_bytes: Vec<u8> = Vec::with_capacity(512);
751 {
752 let mut encoder = BinEncoder::new(&mut vec_bytes);
753 record.emit(&mut encoder).unwrap();
754
755 let rr_class_slice = encoder.slice_of(RR_CLASS_OFFSET, RR_CLASS_OFFSET + 2);
756 assert_eq!(rr_class_slice, &[0x80, 0x01]);
757 }
758
759 let mut decoder = BinDecoder::new(&vec_bytes);
760
761 let got = Record::<RData>::read(&mut decoder).unwrap();
762
763 assert_eq!(got.dns_class, DNSClass::IN);
764 assert!(got.mdns_cache_flush);
765 }
766}