Skip to main content

hickory_proto/rr/
rr_set.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
8use alloc::vec;
9use alloc::vec::Vec;
10use core::{iter::Chain, slice::Iter};
11use tracing::{info, warn};
12
13use crate::rr::{DNSClass, Name, RData, Record, RecordType};
14
15/// Set of resource records associated to a name and type
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct RecordSet {
18    name: Name,
19    record_type: RecordType,
20    dns_class: DNSClass,
21    ttl: u32,
22    records: Vec<Record>,
23    rrsigs: Vec<Record>,
24    serial: u32, // serial number at which this record was modified
25}
26
27impl RecordSet {
28    /// Creates a new Resource Record Set.
29    ///
30    /// # Arguments
31    ///
32    /// * `name` - The label for the `RecordSet`
33    /// * `record_type` - `RecordType` of this `RecordSet`, all records in the `RecordSet` must be of the
34    ///   specified `RecordType`.
35    /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
36    ///   signing for DNSSEC after updates.
37    ///
38    /// # Return value
39    ///
40    /// The newly created Resource Record Set
41    pub fn new(name: Name, record_type: RecordType, serial: u32) -> Self {
42        Self {
43            name,
44            record_type,
45            dns_class: DNSClass::IN,
46            ttl: 0,
47            records: Vec::new(),
48            rrsigs: Vec::new(),
49            serial,
50        }
51    }
52
53    /// Creates a new Resource Record Set.
54    ///
55    /// # Arguments
56    ///
57    /// * `name` - The label for the `RecordSet`
58    /// * `record_type` - `RecordType` of this `RecordSet`, all records in the `RecordSet` must be of the
59    ///   specified `RecordType`.
60    /// * `ttl` - time-to-live for the `RecordSet` in seconds.
61    ///
62    /// # Return value
63    ///
64    /// The newly created Resource Record Set
65    pub fn with_ttl(name: Name, record_type: RecordType, ttl: u32) -> Self {
66        Self {
67            name,
68            record_type,
69            dns_class: DNSClass::IN,
70            ttl,
71            records: Vec::new(),
72            rrsigs: Vec::new(),
73            serial: 0,
74        }
75    }
76
77    /// # Return value
78    ///
79    /// Label of the Resource Record Set
80    pub fn name(&self) -> &Name {
81        &self.name
82    }
83
84    /// # Return value
85    ///
86    /// `RecordType` of the Resource Record Set
87    pub fn record_type(&self) -> RecordType {
88        self.record_type
89    }
90
91    /// Sets the DNSClass to the specified value
92    ///
93    /// This will traverse every record and associate with it the specified dns_class
94    pub fn set_dns_class(&mut self, dns_class: DNSClass) {
95        self.dns_class = dns_class;
96        for r in &mut self.records {
97            r.dns_class = dns_class;
98        }
99    }
100
101    /// Returns the `DNSClass` of the RecordSet
102    pub fn dns_class(&self) -> DNSClass {
103        self.dns_class
104    }
105
106    /// Sets the TTL, in seconds, to the specified value
107    ///
108    /// This will traverse every record and associate with it the specified ttl
109    pub fn set_ttl(&mut self, ttl: u32) {
110        self.ttl = ttl;
111        for r in &mut self.records {
112            r.ttl = ttl;
113        }
114    }
115
116    /// Returns the time-to-live for the record.
117    ///
118    /// # Return value
119    ///
120    /// TTL, time-to-live, of the Resource Record Set, this is the maximum length of time that an
121    /// RecordSet should be cached.
122    pub fn ttl(&self) -> u32 {
123        self.ttl
124    }
125
126    /// Set the records of the RecordSet
127    ///
128    /// # Arguments
129    ///
130    /// * `records` - `self.records` will be replaced with this value
131    pub fn set_records(&mut self, records: Vec<Record>) {
132        self.records = records;
133    }
134
135    /// Set the RRSIGs of the RecordSet
136    ///
137    /// # Arguments
138    ///
139    /// * `rrsigs` - `self.rrsigs` will be replaced with this value
140    pub fn set_rrsigs(&mut self, rrsigs: Vec<Record>) {
141        self.rrsigs = rrsigs;
142    }
143
144    /// return the first record of the RecordSet
145    pub fn record(&self) -> Option<&Record> {
146        self.records.first()
147    }
148
149    /// Return the number of records in the RecordSet
150    pub fn records_count(&self) -> usize {
151        self.records.len()
152    }
153
154    /// Returns a Vec of all records in the set.
155    ///
156    /// # Arguments
157    ///
158    /// * `and_rrsigs` - if true, RRSIGs will be returned if they exist
159    #[cfg(feature = "__dnssec")]
160    pub fn records(&self, and_rrsigs: bool) -> RrsetRecords<'_> {
161        if and_rrsigs {
162            self.records_with_rrsigs()
163        } else {
164            self.records_without_rrsigs()
165        }
166    }
167
168    /// Returns a Vec of all records in the set, with RRSIGs, if present.
169    #[cfg(feature = "__dnssec")]
170    pub fn records_with_rrsigs(&self) -> RrsetRecords<'_> {
171        if self.records.is_empty() {
172            RrsetRecords::Empty
173        } else {
174            RrsetRecords::RecordsAndRrsigs(RecordsAndRrsigsIter(
175                self.records.iter().chain(self.rrsigs.iter()),
176            ))
177        }
178    }
179
180    /// Returns a Vec of all records in the set, without any RRSIGs.
181    pub fn records_without_rrsigs(&self) -> RrsetRecords<'_> {
182        if self.records.is_empty() {
183            RrsetRecords::Empty
184        } else {
185            RrsetRecords::RecordsOnly(self.records.iter())
186        }
187    }
188
189    /// Returns true if there are no records in this set
190    pub fn is_empty(&self) -> bool {
191        self.records.is_empty()
192    }
193
194    /// Returns the serial number at which the record was updated.
195    pub fn serial(&self) -> u32 {
196        self.serial
197    }
198
199    /// Returns a slice of all the Records signatures in the RecordSet
200    pub fn rrsigs(&self) -> &[Record] {
201        &self.rrsigs
202    }
203
204    /// Inserts a Signature for the Record set
205    ///
206    /// Many can be associated with the RecordSet. Once added, the RecordSet should not be changed
207    ///
208    /// # Arguments
209    ///
210    /// * `rrsig` - A signature which covers the RecordSet.
211    pub fn insert_rrsig(&mut self, rrsig: Record) {
212        self.rrsigs.push(rrsig)
213    }
214
215    /// Useful for clearing all signatures when the RecordSet is updated, or keys are rotated.
216    pub fn clear_rrsigs(&mut self) {
217        self.rrsigs.clear()
218    }
219
220    fn updated(&mut self, serial: u32) {
221        self.serial = serial;
222        self.rrsigs.clear(); // on updates, the rrsigs are invalid
223    }
224
225    /// creates a new Record as part of this RecordSet, adding the associated RData
226    ///
227    /// this interface may be deprecated in the future.
228    pub fn new_record(&mut self, rdata: &RData) -> &Record {
229        self.add_rdata(rdata.clone());
230
231        self.records
232            .iter()
233            .find(|r| &r.data == rdata)
234            .expect("insert failed")
235    }
236
237    /// creates a new Record as part of this RecordSet, adding the associated RData
238    pub fn add_rdata(&mut self, rdata: RData) -> bool {
239        debug_assert_eq!(self.record_type, rdata.record_type());
240
241        let record = Record::from_rdata(self.name.clone(), self.ttl, rdata);
242        self.insert(record, 0)
243    }
244
245    /// Inserts a new Resource Record into the Set.
246    ///
247    /// If the record is inserted, the ttl for the most recent record will be used for the ttl of
248    /// the entire resource record set.
249    ///
250    /// This abides by the following restrictions in RFC 2136, April 1997:
251    ///
252    /// ```text
253    /// 1.1.5. The following RR types cannot be appended to an RRset.  If the
254    ///  following comparison rules are met, then an attempt to add the new RR
255    ///  will result in the replacement of the previous RR:
256    ///
257    /// SOA    compare only NAME, CLASS and TYPE -- it is not possible to
258    ///         have more than one SOA per zone, even if any of the data
259    ///         fields differ.
260    ///
261    /// CNAME  compare only NAME, CLASS, and TYPE -- it is not possible
262    ///         to have more than one CNAME RR, even if their data fields
263    ///         differ.
264    /// ```
265    ///
266    /// # Arguments
267    ///
268    /// * `record` - `Record` asserts that the `name` and `record_type` match the `RecordSet`.
269    /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
270    ///   signing for DNSSEC after updates. The serial will only be updated if the
271    ///   record was added.
272    ///
273    /// # Return value
274    ///
275    /// True if the record was inserted.
276    ///
277    /// TODO: make a default add without serial number for basic usage
278    pub fn insert(&mut self, record: Record, serial: u32) -> bool {
279        assert_eq!(&record.name, &self.name);
280        assert_eq!(record.record_type(), self.record_type);
281
282        // RFC 2136                       DNS Update                     April 1997
283        //
284        // 1.1.5. The following RR types cannot be appended to an RRset.  If the
285        //  following comparison rules are met, then an attempt to add the new RR
286        //  will result in the replacement of the previous RR:
287        match record.record_type() {
288            // SOA    compare only NAME, CLASS and TYPE -- it is not possible to
289            //         have more than one SOA per zone, even if any of the data
290            //         fields differ.
291            RecordType::SOA => {
292                assert!(self.records.len() <= 1);
293
294                if let Some(soa_record) = self.records.first() {
295                    match &soa_record.data {
296                        RData::SOA(existing_soa) => {
297                            if let RData::SOA(new_soa) = &record.data {
298                                if new_soa.serial <= existing_soa.serial {
299                                    info!(
300                                        "update ignored serial out of data: {:?} <= {:?}",
301                                        new_soa, existing_soa
302                                    );
303                                    return false;
304                                }
305                            } else {
306                                // not panicking here, b/c this is a bad record from the client or something, ignore
307                                info!("wrong rdata for SOA update: {:?}", record.data);
308                                return false;
309                            }
310                        }
311                        rdata => {
312                            warn!("wrong rdata: {:?}, expected SOA", rdata);
313                            return false;
314                        }
315                    }
316                }
317
318                // if we got here, we're updating...
319                self.records.clear();
320            }
321            // RFC 1034/1035
322            // CNAME  compare only NAME, CLASS, and TYPE -- it is not possible
323            //         to have more than one CNAME RR, even if their data fields
324            //         differ.
325            //
326            // ANAME https://tools.ietf.org/html/draft-ietf-dnsop-aname-04
327            //    2.2.  Coexistence with other types
328            //
329            //   Only one ANAME <target> can be defined per <owner>.  An ANAME RRset
330            //   MUST NOT contain more than one resource record.
331            //
332            //   An ANAME's sibling address records are under the control of ANAME
333            //   processing (see Section 4) and are not first-class records in their
334            //   own right.  They MAY exist in zone files, but they can subsequently
335            //   be altered by ANAME processing.
336            //
337            //   An ANAME record MAY freely coexist at the same owner name with other
338            //   RR types, except they MUST NOT coexist with CNAME or any other RR
339            //   type that restricts the types with which it can itself coexist. That
340            //   means An ANAME record can coexist at the same owner name with A and
341            //   AAAA records.  These are the sibling address records that are updated
342            //   with the target addresses that are retrieved through the ANAME
343            //   substitution process Section 3.
344            //
345            //   Like other types, An ANAME record can coexist with DNAME records at
346            //   the same owner name; in fact, the two can be used cooperatively to
347            //   redirect both the owner name address records (via ANAME) and
348            //   everything under it (via DNAME).
349            RecordType::CNAME | RecordType::ANAME => {
350                assert!(self.records.len() <= 1);
351                self.records.clear();
352            }
353            _ => (),
354        }
355
356        // collect any records to update based on rdata
357        let to_replace: Vec<usize> = self
358            .records
359            .iter()
360            .enumerate()
361            .filter(|&(_, rr)| rr.data == record.data)
362            .map(|(i, _)| i)
363            .collect::<Vec<usize>>();
364
365        // if the Records are identical, ignore the update, update all that are not (ttl, etc.)
366        let mut replaced = false;
367        for i in to_replace {
368            if self.records[i] == record {
369                return false;
370            }
371
372            // TODO: this shouldn't really need a clone since there should only be one...
373            self.records.push(record.clone());
374            self.records.swap_remove(i);
375            self.ttl = record.ttl;
376            self.updated(serial);
377            replaced = true;
378        }
379
380        if !replaced {
381            self.ttl = record.ttl;
382            self.updated(serial);
383            self.records.push(record);
384            true
385        } else {
386            replaced
387        }
388    }
389
390    /// Removes the Resource Record if it exists.
391    ///
392    /// # Arguments
393    ///
394    /// * `record` - `Record` asserts that the `name` and `record_type` match the `RecordSet`. Removes
395    ///   any `record` if the record data, `RData`, match.
396    /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
397    ///   signing for DNSSEC after updates. The serial will only be updated if the
398    ///   record was added.
399    ///
400    /// # Return value
401    ///
402    /// True if a record was removed.
403    pub fn remove(&mut self, record: &Record, serial: u32) -> bool {
404        assert_eq!(record.name, self.name);
405        assert!(
406            record.record_type() == self.record_type || record.record_type() == RecordType::ANY
407        );
408
409        match record.record_type() {
410            // never delete the last NS record
411            RecordType::NS if self.records.len() <= 1 => {
412                info!("ignoring delete of last NS record: {:?}", record);
413                return false;
414            }
415            // never delete SOA
416            RecordType::SOA => {
417                info!("ignored delete of SOA");
418                return false;
419            }
420            _ => (), // move on to the delete
421        }
422
423        // remove the records
424        let old_size = self.records.len();
425        self.records.retain(|rr| rr.data != record.data);
426        let removed = self.records.len() < old_size;
427
428        if removed {
429            self.updated(serial);
430        }
431
432        removed
433    }
434
435    /// Consumes `RecordSet` and returns its components
436    pub fn into_parts(self) -> RecordSetParts {
437        self.into()
438    }
439}
440
441/// Consumes `RecordSet` giving public access to fields of `RecordSet` so they can
442/// be destructured and taken by value
443#[derive(Clone, Debug, PartialEq, Eq)]
444pub struct RecordSetParts {
445    /// Name for this record set
446    pub name: Name,
447    /// Type for this record set
448    pub record_type: RecordType,
449    /// DNS class for this record set
450    pub dns_class: DNSClass,
451    /// Time to live for this record set
452    pub ttl: u32,
453    /// Records in this record set
454    pub records: Vec<Record>,
455    /// RRSIGs for this record set
456    pub rrsigs: Vec<Record>,
457    /// Serial number at which this record was modified
458    pub serial: u32,
459}
460
461impl From<RecordSet> for RecordSetParts {
462    fn from(rset: RecordSet) -> Self {
463        let RecordSet {
464            name,
465            record_type,
466            dns_class,
467            ttl,
468            records,
469            rrsigs,
470            serial,
471        } = rset;
472        Self {
473            name,
474            record_type,
475            dns_class,
476            ttl,
477            records,
478            rrsigs,
479            serial,
480        }
481    }
482}
483
484impl From<Record> for RecordSet {
485    fn from(record: Record) -> Self {
486        Self {
487            name: record.name.clone(),
488            record_type: record.record_type(),
489            dns_class: record.dns_class,
490            ttl: record.ttl,
491            records: vec![record],
492            rrsigs: vec![],
493            serial: 0,
494        }
495    }
496}
497
498impl IntoIterator for RecordSet {
499    type Item = Record;
500    type IntoIter = Chain<vec::IntoIter<Record>, vec::IntoIter<Record>>;
501
502    fn into_iter(self) -> Self::IntoIter {
503        self.records.into_iter().chain(self.rrsigs)
504    }
505}
506
507/// An iterator over all the records and their signatures
508#[cfg(feature = "__dnssec")]
509#[derive(Debug)]
510pub struct RecordsAndRrsigsIter<'r>(Chain<Iter<'r, Record>, Iter<'r, Record>>);
511
512#[cfg(feature = "__dnssec")]
513impl<'r> Iterator for RecordsAndRrsigsIter<'r> {
514    type Item = &'r Record;
515
516    fn next(&mut self) -> Option<Self::Item> {
517        self.0.next()
518    }
519}
520
521/// An iterator over the RecordSet data
522#[derive(Debug)]
523pub enum RrsetRecords<'r> {
524    /// There are no records in the record set
525    Empty,
526    /// The records associated with the record set
527    RecordsOnly(Iter<'r, Record>),
528    /// The records along with their signatures in the record set
529    #[cfg(feature = "__dnssec")]
530    RecordsAndRrsigs(RecordsAndRrsigsIter<'r>),
531}
532
533impl RrsetRecords<'_> {
534    /// This is a best effort emptiness check
535    pub fn is_empty(&self) -> bool {
536        matches!(*self, RrsetRecords::Empty)
537    }
538}
539
540impl<'r> Iterator for RrsetRecords<'r> {
541    type Item = &'r Record;
542
543    fn next(&mut self) -> Option<Self::Item> {
544        match self {
545            RrsetRecords::Empty => None,
546            RrsetRecords::RecordsOnly(i) => i.next(),
547            #[cfg(feature = "__dnssec")]
548            RrsetRecords::RecordsAndRrsigs(i) => i.next(),
549        }
550    }
551}
552
553#[cfg(test)]
554mod test {
555    use core::net::Ipv4Addr;
556    use core::str::FromStr;
557
558    use crate::rr::rdata::{CNAME, NS, SOA};
559    use crate::rr::*;
560
561    #[test]
562    fn test_insert() {
563        let name = Name::from_str("www.example.com.").unwrap();
564        let record_type = RecordType::A;
565        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
566
567        let insert = Record::from_rdata(
568            name.clone(),
569            86400,
570            RData::A(Ipv4Addr::new(93, 184, 216, 24).into()),
571        );
572
573        assert!(rr_set.insert(insert.clone(), 0));
574        assert_eq!(rr_set.records_without_rrsigs().count(), 1);
575        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
576
577        // dups ignored
578        assert!(!rr_set.insert(insert.clone(), 0));
579        assert_eq!(rr_set.records_without_rrsigs().count(), 1);
580        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
581
582        // add one
583        let insert1 = Record::from_rdata(
584            name,
585            86400,
586            RData::A(Ipv4Addr::new(93, 184, 216, 25).into()),
587        );
588        assert!(rr_set.insert(insert1.clone(), 0));
589        assert_eq!(rr_set.records_without_rrsigs().count(), 2);
590        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
591        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert1));
592    }
593
594    #[test]
595    #[allow(clippy::unreadable_literal)]
596    fn test_insert_soa() {
597        let name = Name::from_str("example.com.").unwrap();
598        let record_type = RecordType::SOA;
599        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
600
601        let insert = Record::from_rdata(
602            name.clone(),
603            3600,
604            RData::SOA(SOA::new(
605                Name::from_str("sns.dns.icann.org.").unwrap(),
606                Name::from_str("noc.dns.icann.org.").unwrap(),
607                2015082403,
608                7200,
609                3600,
610                1209600,
611                3600,
612            )),
613        );
614        let same_serial = Record::from_rdata(
615            name.clone(),
616            3600,
617            RData::SOA(SOA::new(
618                Name::from_str("sns.dns.icann.net.").unwrap(),
619                Name::from_str("noc.dns.icann.net.").unwrap(),
620                2015082403,
621                7200,
622                3600,
623                1209600,
624                3600,
625            )),
626        );
627        let new_serial = Record::from_rdata(
628            name,
629            3600,
630            RData::SOA(SOA::new(
631                Name::from_str("sns.dns.icann.net.").unwrap(),
632                Name::from_str("noc.dns.icann.net.").unwrap(),
633                2015082404,
634                7200,
635                3600,
636                1209600,
637                3600,
638            )),
639        );
640
641        assert!(rr_set.insert(insert.clone(), 0));
642        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
643        // same serial number
644        assert!(!rr_set.insert(same_serial.clone(), 0));
645        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
646        assert!(!rr_set.records_without_rrsigs().any(|x| x == &same_serial));
647
648        assert!(rr_set.insert(new_serial.clone(), 0));
649        assert!(!rr_set.insert(same_serial.clone(), 0));
650        assert!(!rr_set.insert(insert.clone(), 0));
651
652        assert!(rr_set.records_without_rrsigs().any(|x| x == &new_serial));
653        assert!(!rr_set.records_without_rrsigs().any(|x| x == &insert));
654        assert!(!rr_set.records_without_rrsigs().any(|x| x == &same_serial));
655    }
656
657    #[test]
658    fn test_insert_cname() {
659        let name = Name::from_str("web.example.com.").unwrap();
660        let cname = Name::from_str("www.example.com.").unwrap();
661        let new_cname = Name::from_str("w2.example.com.").unwrap();
662
663        let record_type = RecordType::CNAME;
664        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
665
666        let insert = Record::from_rdata(name.clone(), 3600, RData::CNAME(CNAME(cname)));
667        let new_record = Record::from_rdata(name, 3600, RData::CNAME(CNAME(new_cname)));
668
669        assert!(rr_set.insert(insert.clone(), 0));
670        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
671
672        // update the record
673        assert!(rr_set.insert(new_record.clone(), 0));
674        assert!(!rr_set.records_without_rrsigs().any(|x| x == &insert));
675        assert!(rr_set.records_without_rrsigs().any(|x| x == &new_record));
676    }
677
678    #[test]
679    fn test_remove() {
680        let name = Name::from_str("www.example.com.").unwrap();
681        let record_type = RecordType::A;
682        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
683
684        let insert = Record::from_rdata(
685            name.clone(),
686            86400,
687            RData::A(Ipv4Addr::new(93, 184, 216, 24).into()),
688        );
689        let insert1 = Record::from_rdata(
690            name,
691            86400,
692            RData::A(Ipv4Addr::new(93, 184, 216, 25).into()),
693        );
694
695        assert!(rr_set.insert(insert.clone(), 0));
696        assert!(rr_set.insert(insert1.clone(), 0));
697
698        assert!(rr_set.remove(&insert, 0));
699        assert!(!rr_set.remove(&insert, 0));
700        assert!(rr_set.remove(&insert1, 0));
701        assert!(!rr_set.remove(&insert1, 0));
702    }
703
704    #[test]
705    #[allow(clippy::unreadable_literal)]
706    fn test_remove_soa() {
707        let name = Name::from_str("www.example.com.").unwrap();
708        let record_type = RecordType::SOA;
709        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
710
711        let insert = Record::from_rdata(
712            name,
713            3600,
714            RData::SOA(SOA::new(
715                Name::from_str("sns.dns.icann.org.").unwrap(),
716                Name::from_str("noc.dns.icann.org.").unwrap(),
717                2015082403,
718                7200,
719                3600,
720                1209600,
721                3600,
722            )),
723        );
724
725        assert!(rr_set.insert(insert.clone(), 0));
726        assert!(!rr_set.remove(&insert, 0));
727        assert!(rr_set.records_without_rrsigs().any(|x| x == &insert));
728    }
729
730    #[test]
731    fn test_remove_ns() {
732        let name = Name::from_str("example.com.").unwrap();
733        let record_type = RecordType::NS;
734        let mut rr_set = RecordSet::new(name.clone(), record_type, 0);
735
736        let ns1 = Record::from_rdata(
737            name.clone(),
738            86400,
739            RData::NS(NS(Name::from_str("a.iana-servers.net.").unwrap())),
740        );
741        let ns2 = Record::from_rdata(
742            name,
743            86400,
744            RData::NS(NS(Name::from_str("b.iana-servers.net.").unwrap())),
745        );
746
747        assert!(rr_set.insert(ns1.clone(), 0));
748        assert!(rr_set.insert(ns2.clone(), 0));
749
750        // ok to remove one, but not two...
751        assert!(rr_set.remove(&ns1, 0));
752        assert!(!rr_set.remove(&ns2, 0));
753
754        // check that we can swap which ones are removed
755        assert!(rr_set.insert(ns1.clone(), 0));
756
757        assert!(rr_set.remove(&ns2, 0));
758        assert!(!rr_set.remove(&ns1, 0));
759    }
760}