Skip to main content

c_ares/dns/
record.rs

1use std::ffi::CString;
2use std::fmt;
3use std::ptr;
4
5use crate::error::{Error, Result};
6use crate::string::AresBuf;
7use crate::utils::{dns_string_as_str, status_to_result};
8
9use super::enums::{
10    DnsCls, DnsFlags, DnsOpcode, DnsParseFlags, DnsRcode, DnsRecordType, DnsSection,
11};
12use super::rr::DnsRr;
13
14/// An owned DNS record (message), wrapping `ares_dns_record_t`.
15///
16/// A `DnsRecord` represents a complete DNS message including header fields,
17/// question section, and resource record sections (answer, authority,
18/// additional).
19///
20/// Created by parsing wire-format data with [`DnsRecord::parse()`].
21pub struct DnsRecord {
22    dnsrec: *mut c_ares_sys::ares_dns_record_t,
23}
24
25impl DnsRecord {
26    /// Create a `DnsRecord` from a raw pointer.
27    ///
28    /// # Safety
29    ///
30    /// The pointer must have been allocated by c-ares (e.g. via
31    /// `ares_dns_record_create` or `ares_dns_record_duplicate`) and the caller
32    /// transfers ownership to this `DnsRecord`.
33    pub(crate) unsafe fn from_raw(dnsrec: *mut c_ares_sys::ares_dns_record_t) -> DnsRecord {
34        DnsRecord { dnsrec }
35    }
36
37    /// Returns the raw pointer for use in FFI calls.
38    pub(crate) fn as_raw(&self) -> *const c_ares_sys::ares_dns_record_t {
39        self.dnsrec
40    }
41
42    /// Parse a complete DNS message from wire-format bytes.
43    ///
44    /// `flags` controls parsing behaviour; use `DnsParseFlags::empty()` for
45    /// default parsing.
46    pub fn parse(data: &[u8], flags: DnsParseFlags) -> Result<DnsRecord> {
47        let mut dnsrec: *mut c_ares_sys::ares_dns_record_t = ptr::null_mut();
48        let status = unsafe {
49            c_ares_sys::ares_dns_parse(data.as_ptr(), data.len(), flags.bits(), &raw mut dnsrec)
50        };
51        status_to_result(status)?;
52        Ok(DnsRecord { dnsrec })
53    }
54
55    /// Returns the DNS query ID.
56    pub fn id(&self) -> u16 {
57        unsafe { c_ares_sys::ares_dns_record_get_id(self.dnsrec) }
58    }
59
60    /// Returns the DNS record flags.
61    pub fn flags(&self) -> DnsFlags {
62        let raw = unsafe { c_ares_sys::ares_dns_record_get_flags(self.dnsrec) };
63        DnsFlags::from_bits_truncate(raw)
64    }
65
66    /// Returns the DNS record opcode.
67    pub fn opcode(&self) -> DnsOpcode {
68        let raw = unsafe { c_ares_sys::ares_dns_record_get_opcode(self.dnsrec) };
69        DnsOpcode::from(raw)
70    }
71
72    /// Returns the DNS record rcode.
73    pub fn rcode(&self) -> DnsRcode {
74        let raw = unsafe { c_ares_sys::ares_dns_record_get_rcode(self.dnsrec) };
75        DnsRcode::from(raw)
76    }
77
78    /// Returns the count of queries in the DNS record.
79    pub fn query_count(&self) -> usize {
80        unsafe { c_ares_sys::ares_dns_record_query_cnt(self.dnsrec) }
81    }
82
83    /// Get the query at the given index.
84    ///
85    /// Returns `(name, record_type, class)` on success.
86    pub fn query_get(&self, idx: usize) -> Result<(&str, DnsRecordType, DnsCls)> {
87        let mut name: *const core::ffi::c_char = ptr::null();
88        let mut qtype = c_ares_sys::ares_dns_rec_type_t::ARES_REC_TYPE_A;
89        let mut qclass = c_ares_sys::ares_dns_class_t::ARES_CLASS_IN;
90        let status = unsafe {
91            c_ares_sys::ares_dns_record_query_get(
92                self.dnsrec,
93                idx,
94                &raw mut name,
95                &raw mut qtype,
96                &raw mut qclass,
97            )
98        };
99        status_to_result(status)?;
100        let name_str = unsafe { dns_string_as_str(name) };
101        Ok((name_str, DnsRecordType::from(qtype), DnsCls::from(qclass)))
102    }
103
104    /// Returns an iterator over the queries in the DNS record.
105    pub fn queries(&self) -> impl Iterator<Item = (&str, DnsRecordType, DnsCls)> {
106        (0..self.query_count()).map_while(move |i| self.query_get(i).ok())
107    }
108
109    /// Returns the count of resource records in the given section.
110    pub fn rr_count(&self, section: DnsSection) -> usize {
111        unsafe { c_ares_sys::ares_dns_record_rr_cnt(self.dnsrec, section.into()) }
112    }
113
114    /// Fetch a non-writable resource record by section and index.
115    ///
116    /// Returns `None` if the index is out of range.
117    pub fn rr(&self, section: DnsSection, idx: usize) -> Option<&DnsRr> {
118        let rr =
119            unsafe { c_ares_sys::ares_dns_record_rr_get_const(self.dnsrec, section.into(), idx) };
120        (!rr.is_null()).then(|| unsafe { &*rr.cast::<DnsRr>() })
121    }
122
123    /// Returns an iterator over the resource records in the given section.
124    pub fn rrs(&self, section: DnsSection) -> impl Iterator<Item = &DnsRr> {
125        (0..self.rr_count(section)).map_while(move |i| self.rr(section, i))
126    }
127
128    /// Create a new DNS record object.
129    ///
130    /// When building a query to send via
131    /// [`Channel::send_dnsrec()`](crate::Channel::send_dnsrec), set `id`
132    /// to `0`.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use c_ares::{DnsRecord, DnsFlags, DnsOpcode, DnsRcode, DnsRecordType, DnsCls};
138    ///
139    /// let mut record = DnsRecord::new(
140    ///     0,
141    ///     DnsFlags::RD,
142    ///     DnsOpcode::Query,
143    ///     DnsRcode::NoError,
144    /// ).unwrap();
145    /// record.query_add("example.com", DnsRecordType::A, DnsCls::IN).unwrap();
146    /// ```
147    pub fn new(id: u16, flags: DnsFlags, opcode: DnsOpcode, rcode: DnsRcode) -> Result<DnsRecord> {
148        let mut dnsrec: *mut c_ares_sys::ares_dns_record_t = ptr::null_mut();
149        let status = unsafe {
150            c_ares_sys::ares_dns_record_create(
151                &raw mut dnsrec,
152                id,
153                flags.bits(),
154                opcode.into(),
155                rcode.into(),
156            )
157        };
158        status_to_result(status)?;
159        Ok(DnsRecord { dnsrec })
160    }
161
162    /// Overwrite the DNS query id.
163    pub fn set_id(&mut self, id: u16) -> &mut Self {
164        unsafe { c_ares_sys::ares_dns_record_set_id(self.dnsrec, id) };
165        self
166    }
167
168    /// Add a query to the DNS record.
169    ///
170    /// Typically a record will have only one query.  Most DNS servers will
171    /// reject queries with more than one question.
172    pub fn query_add(
173        &mut self,
174        name: &str,
175        qtype: DnsRecordType,
176        qclass: DnsCls,
177    ) -> Result<&mut Self> {
178        let c_name = CString::new(name).map_err(|_| Error::EBADSTR)?;
179        let status = unsafe {
180            c_ares_sys::ares_dns_record_query_add(
181                self.dnsrec,
182                c_name.as_ptr(),
183                qtype.into(),
184                qclass.into(),
185            )
186        };
187        status_to_result(status)?;
188        Ok(self)
189    }
190
191    /// Replace the question name at the given index.
192    ///
193    /// This may be used when performing a search with aliases.
194    pub fn query_set_name(&mut self, idx: usize, name: &str) -> Result<&mut Self> {
195        let c_name = CString::new(name).map_err(|_| Error::EBADSTR)?;
196        let status = unsafe {
197            c_ares_sys::ares_dns_record_query_set_name(self.dnsrec, idx, c_name.as_ptr())
198        };
199        status_to_result(status)?;
200        Ok(self)
201    }
202
203    /// Replace the question type at the given index.
204    ///
205    /// This may be used when needing to query more than one address class
206    /// (e.g. A and AAAA).
207    pub fn query_set_type(&mut self, idx: usize, qtype: DnsRecordType) -> Result<&mut Self> {
208        let status =
209            unsafe { c_ares_sys::ares_dns_record_query_set_type(self.dnsrec, idx, qtype.into()) };
210        status_to_result(status)?;
211        Ok(self)
212    }
213
214    /// Add a resource record to the DNS record.
215    ///
216    /// Returns a mutable reference to the created resource record for setting
217    /// RR-specific fields.
218    pub fn rr_add(
219        &mut self,
220        section: DnsSection,
221        name: &str,
222        rr_type: DnsRecordType,
223        rclass: DnsCls,
224        ttl: u32,
225    ) -> Result<&mut DnsRr> {
226        let c_name = CString::new(name).map_err(|_| Error::EBADSTR)?;
227        let mut rr_out: *mut c_ares_sys::ares_dns_rr_t = ptr::null_mut();
228        let status = unsafe {
229            c_ares_sys::ares_dns_record_rr_add(
230                &raw mut rr_out,
231                self.dnsrec,
232                section.into(),
233                c_name.as_ptr(),
234                rr_type.into(),
235                rclass.into(),
236                ttl,
237            )
238        };
239        status_to_result(status)?;
240        Ok(unsafe { &mut *rr_out.cast::<DnsRr>() })
241    }
242
243    /// Fetch a writable resource record by section and index.
244    ///
245    /// Returns `None` if the index is out of range.
246    pub fn rr_mut(&mut self, section: DnsSection, idx: usize) -> Option<&mut DnsRr> {
247        let rr = unsafe { c_ares_sys::ares_dns_record_rr_get(self.dnsrec, section.into(), idx) };
248        (!rr.is_null()).then(|| unsafe { &mut *rr.cast::<DnsRr>() })
249    }
250
251    /// Returns an iterator over mutable resource records in the given section.
252    pub fn rrs_mut(&mut self, section: DnsSection) -> impl Iterator<Item = &mut DnsRr> {
253        let count = self.rr_count(section);
254        let dnsrec = self.dnsrec;
255        (0..count).map_while(move |i| {
256            let rr = unsafe { c_ares_sys::ares_dns_record_rr_get(dnsrec, section.into(), i) };
257            (!rr.is_null()).then(|| unsafe { &mut *rr.cast::<DnsRr>() })
258        })
259    }
260
261    /// Remove the resource record at the given section and index.
262    pub fn rr_del(&mut self, section: DnsSection, idx: usize) -> Result<&mut Self> {
263        let status =
264            unsafe { c_ares_sys::ares_dns_record_rr_del(self.dnsrec, section.into(), idx) };
265        status_to_result(status)?;
266        Ok(self)
267    }
268
269    /// Write a complete DNS message to wire format.
270    pub fn write(&self) -> Result<AresBuf> {
271        let mut buf: *mut core::ffi::c_uchar = ptr::null_mut();
272        let mut buf_len: usize = 0;
273        let status =
274            unsafe { c_ares_sys::ares_dns_write(self.dnsrec, &raw mut buf, &raw mut buf_len) };
275        status_to_result(status)?;
276        Ok(AresBuf::new(buf, buf_len))
277    }
278
279    /// Create a deep copy of this DNS record.
280    pub fn try_clone(&self) -> Result<DnsRecord> {
281        let dup = unsafe { c_ares_sys::ares_dns_record_duplicate(self.dnsrec) };
282        if dup.is_null() {
283            Err(Error::ENOMEM)
284        } else {
285            Ok(DnsRecord { dnsrec: dup })
286        }
287    }
288}
289
290impl Drop for DnsRecord {
291    fn drop(&mut self) {
292        unsafe { c_ares_sys::ares_dns_record_destroy(self.dnsrec) }
293    }
294}
295
296unsafe impl Send for DnsRecord {}
297unsafe impl Sync for DnsRecord {}
298
299impl fmt::Debug for DnsRecord {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        f.debug_struct("DnsRecord")
302            .field("id", &self.id())
303            .field("flags", &self.flags())
304            .field("opcode", &self.opcode())
305            .field("rcode", &self.rcode())
306            .field("query_count", &self.query_count())
307            .field("answer_count", &self.rr_count(DnsSection::Answer))
308            .field("authority_count", &self.rr_count(DnsSection::Authority))
309            .field("additional_count", &self.rr_count(DnsSection::Additional))
310            .finish()
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use std::net::Ipv4Addr;
317
318    use crate::dns::*;
319    use crate::error::Error;
320
321    #[test]
322    fn parse_invalid_data() {
323        let result = DnsRecord::parse(&[], DnsParseFlags::empty());
324        assert!(result.is_err());
325    }
326
327    #[test]
328    fn parse_short_data() {
329        let result = DnsRecord::parse(&[0; 4], DnsParseFlags::empty());
330        assert!(result.is_err());
331    }
332
333    // Parse a minimal valid DNS query message (12-byte header + question).
334    // This is a query for "." type A class IN.
335    #[test]
336    fn parse_minimal_query() {
337        #[rustfmt::skip]
338        let data: &[u8] = &[
339            0x00, 0x01, // ID = 1
340            0x01, 0x00, // Flags: RD=1 (standard query)
341            0x00, 0x01, // QDCOUNT = 1
342            0x00, 0x00, // ANCOUNT = 0
343            0x00, 0x00, // NSCOUNT = 0
344            0x00, 0x00, // ARCOUNT = 0
345            0x00,       // root label (.)
346            0x00, 0x01, // QTYPE = A
347            0x00, 0x01, // QCLASS = IN
348        ];
349        let rec = DnsRecord::parse(data, DnsParseFlags::empty()).expect("parse should succeed");
350        assert_eq!(rec.id(), 1);
351        assert!(rec.flags().contains(DnsFlags::RD));
352        assert_eq!(rec.opcode(), DnsOpcode::Query);
353        assert_eq!(rec.rcode(), DnsRcode::NoError);
354        assert_eq!(rec.query_count(), 1);
355
356        let (name, qtype, qclass) = rec.query_get(0).expect("query_get should succeed");
357        assert_eq!(name, "");
358        assert_eq!(qtype, DnsRecordType::A);
359        assert_eq!(qclass, DnsCls::IN);
360
361        assert_eq!(rec.rr_count(DnsSection::Answer), 0);
362        assert!(rec.rr(DnsSection::Answer, 0).is_none());
363    }
364
365    #[test]
366    fn query_get_preserves_unknown_query_type() {
367        #[rustfmt::skip]
368        let data: &[u8] = &[
369            0x00, 0x01, // ID = 1
370            0x01, 0x00, // Flags: RD=1 (standard query)
371            0x00, 0x01, // QDCOUNT = 1
372            0x00, 0x00, // ANCOUNT = 0
373            0x00, 0x00, // NSCOUNT = 0
374            0x00, 0x00, // ARCOUNT = 0
375            0x00,       // root label (.)
376            0xff, 0x00, // QTYPE = unknown value accepted by c-ares
377            0x00, 0x01, // QCLASS = IN
378        ];
379        let rec = DnsRecord::parse(data, DnsParseFlags::empty()).expect("parse should succeed");
380        assert_eq!(rec.query_count(), 1);
381        let (_, qtype, qclass) = rec.query_get(0).expect("query_get");
382        assert_eq!(qtype, DnsRecordType::UNKNOWN(0xff00));
383        assert_eq!(qclass, DnsCls::IN);
384    }
385
386    // Parse a DNS response with an A record answer.
387    #[test]
388    fn parse_a_response() {
389        #[rustfmt::skip]
390        let data: &[u8] = &[
391            0x00, 0x02,             // ID = 2
392            0x81, 0x80,             // Flags: QR=1 RD=1 RA=1 (standard response)
393            0x00, 0x01,             // QDCOUNT = 1
394            0x00, 0x01,             // ANCOUNT = 1
395            0x00, 0x00,             // NSCOUNT = 0
396            0x00, 0x00,             // ARCOUNT = 0
397            // Question: example. A IN
398            0x07, b'e', b'x', b'a', b'm', b'p', b'l', b'e',
399            0x00,                   // root
400            0x00, 0x01,             // QTYPE = A
401            0x00, 0x01,             // QCLASS = IN
402            // Answer: example. A IN 300 1.2.3.4
403            0xc0, 0x0c,             // pointer to name at offset 12
404            0x00, 0x01,             // TYPE = A
405            0x00, 0x01,             // CLASS = IN
406            0x00, 0x00, 0x01, 0x2c, // TTL = 300
407            0x00, 0x04,             // RDLENGTH = 4
408            0x01, 0x02, 0x03, 0x04, // RDATA = 1.2.3.4
409        ];
410        let rec = DnsRecord::parse(data, DnsParseFlags::empty()).expect("parse should succeed");
411        assert_eq!(rec.id(), 2);
412        assert!(rec.flags().contains(DnsFlags::QR));
413        assert!(rec.flags().contains(DnsFlags::RD));
414        assert!(rec.flags().contains(DnsFlags::RA));
415        assert_eq!(rec.rr_count(DnsSection::Answer), 1);
416
417        let rr = rec.rr(DnsSection::Answer, 0).expect("rr should exist");
418        assert_eq!(rr.rr_type(), DnsRecordType::A);
419        assert_eq!(rr.dns_class(), DnsCls::IN);
420        assert_eq!(rr.ttl(), 300);
421        assert_eq!(rr.name(), "example");
422
423        let addr = rr.get_addr(DnsRrKey::A_ADDR).expect("should have addr");
424        assert_eq!(addr, Ipv4Addr::new(1, 2, 3, 4));
425    }
426
427    #[test]
428    fn rr_preserves_unknown_class_on_raw_rr() {
429        #[rustfmt::skip]
430        let data: &[u8] = &[
431            0x00, 0x03,             // ID = 3
432            0x81, 0x80,             // Flags: QR=1 RD=1 RA=1 (standard response)
433            0x00, 0x01,             // QDCOUNT = 1
434            0x00, 0x01,             // ANCOUNT = 1
435            0x00, 0x00,             // NSCOUNT = 0
436            0x00, 0x00,             // ARCOUNT = 0
437            // Question: example. A IN
438            0x07, b'e', b'x', b'a', b'm', b'p', b'l', b'e',
439            0x00,                   // root
440            0x00, 0x01,             // QTYPE = A
441            0x00, 0x01,             // QCLASS = IN
442            // Answer: example. unknown type, unknown class, empty RDATA
443            0xc0, 0x0c,             // pointer to name at offset 12
444            0xff, 0x00,             // TYPE = unknown; c-ares parses as RAW_RR
445            0xff, 0x00,             // CLASS = unknown but accepted for RAW_RR
446            0x00, 0x00, 0x00, 0x00, // TTL = 0
447            0x00, 0x00,             // RDLENGTH = 0
448        ];
449        let rec = DnsRecord::parse(data, DnsParseFlags::empty()).expect("parse should succeed");
450        let rr = rec.rr(DnsSection::Answer, 0).expect("rr should exist");
451
452        // An unknown wire type is surfaced as RAW_RR, and the unknown class is
453        // preserved rather than producing an invalid enum value.
454        assert_eq!(rr.rr_type(), DnsRecordType::RAW_RR);
455        assert_eq!(rr.dns_class(), DnsCls::UNKNOWN(0xff00));
456        assert!(matches!(rr.as_typed(), TypedRr::RawRr(_)));
457    }
458
459    #[test]
460    fn dns_record_is_send() {
461        fn assert_send<T: Send>() {}
462        assert_send::<DnsRecord>();
463    }
464
465    #[test]
466    fn dns_record_is_sync() {
467        fn assert_sync<T: Sync>() {}
468        assert_sync::<DnsRecord>();
469    }
470
471    #[test]
472    fn create_empty_record() {
473        let rec = DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError)
474            .expect("create should succeed");
475        assert_eq!(rec.id(), 0);
476        assert!(rec.flags().contains(DnsFlags::RD));
477        assert_eq!(rec.opcode(), DnsOpcode::Query);
478        assert_eq!(rec.rcode(), DnsRcode::NoError);
479        assert_eq!(rec.query_count(), 0);
480    }
481
482    #[test]
483    fn create_and_add_query() {
484        let mut rec = DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError)
485            .expect("create should succeed");
486        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
487            .expect("query_add should succeed");
488        assert_eq!(rec.query_count(), 1);
489        let (name, qtype, qclass) = rec.query_get(0).expect("query_get should succeed");
490        assert_eq!(name, "example.com");
491        assert_eq!(qtype, DnsRecordType::A);
492        assert_eq!(qclass, DnsCls::IN);
493    }
494
495    #[test]
496    fn set_id() {
497        let mut rec = DnsRecord::new(0, DnsFlags::empty(), DnsOpcode::Query, DnsRcode::NoError)
498            .expect("create should succeed");
499        assert_eq!(rec.id(), 0);
500        rec.set_id(42);
501        assert_eq!(rec.id(), 42);
502    }
503
504    #[test]
505    fn query_set_name_and_type() {
506        let mut rec = DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError)
507            .expect("create should succeed");
508        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
509            .expect("query_add");
510        rec.query_set_name(0, "other.com").expect("set_name");
511        rec.query_set_type(0, DnsRecordType::AAAA)
512            .expect("set_type");
513        let (name, qtype, _) = rec.query_get(0).expect("query_get");
514        assert_eq!(name, "other.com");
515        assert_eq!(qtype, DnsRecordType::AAAA);
516    }
517
518    #[test]
519    fn rr_mut_and_rr_del() {
520        let mut rec =
521            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
522        rec.query_add("example.com", DnsRecordType::MX, DnsCls::IN)
523            .expect("query_add");
524        let rr = rec
525            .rr_add(
526                DnsSection::Answer,
527                "example.com",
528                DnsRecordType::MX,
529                DnsCls::IN,
530                600,
531            )
532            .expect("rr_add");
533        rr.set_u16(DnsRrKey::MX_PREFERENCE, 10).expect("set_u16");
534        rr.set_str(DnsRrKey::MX_EXCHANGE, "mail.example.com")
535            .expect("set_str");
536
537        // Verify via rr_mut
538        let rr = rec.rr_mut(DnsSection::Answer, 0).expect("rr_mut");
539        assert_eq!(rr.get_u16(DnsRrKey::MX_PREFERENCE), 10);
540        assert_eq!(rr.get_str(DnsRrKey::MX_EXCHANGE), Some("mail.example.com"));
541
542        // Delete it
543        rec.rr_del(DnsSection::Answer, 0).expect("rr_del");
544        assert_eq!(rec.rr_count(DnsSection::Answer), 0);
545    }
546
547    #[test]
548    fn rrs_mut_update_ttl() {
549        let mut rec =
550            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
551        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
552            .expect("query_add");
553        for i in 0..3u8 {
554            let rr = rec
555                .rr_add(
556                    DnsSection::Answer,
557                    "example.com",
558                    DnsRecordType::A,
559                    DnsCls::IN,
560                    300,
561                )
562                .expect("rr_add");
563            rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(10, 0, 0, i))
564                .expect("set_addr");
565        }
566
567        // Use rrs_mut() to update all MX preferences (here, re-set addresses)
568        for rr in rec.rrs_mut(DnsSection::Answer) {
569            let addr = rr.get_addr(DnsRrKey::A_ADDR).expect("get_addr");
570            // Shift each address by adding 100 to the last octet
571            let octets = addr.octets();
572            rr.set_addr(
573                DnsRrKey::A_ADDR,
574                Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3] + 100),
575            )
576            .expect("set_addr");
577        }
578
579        // Verify via read-only iterator
580        let addrs: Vec<_> = rec
581            .rrs(DnsSection::Answer)
582            .map(|rr| rr.get_addr(DnsRrKey::A_ADDR).unwrap())
583            .collect();
584        assert_eq!(
585            addrs,
586            vec![
587                Ipv4Addr::new(10, 0, 0, 100),
588                Ipv4Addr::new(10, 0, 0, 101),
589                Ipv4Addr::new(10, 0, 0, 102),
590            ]
591        );
592    }
593
594    #[test]
595    fn write_and_reparse() {
596        let mut rec =
597            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
598        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
599            .expect("query_add");
600
601        let wire = rec.write().expect("write");
602        assert!(!wire.is_empty());
603
604        // Re-parse and verify
605        let rec2 = DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("parse");
606        assert_eq!(rec2.query_count(), 1);
607        let (name, qtype, qclass) = rec2.query_get(0).expect("query_get");
608        assert_eq!(name, "example.com");
609        assert_eq!(qtype, DnsRecordType::A);
610        assert_eq!(qclass, DnsCls::IN);
611    }
612
613    #[test]
614    fn duplicate_record() {
615        let mut rec =
616            DnsRecord::new(42, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
617        rec.query_add("test.org", DnsRecordType::AAAA, DnsCls::IN)
618            .expect("query_add");
619
620        let dup = rec.try_clone().expect("try_clone");
621        assert_eq!(dup.id(), 42);
622        assert_eq!(dup.query_count(), 1);
623        let (name, qtype, _) = dup.query_get(0).expect("query_get");
624        assert_eq!(name, "test.org");
625        assert_eq!(qtype, DnsRecordType::AAAA);
626    }
627
628    // --- Coverage: out-of-range rr access ---
629    #[test]
630    fn rr_out_of_range() {
631        let rec =
632            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
633        assert!(rec.rr(DnsSection::Answer, 0).is_none());
634        assert!(rec.rr(DnsSection::Answer, 100).is_none());
635        assert!(rec.rr(DnsSection::Authority, 0).is_none());
636        assert!(rec.rr(DnsSection::Additional, 0).is_none());
637    }
638
639    // --- Coverage: rr_mut out-of-range ---
640    #[test]
641    fn rr_mut_out_of_range() {
642        let mut rec =
643            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
644        assert!(rec.rr_mut(DnsSection::Answer, 0).is_none());
645        assert!(rec.rr_mut(DnsSection::Answer, 100).is_none());
646    }
647
648    // --- Coverage: rr_del out-of-range ---
649    #[test]
650    fn rr_del_out_of_range() {
651        let mut rec =
652            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
653        let result = rec.rr_del(DnsSection::Answer, 0);
654        assert!(result.is_err());
655    }
656
657    // --- Coverage: multiple queries ---
658    #[test]
659    fn multiple_queries() {
660        let mut rec =
661            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
662        rec.query_add("a.example.com", DnsRecordType::A, DnsCls::IN)
663            .expect("add 0");
664        rec.query_add("b.example.com", DnsRecordType::AAAA, DnsCls::IN)
665            .expect("add 1");
666        rec.query_add("c.example.com", DnsRecordType::MX, DnsCls::IN)
667            .expect("add 2");
668        assert_eq!(rec.query_count(), 3);
669
670        let (n, t, _) = rec.query_get(0).expect("get 0");
671        assert_eq!(n, "a.example.com");
672        assert_eq!(t, DnsRecordType::A);
673
674        let (n, t, _) = rec.query_get(1).expect("get 1");
675        assert_eq!(n, "b.example.com");
676        assert_eq!(t, DnsRecordType::AAAA);
677
678        let (n, t, _) = rec.query_get(2).expect("get 2");
679        assert_eq!(n, "c.example.com");
680        assert_eq!(t, DnsRecordType::MX);
681    }
682
683    // --- Coverage: multiple RRs in the same section ---
684    #[test]
685    fn multiple_rrs_same_section() {
686        let mut rec =
687            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
688        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
689            .expect("query_add");
690        for i in 0..5 {
691            let rr = rec
692                .rr_add(
693                    DnsSection::Answer,
694                    "example.com",
695                    DnsRecordType::A,
696                    DnsCls::IN,
697                    300,
698                )
699                .expect("rr_add");
700            rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(10, 0, 0, i))
701                .expect("set_addr");
702        }
703        assert_eq!(rec.rr_count(DnsSection::Answer), 5);
704        for i in 0..5 {
705            let rr = rec.rr(DnsSection::Answer, i).expect("rr");
706            assert_eq!(
707                rr.get_addr(DnsRrKey::A_ADDR),
708                Some(Ipv4Addr::new(10, 0, 0, i as u8))
709            );
710        }
711    }
712
713    // --- Coverage: Authority and Additional sections ---
714    #[test]
715    fn authority_and_additional_sections() {
716        let mut rec =
717            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
718        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
719            .expect("query_add");
720
721        // Add an NS record in the authority section
722        let rr = rec
723            .rr_add(
724                DnsSection::Authority,
725                "example.com",
726                DnsRecordType::NS,
727                DnsCls::IN,
728                3600,
729            )
730            .expect("rr_add authority");
731        rr.set_str(DnsRrKey::NS_NSDNAME, "ns1.example.com")
732            .expect("set ns name");
733
734        // Add an A record in the additional section
735        let rr = rec
736            .rr_add(
737                DnsSection::Additional,
738                "ns1.example.com",
739                DnsRecordType::A,
740                DnsCls::IN,
741                3600,
742            )
743            .expect("rr_add additional");
744        rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(192, 0, 2, 1))
745            .expect("set_addr");
746
747        assert_eq!(rec.rr_count(DnsSection::Answer), 0);
748        assert_eq!(rec.rr_count(DnsSection::Authority), 1);
749        assert_eq!(rec.rr_count(DnsSection::Additional), 1);
750
751        let ns = rec.rr(DnsSection::Authority, 0).expect("rr authority");
752        assert_eq!(ns.rr_type(), DnsRecordType::NS);
753        assert_eq!(ns.get_str(DnsRrKey::NS_NSDNAME), Some("ns1.example.com"));
754
755        let a = rec.rr(DnsSection::Additional, 0).expect("rr additional");
756        assert_eq!(a.rr_type(), DnsRecordType::A);
757        assert_eq!(
758            a.get_addr(DnsRrKey::A_ADDR),
759            Some(Ipv4Addr::new(192, 0, 2, 1))
760        );
761    }
762
763    // --- Coverage: write/reparse with answer RRs (full round trip) ---
764    #[test]
765    fn write_reparse_with_answer() {
766        let mut rec = DnsRecord::new(
767            100,
768            DnsFlags::QR | DnsFlags::RD | DnsFlags::RA,
769            DnsOpcode::Query,
770            DnsRcode::NoError,
771        )
772        .expect("create");
773        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
774            .expect("query_add");
775        let rr = rec
776            .rr_add(
777                DnsSection::Answer,
778                "example.com",
779                DnsRecordType::A,
780                DnsCls::IN,
781                300,
782            )
783            .expect("rr_add");
784        rr.set_addr(DnsRrKey::A_ADDR, Ipv4Addr::new(93, 184, 216, 34))
785            .expect("set_addr");
786
787        let wire = rec.write().expect("write");
788        let rec2 = DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("reparse");
789        assert_eq!(rec2.id(), 100);
790        assert!(rec2.flags().contains(DnsFlags::QR));
791        assert!(rec2.flags().contains(DnsFlags::RD));
792        assert!(rec2.flags().contains(DnsFlags::RA));
793        assert_eq!(rec2.rr_count(DnsSection::Answer), 1);
794        let rr = rec2.rr(DnsSection::Answer, 0).expect("rr");
795        assert_eq!(
796            rr.get_addr(DnsRrKey::A_ADDR),
797            Some(Ipv4Addr::new(93, 184, 216, 34))
798        );
799    }
800
801    // --- Coverage: SOA full write/reparse round trip ---
802    #[test]
803    fn soa_write_reparse() {
804        let mut rec = DnsRecord::new(
805            5,
806            DnsFlags::QR | DnsFlags::AA | DnsFlags::RD,
807            DnsOpcode::Query,
808            DnsRcode::NoError,
809        )
810        .expect("create");
811        rec.query_add("example.com", DnsRecordType::SOA, DnsCls::IN)
812            .expect("query_add");
813        let rr = rec
814            .rr_add(
815                DnsSection::Answer,
816                "example.com",
817                DnsRecordType::SOA,
818                DnsCls::IN,
819                86400,
820            )
821            .expect("rr_add");
822        rr.set_str(DnsRrKey::SOA_MNAME, "ns1.example.com")
823            .expect("mname");
824        rr.set_str(DnsRrKey::SOA_RNAME, "hostmaster.example.com")
825            .expect("rname");
826        rr.set_u32(DnsRrKey::SOA_SERIAL, 1).expect("serial");
827        rr.set_u32(DnsRrKey::SOA_REFRESH, 3600).expect("refresh");
828        rr.set_u32(DnsRrKey::SOA_RETRY, 900).expect("retry");
829        rr.set_u32(DnsRrKey::SOA_EXPIRE, 604800).expect("expire");
830        rr.set_u32(DnsRrKey::SOA_MINIMUM, 60).expect("minimum");
831
832        let wire = rec.write().expect("write");
833        let rec2 = DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("reparse");
834        assert_eq!(rec2.id(), 5);
835        assert!(rec2.flags().contains(DnsFlags::AA));
836        let rr = rec2.rr(DnsSection::Answer, 0).expect("rr");
837        assert_eq!(rr.get_str(DnsRrKey::SOA_MNAME), Some("ns1.example.com"));
838        assert_eq!(
839            rr.get_str(DnsRrKey::SOA_RNAME),
840            Some("hostmaster.example.com")
841        );
842        assert_eq!(rr.get_u32(DnsRrKey::SOA_SERIAL), 1);
843        assert_eq!(rr.get_u32(DnsRrKey::SOA_EXPIRE), 604800);
844    }
845
846    // --- Coverage: TXT abin write/reparse round trip ---
847    #[test]
848    fn txt_abin_write_reparse() {
849        let mut rec =
850            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
851        rec.query_add("example.com", DnsRecordType::TXT, DnsCls::IN)
852            .expect("query_add");
853        let rr = rec
854            .rr_add(
855                DnsSection::Answer,
856                "example.com",
857                DnsRecordType::TXT,
858                DnsCls::IN,
859                300,
860            )
861            .expect("rr_add");
862        rr.add_abin(DnsRrKey::TXT_DATA, b"v=spf1 include:example.com ~all")
863            .expect("add_abin");
864
865        let wire = rec.write().expect("write");
866        let rec2 = DnsRecord::parse(&wire, DnsParseFlags::empty()).expect("reparse");
867        let rr = rec2.rr(DnsSection::Answer, 0).expect("rr");
868        assert_eq!(rr.get_abin_count(DnsRrKey::TXT_DATA), 1);
869        assert_eq!(
870            rr.get_abin(DnsRrKey::TXT_DATA, 0).unwrap(),
871            b"v=spf1 include:example.com ~all"
872        );
873    }
874
875    // --- Coverage: CString null byte errors ---
876    #[test]
877    fn query_add_rejects_null_byte() {
878        let mut rec =
879            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
880        let result = rec.query_add("ex\0ample.com", DnsRecordType::A, DnsCls::IN);
881        assert!(matches!(result, Err(Error::EBADSTR)));
882    }
883
884    #[test]
885    fn query_set_name_rejects_null_byte() {
886        let mut rec =
887            DnsRecord::new(0, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
888        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
889            .expect("query_add");
890        let result = rec.query_set_name(0, "ex\0ample.com");
891        assert!(matches!(result, Err(Error::EBADSTR)));
892    }
893
894    #[test]
895    fn rr_add_rejects_null_byte() {
896        let mut rec =
897            DnsRecord::new(0, DnsFlags::QR, DnsOpcode::Query, DnsRcode::NoError).expect("create");
898        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
899            .expect("query_add");
900        let result = rec.rr_add(
901            DnsSection::Answer,
902            "ex\0ample.com",
903            DnsRecordType::A,
904            DnsCls::IN,
905            300,
906        );
907        assert!(matches!(result, Err(Error::EBADSTR)));
908    }
909
910    #[test]
911    fn debug_dns_record() {
912        let rec =
913            DnsRecord::new(42, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
914        let debug = format!("{rec:?}");
915        assert!(debug.contains("DnsRecord"));
916        assert!(debug.contains("42"));
917    }
918
919    #[test]
920    fn clone_dns_record() {
921        let mut rec =
922            DnsRecord::new(7, DnsFlags::RD, DnsOpcode::Query, DnsRcode::NoError).expect("create");
923        rec.query_add("example.com", DnsRecordType::A, DnsCls::IN)
924            .expect("query_add");
925
926        let cloned = rec.try_clone().expect("try_clone");
927        assert_eq!(cloned.id(), 7);
928        assert_eq!(cloned.query_count(), 1);
929        let (name, qtype, _) = cloned.query_get(0).expect("query_get");
930        assert_eq!(name, "example.com");
931        assert_eq!(qtype, DnsRecordType::A);
932
933        // Mutating the original should not affect the clone.
934        rec.set_id(99);
935        assert_eq!(cloned.id(), 7);
936    }
937}