Skip to main content

stackforge_core/layer/dns/
rdata.rs

1//! DNS Resource Record Data (RDATA) parsing and building.
2//!
3//! Implements parsing and serialization for all common DNS record types,
4//! including DNSSEC records (RRSIG, NSEC, NSEC3, DNSKEY, DS),
5//! service records (SRV, SVCB, HTTPS, NAPTR, TLSA, CAA),
6//! and standard records (A, AAAA, MX, SOA, TXT, CNAME, NS, PTR, DNAME).
7
8use std::collections::HashMap;
9use std::net::{Ipv4Addr, Ipv6Addr};
10
11use super::bitmap;
12use super::edns::EdnsOption;
13use super::svcb::SvcParam;
14use super::types::{self, rr_type};
15use crate::layer::field::FieldError;
16use crate::layer::field_ext::DnsName;
17
18/// Parsed DNS resource record data.
19///
20/// Each variant corresponds to one or more DNS RR types and contains
21/// the fully parsed fields of that record's RDATA section.
22#[derive(Debug, Clone, PartialEq)]
23pub enum DnsRData {
24    /// A record: IPv4 address (RFC 1035).
25    A(Ipv4Addr),
26
27    /// AAAA record: IPv6 address (RFC 3596).
28    AAAA(Ipv6Addr),
29
30    /// NS record: authoritative name server (RFC 1035).
31    NS(DnsName),
32
33    /// CNAME record: canonical name alias (RFC 1035).
34    CNAME(DnsName),
35
36    /// PTR record: pointer to a domain name (RFC 1035).
37    PTR(DnsName),
38
39    /// DNAME record: delegation of a subtree (RFC 6672).
40    DNAME(DnsName),
41
42    /// MX record: mail exchange (RFC 1035).
43    MX { preference: u16, exchange: DnsName },
44
45    /// TXT record: text strings (RFC 1035).
46    /// Contains multiple character-strings, each up to 255 bytes.
47    TXT(Vec<Vec<u8>>),
48
49    /// SOA record: start of authority (RFC 1035).
50    SOA {
51        mname: DnsName,
52        rname: DnsName,
53        serial: u32,
54        refresh: u32,
55        retry: u32,
56        expire: u32,
57        minimum: u32,
58    },
59
60    /// SRV record: service location (RFC 2782).
61    SRV {
62        priority: u16,
63        weight: u16,
64        port: u16,
65        target: DnsName,
66    },
67
68    /// HINFO record: host information (RFC 1035).
69    HINFO { cpu: Vec<u8>, os: Vec<u8> },
70
71    /// NAPTR record: naming authority pointer (RFC 3403).
72    NAPTR {
73        order: u16,
74        preference: u16,
75        flags: Vec<u8>,
76        services: Vec<u8>,
77        regexp: Vec<u8>,
78        replacement: DnsName,
79    },
80
81    /// SVCB record: service binding (RFC 9460).
82    SVCB {
83        priority: u16,
84        target: DnsName,
85        params: Vec<SvcParam>,
86    },
87
88    /// HTTPS record: HTTPS service binding (RFC 9460).
89    HTTPS {
90        priority: u16,
91        target: DnsName,
92        params: Vec<SvcParam>,
93    },
94
95    /// CAA record: certificate authority authorization (RFC 8659).
96    CAA {
97        flags: u8,
98        tag: String,
99        value: Vec<u8>,
100    },
101
102    /// RRSIG record: DNSSEC signature (RFC 4034).
103    RRSIG {
104        type_covered: u16,
105        algorithm: u8,
106        labels: u8,
107        original_ttl: u32,
108        sig_expiration: u32,
109        sig_inception: u32,
110        key_tag: u16,
111        signer_name: DnsName,
112        signature: Vec<u8>,
113    },
114
115    /// NSEC record: authenticated denial of existence (RFC 4034).
116    NSEC {
117        next_domain: DnsName,
118        type_bitmaps: Vec<u16>,
119    },
120
121    /// NSEC3 record: hashed authenticated denial of existence (RFC 5155).
122    NSEC3 {
123        hash_algorithm: u8,
124        flags: u8,
125        iterations: u16,
126        salt: Vec<u8>,
127        next_hashed: Vec<u8>,
128        type_bitmaps: Vec<u16>,
129    },
130
131    /// NSEC3PARAM record: NSEC3 parameters (RFC 5155).
132    NSEC3PARAM {
133        hash_algorithm: u8,
134        flags: u8,
135        iterations: u16,
136        salt: Vec<u8>,
137    },
138
139    /// DNSKEY record: DNSSEC public key (RFC 4034).
140    DNSKEY {
141        flags: u16,
142        protocol: u8,
143        algorithm: u8,
144        public_key: Vec<u8>,
145    },
146
147    /// DS record: delegation signer (RFC 4034).
148    DS {
149        key_tag: u16,
150        algorithm: u8,
151        digest_type: u8,
152        digest: Vec<u8>,
153    },
154
155    /// DLV record: DNSSEC lookaside validation (RFC 4431).
156    DLV {
157        key_tag: u16,
158        algorithm: u8,
159        digest_type: u8,
160        digest: Vec<u8>,
161    },
162
163    /// TSIG record: transaction signature (RFC 8945).
164    TSIG {
165        algorithm_name: DnsName,
166        time_signed: u64,
167        fudge: u16,
168        mac: Vec<u8>,
169        original_id: u16,
170        error: u16,
171        other_data: Vec<u8>,
172    },
173
174    /// TLSA record: TLS authentication (RFC 6698).
175    TLSA {
176        usage: u8,
177        selector: u8,
178        matching_type: u8,
179        cert_data: Vec<u8>,
180    },
181
182    /// OPT pseudo-record: EDNS(0) options (RFC 6891).
183    OPT(Vec<EdnsOption>),
184
185    /// Unknown/unsupported record type.
186    Unknown { rtype: u16, data: Vec<u8> },
187}
188
189impl DnsRData {
190    /// Parse RDATA from wire format.
191    ///
192    /// # Arguments
193    /// * `rtype` - DNS record type number
194    /// * `packet` - Full DNS packet buffer (needed for name compression pointer resolution)
195    /// * `rdata_offset` - Byte offset where RDATA begins in `packet`
196    /// * `rdlength` - Length of RDATA in bytes
197    pub fn parse(
198        rtype: u16,
199        packet: &[u8],
200        rdata_offset: usize,
201        rdlength: u16,
202    ) -> Result<Self, FieldError> {
203        let rdlen = rdlength as usize;
204        let rdata_end = rdata_offset + rdlen;
205
206        // Bounds check
207        if rdata_end > packet.len() {
208            return Err(FieldError::BufferTooShort {
209                offset: rdata_offset,
210                need: rdlen,
211                have: packet.len().saturating_sub(rdata_offset),
212            });
213        }
214
215        let rdata = &packet[rdata_offset..rdata_end];
216
217        match rtype {
218            rr_type::A => Self::parse_a(rdata),
219            rr_type::AAAA => Self::parse_aaaa(rdata),
220            rr_type::NS => {
221                Self::parse_name_record(packet, rdata_offset, rdlen, |n| DnsRData::NS(n))
222            }
223            rr_type::CNAME => {
224                Self::parse_name_record(packet, rdata_offset, rdlen, |n| DnsRData::CNAME(n))
225            }
226            rr_type::PTR => {
227                Self::parse_name_record(packet, rdata_offset, rdlen, |n| DnsRData::PTR(n))
228            }
229            rr_type::DNAME => {
230                Self::parse_name_record(packet, rdata_offset, rdlen, |n| DnsRData::DNAME(n))
231            }
232            rr_type::MX => Self::parse_mx(packet, rdata_offset, rdata),
233            rr_type::TXT => Self::parse_txt(rdata),
234            rr_type::SOA => Self::parse_soa(packet, rdata_offset, rdata_end),
235            rr_type::SRV => Self::parse_srv(packet, rdata_offset, rdata),
236            rr_type::HINFO => Self::parse_hinfo(rdata),
237            rr_type::NAPTR => Self::parse_naptr(packet, rdata_offset, rdata_end),
238            rr_type::SVCB => Self::parse_svcb(packet, rdata_offset, rdata, rdlen, false),
239            rr_type::HTTPS => Self::parse_svcb(packet, rdata_offset, rdata, rdlen, true),
240            rr_type::CAA => Self::parse_caa(rdata),
241            rr_type::RRSIG => Self::parse_rrsig(packet, rdata_offset, rdata, rdlen),
242            rr_type::NSEC => Self::parse_nsec(packet, rdata_offset, rdata, rdlen),
243            rr_type::NSEC3 => Self::parse_nsec3(rdata),
244            rr_type::NSEC3PARAM => Self::parse_nsec3param(rdata),
245            rr_type::DNSKEY => Self::parse_dnskey(rdata),
246            rr_type::DS => Self::parse_ds(rdata),
247            rr_type::DLV => Self::parse_dlv(rdata),
248            rr_type::TSIG => Self::parse_tsig(packet, rdata_offset, rdata_end),
249            rr_type::TLSA => Self::parse_tlsa(rdata),
250            rr_type::OPT => Self::parse_opt(rdata),
251            _ => Ok(DnsRData::Unknown {
252                rtype,
253                data: rdata.to_vec(),
254            }),
255        }
256    }
257
258    // ========================================================================
259    // Individual type parsers
260    // ========================================================================
261
262    fn parse_a(rdata: &[u8]) -> Result<Self, FieldError> {
263        if rdata.len() != 4 {
264            return Err(FieldError::InvalidValue(format!(
265                "A record RDATA must be 4 bytes, got {}",
266                rdata.len()
267            )));
268        }
269        Ok(DnsRData::A(Ipv4Addr::new(
270            rdata[0], rdata[1], rdata[2], rdata[3],
271        )))
272    }
273
274    fn parse_aaaa(rdata: &[u8]) -> Result<Self, FieldError> {
275        if rdata.len() != 16 {
276            return Err(FieldError::InvalidValue(format!(
277                "AAAA record RDATA must be 16 bytes, got {}",
278                rdata.len()
279            )));
280        }
281        let mut bytes = [0u8; 16];
282        bytes.copy_from_slice(rdata);
283        Ok(DnsRData::AAAA(Ipv6Addr::from(bytes)))
284    }
285
286    /// Parse a record whose RDATA is a single domain name (NS, CNAME, PTR, DNAME).
287    fn parse_name_record<F>(
288        packet: &[u8],
289        rdata_offset: usize,
290        _rdlen: usize,
291        constructor: F,
292    ) -> Result<Self, FieldError>
293    where
294        F: FnOnce(DnsName) -> DnsRData,
295    {
296        let (name, _consumed) = DnsName::decode(packet, rdata_offset)?;
297        Ok(constructor(name))
298    }
299
300    fn parse_mx(packet: &[u8], rdata_offset: usize, rdata: &[u8]) -> Result<Self, FieldError> {
301        if rdata.len() < 2 {
302            return Err(FieldError::BufferTooShort {
303                offset: 0,
304                need: 2,
305                have: rdata.len(),
306            });
307        }
308        let preference = u16::from_be_bytes([rdata[0], rdata[1]]);
309        let (exchange, _consumed) = DnsName::decode(packet, rdata_offset + 2)?;
310        Ok(DnsRData::MX {
311            preference,
312            exchange,
313        })
314    }
315
316    fn parse_txt(rdata: &[u8]) -> Result<Self, FieldError> {
317        let mut strings = Vec::new();
318        let mut pos = 0;
319
320        while pos < rdata.len() {
321            let str_len = rdata[pos] as usize;
322            pos += 1;
323
324            if pos + str_len > rdata.len() {
325                return Err(FieldError::BufferTooShort {
326                    offset: pos,
327                    need: str_len,
328                    have: rdata.len() - pos,
329                });
330            }
331
332            strings.push(rdata[pos..pos + str_len].to_vec());
333            pos += str_len;
334        }
335
336        Ok(DnsRData::TXT(strings))
337    }
338
339    fn parse_soa(packet: &[u8], rdata_offset: usize, rdata_end: usize) -> Result<Self, FieldError> {
340        let (mname, consumed1) = DnsName::decode(packet, rdata_offset)?;
341        let (rname, consumed2) = DnsName::decode(packet, rdata_offset + consumed1)?;
342
343        let fixed_start = rdata_offset + consumed1 + consumed2;
344        if fixed_start + 20 > rdata_end {
345            return Err(FieldError::BufferTooShort {
346                offset: fixed_start,
347                need: 20,
348                have: rdata_end.saturating_sub(fixed_start),
349            });
350        }
351
352        let fixed = &packet[fixed_start..fixed_start + 20];
353        let serial = u32::from_be_bytes([fixed[0], fixed[1], fixed[2], fixed[3]]);
354        let refresh = u32::from_be_bytes([fixed[4], fixed[5], fixed[6], fixed[7]]);
355        let retry = u32::from_be_bytes([fixed[8], fixed[9], fixed[10], fixed[11]]);
356        let expire = u32::from_be_bytes([fixed[12], fixed[13], fixed[14], fixed[15]]);
357        let minimum = u32::from_be_bytes([fixed[16], fixed[17], fixed[18], fixed[19]]);
358
359        Ok(DnsRData::SOA {
360            mname,
361            rname,
362            serial,
363            refresh,
364            retry,
365            expire,
366            minimum,
367        })
368    }
369
370    fn parse_srv(packet: &[u8], rdata_offset: usize, rdata: &[u8]) -> Result<Self, FieldError> {
371        if rdata.len() < 6 {
372            return Err(FieldError::BufferTooShort {
373                offset: 0,
374                need: 6,
375                have: rdata.len(),
376            });
377        }
378        let priority = u16::from_be_bytes([rdata[0], rdata[1]]);
379        let weight = u16::from_be_bytes([rdata[2], rdata[3]]);
380        let port = u16::from_be_bytes([rdata[4], rdata[5]]);
381        let (target, _consumed) = DnsName::decode(packet, rdata_offset + 6)?;
382
383        Ok(DnsRData::SRV {
384            priority,
385            weight,
386            port,
387            target,
388        })
389    }
390
391    fn parse_hinfo(rdata: &[u8]) -> Result<Self, FieldError> {
392        if rdata.is_empty() {
393            return Err(FieldError::BufferTooShort {
394                offset: 0,
395                need: 1,
396                have: 0,
397            });
398        }
399
400        let cpu_len = rdata[0] as usize;
401        if 1 + cpu_len >= rdata.len() {
402            return Err(FieldError::BufferTooShort {
403                offset: 1,
404                need: cpu_len + 1, // +1 for the OS length byte
405                have: rdata.len() - 1,
406            });
407        }
408        let cpu = rdata[1..1 + cpu_len].to_vec();
409
410        let os_start = 1 + cpu_len;
411        let os_len = rdata[os_start] as usize;
412        if os_start + 1 + os_len > rdata.len() {
413            return Err(FieldError::BufferTooShort {
414                offset: os_start + 1,
415                need: os_len,
416                have: rdata.len() - os_start - 1,
417            });
418        }
419        let os = rdata[os_start + 1..os_start + 1 + os_len].to_vec();
420
421        Ok(DnsRData::HINFO { cpu, os })
422    }
423
424    fn parse_naptr(
425        packet: &[u8],
426        rdata_offset: usize,
427        rdata_end: usize,
428    ) -> Result<Self, FieldError> {
429        let rdata = &packet[rdata_offset..rdata_end];
430        if rdata.len() < 4 {
431            return Err(FieldError::BufferTooShort {
432                offset: 0,
433                need: 4,
434                have: rdata.len(),
435            });
436        }
437
438        let order = u16::from_be_bytes([rdata[0], rdata[1]]);
439        let preference = u16::from_be_bytes([rdata[2], rdata[3]]);
440        let mut pos = 4;
441
442        // Parse three character-strings: flags, services, regexp
443        let (flags, consumed) = Self::parse_character_string(rdata, pos)?;
444        pos += consumed;
445
446        let (services, consumed) = Self::parse_character_string(rdata, pos)?;
447        pos += consumed;
448
449        let (regexp, consumed) = Self::parse_character_string(rdata, pos)?;
450        pos += consumed;
451
452        // The replacement name may use compression pointers
453        let (replacement, _consumed) = DnsName::decode(packet, rdata_offset + pos)?;
454
455        Ok(DnsRData::NAPTR {
456            order,
457            preference,
458            flags,
459            services,
460            regexp,
461            replacement,
462        })
463    }
464
465    fn parse_svcb(
466        packet: &[u8],
467        rdata_offset: usize,
468        rdata: &[u8],
469        rdlen: usize,
470        is_https: bool,
471    ) -> Result<Self, FieldError> {
472        if rdata.len() < 2 {
473            return Err(FieldError::BufferTooShort {
474                offset: 0,
475                need: 2,
476                have: rdata.len(),
477            });
478        }
479
480        let priority = u16::from_be_bytes([rdata[0], rdata[1]]);
481        let (target, name_consumed) = DnsName::decode(packet, rdata_offset + 2)?;
482
483        let params_start = 2 + name_consumed;
484        let params = if params_start < rdlen {
485            SvcParam::parse_all(&rdata[params_start..])?
486        } else {
487            Vec::new()
488        };
489
490        if is_https {
491            Ok(DnsRData::HTTPS {
492                priority,
493                target,
494                params,
495            })
496        } else {
497            Ok(DnsRData::SVCB {
498                priority,
499                target,
500                params,
501            })
502        }
503    }
504
505    fn parse_caa(rdata: &[u8]) -> Result<Self, FieldError> {
506        if rdata.len() < 2 {
507            return Err(FieldError::BufferTooShort {
508                offset: 0,
509                need: 2,
510                have: rdata.len(),
511            });
512        }
513
514        let flags = rdata[0];
515        let tag_len = rdata[1] as usize;
516
517        if 2 + tag_len > rdata.len() {
518            return Err(FieldError::BufferTooShort {
519                offset: 2,
520                need: tag_len,
521                have: rdata.len() - 2,
522            });
523        }
524
525        let tag = String::from_utf8_lossy(&rdata[2..2 + tag_len]).into_owned();
526        let value = rdata[2 + tag_len..].to_vec();
527
528        Ok(DnsRData::CAA { flags, tag, value })
529    }
530
531    fn parse_rrsig(
532        packet: &[u8],
533        rdata_offset: usize,
534        rdata: &[u8],
535        rdlen: usize,
536    ) -> Result<Self, FieldError> {
537        if rdata.len() < 18 {
538            return Err(FieldError::BufferTooShort {
539                offset: 0,
540                need: 18,
541                have: rdata.len(),
542            });
543        }
544
545        let type_covered = u16::from_be_bytes([rdata[0], rdata[1]]);
546        let algorithm = rdata[2];
547        let labels = rdata[3];
548        let original_ttl = u32::from_be_bytes([rdata[4], rdata[5], rdata[6], rdata[7]]);
549        let sig_expiration = u32::from_be_bytes([rdata[8], rdata[9], rdata[10], rdata[11]]);
550        let sig_inception = u32::from_be_bytes([rdata[12], rdata[13], rdata[14], rdata[15]]);
551        let key_tag = u16::from_be_bytes([rdata[16], rdata[17]]);
552
553        let (signer_name, name_consumed) = DnsName::decode(packet, rdata_offset + 18)?;
554        let sig_start = 18 + name_consumed;
555
556        let signature = if sig_start < rdlen {
557            rdata[sig_start..].to_vec()
558        } else {
559            Vec::new()
560        };
561
562        Ok(DnsRData::RRSIG {
563            type_covered,
564            algorithm,
565            labels,
566            original_ttl,
567            sig_expiration,
568            sig_inception,
569            key_tag,
570            signer_name,
571            signature,
572        })
573    }
574
575    fn parse_nsec(
576        packet: &[u8],
577        rdata_offset: usize,
578        rdata: &[u8],
579        rdlen: usize,
580    ) -> Result<Self, FieldError> {
581        let (next_domain, name_consumed) = DnsName::decode(packet, rdata_offset)?;
582
583        let type_bitmaps = if name_consumed < rdlen {
584            bitmap::bitmap_to_rr_list(&rdata[name_consumed..])?
585        } else {
586            Vec::new()
587        };
588
589        Ok(DnsRData::NSEC {
590            next_domain,
591            type_bitmaps,
592        })
593    }
594
595    fn parse_nsec3(rdata: &[u8]) -> Result<Self, FieldError> {
596        if rdata.len() < 5 {
597            return Err(FieldError::BufferTooShort {
598                offset: 0,
599                need: 5,
600                have: rdata.len(),
601            });
602        }
603
604        let hash_algorithm = rdata[0];
605        let flags = rdata[1];
606        let iterations = u16::from_be_bytes([rdata[2], rdata[3]]);
607        let salt_len = rdata[4] as usize;
608        let mut pos = 5;
609
610        if pos + salt_len >= rdata.len() {
611            return Err(FieldError::BufferTooShort {
612                offset: pos,
613                need: salt_len + 1, // +1 for hash length byte
614                have: rdata.len() - pos,
615            });
616        }
617        let salt = rdata[pos..pos + salt_len].to_vec();
618        pos += salt_len;
619
620        let hash_len = rdata[pos] as usize;
621        pos += 1;
622
623        if pos + hash_len > rdata.len() {
624            return Err(FieldError::BufferTooShort {
625                offset: pos,
626                need: hash_len,
627                have: rdata.len() - pos,
628            });
629        }
630        let next_hashed = rdata[pos..pos + hash_len].to_vec();
631        pos += hash_len;
632
633        let type_bitmaps = if pos < rdata.len() {
634            bitmap::bitmap_to_rr_list(&rdata[pos..])?
635        } else {
636            Vec::new()
637        };
638
639        Ok(DnsRData::NSEC3 {
640            hash_algorithm,
641            flags,
642            iterations,
643            salt,
644            next_hashed,
645            type_bitmaps,
646        })
647    }
648
649    fn parse_nsec3param(rdata: &[u8]) -> Result<Self, FieldError> {
650        if rdata.len() < 5 {
651            return Err(FieldError::BufferTooShort {
652                offset: 0,
653                need: 5,
654                have: rdata.len(),
655            });
656        }
657
658        let hash_algorithm = rdata[0];
659        let flags = rdata[1];
660        let iterations = u16::from_be_bytes([rdata[2], rdata[3]]);
661        let salt_len = rdata[4] as usize;
662
663        if 5 + salt_len > rdata.len() {
664            return Err(FieldError::BufferTooShort {
665                offset: 5,
666                need: salt_len,
667                have: rdata.len() - 5,
668            });
669        }
670
671        let salt = rdata[5..5 + salt_len].to_vec();
672
673        Ok(DnsRData::NSEC3PARAM {
674            hash_algorithm,
675            flags,
676            iterations,
677            salt,
678        })
679    }
680
681    fn parse_dnskey(rdata: &[u8]) -> Result<Self, FieldError> {
682        if rdata.len() < 4 {
683            return Err(FieldError::BufferTooShort {
684                offset: 0,
685                need: 4,
686                have: rdata.len(),
687            });
688        }
689
690        let flags = u16::from_be_bytes([rdata[0], rdata[1]]);
691        let protocol = rdata[2];
692        let algorithm = rdata[3];
693        let public_key = rdata[4..].to_vec();
694
695        Ok(DnsRData::DNSKEY {
696            flags,
697            protocol,
698            algorithm,
699            public_key,
700        })
701    }
702
703    fn parse_ds(rdata: &[u8]) -> Result<Self, FieldError> {
704        if rdata.len() < 4 {
705            return Err(FieldError::BufferTooShort {
706                offset: 0,
707                need: 4,
708                have: rdata.len(),
709            });
710        }
711
712        let key_tag = u16::from_be_bytes([rdata[0], rdata[1]]);
713        let algorithm = rdata[2];
714        let digest_type = rdata[3];
715        let digest = rdata[4..].to_vec();
716
717        Ok(DnsRData::DS {
718            key_tag,
719            algorithm,
720            digest_type,
721            digest,
722        })
723    }
724
725    fn parse_dlv(rdata: &[u8]) -> Result<Self, FieldError> {
726        // DLV has the same format as DS
727        if rdata.len() < 4 {
728            return Err(FieldError::BufferTooShort {
729                offset: 0,
730                need: 4,
731                have: rdata.len(),
732            });
733        }
734
735        let key_tag = u16::from_be_bytes([rdata[0], rdata[1]]);
736        let algorithm = rdata[2];
737        let digest_type = rdata[3];
738        let digest = rdata[4..].to_vec();
739
740        Ok(DnsRData::DLV {
741            key_tag,
742            algorithm,
743            digest_type,
744            digest,
745        })
746    }
747
748    fn parse_tsig(
749        packet: &[u8],
750        rdata_offset: usize,
751        rdata_end: usize,
752    ) -> Result<Self, FieldError> {
753        let (algorithm_name, name_consumed) = DnsName::decode(packet, rdata_offset)?;
754        let mut pos = rdata_offset + name_consumed;
755
756        // Time signed: 48-bit (6 bytes)
757        if pos + 6 > rdata_end {
758            return Err(FieldError::BufferTooShort {
759                offset: pos,
760                need: 6,
761                have: rdata_end.saturating_sub(pos),
762            });
763        }
764        let time_signed = ((packet[pos] as u64) << 40)
765            | ((packet[pos + 1] as u64) << 32)
766            | ((packet[pos + 2] as u64) << 24)
767            | ((packet[pos + 3] as u64) << 16)
768            | ((packet[pos + 4] as u64) << 8)
769            | (packet[pos + 5] as u64);
770        pos += 6;
771
772        // Fudge: 2 bytes
773        if pos + 2 > rdata_end {
774            return Err(FieldError::BufferTooShort {
775                offset: pos,
776                need: 2,
777                have: rdata_end.saturating_sub(pos),
778            });
779        }
780        let fudge = u16::from_be_bytes([packet[pos], packet[pos + 1]]);
781        pos += 2;
782
783        // MAC size + MAC
784        if pos + 2 > rdata_end {
785            return Err(FieldError::BufferTooShort {
786                offset: pos,
787                need: 2,
788                have: rdata_end.saturating_sub(pos),
789            });
790        }
791        let mac_size = u16::from_be_bytes([packet[pos], packet[pos + 1]]) as usize;
792        pos += 2;
793
794        if pos + mac_size > rdata_end {
795            return Err(FieldError::BufferTooShort {
796                offset: pos,
797                need: mac_size,
798                have: rdata_end.saturating_sub(pos),
799            });
800        }
801        let mac = packet[pos..pos + mac_size].to_vec();
802        pos += mac_size;
803
804        // Original ID, Error, Other Data Length + Other Data
805        if pos + 6 > rdata_end {
806            return Err(FieldError::BufferTooShort {
807                offset: pos,
808                need: 6,
809                have: rdata_end.saturating_sub(pos),
810            });
811        }
812        let original_id = u16::from_be_bytes([packet[pos], packet[pos + 1]]);
813        let error = u16::from_be_bytes([packet[pos + 2], packet[pos + 3]]);
814        let other_len = u16::from_be_bytes([packet[pos + 4], packet[pos + 5]]) as usize;
815        pos += 6;
816
817        if pos + other_len > rdata_end {
818            return Err(FieldError::BufferTooShort {
819                offset: pos,
820                need: other_len,
821                have: rdata_end.saturating_sub(pos),
822            });
823        }
824        let other_data = packet[pos..pos + other_len].to_vec();
825
826        Ok(DnsRData::TSIG {
827            algorithm_name,
828            time_signed,
829            fudge,
830            mac,
831            original_id,
832            error,
833            other_data,
834        })
835    }
836
837    fn parse_tlsa(rdata: &[u8]) -> Result<Self, FieldError> {
838        if rdata.len() < 3 {
839            return Err(FieldError::BufferTooShort {
840                offset: 0,
841                need: 3,
842                have: rdata.len(),
843            });
844        }
845
846        let usage = rdata[0];
847        let selector = rdata[1];
848        let matching_type = rdata[2];
849        let cert_data = rdata[3..].to_vec();
850
851        Ok(DnsRData::TLSA {
852            usage,
853            selector,
854            matching_type,
855            cert_data,
856        })
857    }
858
859    fn parse_opt(rdata: &[u8]) -> Result<Self, FieldError> {
860        let options = EdnsOption::parse_all(rdata)?;
861        Ok(DnsRData::OPT(options))
862    }
863
864    /// Parse a DNS character-string (length-prefixed byte string).
865    /// Returns the data and total bytes consumed (including length byte).
866    fn parse_character_string(data: &[u8], offset: usize) -> Result<(Vec<u8>, usize), FieldError> {
867        if offset >= data.len() {
868            return Err(FieldError::BufferTooShort {
869                offset,
870                need: 1,
871                have: 0,
872            });
873        }
874        let len = data[offset] as usize;
875        if offset + 1 + len > data.len() {
876            return Err(FieldError::BufferTooShort {
877                offset: offset + 1,
878                need: len,
879                have: data.len() - offset - 1,
880            });
881        }
882        Ok((data[offset + 1..offset + 1 + len].to_vec(), 1 + len))
883    }
884
885    // ========================================================================
886    // Build (serialize) without compression
887    // ========================================================================
888
889    /// Serialize RDATA to wire format without DNS name compression.
890    pub fn build(&self) -> Vec<u8> {
891        match self {
892            DnsRData::A(addr) => addr.octets().to_vec(),
893
894            DnsRData::AAAA(addr) => addr.octets().to_vec(),
895
896            DnsRData::NS(name)
897            | DnsRData::CNAME(name)
898            | DnsRData::PTR(name)
899            | DnsRData::DNAME(name) => name.encode(),
900
901            DnsRData::MX {
902                preference,
903                exchange,
904            } => {
905                let mut out = Vec::new();
906                out.extend_from_slice(&preference.to_be_bytes());
907                out.extend_from_slice(&exchange.encode());
908                out
909            }
910
911            DnsRData::TXT(strings) => {
912                let mut out = Vec::new();
913                for s in strings {
914                    out.push(s.len() as u8);
915                    out.extend_from_slice(s);
916                }
917                out
918            }
919
920            DnsRData::SOA {
921                mname,
922                rname,
923                serial,
924                refresh,
925                retry,
926                expire,
927                minimum,
928            } => {
929                let mut out = Vec::new();
930                out.extend_from_slice(&mname.encode());
931                out.extend_from_slice(&rname.encode());
932                out.extend_from_slice(&serial.to_be_bytes());
933                out.extend_from_slice(&refresh.to_be_bytes());
934                out.extend_from_slice(&retry.to_be_bytes());
935                out.extend_from_slice(&expire.to_be_bytes());
936                out.extend_from_slice(&minimum.to_be_bytes());
937                out
938            }
939
940            DnsRData::SRV {
941                priority,
942                weight,
943                port,
944                target,
945            } => {
946                let mut out = Vec::new();
947                out.extend_from_slice(&priority.to_be_bytes());
948                out.extend_from_slice(&weight.to_be_bytes());
949                out.extend_from_slice(&port.to_be_bytes());
950                out.extend_from_slice(&target.encode());
951                out
952            }
953
954            DnsRData::HINFO { cpu, os } => {
955                let mut out = Vec::new();
956                out.push(cpu.len() as u8);
957                out.extend_from_slice(cpu);
958                out.push(os.len() as u8);
959                out.extend_from_slice(os);
960                out
961            }
962
963            DnsRData::NAPTR {
964                order,
965                preference,
966                flags,
967                services,
968                regexp,
969                replacement,
970            } => {
971                let mut out = Vec::new();
972                out.extend_from_slice(&order.to_be_bytes());
973                out.extend_from_slice(&preference.to_be_bytes());
974                out.push(flags.len() as u8);
975                out.extend_from_slice(flags);
976                out.push(services.len() as u8);
977                out.extend_from_slice(services);
978                out.push(regexp.len() as u8);
979                out.extend_from_slice(regexp);
980                out.extend_from_slice(&replacement.encode());
981                out
982            }
983
984            DnsRData::SVCB {
985                priority,
986                target,
987                params,
988            }
989            | DnsRData::HTTPS {
990                priority,
991                target,
992                params,
993            } => {
994                let mut out = Vec::new();
995                out.extend_from_slice(&priority.to_be_bytes());
996                out.extend_from_slice(&target.encode());
997                for p in params {
998                    out.extend_from_slice(&p.build());
999                }
1000                out
1001            }
1002
1003            DnsRData::CAA { flags, tag, value } => {
1004                let mut out = Vec::new();
1005                out.push(*flags);
1006                out.push(tag.len() as u8);
1007                out.extend_from_slice(tag.as_bytes());
1008                out.extend_from_slice(value);
1009                out
1010            }
1011
1012            DnsRData::RRSIG {
1013                type_covered,
1014                algorithm,
1015                labels,
1016                original_ttl,
1017                sig_expiration,
1018                sig_inception,
1019                key_tag,
1020                signer_name,
1021                signature,
1022            } => {
1023                let mut out = Vec::new();
1024                out.extend_from_slice(&type_covered.to_be_bytes());
1025                out.push(*algorithm);
1026                out.push(*labels);
1027                out.extend_from_slice(&original_ttl.to_be_bytes());
1028                out.extend_from_slice(&sig_expiration.to_be_bytes());
1029                out.extend_from_slice(&sig_inception.to_be_bytes());
1030                out.extend_from_slice(&key_tag.to_be_bytes());
1031                out.extend_from_slice(&signer_name.encode());
1032                out.extend_from_slice(signature);
1033                out
1034            }
1035
1036            DnsRData::NSEC {
1037                next_domain,
1038                type_bitmaps,
1039            } => {
1040                let mut out = Vec::new();
1041                out.extend_from_slice(&next_domain.encode());
1042                out.extend_from_slice(&bitmap::rr_list_to_bitmap(type_bitmaps));
1043                out
1044            }
1045
1046            DnsRData::NSEC3 {
1047                hash_algorithm,
1048                flags,
1049                iterations,
1050                salt,
1051                next_hashed,
1052                type_bitmaps,
1053            } => {
1054                let mut out = Vec::new();
1055                out.push(*hash_algorithm);
1056                out.push(*flags);
1057                out.extend_from_slice(&iterations.to_be_bytes());
1058                out.push(salt.len() as u8);
1059                out.extend_from_slice(salt);
1060                out.push(next_hashed.len() as u8);
1061                out.extend_from_slice(next_hashed);
1062                out.extend_from_slice(&bitmap::rr_list_to_bitmap(type_bitmaps));
1063                out
1064            }
1065
1066            DnsRData::NSEC3PARAM {
1067                hash_algorithm,
1068                flags,
1069                iterations,
1070                salt,
1071            } => {
1072                let mut out = Vec::new();
1073                out.push(*hash_algorithm);
1074                out.push(*flags);
1075                out.extend_from_slice(&iterations.to_be_bytes());
1076                out.push(salt.len() as u8);
1077                out.extend_from_slice(salt);
1078                out
1079            }
1080
1081            DnsRData::DNSKEY {
1082                flags,
1083                protocol,
1084                algorithm,
1085                public_key,
1086            } => {
1087                let mut out = Vec::new();
1088                out.extend_from_slice(&flags.to_be_bytes());
1089                out.push(*protocol);
1090                out.push(*algorithm);
1091                out.extend_from_slice(public_key);
1092                out
1093            }
1094
1095            DnsRData::DS {
1096                key_tag,
1097                algorithm,
1098                digest_type,
1099                digest,
1100            }
1101            | DnsRData::DLV {
1102                key_tag,
1103                algorithm,
1104                digest_type,
1105                digest,
1106            } => {
1107                let mut out = Vec::new();
1108                out.extend_from_slice(&key_tag.to_be_bytes());
1109                out.push(*algorithm);
1110                out.push(*digest_type);
1111                out.extend_from_slice(digest);
1112                out
1113            }
1114
1115            DnsRData::TSIG {
1116                algorithm_name,
1117                time_signed,
1118                fudge,
1119                mac,
1120                original_id,
1121                error,
1122                other_data,
1123            } => {
1124                let mut out = Vec::new();
1125                out.extend_from_slice(&algorithm_name.encode());
1126                // Time signed: 48 bits (6 bytes)
1127                out.push((time_signed >> 40) as u8);
1128                out.push((time_signed >> 32) as u8);
1129                out.push((time_signed >> 24) as u8);
1130                out.push((time_signed >> 16) as u8);
1131                out.push((time_signed >> 8) as u8);
1132                out.push(*time_signed as u8);
1133                out.extend_from_slice(&fudge.to_be_bytes());
1134                out.extend_from_slice(&(mac.len() as u16).to_be_bytes());
1135                out.extend_from_slice(mac);
1136                out.extend_from_slice(&original_id.to_be_bytes());
1137                out.extend_from_slice(&error.to_be_bytes());
1138                out.extend_from_slice(&(other_data.len() as u16).to_be_bytes());
1139                out.extend_from_slice(other_data);
1140                out
1141            }
1142
1143            DnsRData::TLSA {
1144                usage,
1145                selector,
1146                matching_type,
1147                cert_data,
1148            } => {
1149                let mut out = Vec::new();
1150                out.push(*usage);
1151                out.push(*selector);
1152                out.push(*matching_type);
1153                out.extend_from_slice(cert_data);
1154                out
1155            }
1156
1157            DnsRData::OPT(options) => {
1158                let mut out = Vec::new();
1159                for opt in options {
1160                    out.extend_from_slice(&opt.build());
1161                }
1162                out
1163            }
1164
1165            DnsRData::Unknown { data, .. } => data.clone(),
1166        }
1167    }
1168
1169    // ========================================================================
1170    // Build with compression
1171    // ========================================================================
1172
1173    /// Serialize RDATA to wire format with DNS name compression.
1174    ///
1175    /// # Arguments
1176    /// * `offset` - Byte offset where this RDATA will be placed in the packet.
1177    ///   Used to record name positions in the compression map.
1178    /// * `map` - Compression map tracking previously written domain names and
1179    ///   their offsets.
1180    pub fn build_compressed(&self, offset: usize, map: &mut HashMap<String, u16>) -> Vec<u8> {
1181        match self {
1182            DnsRData::NS(name)
1183            | DnsRData::CNAME(name)
1184            | DnsRData::PTR(name)
1185            | DnsRData::DNAME(name) => name.encode_compressed(offset, map),
1186
1187            DnsRData::MX {
1188                preference,
1189                exchange,
1190            } => {
1191                let mut out = Vec::new();
1192                out.extend_from_slice(&preference.to_be_bytes());
1193                let name_bytes = exchange.encode_compressed(offset + 2, map);
1194                out.extend_from_slice(&name_bytes);
1195                out
1196            }
1197
1198            DnsRData::SOA {
1199                mname,
1200                rname,
1201                serial,
1202                refresh,
1203                retry,
1204                expire,
1205                minimum,
1206            } => {
1207                let mut out = Vec::new();
1208                let mname_bytes = mname.encode_compressed(offset, map);
1209                out.extend_from_slice(&mname_bytes);
1210                let rname_bytes = rname.encode_compressed(offset + mname_bytes.len(), map);
1211                out.extend_from_slice(&rname_bytes);
1212                out.extend_from_slice(&serial.to_be_bytes());
1213                out.extend_from_slice(&refresh.to_be_bytes());
1214                out.extend_from_slice(&retry.to_be_bytes());
1215                out.extend_from_slice(&expire.to_be_bytes());
1216                out.extend_from_slice(&minimum.to_be_bytes());
1217                out
1218            }
1219
1220            DnsRData::SRV {
1221                priority,
1222                weight,
1223                port,
1224                target,
1225            } => {
1226                let mut out = Vec::new();
1227                out.extend_from_slice(&priority.to_be_bytes());
1228                out.extend_from_slice(&weight.to_be_bytes());
1229                out.extend_from_slice(&port.to_be_bytes());
1230                // RFC 2782: SRV target MUST NOT use compression
1231                out.extend_from_slice(&target.encode());
1232                out
1233            }
1234
1235            DnsRData::NAPTR {
1236                order,
1237                preference,
1238                flags,
1239                services,
1240                regexp,
1241                replacement,
1242            } => {
1243                let mut out = Vec::new();
1244                out.extend_from_slice(&order.to_be_bytes());
1245                out.extend_from_slice(&preference.to_be_bytes());
1246                out.push(flags.len() as u8);
1247                out.extend_from_slice(flags);
1248                out.push(services.len() as u8);
1249                out.extend_from_slice(services);
1250                out.push(regexp.len() as u8);
1251                out.extend_from_slice(regexp);
1252                let name_bytes = replacement.encode_compressed(offset + out.len(), map);
1253                out.extend_from_slice(&name_bytes);
1254                out
1255            }
1256
1257            DnsRData::RRSIG {
1258                type_covered,
1259                algorithm,
1260                labels,
1261                original_ttl,
1262                sig_expiration,
1263                sig_inception,
1264                key_tag,
1265                signer_name,
1266                signature,
1267            } => {
1268                let mut out = Vec::new();
1269                out.extend_from_slice(&type_covered.to_be_bytes());
1270                out.push(*algorithm);
1271                out.push(*labels);
1272                out.extend_from_slice(&original_ttl.to_be_bytes());
1273                out.extend_from_slice(&sig_expiration.to_be_bytes());
1274                out.extend_from_slice(&sig_inception.to_be_bytes());
1275                out.extend_from_slice(&key_tag.to_be_bytes());
1276                // RFC 4034: signer name MUST NOT use compression in DNSSEC
1277                // However, some implementations do. We support it for flexibility.
1278                let name_bytes = signer_name.encode_compressed(offset + out.len(), map);
1279                out.extend_from_slice(&name_bytes);
1280                out.extend_from_slice(signature);
1281                out
1282            }
1283
1284            DnsRData::NSEC {
1285                next_domain,
1286                type_bitmaps,
1287            } => {
1288                let mut out = Vec::new();
1289                // RFC 4034: NSEC next domain MUST NOT use compression
1290                out.extend_from_slice(&next_domain.encode());
1291                out.extend_from_slice(&bitmap::rr_list_to_bitmap(type_bitmaps));
1292                out
1293            }
1294
1295            // For SVCB/HTTPS, the target name MUST NOT be compressed per RFC 9460
1296            DnsRData::SVCB { .. } | DnsRData::HTTPS { .. } => self.build(),
1297
1298            // These types do not contain compressible names
1299            _ => self.build(),
1300        }
1301    }
1302
1303    // ========================================================================
1304    // Summary
1305    // ========================================================================
1306
1307    /// Get a human-readable summary of this RDATA.
1308    pub fn summary(&self) -> String {
1309        match self {
1310            DnsRData::A(addr) => addr.to_string(),
1311
1312            DnsRData::AAAA(addr) => addr.to_string(),
1313
1314            DnsRData::NS(name) => format!("NS {}", name),
1315
1316            DnsRData::CNAME(name) => format!("CNAME {}", name),
1317
1318            DnsRData::PTR(name) => format!("PTR {}", name),
1319
1320            DnsRData::DNAME(name) => format!("DNAME {}", name),
1321
1322            DnsRData::MX {
1323                preference,
1324                exchange,
1325            } => {
1326                format!("MX {} {}", preference, exchange)
1327            }
1328
1329            DnsRData::TXT(strings) => {
1330                let parts: Vec<String> = strings
1331                    .iter()
1332                    .map(|s| format!("\"{}\"", String::from_utf8_lossy(s)))
1333                    .collect();
1334                format!("TXT {}", parts.join(" "))
1335            }
1336
1337            DnsRData::SOA {
1338                mname,
1339                rname,
1340                serial,
1341                refresh,
1342                retry,
1343                expire,
1344                minimum,
1345            } => {
1346                format!(
1347                    "SOA {} {} {} {} {} {} {}",
1348                    mname, rname, serial, refresh, retry, expire, minimum
1349                )
1350            }
1351
1352            DnsRData::SRV {
1353                priority,
1354                weight,
1355                port,
1356                target,
1357            } => {
1358                format!("SRV {} {} {} {}", priority, weight, port, target)
1359            }
1360
1361            DnsRData::HINFO { cpu, os } => {
1362                format!(
1363                    "HINFO \"{}\" \"{}\"",
1364                    String::from_utf8_lossy(cpu),
1365                    String::from_utf8_lossy(os)
1366                )
1367            }
1368
1369            DnsRData::NAPTR {
1370                order,
1371                preference,
1372                flags,
1373                services,
1374                regexp,
1375                replacement,
1376            } => {
1377                format!(
1378                    "NAPTR {} {} \"{}\" \"{}\" \"{}\" {}",
1379                    order,
1380                    preference,
1381                    String::from_utf8_lossy(flags),
1382                    String::from_utf8_lossy(services),
1383                    String::from_utf8_lossy(regexp),
1384                    replacement
1385                )
1386            }
1387
1388            DnsRData::SVCB {
1389                priority,
1390                target,
1391                params,
1392            } => {
1393                let param_strs: Vec<String> = params
1394                    .iter()
1395                    .map(|p| {
1396                        let val = p.build_value();
1397                        format!("key{}={} bytes", p.key(), val.len())
1398                    })
1399                    .collect();
1400                format!("SVCB {} {} {}", priority, target, param_strs.join(" "))
1401            }
1402
1403            DnsRData::HTTPS {
1404                priority,
1405                target,
1406                params,
1407            } => {
1408                let param_strs: Vec<String> = params
1409                    .iter()
1410                    .map(|p| {
1411                        let val = p.build_value();
1412                        format!("key{}={} bytes", p.key(), val.len())
1413                    })
1414                    .collect();
1415                format!("HTTPS {} {} {}", priority, target, param_strs.join(" "))
1416            }
1417
1418            DnsRData::CAA { flags, tag, value } => {
1419                format!(
1420                    "CAA {} {} \"{}\"",
1421                    flags,
1422                    tag,
1423                    String::from_utf8_lossy(value)
1424                )
1425            }
1426
1427            DnsRData::RRSIG {
1428                type_covered,
1429                algorithm,
1430                labels,
1431                original_ttl,
1432                key_tag,
1433                signer_name,
1434                ..
1435            } => {
1436                format!(
1437                    "RRSIG {} {} {} {} {} {}",
1438                    types::dns_type_name(*type_covered),
1439                    algorithm,
1440                    labels,
1441                    original_ttl,
1442                    key_tag,
1443                    signer_name
1444                )
1445            }
1446
1447            DnsRData::NSEC {
1448                next_domain,
1449                type_bitmaps,
1450            } => {
1451                let type_names: Vec<&str> = type_bitmaps
1452                    .iter()
1453                    .map(|t| types::dns_type_name(*t))
1454                    .collect();
1455                format!("NSEC {} [{}]", next_domain, type_names.join(" "))
1456            }
1457
1458            DnsRData::NSEC3 {
1459                hash_algorithm,
1460                iterations,
1461                next_hashed,
1462                type_bitmaps,
1463                ..
1464            } => {
1465                let type_names: Vec<&str> = type_bitmaps
1466                    .iter()
1467                    .map(|t| types::dns_type_name(*t))
1468                    .collect();
1469                format!(
1470                    "NSEC3 alg={} iter={} hash={} [{}]",
1471                    hash_algorithm,
1472                    iterations,
1473                    hex(next_hashed),
1474                    type_names.join(" ")
1475                )
1476            }
1477
1478            DnsRData::NSEC3PARAM {
1479                hash_algorithm,
1480                flags,
1481                iterations,
1482                salt,
1483            } => {
1484                format!(
1485                    "NSEC3PARAM alg={} flags={} iter={} salt={}",
1486                    hash_algorithm,
1487                    flags,
1488                    iterations,
1489                    if salt.is_empty() {
1490                        "-".to_string()
1491                    } else {
1492                        hex(salt)
1493                    }
1494                )
1495            }
1496
1497            DnsRData::DNSKEY {
1498                flags,
1499                protocol,
1500                algorithm,
1501                public_key,
1502            } => {
1503                format!(
1504                    "DNSKEY flags={} proto={} alg={} key={} bytes",
1505                    flags,
1506                    protocol,
1507                    algorithm,
1508                    public_key.len()
1509                )
1510            }
1511
1512            DnsRData::DS {
1513                key_tag,
1514                algorithm,
1515                digest_type,
1516                digest,
1517            } => {
1518                format!(
1519                    "DS {} {} {} {}",
1520                    key_tag,
1521                    algorithm,
1522                    digest_type,
1523                    hex(digest)
1524                )
1525            }
1526
1527            DnsRData::DLV {
1528                key_tag,
1529                algorithm,
1530                digest_type,
1531                digest,
1532            } => {
1533                format!(
1534                    "DLV {} {} {} {}",
1535                    key_tag,
1536                    algorithm,
1537                    digest_type,
1538                    hex(digest)
1539                )
1540            }
1541
1542            DnsRData::TSIG {
1543                algorithm_name,
1544                original_id,
1545                error,
1546                mac,
1547                ..
1548            } => {
1549                format!(
1550                    "TSIG {} id={} err={} mac={} bytes",
1551                    algorithm_name,
1552                    original_id,
1553                    error,
1554                    mac.len()
1555                )
1556            }
1557
1558            DnsRData::TLSA {
1559                usage,
1560                selector,
1561                matching_type,
1562                cert_data,
1563            } => {
1564                format!(
1565                    "TLSA {} {} {} {} bytes",
1566                    usage,
1567                    selector,
1568                    matching_type,
1569                    cert_data.len()
1570                )
1571            }
1572
1573            DnsRData::OPT(options) => {
1574                if options.is_empty() {
1575                    "OPT (empty)".to_string()
1576                } else {
1577                    let summaries: Vec<String> = options.iter().map(|o| o.summary()).collect();
1578                    format!("OPT [{}]", summaries.join(", "))
1579                }
1580            }
1581
1582            DnsRData::Unknown { rtype, data } => {
1583                format!("TYPE{} ({} bytes)", rtype, data.len())
1584            }
1585        }
1586    }
1587}
1588
1589/// Convert bytes to a hex string.
1590fn hex(data: &[u8]) -> String {
1591    data.iter().map(|b| format!("{:02x}", b)).collect()
1592}
1593
1594// ============================================================================
1595// Tests
1596// ============================================================================
1597
1598#[cfg(test)]
1599mod tests {
1600    use super::*;
1601
1602    /// Helper: build RDATA, then parse it back, treating the built bytes as
1603    /// both the packet and the RDATA region (no compression pointers needed
1604    /// for uncompressed names).
1605    fn roundtrip(rtype: u16, rdata: &DnsRData) -> DnsRData {
1606        let built = rdata.build();
1607        DnsRData::parse(rtype, &built, 0, built.len() as u16).unwrap()
1608    }
1609
1610    // ====================================================================
1611    // A record
1612    // ====================================================================
1613
1614    #[test]
1615    fn test_a_parse() {
1616        let wire = [192, 0, 2, 1]; // 192.0.2.1
1617        let rdata = DnsRData::parse(rr_type::A, &wire, 0, 4).unwrap();
1618        assert_eq!(rdata, DnsRData::A(Ipv4Addr::new(192, 0, 2, 1)));
1619    }
1620
1621    #[test]
1622    fn test_a_build_roundtrip() {
1623        let original = DnsRData::A(Ipv4Addr::new(10, 0, 0, 1));
1624        let result = roundtrip(rr_type::A, &original);
1625        assert_eq!(result, original);
1626    }
1627
1628    #[test]
1629    fn test_a_invalid_length() {
1630        let wire = [192, 0, 2]; // 3 bytes instead of 4
1631        assert!(DnsRData::parse(rr_type::A, &wire, 0, 3).is_err());
1632    }
1633
1634    // ====================================================================
1635    // AAAA record
1636    // ====================================================================
1637
1638    #[test]
1639    fn test_aaaa_parse() {
1640        let wire = [
1641            0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1642            0x00, 0x01,
1643        ];
1644        let rdata = DnsRData::parse(rr_type::AAAA, &wire, 0, 16).unwrap();
1645        assert_eq!(rdata, DnsRData::AAAA("2001:db8::1".parse().unwrap()));
1646    }
1647
1648    #[test]
1649    fn test_aaaa_build_roundtrip() {
1650        let original = DnsRData::AAAA("::1".parse().unwrap());
1651        let result = roundtrip(rr_type::AAAA, &original);
1652        assert_eq!(result, original);
1653    }
1654
1655    #[test]
1656    fn test_aaaa_invalid_length() {
1657        let wire = [0u8; 15]; // 15 bytes instead of 16
1658        assert!(DnsRData::parse(rr_type::AAAA, &wire, 0, 15).is_err());
1659    }
1660
1661    // ====================================================================
1662    // MX record
1663    // ====================================================================
1664
1665    #[test]
1666    fn test_mx_parse() {
1667        // preference=10, exchange=mail.example.com
1668        let mut wire = Vec::new();
1669        wire.extend_from_slice(&10u16.to_be_bytes());
1670        wire.extend_from_slice(&DnsName::from("mail.example.com").encode());
1671        let rdlen = wire.len() as u16;
1672
1673        let rdata = DnsRData::parse(rr_type::MX, &wire, 0, rdlen).unwrap();
1674        match &rdata {
1675            DnsRData::MX {
1676                preference,
1677                exchange,
1678            } => {
1679                assert_eq!(*preference, 10);
1680                assert_eq!(exchange.to_fqdn(), "mail.example.com.");
1681            }
1682            _ => panic!("expected MX, got {:?}", rdata),
1683        }
1684    }
1685
1686    #[test]
1687    fn test_mx_build_roundtrip() {
1688        let original = DnsRData::MX {
1689            preference: 20,
1690            exchange: DnsName::from("mx.test.org"),
1691        };
1692        let result = roundtrip(rr_type::MX, &original);
1693        assert_eq!(result, original);
1694    }
1695
1696    // ====================================================================
1697    // TXT record
1698    // ====================================================================
1699
1700    #[test]
1701    fn test_txt_single_string() {
1702        let text = b"v=spf1 include:example.com ~all";
1703        let mut wire = Vec::new();
1704        wire.push(text.len() as u8);
1705        wire.extend_from_slice(text);
1706        let rdlen = wire.len() as u16;
1707
1708        let rdata = DnsRData::parse(rr_type::TXT, &wire, 0, rdlen).unwrap();
1709        match &rdata {
1710            DnsRData::TXT(strings) => {
1711                assert_eq!(strings.len(), 1);
1712                assert_eq!(strings[0], text.to_vec());
1713            }
1714            _ => panic!("expected TXT"),
1715        }
1716    }
1717
1718    #[test]
1719    fn test_txt_multiple_strings() {
1720        let s1 = b"hello";
1721        let s2 = b"world";
1722        let mut wire = Vec::new();
1723        wire.push(s1.len() as u8);
1724        wire.extend_from_slice(s1);
1725        wire.push(s2.len() as u8);
1726        wire.extend_from_slice(s2);
1727        let rdlen = wire.len() as u16;
1728
1729        let rdata = DnsRData::parse(rr_type::TXT, &wire, 0, rdlen).unwrap();
1730        match &rdata {
1731            DnsRData::TXT(strings) => {
1732                assert_eq!(strings.len(), 2);
1733                assert_eq!(strings[0], s1.to_vec());
1734                assert_eq!(strings[1], s2.to_vec());
1735            }
1736            _ => panic!("expected TXT"),
1737        }
1738    }
1739
1740    #[test]
1741    fn test_txt_zero_length_string() {
1742        let mut wire = Vec::new();
1743        wire.push(0); // zero-length string
1744        wire.push(5); // "hello"
1745        wire.extend_from_slice(b"hello");
1746        let rdlen = wire.len() as u16;
1747
1748        let rdata = DnsRData::parse(rr_type::TXT, &wire, 0, rdlen).unwrap();
1749        match &rdata {
1750            DnsRData::TXT(strings) => {
1751                assert_eq!(strings.len(), 2);
1752                assert!(strings[0].is_empty());
1753                assert_eq!(strings[1], b"hello".to_vec());
1754            }
1755            _ => panic!("expected TXT"),
1756        }
1757    }
1758
1759    #[test]
1760    fn test_txt_build_roundtrip() {
1761        let original = DnsRData::TXT(vec![
1762            b"first".to_vec(),
1763            Vec::new(), // empty string
1764            b"third".to_vec(),
1765        ]);
1766        let result = roundtrip(rr_type::TXT, &original);
1767        assert_eq!(result, original);
1768    }
1769
1770    // ====================================================================
1771    // SOA record
1772    // ====================================================================
1773
1774    #[test]
1775    fn test_soa_parse_and_roundtrip() {
1776        let original = DnsRData::SOA {
1777            mname: DnsName::from("ns1.example.com"),
1778            rname: DnsName::from("admin.example.com"),
1779            serial: 2024010101,
1780            refresh: 3600,
1781            retry: 900,
1782            expire: 604800,
1783            minimum: 86400,
1784        };
1785        let result = roundtrip(rr_type::SOA, &original);
1786        assert_eq!(result, original);
1787    }
1788
1789    #[test]
1790    fn test_soa_with_compressed_names() {
1791        // Build a fake packet where SOA names share a suffix with an earlier name.
1792        // First, write "example.com" name, then the SOA RDATA with compressed names.
1793        let example_name = DnsName::from("example.com");
1794        let mut packet = example_name.encode(); // offset 0: "example.com"
1795        let rdata_offset = packet.len();
1796
1797        // SOA MNAME: ns1.example.com (with "example.com" compressed to pointer 0)
1798        let mut compression_map = HashMap::new();
1799        compression_map.insert("example.com".to_string(), 0u16);
1800
1801        let mname = DnsName::from("ns1.example.com");
1802        let mname_bytes = mname.encode_compressed(rdata_offset, &mut compression_map);
1803        packet.extend_from_slice(&mname_bytes);
1804
1805        let rname = DnsName::from("admin.example.com");
1806        let rname_offset = rdata_offset + mname_bytes.len();
1807        let rname_bytes = rname.encode_compressed(rname_offset, &mut compression_map);
1808        packet.extend_from_slice(&rname_bytes);
1809
1810        // Fixed fields
1811        packet.extend_from_slice(&2024010101u32.to_be_bytes());
1812        packet.extend_from_slice(&3600u32.to_be_bytes());
1813        packet.extend_from_slice(&900u32.to_be_bytes());
1814        packet.extend_from_slice(&604800u32.to_be_bytes());
1815        packet.extend_from_slice(&86400u32.to_be_bytes());
1816
1817        let rdlen = (packet.len() - rdata_offset) as u16;
1818        let rdata = DnsRData::parse(rr_type::SOA, &packet, rdata_offset, rdlen).unwrap();
1819
1820        match &rdata {
1821            DnsRData::SOA {
1822                mname,
1823                rname,
1824                serial,
1825                ..
1826            } => {
1827                assert_eq!(mname.to_fqdn(), "ns1.example.com.");
1828                assert_eq!(rname.to_fqdn(), "admin.example.com.");
1829                assert_eq!(*serial, 2024010101);
1830            }
1831            _ => panic!("expected SOA"),
1832        }
1833    }
1834
1835    // ====================================================================
1836    // SRV record
1837    // ====================================================================
1838
1839    #[test]
1840    fn test_srv_build_roundtrip() {
1841        let original = DnsRData::SRV {
1842            priority: 10,
1843            weight: 60,
1844            port: 5060,
1845            target: DnsName::from("sipserver.example.com"),
1846        };
1847        let result = roundtrip(rr_type::SRV, &original);
1848        assert_eq!(result, original);
1849    }
1850
1851    // ====================================================================
1852    // CNAME / NS / PTR
1853    // ====================================================================
1854
1855    #[test]
1856    fn test_cname_roundtrip() {
1857        let original = DnsRData::CNAME(DnsName::from("www.example.com"));
1858        let result = roundtrip(rr_type::CNAME, &original);
1859        assert_eq!(result, original);
1860    }
1861
1862    #[test]
1863    fn test_ns_roundtrip() {
1864        let original = DnsRData::NS(DnsName::from("ns1.example.com"));
1865        let result = roundtrip(rr_type::NS, &original);
1866        assert_eq!(result, original);
1867    }
1868
1869    #[test]
1870    fn test_ptr_roundtrip() {
1871        let original = DnsRData::PTR(DnsName::from("1.2.0.192.in-addr.arpa"));
1872        let result = roundtrip(rr_type::PTR, &original);
1873        assert_eq!(result, original);
1874    }
1875
1876    // ====================================================================
1877    // HINFO record
1878    // ====================================================================
1879
1880    #[test]
1881    fn test_hinfo_roundtrip() {
1882        let original = DnsRData::HINFO {
1883            cpu: b"x86_64".to_vec(),
1884            os: b"Linux".to_vec(),
1885        };
1886        let result = roundtrip(rr_type::HINFO, &original);
1887        assert_eq!(result, original);
1888    }
1889
1890    // ====================================================================
1891    // CAA record
1892    // ====================================================================
1893
1894    #[test]
1895    fn test_caa_roundtrip() {
1896        let original = DnsRData::CAA {
1897            flags: 0,
1898            tag: "issue".to_string(),
1899            value: b"letsencrypt.org".to_vec(),
1900        };
1901        let result = roundtrip(rr_type::CAA, &original);
1902        assert_eq!(result, original);
1903    }
1904
1905    // ====================================================================
1906    // DNSKEY record
1907    // ====================================================================
1908
1909    #[test]
1910    fn test_dnskey_roundtrip() {
1911        let original = DnsRData::DNSKEY {
1912            flags: 257,    // KSK
1913            protocol: 3,   // DNSSEC
1914            algorithm: 13, // ECDSAP256SHA256
1915            public_key: vec![0x01, 0x02, 0x03, 0x04],
1916        };
1917        let result = roundtrip(rr_type::DNSKEY, &original);
1918        assert_eq!(result, original);
1919    }
1920
1921    // ====================================================================
1922    // DS record
1923    // ====================================================================
1924
1925    #[test]
1926    fn test_ds_roundtrip() {
1927        let original = DnsRData::DS {
1928            key_tag: 12345,
1929            algorithm: 8,
1930            digest_type: 2,
1931            digest: vec![0xAA, 0xBB, 0xCC, 0xDD],
1932        };
1933        let result = roundtrip(rr_type::DS, &original);
1934        assert_eq!(result, original);
1935    }
1936
1937    // ====================================================================
1938    // NSEC record
1939    // ====================================================================
1940
1941    #[test]
1942    fn test_nsec_roundtrip() {
1943        let original = DnsRData::NSEC {
1944            next_domain: DnsName::from("next.example.com"),
1945            type_bitmaps: vec![1, 2, 5, 6, 15, 16, 28, 46, 47], // A NS CNAME SOA MX TXT AAAA RRSIG NSEC
1946        };
1947        let result = roundtrip(rr_type::NSEC, &original);
1948        assert_eq!(result, original);
1949    }
1950
1951    // ====================================================================
1952    // NSEC3 record
1953    // ====================================================================
1954
1955    #[test]
1956    fn test_nsec3_roundtrip() {
1957        let original = DnsRData::NSEC3 {
1958            hash_algorithm: 1,
1959            flags: 0,
1960            iterations: 10,
1961            salt: vec![0xAA, 0xBB],
1962            next_hashed: vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
1963            type_bitmaps: vec![1, 28], // A AAAA
1964        };
1965        let result = roundtrip(rr_type::NSEC3, &original);
1966        assert_eq!(result, original);
1967    }
1968
1969    #[test]
1970    fn test_nsec3_empty_salt() {
1971        let original = DnsRData::NSEC3 {
1972            hash_algorithm: 1,
1973            flags: 1,
1974            iterations: 0,
1975            salt: Vec::new(),
1976            next_hashed: vec![0x01, 0x02, 0x03, 0x04],
1977            type_bitmaps: vec![1],
1978        };
1979        let result = roundtrip(rr_type::NSEC3, &original);
1980        assert_eq!(result, original);
1981    }
1982
1983    // ====================================================================
1984    // NSEC3PARAM record
1985    // ====================================================================
1986
1987    #[test]
1988    fn test_nsec3param_roundtrip() {
1989        let original = DnsRData::NSEC3PARAM {
1990            hash_algorithm: 1,
1991            flags: 0,
1992            iterations: 10,
1993            salt: vec![0xDE, 0xAD],
1994        };
1995        let result = roundtrip(rr_type::NSEC3PARAM, &original);
1996        assert_eq!(result, original);
1997    }
1998
1999    // ====================================================================
2000    // RRSIG record
2001    // ====================================================================
2002
2003    #[test]
2004    fn test_rrsig_roundtrip() {
2005        let original = DnsRData::RRSIG {
2006            type_covered: rr_type::A,
2007            algorithm: 8,
2008            labels: 3,
2009            original_ttl: 3600,
2010            sig_expiration: 1700000000,
2011            sig_inception: 1690000000,
2012            key_tag: 54321,
2013            signer_name: DnsName::from("example.com"),
2014            signature: vec![0x01, 0x02, 0x03, 0x04, 0x05],
2015        };
2016        let result = roundtrip(rr_type::RRSIG, &original);
2017        assert_eq!(result, original);
2018    }
2019
2020    // ====================================================================
2021    // TLSA record
2022    // ====================================================================
2023
2024    #[test]
2025    fn test_tlsa_roundtrip() {
2026        let original = DnsRData::TLSA {
2027            usage: 3,         // DANE-EE
2028            selector: 1,      // SPKI
2029            matching_type: 1, // SHA-256
2030            cert_data: vec![0xAB; 32],
2031        };
2032        let result = roundtrip(rr_type::TLSA, &original);
2033        assert_eq!(result, original);
2034    }
2035
2036    // ====================================================================
2037    // NAPTR record
2038    // ====================================================================
2039
2040    #[test]
2041    fn test_naptr_roundtrip() {
2042        let original = DnsRData::NAPTR {
2043            order: 100,
2044            preference: 10,
2045            flags: b"s".to_vec(),
2046            services: b"SIP+D2T".to_vec(),
2047            regexp: Vec::new(),
2048            replacement: DnsName::from("_sip._tcp.example.com"),
2049        };
2050        let result = roundtrip(rr_type::NAPTR, &original);
2051        assert_eq!(result, original);
2052    }
2053
2054    // ====================================================================
2055    // SVCB / HTTPS records
2056    // ====================================================================
2057
2058    #[test]
2059    fn test_svcb_roundtrip() {
2060        let original = DnsRData::SVCB {
2061            priority: 1,
2062            target: DnsName::from("svc.example.com"),
2063            params: vec![SvcParam::Alpn(vec!["h2".to_string()]), SvcParam::Port(443)],
2064        };
2065        let result = roundtrip(rr_type::SVCB, &original);
2066        assert_eq!(result, original);
2067    }
2068
2069    #[test]
2070    fn test_https_roundtrip() {
2071        let original = DnsRData::HTTPS {
2072            priority: 1,
2073            target: DnsName::root(),
2074            params: vec![SvcParam::Alpn(vec!["h2".to_string(), "h3".to_string()])],
2075        };
2076        let result = roundtrip(rr_type::HTTPS, &original);
2077        assert_eq!(result, original);
2078    }
2079
2080    // ====================================================================
2081    // OPT record
2082    // ====================================================================
2083
2084    #[test]
2085    fn test_opt_roundtrip() {
2086        let original = DnsRData::OPT(vec![
2087            EdnsOption::NSID(b"my-ns".to_vec()),
2088            EdnsOption::Cookie {
2089                client: vec![1, 2, 3, 4, 5, 6, 7, 8],
2090                server: vec![11, 12, 13, 14, 15, 16, 17, 18],
2091            },
2092        ]);
2093        let result = roundtrip(rr_type::OPT, &original);
2094        assert_eq!(result, original);
2095    }
2096
2097    #[test]
2098    fn test_opt_empty() {
2099        let original = DnsRData::OPT(vec![]);
2100        let result = roundtrip(rr_type::OPT, &original);
2101        assert_eq!(result, original);
2102    }
2103
2104    // ====================================================================
2105    // TSIG record
2106    // ====================================================================
2107
2108    #[test]
2109    fn test_tsig_roundtrip() {
2110        let original = DnsRData::TSIG {
2111            algorithm_name: DnsName::from("hmac-sha256"),
2112            time_signed: 1700000000,
2113            fudge: 300,
2114            mac: vec![0xAA; 32],
2115            original_id: 0x1234,
2116            error: 0,
2117            other_data: Vec::new(),
2118        };
2119        let result = roundtrip(rr_type::TSIG, &original);
2120        assert_eq!(result, original);
2121    }
2122
2123    // ====================================================================
2124    // DLV record
2125    // ====================================================================
2126
2127    #[test]
2128    fn test_dlv_roundtrip() {
2129        let original = DnsRData::DLV {
2130            key_tag: 9999,
2131            algorithm: 13,
2132            digest_type: 2,
2133            digest: vec![0x11, 0x22, 0x33, 0x44],
2134        };
2135        let result = roundtrip(rr_type::DLV, &original);
2136        assert_eq!(result, original);
2137    }
2138
2139    // ====================================================================
2140    // DNAME record
2141    // ====================================================================
2142
2143    #[test]
2144    fn test_dname_roundtrip() {
2145        let original = DnsRData::DNAME(DnsName::from("target.example.com"));
2146        let result = roundtrip(rr_type::DNAME, &original);
2147        assert_eq!(result, original);
2148    }
2149
2150    // ====================================================================
2151    // Unknown record
2152    // ====================================================================
2153
2154    #[test]
2155    fn test_unknown_roundtrip() {
2156        let original = DnsRData::Unknown {
2157            rtype: 9999,
2158            data: vec![0x01, 0x02, 0x03],
2159        };
2160        let built = original.build();
2161        let parsed = DnsRData::parse(9999, &built, 0, built.len() as u16).unwrap();
2162        assert_eq!(parsed, original);
2163    }
2164
2165    // ====================================================================
2166    // Compression tests
2167    // ====================================================================
2168
2169    #[test]
2170    fn test_mx_compressed() {
2171        let rdata = DnsRData::MX {
2172            preference: 10,
2173            exchange: DnsName::from("mail.example.com"),
2174        };
2175
2176        let mut map = HashMap::new();
2177        // Pretend "example.com" was written at offset 20
2178        map.insert("example.com".to_string(), 20u16);
2179
2180        let compressed = rdata.build_compressed(100, &mut map);
2181        let uncompressed = rdata.build();
2182
2183        // Compressed should be shorter because "example.com" is replaced by a pointer
2184        assert!(compressed.len() < uncompressed.len());
2185
2186        // Preference should still be at the start
2187        assert_eq!(u16::from_be_bytes([compressed[0], compressed[1]]), 10);
2188    }
2189
2190    #[test]
2191    fn test_soa_compressed() {
2192        let rdata = DnsRData::SOA {
2193            mname: DnsName::from("ns1.example.com"),
2194            rname: DnsName::from("admin.example.com"),
2195            serial: 1,
2196            refresh: 2,
2197            retry: 3,
2198            expire: 4,
2199            minimum: 5,
2200        };
2201
2202        let mut map = HashMap::new();
2203        let compressed = rdata.build_compressed(0, &mut map);
2204
2205        // After writing mname, "example.com" should be in the map,
2206        // so rname should compress "example.com" part.
2207        let uncompressed = rdata.build();
2208        assert!(compressed.len() < uncompressed.len());
2209    }
2210
2211    // ====================================================================
2212    // Summary tests
2213    // ====================================================================
2214
2215    #[test]
2216    fn test_summary_a() {
2217        let rdata = DnsRData::A(Ipv4Addr::new(1, 2, 3, 4));
2218        assert_eq!(rdata.summary(), "1.2.3.4");
2219    }
2220
2221    #[test]
2222    fn test_summary_aaaa() {
2223        let rdata = DnsRData::AAAA("::1".parse().unwrap());
2224        assert_eq!(rdata.summary(), "::1");
2225    }
2226
2227    #[test]
2228    fn test_summary_mx() {
2229        let rdata = DnsRData::MX {
2230            preference: 10,
2231            exchange: DnsName::from("mail.example.com"),
2232        };
2233        assert_eq!(rdata.summary(), "MX 10 mail.example.com.");
2234    }
2235
2236    #[test]
2237    fn test_summary_txt() {
2238        let rdata = DnsRData::TXT(vec![b"hello world".to_vec()]);
2239        assert_eq!(rdata.summary(), "TXT \"hello world\"");
2240    }
2241
2242    #[test]
2243    fn test_summary_unknown() {
2244        let rdata = DnsRData::Unknown {
2245            rtype: 9999,
2246            data: vec![0; 10],
2247        };
2248        assert_eq!(rdata.summary(), "TYPE9999 (10 bytes)");
2249    }
2250}