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