Skip to main content

packet_dissector_dns/
lib.rs

1//! DNS (Domain Name System) dissector.
2//!
3//! ## References
4//! - RFC 1035: <https://www.rfc-editor.org/rfc/rfc1035>
5//! - RFC 3596 (AAAA record): <https://www.rfc-editor.org/rfc/rfc3596>
6//! - RFC 4035 (DNSSEC, adds AD/CD flags): <https://www.rfc-editor.org/rfc/rfc4035>
7//! - RFC 6891 (EDNS0, extends RCODE/UDP payload): <https://www.rfc-editor.org/rfc/rfc6891>
8//! - RFC 7766 (DNS over TCP, updates §4.2.2): <https://www.rfc-editor.org/rfc/rfc7766>
9//! - RFC 7828 (EDNS0 TCP Keepalive): <https://www.rfc-editor.org/rfc/rfc7828>
10//! - RFC 2782 (SRV record): <https://www.rfc-editor.org/rfc/rfc2782>
11//! - RFC 3403 (NAPTR record): <https://www.rfc-editor.org/rfc/rfc3403>
12//! - RFC 4255 (SSHFP record): <https://www.rfc-editor.org/rfc/rfc4255>
13//! - RFC 6672 (DNAME record): <https://www.rfc-editor.org/rfc/rfc6672>
14//! - RFC 6698 (TLSA record): <https://www.rfc-editor.org/rfc/rfc6698>
15//! - RFC 8659 (CAA record): <https://www.rfc-editor.org/rfc/rfc8659>
16//! - RFC 4035 (DNSSEC records: DNSKEY, DS, RRSIG, NSEC): <https://www.rfc-editor.org/rfc/rfc4035>
17//! - RFC 5155 (NSEC3): <https://www.rfc-editor.org/rfc/rfc5155>
18//! - RFC 7344 (CDS/CDNSKEY records): <https://www.rfc-editor.org/rfc/rfc7344>
19//! - RFC 9460 (SVCB/HTTPS records): <https://www.rfc-editor.org/rfc/rfc9460>
20
21#![deny(missing_docs)]
22
23use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
24use packet_dissector_core::error::PacketError;
25use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue, FormatContext};
26use packet_dissector_core::packet::DissectBuffer;
27use packet_dissector_core::util::{read_be_u16, read_be_u32};
28
29/// DNS header size (fixed 12 bytes).
30const HEADER_SIZE: usize = 12;
31
32/// Returns a human-readable name for DNS QTYPE / TYPE values.
33///
34/// RFC 1035, Section 3.2.2; RFC 3596 (AAAA); RFC 2782 (SRV); RFC 6891 (OPT); RFC 9460 (HTTPS).
35pub fn dns_type_name(v: u16) -> Option<&'static str> {
36    match v {
37        1 => Some("A"),
38        2 => Some("NS"),
39        5 => Some("CNAME"),
40        6 => Some("SOA"),
41        12 => Some("PTR"),
42        15 => Some("MX"),
43        16 => Some("TXT"),
44        28 => Some("AAAA"),
45        29 => Some("LOC"),
46        33 => Some("SRV"),
47        35 => Some("NAPTR"),
48        41 => Some("OPT"),
49        43 => Some("DS"),
50        46 => Some("RRSIG"),
51        47 => Some("NSEC"),
52        48 => Some("DNSKEY"),
53        50 => Some("NSEC3"),
54        52 => Some("TLSA"),
55        65 => Some("HTTPS"),
56        255 => Some("ANY"),
57        _ => None,
58    }
59}
60
61/// Returns a human-readable name for DNS CLASS values.
62///
63/// RFC 1035, Section 3.2.4.
64fn dns_class_name(v: u16) -> Option<&'static str> {
65    match v {
66        1 => Some("IN"),
67        3 => Some("CH"),
68        4 => Some("HS"),
69        255 => Some("ANY"),
70        _ => None,
71    }
72}
73
74/// Returns a human-readable name for DNS opcode values.
75///
76/// RFC 1035, Section 4.1.1; RFC 1996 (NOTIFY); RFC 2136 (UPDATE).
77fn dns_opcode_name(v: u8) -> Option<&'static str> {
78    match v {
79        0 => Some("QUERY"),
80        1 => Some("IQUERY"),
81        2 => Some("STATUS"),
82        4 => Some("NOTIFY"),
83        5 => Some("UPDATE"),
84        _ => None,
85    }
86}
87
88/// Returns a human-readable name for DNS RCODE values.
89///
90/// RFC 1035, Section 4.1.1.
91pub fn dns_rcode_name(v: u8) -> Option<&'static str> {
92    match v {
93        0 => Some("NOERROR"),
94        1 => Some("FORMERR"),
95        2 => Some("SERVFAIL"),
96        3 => Some("NXDOMAIN"),
97        4 => Some("NOTIMP"),
98        5 => Some("REFUSED"),
99        _ => None,
100    }
101}
102
103/// Maximum pointer follow depth to prevent infinite loops.
104const MAX_POINTER_DEPTH: usize = 128;
105// RFC 1035, Section 3.2.2 — TYPE values
106const TYPE_A: u16 = 1;
107const TYPE_NS: u16 = 2;
108const TYPE_CNAME: u16 = 5;
109const TYPE_SOA: u16 = 6;
110const TYPE_PTR: u16 = 12;
111const TYPE_MX: u16 = 15;
112const TYPE_TXT: u16 = 16;
113// RFC 3596 — AAAA record
114const TYPE_AAAA: u16 = 28;
115// RFC 2782 — SRV record
116const TYPE_SRV: u16 = 33;
117// RFC 3403 — NAPTR record
118const TYPE_NAPTR: u16 = 35;
119// RFC 6672 — DNAME record
120const TYPE_DNAME: u16 = 39;
121// RFC 6891 — OPT pseudo-record (EDNS0)
122const TYPE_OPT: u16 = 41;
123// RFC 4035 — DNSSEC records
124const TYPE_DS: u16 = 43;
125const TYPE_RRSIG: u16 = 46;
126const TYPE_NSEC: u16 = 47;
127const TYPE_DNSKEY: u16 = 48;
128// RFC 5155 — NSEC3 / NSEC3PARAM
129const TYPE_NSEC3: u16 = 50;
130const TYPE_NSEC3PARAM: u16 = 51;
131// RFC 4255 — SSHFP record
132const TYPE_SSHFP: u16 = 44;
133// RFC 6698 — TLSA record
134const TYPE_TLSA: u16 = 52;
135// RFC 7344 — CDS/CDNSKEY records
136const TYPE_CDS: u16 = 59;
137const TYPE_CDNSKEY: u16 = 60;
138// RFC 9460 — SVCB/HTTPS records
139const TYPE_SVCB: u16 = 64;
140const TYPE_HTTPS: u16 = 65;
141// RFC 8659 — CAA record
142const TYPE_CAA: u16 = 257;
143
144// -- Field descriptor index constants for dns_field_descriptors! (main array) --
145const FD_ID: usize = 1;
146const FD_QR: usize = 2;
147const FD_OPCODE: usize = 3;
148const FD_AA: usize = 4;
149const FD_TC: usize = 5;
150const FD_RD: usize = 6;
151const FD_RA: usize = 7;
152const FD_Z: usize = 8;
153const FD_AD: usize = 9;
154const FD_CD: usize = 10;
155const FD_RCODE: usize = 11;
156const FD_QDCOUNT: usize = 12;
157const FD_ANCOUNT: usize = 13;
158const FD_NSCOUNT: usize = 14;
159const FD_ARCOUNT: usize = 15;
160const FD_QUESTIONS: usize = 16;
161const FD_ANSWERS: usize = 17;
162const FD_AUTHORITIES: usize = 18;
163const FD_ADDITIONALS: usize = 19;
164
165// -- Field descriptor index constants for QUESTION_CHILD_FIELDS --
166const QFD_NAME: usize = 0;
167const QFD_TYPE: usize = 1;
168const QFD_CLASS: usize = 2;
169// RFC 6762, Section 18.12 — top bit of qclass in the Question Section is the
170// "QU" (unicast-response) bit, not part of the class. Emitted only in mDNS
171// mode; the DNS dissector leaves this descriptor unused.
172const QFD_QU: usize = 3;
173// NOTE: type/class have display_fn for dns_type_name/dns_class_name; no separate _name fields.
174
175// -- Field descriptor index constants for EDNS_OPTION_CHILD_FIELDS --
176const EOFD_CODE: usize = 0;
177const EOFD_LENGTH: usize = 1;
178const EOFD_DATA: usize = 2;
179const EOFD_TIMEOUT: usize = 3;
180// NOTE: code has display_fn for edns_option_code_name; no separate code_name field.
181
182// -- Field descriptor index constants for RR_CHILD_FIELDS --
183// NOTE: type/class have display_fn for dns_type_name/dns_class_name; no separate _name fields.
184const RRFD_NAME: usize = 0;
185const RRFD_TYPE: usize = 1;
186const RRFD_CLASS: usize = 2;
187const RRFD_TTL: usize = 3;
188const RRFD_RDLENGTH: usize = 4;
189const RRFD_RDATA: usize = 5;
190const RRFD_UDP_PAYLOAD_SIZE: usize = 6;
191const RRFD_EXTENDED_RCODE: usize = 7;
192const RRFD_EDNS_VERSION: usize = 8;
193const RRFD_DO_BIT: usize = 9;
194const RRFD_EDNS_OPTIONS: usize = 10;
195const RRFD_RDATA_PREFERENCE: usize = 11;
196const RRFD_RDATA_EXCHANGE: usize = 12;
197const RRFD_RDATA_MNAME: usize = 13;
198const RRFD_RDATA_RNAME: usize = 14;
199const RRFD_RDATA_SERIAL: usize = 15;
200const RRFD_RDATA_REFRESH: usize = 16;
201const RRFD_RDATA_RETRY: usize = 17;
202const RRFD_RDATA_EXPIRE: usize = 18;
203const RRFD_RDATA_MINIMUM: usize = 19;
204const RRFD_RDATA_PRIORITY: usize = 20;
205const RRFD_RDATA_WEIGHT: usize = 21;
206const RRFD_RDATA_PORT: usize = 22;
207const RRFD_RDATA_TARGET: usize = 23;
208const RRFD_RDATA_ORDER: usize = 24;
209const RRFD_RDATA_FLAGS: usize = 25;
210const RRFD_RDATA_SERVICES: usize = 26;
211const RRFD_RDATA_REGEXP: usize = 27;
212const RRFD_RDATA_REPLACEMENT: usize = 28;
213const RRFD_RDATA_ALGORITHM: usize = 29;
214const RRFD_RDATA_FINGERPRINT_TYPE: usize = 30;
215const RRFD_RDATA_FINGERPRINT: usize = 31;
216const RRFD_RDATA_KEY_TAG: usize = 32;
217const RRFD_RDATA_DIGEST_TYPE: usize = 33;
218const RRFD_RDATA_DIGEST: usize = 34;
219const RRFD_RDATA_TYPE_COVERED: usize = 35;
220const RRFD_RDATA_LABELS: usize = 36;
221const RRFD_RDATA_ORIGINAL_TTL: usize = 37;
222const RRFD_RDATA_SIGNATURE_EXPIRATION: usize = 38;
223const RRFD_RDATA_SIGNATURE_INCEPTION: usize = 39;
224const RRFD_RDATA_SIGNER_NAME: usize = 40;
225const RRFD_RDATA_SIGNATURE: usize = 41;
226const RRFD_RDATA_NEXT_DOMAIN_NAME: usize = 42;
227const RRFD_RDATA_TYPE_BITMAPS: usize = 43;
228const RRFD_RDATA_PROTOCOL: usize = 44;
229const RRFD_RDATA_PUBLIC_KEY: usize = 45;
230const RRFD_RDATA_HASH_ALGORITHM: usize = 46;
231const RRFD_RDATA_ITERATIONS: usize = 47;
232const RRFD_RDATA_SALT_LENGTH: usize = 48;
233const RRFD_RDATA_SALT: usize = 49;
234const RRFD_RDATA_HASH_LENGTH: usize = 50;
235const RRFD_RDATA_NEXT_HASHED_OWNER: usize = 51;
236const RRFD_RDATA_CERT_USAGE: usize = 52;
237const RRFD_RDATA_SELECTOR: usize = 53;
238const RRFD_RDATA_MATCHING_TYPE: usize = 54;
239const RRFD_RDATA_CERT_ASSOC_DATA: usize = 55;
240const RRFD_RDATA_TAG: usize = 56;
241const RRFD_RDATA_VALUE: usize = 57;
242const RRFD_RDATA_PARAMS: usize = 58;
243// RFC 6762, Section 18.13 / 10.2 — cache-flush bit (top bit of rrclass).
244// Emitted only in mDNS mode for non-OPT records; the DNS dissector leaves
245// this descriptor unused.
246const RRFD_CACHE_FLUSH: usize = 59;
247
248/// DNS dissector.
249pub struct DnsDissector;
250
251/// Write a DNS domain name as a JSON-quoted string directly to the writer.
252///
253/// Walks the label-compressed wire format starting at `field_range.start`
254/// within the DNS layer (`layer_range`) of `packet_data`, following
255/// compression pointers as needed. Produces output like `"example.com"`.
256///
257/// Used as [`FormatFn`](packet_dissector_core::field::FormatFn) on DNS name
258/// fields so that dissection stores only raw byte offsets (zero allocation)
259/// and the human-readable dotted name is reconstructed at serialization time.
260pub fn write_dns_name(
261    _value: &FieldValue<'_>,
262    ctx: &FormatContext<'_>,
263    w: &mut dyn std::io::Write,
264) -> std::io::Result<()> {
265    let layer_start = ctx.layer_range.start as usize;
266    let layer_end = ctx.layer_range.end.min(ctx.packet_data.len() as u32) as usize;
267    let msg = &ctx.packet_data[layer_start..layer_end];
268    let name_pos = (ctx.field_range.start as usize).saturating_sub(layer_start);
269
270    w.write_all(b"\"")?;
271
272    let mut cursor = name_pos;
273    let mut first = true;
274    let mut depth = 0u8;
275
276    loop {
277        if depth >= MAX_POINTER_DEPTH as u8 || cursor >= msg.len() {
278            break;
279        }
280        depth += 1;
281
282        let byte = msg[cursor];
283        match byte & 0xC0 {
284            0x00 => {
285                let len = byte as usize;
286                if len == 0 {
287                    break; // root terminator
288                }
289                if cursor + 1 + len > msg.len() {
290                    break;
291                }
292                if !first {
293                    w.write_all(b".")?;
294                }
295                first = false;
296                w.write_all(&msg[cursor + 1..cursor + 1 + len])?;
297                cursor += 1 + len;
298            }
299            0xC0 => {
300                if cursor + 1 >= msg.len() {
301                    break;
302                }
303                let offset = (((byte as usize) & 0x3F) << 8) | (msg[cursor + 1] as usize);
304                cursor = offset;
305            }
306            _ => break, // reserved
307        }
308    }
309
310    if first {
311        // empty name = root "."
312        w.write_all(b".")?;
313    }
314
315    w.write_all(b"\"")
316}
317
318/// Parse a DNS domain name from the message, handling label compression.
319///
320/// Returns `(domain_name, bytes_consumed_from_pos)`.
321/// `msg` is the entire DNS message (for pointer resolution).
322/// `pos` is the current read position within `msg`.
323fn parse_name(msg: &[u8], pos: usize) -> Result<usize, PacketError> {
324    let mut cursor = pos;
325    let mut consumed = 0;
326    let mut followed_pointer = false;
327    let mut depth = 0;
328    // RFC 1035, Section 3.1 — total name wire representation must be ≤ 255 octets
329    let mut wire_len: usize = 0;
330
331    loop {
332        if depth >= MAX_POINTER_DEPTH {
333            return Err(PacketError::InvalidHeader("DNS name pointer loop detected"));
334        }
335        depth += 1;
336
337        if cursor >= msg.len() {
338            return Err(PacketError::Truncated {
339                expected: cursor + 1,
340                actual: msg.len(),
341            });
342        }
343
344        let byte = msg[cursor];
345
346        match byte & 0xC0 {
347            // Label
348            0x00 => {
349                let len = byte as usize;
350                if len == 0 {
351                    // Root terminator: count the 1-byte zero label
352                    wire_len += 1;
353                    if wire_len > 255 {
354                        return Err(PacketError::InvalidHeader(
355                            "DNS name too long (exceeds 255 octets)",
356                        ));
357                    }
358                    if !followed_pointer {
359                        consumed += 1;
360                    }
361                    break;
362                }
363                // RFC 1035, Section 3.1 — count 1 length byte + label content
364                wire_len += 1 + len;
365                if wire_len > 255 {
366                    return Err(PacketError::InvalidHeader(
367                        "DNS name too long (exceeds 255 octets)",
368                    ));
369                }
370                if cursor + 1 + len > msg.len() {
371                    return Err(PacketError::Truncated {
372                        expected: cursor + 1 + len,
373                        actual: msg.len(),
374                    });
375                }
376                cursor += 1 + len;
377                if !followed_pointer {
378                    consumed += 1 + len;
379                }
380            }
381            // Pointer
382            0xC0 => {
383                if cursor + 1 >= msg.len() {
384                    return Err(PacketError::Truncated {
385                        expected: cursor + 2,
386                        actual: msg.len(),
387                    });
388                }
389                let offset = (read_be_u16(msg, cursor)? & 0x3FFF) as usize;
390                if !followed_pointer {
391                    consumed += 2;
392                    followed_pointer = true;
393                }
394                cursor = offset;
395            }
396            // Reserved (01, 10)
397            _ => {
398                return Err(PacketError::InvalidHeader("DNS name: reserved label type"));
399            }
400        }
401    }
402
403    Ok(consumed)
404}
405
406/// Parse RDATA into typed fields based on the record type.
407///
408/// `msg` is the full DNS message (needed for name compression in RDATA).
409/// `rdata_offset` is the absolute offset of RDATA within `msg`.
410/// `rdata` is the RDATA slice.
411/// `rtype` is the DNS record type.
412/// `abs_offset` is the absolute byte offset in the original packet for field ranges.
413///
414/// Returns a list of sub-fields. If the type is unknown or parsing fails,
415/// falls back to a single `FieldValue::Bytes` field.
416fn parse_rdata<'pkt>(
417    buf: &mut DissectBuffer<'pkt>,
418    msg: &'pkt [u8],
419    rdata_offset: usize,
420    rdata: &'pkt [u8],
421    rtype: u16,
422    abs_offset: usize,
423) {
424    let rdata_range = abs_offset..abs_offset + rdata.len();
425
426    match rtype {
427        // RFC 1035, Section 3.4.1 — A record: 4-byte IPv4 address
428        TYPE_A if rdata.len() == 4 => {
429            buf.push_field(
430                &RR_CHILD_FIELDS[RRFD_RDATA],
431                FieldValue::Ipv4Addr([rdata[0], rdata[1], rdata[2], rdata[3]]),
432                rdata_range,
433            );
434            return;
435        }
436        // RFC 3596 — AAAA record: 16-byte IPv6 address
437        TYPE_AAAA if rdata.len() == 16 => {
438            let addr: [u8; 16] = [
439                rdata[0], rdata[1], rdata[2], rdata[3], rdata[4], rdata[5], rdata[6], rdata[7],
440                rdata[8], rdata[9], rdata[10], rdata[11], rdata[12], rdata[13], rdata[14],
441                rdata[15],
442            ];
443            buf.push_field(
444                &RR_CHILD_FIELDS[RRFD_RDATA],
445                FieldValue::Ipv6Addr(addr),
446                rdata_range,
447            );
448            return;
449        }
450        // RFC 1035, Section 3.3.1/3.3.11/3.3.12 — CNAME/NS/PTR
451        // RFC 6672 — DNAME: a single domain name
452        TYPE_CNAME | TYPE_NS | TYPE_PTR | TYPE_DNAME => {
453            if let Ok(consumed) = parse_name(msg, rdata_offset) {
454                let _ = consumed;
455                buf.push_field(
456                    &RR_CHILD_FIELDS[RRFD_RDATA],
457                    FieldValue::Bytes(&msg[rdata_offset..rdata_offset + rdata.len()]),
458                    rdata_range,
459                );
460                return;
461            }
462        }
463        // RFC 1035, Section 3.3.9 — MX: preference (U16) + exchange (domain name)
464        TYPE_MX if rdata.len() >= 3 => {
465            let preference = read_be_u16(rdata, 0).unwrap_or_default();
466            if parse_name(msg, rdata_offset + 2).is_ok() {
467                buf.push_field(
468                    &RR_CHILD_FIELDS[RRFD_RDATA_PREFERENCE],
469                    FieldValue::U16(preference),
470                    abs_offset..abs_offset + 2,
471                );
472                buf.push_field(
473                    &RR_CHILD_FIELDS[RRFD_RDATA_EXCHANGE],
474                    FieldValue::Bytes(&msg[rdata_offset + 2..rdata_offset + rdata.len()]),
475                    abs_offset + 2..abs_offset + rdata.len(),
476                );
477                return;
478            }
479        }
480        // RFC 1035, Section 3.3.14 — TXT: one or more character-strings
481        TYPE_TXT => {
482            // Store raw TXT RDATA bytes — character-string decoding deferred to FormatFn.
483            buf.push_field(
484                &RR_CHILD_FIELDS[RRFD_RDATA],
485                FieldValue::Bytes(rdata),
486                rdata_range,
487            );
488            return;
489        }
490        // RFC 1035, Section 3.3.13 — SOA
491        TYPE_SOA => {
492            if let Ok(mname_len) = parse_name(msg, rdata_offset) {
493                let rname_off = rdata_offset + mname_len;
494                if let Ok(rname_len) = parse_name(msg, rname_off) {
495                    let timers_off = mname_len + rname_len;
496                    if timers_off + 20 <= rdata.len() {
497                        let t = timers_off;
498                        let serial = read_be_u32(rdata, t).unwrap_or_default();
499                        let refresh = read_be_u32(rdata, t + 4).unwrap_or_default();
500                        let retry = read_be_u32(rdata, t + 8).unwrap_or_default();
501                        let expire = read_be_u32(rdata, t + 12).unwrap_or_default();
502                        let minimum = read_be_u32(rdata, t + 16).unwrap_or_default();
503                        let mname_end = abs_offset + mname_len;
504                        let rname_end = mname_end + rname_len;
505                        buf.push_field(
506                            &RR_CHILD_FIELDS[RRFD_RDATA_MNAME],
507                            FieldValue::Bytes(&msg[rdata_offset..rdata_offset + mname_len]),
508                            abs_offset..mname_end,
509                        );
510                        buf.push_field(
511                            &RR_CHILD_FIELDS[RRFD_RDATA_RNAME],
512                            FieldValue::Bytes(&msg[rname_off..rname_off + rname_len]),
513                            mname_end..rname_end,
514                        );
515                        buf.push_field(
516                            &RR_CHILD_FIELDS[RRFD_RDATA_SERIAL],
517                            FieldValue::U32(serial),
518                            rname_end..rname_end + 4,
519                        );
520                        buf.push_field(
521                            &RR_CHILD_FIELDS[RRFD_RDATA_REFRESH],
522                            FieldValue::U32(refresh),
523                            rname_end + 4..rname_end + 8,
524                        );
525                        buf.push_field(
526                            &RR_CHILD_FIELDS[RRFD_RDATA_RETRY],
527                            FieldValue::U32(retry),
528                            rname_end + 8..rname_end + 12,
529                        );
530                        buf.push_field(
531                            &RR_CHILD_FIELDS[RRFD_RDATA_EXPIRE],
532                            FieldValue::U32(expire),
533                            rname_end + 12..rname_end + 16,
534                        );
535                        buf.push_field(
536                            &RR_CHILD_FIELDS[RRFD_RDATA_MINIMUM],
537                            FieldValue::U32(minimum),
538                            rname_end + 16..rname_end + 20,
539                        );
540                        return;
541                    }
542                }
543            }
544        }
545        // RFC 2782 — SRV: priority(2) + weight(2) + port(2) + target(name)
546        TYPE_SRV if rdata.len() >= 7 => {
547            let priority = read_be_u16(rdata, 0).unwrap_or_default();
548            let weight = read_be_u16(rdata, 2).unwrap_or_default();
549            let port = read_be_u16(rdata, 4).unwrap_or_default();
550            if parse_name(msg, rdata_offset + 6).is_ok() {
551                buf.push_field(
552                    &RR_CHILD_FIELDS[RRFD_RDATA_PRIORITY],
553                    FieldValue::U16(priority),
554                    abs_offset..abs_offset + 2,
555                );
556                buf.push_field(
557                    &RR_CHILD_FIELDS[RRFD_RDATA_WEIGHT],
558                    FieldValue::U16(weight),
559                    abs_offset + 2..abs_offset + 4,
560                );
561                buf.push_field(
562                    &RR_CHILD_FIELDS[RRFD_RDATA_PORT],
563                    FieldValue::U16(port),
564                    abs_offset + 4..abs_offset + 6,
565                );
566                buf.push_field(
567                    &RR_CHILD_FIELDS[RRFD_RDATA_TARGET],
568                    FieldValue::Bytes(&msg[rdata_offset + 6..rdata_offset + rdata.len()]),
569                    abs_offset + 6..abs_offset + rdata.len(),
570                );
571                return;
572            }
573        }
574        // RFC 3403 — NAPTR: order(2) + preference(2) + flags(charstr) + services(charstr) + regexp(charstr) + replacement(name)
575        TYPE_NAPTR if rdata.len() >= 7 => {
576            let order = read_be_u16(rdata, 0).unwrap_or_default();
577            let preference = read_be_u16(rdata, 2).unwrap_or_default();
578            let mut pos = 4;
579            // Parse three character-strings: flags, services, regexp.
580            // Fixed-size array to keep dissection zero-allocation.
581            let mut byte_ranges: [(usize, usize); 3] = [(0, 0); 3];
582            let mut n = 0usize;
583            for _ in 0..3 {
584                if pos >= rdata.len() {
585                    break;
586                }
587                let str_len = rdata[pos] as usize;
588                let str_start = pos;
589                pos += 1;
590                if pos + str_len > rdata.len() {
591                    break;
592                }
593                byte_ranges[n] = (str_start, pos + str_len);
594                n += 1;
595                pos += str_len;
596            }
597            if n == 3 && parse_name(msg, rdata_offset + pos).is_ok() {
598                buf.push_field(
599                    &RR_CHILD_FIELDS[RRFD_RDATA_ORDER],
600                    FieldValue::U16(order),
601                    abs_offset..abs_offset + 2,
602                );
603                buf.push_field(
604                    &RR_CHILD_FIELDS[RRFD_RDATA_PREFERENCE],
605                    FieldValue::U16(preference),
606                    abs_offset + 2..abs_offset + 4,
607                );
608                buf.push_field(
609                    &RR_CHILD_FIELDS[RRFD_RDATA_FLAGS],
610                    FieldValue::Bytes(&rdata[byte_ranges[0].0..byte_ranges[0].1]),
611                    abs_offset + byte_ranges[0].0..abs_offset + byte_ranges[0].1,
612                );
613                buf.push_field(
614                    &RR_CHILD_FIELDS[RRFD_RDATA_SERVICES],
615                    FieldValue::Bytes(&rdata[byte_ranges[1].0..byte_ranges[1].1]),
616                    abs_offset + byte_ranges[1].0..abs_offset + byte_ranges[1].1,
617                );
618                buf.push_field(
619                    &RR_CHILD_FIELDS[RRFD_RDATA_REGEXP],
620                    FieldValue::Bytes(&rdata[byte_ranges[2].0..byte_ranges[2].1]),
621                    abs_offset + byte_ranges[2].0..abs_offset + byte_ranges[2].1,
622                );
623                buf.push_field(
624                    &RR_CHILD_FIELDS[RRFD_RDATA_REPLACEMENT],
625                    FieldValue::Bytes(&msg[rdata_offset + pos..rdata_offset + rdata.len()]),
626                    abs_offset + pos..abs_offset + rdata.len(),
627                );
628                return;
629            }
630        }
631        // RFC 4255 — SSHFP: algorithm(1) + fingerprint_type(1) + fingerprint(rest)
632        TYPE_SSHFP if rdata.len() >= 2 => {
633            buf.push_field(
634                &RR_CHILD_FIELDS[RRFD_RDATA_ALGORITHM],
635                FieldValue::U8(rdata[0]),
636                abs_offset..abs_offset + 1,
637            );
638            buf.push_field(
639                &RR_CHILD_FIELDS[RRFD_RDATA_FINGERPRINT_TYPE],
640                FieldValue::U8(rdata[1]),
641                abs_offset + 1..abs_offset + 2,
642            );
643            buf.push_field(
644                &RR_CHILD_FIELDS[RRFD_RDATA_FINGERPRINT],
645                FieldValue::Bytes(&rdata[2..]),
646                abs_offset + 2..abs_offset + rdata.len(),
647            );
648            return;
649        }
650        // RFC 6698 — TLSA: cert_usage(1) + selector(1) + matching_type(1) + cert_assoc_data(rest)
651        TYPE_TLSA if rdata.len() >= 3 => {
652            buf.push_field(
653                &RR_CHILD_FIELDS[RRFD_RDATA_CERT_USAGE],
654                FieldValue::U8(rdata[0]),
655                abs_offset..abs_offset + 1,
656            );
657            buf.push_field(
658                &RR_CHILD_FIELDS[RRFD_RDATA_SELECTOR],
659                FieldValue::U8(rdata[1]),
660                abs_offset + 1..abs_offset + 2,
661            );
662            buf.push_field(
663                &RR_CHILD_FIELDS[RRFD_RDATA_MATCHING_TYPE],
664                FieldValue::U8(rdata[2]),
665                abs_offset + 2..abs_offset + 3,
666            );
667            buf.push_field(
668                &RR_CHILD_FIELDS[RRFD_RDATA_CERT_ASSOC_DATA],
669                FieldValue::Bytes(&rdata[3..]),
670                abs_offset + 3..abs_offset + rdata.len(),
671            );
672            return;
673        }
674        // RFC 4035 — DS / RFC 7344 — CDS: key_tag(2) + algorithm(1) + digest_type(1) + digest(rest)
675        TYPE_DS | TYPE_CDS if rdata.len() >= 4 => {
676            let key_tag = read_be_u16(rdata, 0).unwrap_or_default();
677            buf.push_field(
678                &RR_CHILD_FIELDS[RRFD_RDATA_KEY_TAG],
679                FieldValue::U16(key_tag),
680                abs_offset..abs_offset + 2,
681            );
682            buf.push_field(
683                &RR_CHILD_FIELDS[RRFD_RDATA_ALGORITHM],
684                FieldValue::U8(rdata[2]),
685                abs_offset + 2..abs_offset + 3,
686            );
687            buf.push_field(
688                &RR_CHILD_FIELDS[RRFD_RDATA_DIGEST_TYPE],
689                FieldValue::U8(rdata[3]),
690                abs_offset + 3..abs_offset + 4,
691            );
692            buf.push_field(
693                &RR_CHILD_FIELDS[RRFD_RDATA_DIGEST],
694                FieldValue::Bytes(&rdata[4..]),
695                abs_offset + 4..abs_offset + rdata.len(),
696            );
697            return;
698        }
699        // RFC 4035 — RRSIG: type_covered(2) + algorithm(1) + labels(1) + original_ttl(4)
700        //   + sig_expiration(4) + sig_inception(4) + key_tag(2) + signer_name + signature
701        TYPE_RRSIG if rdata.len() >= 18 => {
702            let type_covered = read_be_u16(rdata, 0).unwrap_or_default();
703            let algorithm = rdata[2];
704            let labels = rdata[3];
705            let original_ttl = read_be_u32(rdata, 4).unwrap_or_default();
706            let sig_expiration = read_be_u32(rdata, 8).unwrap_or_default();
707            let sig_inception = read_be_u32(rdata, 12).unwrap_or_default();
708            let key_tag = read_be_u16(rdata, 16).unwrap_or_default();
709            if let Ok(signer_name_len) = parse_name(msg, rdata_offset + 18) {
710                let sig_start = 18 + signer_name_len;
711                buf.push_field(
712                    &RR_CHILD_FIELDS[RRFD_RDATA_TYPE_COVERED],
713                    FieldValue::U16(type_covered),
714                    abs_offset..abs_offset + 2,
715                );
716                buf.push_field(
717                    &RR_CHILD_FIELDS[RRFD_RDATA_ALGORITHM],
718                    FieldValue::U8(algorithm),
719                    abs_offset + 2..abs_offset + 3,
720                );
721                buf.push_field(
722                    &RR_CHILD_FIELDS[RRFD_RDATA_LABELS],
723                    FieldValue::U8(labels),
724                    abs_offset + 3..abs_offset + 4,
725                );
726                buf.push_field(
727                    &RR_CHILD_FIELDS[RRFD_RDATA_ORIGINAL_TTL],
728                    FieldValue::U32(original_ttl),
729                    abs_offset + 4..abs_offset + 8,
730                );
731                buf.push_field(
732                    &RR_CHILD_FIELDS[RRFD_RDATA_SIGNATURE_EXPIRATION],
733                    FieldValue::U32(sig_expiration),
734                    abs_offset + 8..abs_offset + 12,
735                );
736                buf.push_field(
737                    &RR_CHILD_FIELDS[RRFD_RDATA_SIGNATURE_INCEPTION],
738                    FieldValue::U32(sig_inception),
739                    abs_offset + 12..abs_offset + 16,
740                );
741                buf.push_field(
742                    &RR_CHILD_FIELDS[RRFD_RDATA_KEY_TAG],
743                    FieldValue::U16(key_tag),
744                    abs_offset + 16..abs_offset + 18,
745                );
746                buf.push_field(
747                    &RR_CHILD_FIELDS[RRFD_RDATA_SIGNER_NAME],
748                    FieldValue::Bytes(&msg[rdata_offset + 18..rdata_offset + sig_start]),
749                    abs_offset + 18..abs_offset + sig_start,
750                );
751                buf.push_field(
752                    &RR_CHILD_FIELDS[RRFD_RDATA_SIGNATURE],
753                    FieldValue::Bytes(&rdata[sig_start..]),
754                    abs_offset + sig_start..abs_offset + rdata.len(),
755                );
756                return;
757            }
758        }
759        // RFC 4035 — NSEC: next_domain_name + type_bitmaps
760        TYPE_NSEC => {
761            if let Ok(name_len) = parse_name(msg, rdata_offset) {
762                buf.push_field(
763                    &RR_CHILD_FIELDS[RRFD_RDATA_NEXT_DOMAIN_NAME],
764                    FieldValue::Bytes(&msg[rdata_offset..rdata_offset + name_len]),
765                    abs_offset..abs_offset + name_len,
766                );
767                buf.push_field(
768                    &RR_CHILD_FIELDS[RRFD_RDATA_TYPE_BITMAPS],
769                    FieldValue::Bytes(&rdata[name_len..]),
770                    abs_offset + name_len..abs_offset + rdata.len(),
771                );
772                return;
773            }
774        }
775        // RFC 4035 — DNSKEY / RFC 7344 — CDNSKEY: flags(2) + protocol(1) + algorithm(1) + public_key(rest)
776        TYPE_DNSKEY | TYPE_CDNSKEY if rdata.len() >= 4 => {
777            let flags = read_be_u16(rdata, 0).unwrap_or_default();
778            buf.push_field(
779                &RR_CHILD_FIELDS[RRFD_RDATA_FLAGS],
780                FieldValue::U16(flags),
781                abs_offset..abs_offset + 2,
782            );
783            buf.push_field(
784                &RR_CHILD_FIELDS[RRFD_RDATA_PROTOCOL],
785                FieldValue::U8(rdata[2]),
786                abs_offset + 2..abs_offset + 3,
787            );
788            buf.push_field(
789                &RR_CHILD_FIELDS[RRFD_RDATA_ALGORITHM],
790                FieldValue::U8(rdata[3]),
791                abs_offset + 3..abs_offset + 4,
792            );
793            buf.push_field(
794                &RR_CHILD_FIELDS[RRFD_RDATA_PUBLIC_KEY],
795                FieldValue::Bytes(&rdata[4..]),
796                abs_offset + 4..abs_offset + rdata.len(),
797            );
798            return;
799        }
800        // RFC 5155 — NSEC3: hash_alg(1) + flags(1) + iterations(2) + salt_len(1) + salt
801        //   + hash_len(1) + next_hashed_owner + type_bitmaps
802        TYPE_NSEC3 if rdata.len() >= 5 => {
803            let hash_algorithm = rdata[0];
804            let flags = rdata[1];
805            let iterations = read_be_u16(rdata, 2).unwrap_or_default();
806            let salt_length = rdata[4] as usize;
807            let salt_end = 5 + salt_length;
808            if salt_end < rdata.len() {
809                let hash_length = rdata[salt_end] as usize;
810                let hash_start = salt_end + 1;
811                let hash_end = hash_start + hash_length;
812                if hash_end <= rdata.len() {
813                    buf.push_field(
814                        &RR_CHILD_FIELDS[RRFD_RDATA_HASH_ALGORITHM],
815                        FieldValue::U8(hash_algorithm),
816                        abs_offset..abs_offset + 1,
817                    );
818                    buf.push_field(
819                        &RR_CHILD_FIELDS[RRFD_RDATA_FLAGS],
820                        FieldValue::U8(flags),
821                        abs_offset + 1..abs_offset + 2,
822                    );
823                    buf.push_field(
824                        &RR_CHILD_FIELDS[RRFD_RDATA_ITERATIONS],
825                        FieldValue::U16(iterations),
826                        abs_offset + 2..abs_offset + 4,
827                    );
828                    buf.push_field(
829                        &RR_CHILD_FIELDS[RRFD_RDATA_SALT_LENGTH],
830                        FieldValue::U8(salt_length as u8),
831                        abs_offset + 4..abs_offset + 5,
832                    );
833                    buf.push_field(
834                        &RR_CHILD_FIELDS[RRFD_RDATA_SALT],
835                        FieldValue::Bytes(&rdata[5..salt_end]),
836                        abs_offset + 5..abs_offset + salt_end,
837                    );
838                    buf.push_field(
839                        &RR_CHILD_FIELDS[RRFD_RDATA_HASH_LENGTH],
840                        FieldValue::U8(hash_length as u8),
841                        abs_offset + salt_end..abs_offset + hash_start,
842                    );
843                    buf.push_field(
844                        &RR_CHILD_FIELDS[RRFD_RDATA_NEXT_HASHED_OWNER],
845                        FieldValue::Bytes(&rdata[hash_start..hash_end]),
846                        abs_offset + hash_start..abs_offset + hash_end,
847                    );
848                    buf.push_field(
849                        &RR_CHILD_FIELDS[RRFD_RDATA_TYPE_BITMAPS],
850                        FieldValue::Bytes(&rdata[hash_end..]),
851                        abs_offset + hash_end..abs_offset + rdata.len(),
852                    );
853                    return;
854                }
855            }
856        }
857        // RFC 5155 §4.2 — NSEC3PARAM: hash_alg(1) + flags(1) + iterations(2) + salt_len(1) + salt
858        TYPE_NSEC3PARAM if rdata.len() >= 5 => {
859            let hash_algorithm = rdata[0];
860            let flags = rdata[1];
861            let iterations = read_be_u16(rdata, 2).unwrap_or_default();
862            let salt_length = rdata[4] as usize;
863            if 5 + salt_length <= rdata.len() {
864                buf.push_field(
865                    &RR_CHILD_FIELDS[RRFD_RDATA_HASH_ALGORITHM],
866                    FieldValue::U8(hash_algorithm),
867                    abs_offset..abs_offset + 1,
868                );
869                buf.push_field(
870                    &RR_CHILD_FIELDS[RRFD_RDATA_FLAGS],
871                    FieldValue::U8(flags),
872                    abs_offset + 1..abs_offset + 2,
873                );
874                buf.push_field(
875                    &RR_CHILD_FIELDS[RRFD_RDATA_ITERATIONS],
876                    FieldValue::U16(iterations),
877                    abs_offset + 2..abs_offset + 4,
878                );
879                buf.push_field(
880                    &RR_CHILD_FIELDS[RRFD_RDATA_SALT_LENGTH],
881                    FieldValue::U8(salt_length as u8),
882                    abs_offset + 4..abs_offset + 5,
883                );
884                buf.push_field(
885                    &RR_CHILD_FIELDS[RRFD_RDATA_SALT],
886                    FieldValue::Bytes(&rdata[5..5 + salt_length]),
887                    abs_offset + 5..abs_offset + 5 + salt_length,
888                );
889                return;
890            }
891        }
892        // RFC 9460 — SVCB/HTTPS: SvcPriority(2) + TargetName(name) + SvcParams(rest)
893        TYPE_SVCB | TYPE_HTTPS if rdata.len() >= 3 => {
894            let priority = read_be_u16(rdata, 0).unwrap_or_default();
895            if let Ok(target_len) = parse_name(msg, rdata_offset + 2) {
896                let params_start = 2 + target_len;
897                buf.push_field(
898                    &RR_CHILD_FIELDS[RRFD_RDATA_PRIORITY],
899                    FieldValue::U16(priority),
900                    abs_offset..abs_offset + 2,
901                );
902                buf.push_field(
903                    &RR_CHILD_FIELDS[RRFD_RDATA_TARGET],
904                    FieldValue::Bytes(&msg[rdata_offset + 2..rdata_offset + params_start]),
905                    abs_offset + 2..abs_offset + params_start,
906                );
907                if params_start <= rdata.len() {
908                    buf.push_field(
909                        &RR_CHILD_FIELDS[RRFD_RDATA_PARAMS],
910                        FieldValue::Bytes(&rdata[params_start..]),
911                        abs_offset + params_start..abs_offset + rdata.len(),
912                    );
913                }
914                return;
915            }
916        }
917        // RFC 8659 — CAA: flags(1) + tag_length(1) + tag + value
918        TYPE_CAA if rdata.len() >= 2 => {
919            let flags = rdata[0];
920            let tag_len = rdata[1] as usize;
921            if 2 + tag_len <= rdata.len() {
922                buf.push_field(
923                    &RR_CHILD_FIELDS[RRFD_RDATA_FLAGS],
924                    FieldValue::U8(flags),
925                    abs_offset..abs_offset + 1,
926                );
927                buf.push_field(
928                    &RR_CHILD_FIELDS[RRFD_RDATA_TAG],
929                    FieldValue::Bytes(&rdata[2..2 + tag_len]),
930                    abs_offset + 2..abs_offset + 2 + tag_len,
931                );
932                buf.push_field(
933                    &RR_CHILD_FIELDS[RRFD_RDATA_VALUE],
934                    FieldValue::Bytes(&rdata[2 + tag_len..]),
935                    abs_offset + 2 + tag_len..abs_offset + rdata.len(),
936                );
937                return;
938            }
939        }
940        _ => {}
941    }
942
943    // Fallback: raw bytes for unknown or malformed rdata
944    buf.push_field(
945        &RR_CHILD_FIELDS[RRFD_RDATA],
946        FieldValue::Bytes(rdata),
947        rdata_range,
948    );
949}
950
951/// EDNS0 option code for TCP Keepalive.
952///
953/// RFC 7828, Section 3 — <https://www.rfc-editor.org/rfc/rfc7828#section-3>
954const EDNS_OPT_TCP_KEEPALIVE: u16 = 11;
955
956/// Returns a human-readable name for EDNS0 option codes.
957///
958/// RFC 6891, Section 6.1.2 — <https://www.rfc-editor.org/rfc/rfc6891#section-6.1.2>
959fn edns_option_code_name(code: u16) -> Option<&'static str> {
960    match code {
961        3 => Some("NSID"),
962        8 => Some("CLIENT-SUBNET"),
963        10 => Some("COOKIE"),
964        EDNS_OPT_TCP_KEEPALIVE => Some("TCP-KEEPALIVE"),
965        15 => Some("EXTENDED-DNS-ERROR"),
966        _ => None,
967    }
968}
969
970/// Parse EDNS0 options from OPT RDATA.
971///
972/// Each option is: code(2) + length(2) + data(length).
973///
974/// Known options are decoded with structured sub-fields:
975/// - TCP Keepalive (code 11): RFC 7828 — <https://www.rfc-editor.org/rfc/rfc7828>
976fn parse_edns_options<'pkt>(buf: &mut DissectBuffer<'pkt>, rdata: &'pkt [u8], abs_offset: usize) {
977    let mut pos = 0;
978    while pos + 4 <= rdata.len() {
979        let code = read_be_u16(rdata, pos).unwrap_or_default();
980        let length = read_be_u16(rdata, pos + 2).unwrap_or_default() as usize;
981        if pos + 4 + length > rdata.len() {
982            break;
983        }
984        let option_data = &rdata[pos + 4..pos + 4 + length];
985        let option_start = abs_offset + pos;
986        let option_end = option_start + 4 + length;
987
988        let obj_idx = buf.begin_container(
989            &FD_EDNS_OPTION,
990            FieldValue::Object(0..0),
991            option_start..option_end,
992        );
993
994        buf.push_field(
995            &EDNS_OPTION_CHILD_FIELDS[EOFD_CODE],
996            FieldValue::U16(code),
997            option_start..option_start + 2,
998        );
999        buf.push_field(
1000            &EDNS_OPTION_CHILD_FIELDS[EOFD_LENGTH],
1001            FieldValue::U16(length as u16),
1002            option_start + 2..option_start + 4,
1003        );
1004
1005        // RFC 7828, Section 3 — edns-tcp-keepalive option
1006        // <https://www.rfc-editor.org/rfc/rfc7828#section-3>
1007        // Format: optional 2-byte timeout in units of 100 milliseconds.
1008        // Length is 0 (query, no timeout) or 2 (response, with timeout).
1009        if code == EDNS_OPT_TCP_KEEPALIVE && length == 2 {
1010            let timeout = read_be_u16(option_data, 0).unwrap_or_default();
1011            buf.push_field(
1012                &EDNS_OPTION_CHILD_FIELDS[EOFD_TIMEOUT],
1013                FieldValue::U16(timeout),
1014                option_start + 4..option_end,
1015            );
1016        } else if code == EDNS_OPT_TCP_KEEPALIVE && length == 0 {
1017            // Query form: no timeout field, no data to emit.
1018        } else {
1019            buf.push_field(
1020                &EDNS_OPTION_CHILD_FIELDS[EOFD_DATA],
1021                FieldValue::Bytes(option_data),
1022                option_start + 4..option_end,
1023            );
1024        }
1025
1026        buf.end_container(obj_idx);
1027        pos += 4 + length;
1028    }
1029}
1030
1031/// Descriptor for the EDNS0 option Object container itself.
1032///
1033/// `display_fn` is invoked by
1034/// [`DissectBuffer::resolve_container_display_name`] with the container's
1035/// children, so the outer label resolves to the option name (e.g.
1036/// "COOKIE") instead of colliding with the inner `Code` field.
1037static FD_EDNS_OPTION: FieldDescriptor = FieldDescriptor {
1038    name: "edns_option",
1039    display_name: "EDNS Option",
1040    field_type: FieldType::Object,
1041    optional: false,
1042    children: None,
1043    display_fn: Some(|v, children| match v {
1044        FieldValue::Object(_) => children.iter().find_map(|f| match (f.name(), &f.value) {
1045            ("code", FieldValue::U16(c)) => edns_option_code_name(*c),
1046            _ => None,
1047        }),
1048        _ => None,
1049    }),
1050    format_fn: None,
1051};
1052
1053/// Child field descriptors for EDNS0 option entries.
1054///
1055/// RFC 6891, Section 6.1.2 — <https://www.rfc-editor.org/rfc/rfc6891#section-6.1.2>
1056/// RFC 7828 (TCP Keepalive option) — <https://www.rfc-editor.org/rfc/rfc7828>
1057static EDNS_OPTION_CHILD_FIELDS: &[FieldDescriptor] = &[
1058    FieldDescriptor {
1059        name: "code",
1060        display_name: "Code",
1061        field_type: FieldType::U16,
1062        optional: false,
1063        children: None,
1064        display_fn: Some(|v, _siblings| match v {
1065            FieldValue::U16(c) => edns_option_code_name(*c),
1066            _ => None,
1067        }),
1068        format_fn: None,
1069    },
1070    FieldDescriptor::new("length", "Length", FieldType::U16),
1071    FieldDescriptor::new("data", "Data", FieldType::Bytes).optional(),
1072    // RFC 7828, Section 3 — TCP Keepalive timeout (in 100ms units).
1073    FieldDescriptor::new("timeout", "Timeout", FieldType::U16).optional(),
1074];
1075
1076/// Child field descriptors for question section entries.
1077///
1078/// The `qu` descriptor is emitted only by the mDNS parsing path per
1079/// RFC 6762, Section 18.12 — <https://www.rfc-editor.org/rfc/rfc6762#section-18.12>.
1080static QUESTION_CHILD_FIELDS: &[FieldDescriptor] = &[
1081    FieldDescriptor::new("name", "Name", FieldType::Bytes).with_format_fn(write_dns_name),
1082    FieldDescriptor {
1083        name: "type",
1084        display_name: "Type",
1085        field_type: FieldType::U16,
1086        optional: false,
1087        children: None,
1088        display_fn: Some(|v, _siblings| match v {
1089            FieldValue::U16(t) => dns_type_name(*t),
1090            _ => None,
1091        }),
1092        format_fn: None,
1093    },
1094    FieldDescriptor {
1095        name: "class",
1096        display_name: "Class",
1097        field_type: FieldType::U16,
1098        optional: false,
1099        children: None,
1100        display_fn: Some(|v, _siblings| match v {
1101            FieldValue::U16(c) => dns_class_name(*c),
1102            _ => None,
1103        }),
1104        format_fn: None,
1105    },
1106    // RFC 6762, Section 18.12 / 5.4 — unicast-response bit (top bit of qclass).
1107    // <https://www.rfc-editor.org/rfc/rfc6762#section-18.12>
1108    FieldDescriptor::new("qu", "Unicast Response", FieldType::U8).optional(),
1109];
1110
1111/// Child field descriptors for resource record entries (answers, authorities, additionals).
1112///
1113/// This is a union of all fields emitted by [`parse_rdata`] across every supported
1114/// record type.  Fields that only appear for certain record types are marked `optional`.
1115static RR_CHILD_FIELDS: &[FieldDescriptor] = &[
1116    // -- Common RR fields (all record types) --
1117    FieldDescriptor::new("name", "Name", FieldType::Bytes).with_format_fn(write_dns_name),
1118    FieldDescriptor {
1119        name: "type",
1120        display_name: "Type",
1121        field_type: FieldType::U16,
1122        optional: false,
1123        children: None,
1124        display_fn: Some(|v, _siblings| match v {
1125            FieldValue::U16(t) => dns_type_name(*t),
1126            _ => None,
1127        }),
1128        format_fn: None,
1129    },
1130    FieldDescriptor {
1131        name: "class",
1132        display_name: "Class",
1133        field_type: FieldType::U16,
1134        optional: true,
1135        children: None,
1136        display_fn: Some(|v, _siblings| match v {
1137            FieldValue::U16(c) => dns_class_name(*c),
1138            _ => None,
1139        }),
1140        format_fn: None,
1141    },
1142    FieldDescriptor::new("ttl", "TTL", FieldType::U32).optional(),
1143    FieldDescriptor::new("rdlength", "Data Length", FieldType::U16).optional(),
1144    // A / AAAA / CNAME / NS / PTR / DNAME / TXT / fallback
1145    FieldDescriptor::new("rdata", "Data", FieldType::Str).optional(),
1146    // -- OPT (EDNS0) --
1147    FieldDescriptor::new("udp_payload_size", "UDP Payload Size", FieldType::U16).optional(),
1148    FieldDescriptor::new("extended_rcode", "Extended RCODE", FieldType::U8).optional(),
1149    FieldDescriptor::new("edns_version", "EDNS Version", FieldType::U8).optional(),
1150    FieldDescriptor::new("do_bit", "DO Bit", FieldType::U8).optional(),
1151    FieldDescriptor::new("edns_options", "EDNS Options", FieldType::Array)
1152        .optional()
1153        .with_children(EDNS_OPTION_CHILD_FIELDS),
1154    // -- MX --
1155    FieldDescriptor::new("rdata_preference", "Preference", FieldType::U16).optional(),
1156    FieldDescriptor::new("rdata_exchange", "Mail Exchange", FieldType::Str).optional(),
1157    // -- SOA --
1158    FieldDescriptor::new("rdata_mname", "Primary Name Server", FieldType::Str).optional(),
1159    FieldDescriptor::new(
1160        "rdata_rname",
1161        "Responsible Authority Mailbox",
1162        FieldType::Str,
1163    )
1164    .optional(),
1165    FieldDescriptor::new("rdata_serial", "Serial Number", FieldType::U32).optional(),
1166    FieldDescriptor::new("rdata_refresh", "Refresh Interval", FieldType::U32).optional(),
1167    FieldDescriptor::new("rdata_retry", "Retry Interval", FieldType::U32).optional(),
1168    FieldDescriptor::new("rdata_expire", "Expire Limit", FieldType::U32).optional(),
1169    FieldDescriptor::new("rdata_minimum", "Minimum TTL", FieldType::U32).optional(),
1170    // -- SRV / SVCB / HTTPS --
1171    FieldDescriptor::new("rdata_priority", "Priority", FieldType::U16).optional(),
1172    FieldDescriptor::new("rdata_weight", "Weight", FieldType::U16).optional(),
1173    FieldDescriptor::new("rdata_port", "Port", FieldType::U16).optional(),
1174    FieldDescriptor::new("rdata_target", "Target", FieldType::Str).optional(),
1175    // -- NAPTR --
1176    FieldDescriptor::new("rdata_order", "Order", FieldType::U16).optional(),
1177    FieldDescriptor::new("rdata_flags", "Flags", FieldType::Str).optional(),
1178    FieldDescriptor::new("rdata_services", "Service", FieldType::Str).optional(),
1179    FieldDescriptor::new("rdata_regexp", "Regular Expression", FieldType::Str).optional(),
1180    FieldDescriptor::new("rdata_replacement", "Replacement", FieldType::Str).optional(),
1181    // -- SSHFP --
1182    FieldDescriptor::new("rdata_algorithm", "Algorithm", FieldType::U8).optional(),
1183    FieldDescriptor::new("rdata_fingerprint_type", "Fingerprint Type", FieldType::U8).optional(),
1184    FieldDescriptor::new("rdata_fingerprint", "Fingerprint", FieldType::Bytes).optional(),
1185    // -- DS / CDS --
1186    FieldDescriptor::new("rdata_key_tag", "Key Tag", FieldType::U16).optional(),
1187    FieldDescriptor::new("rdata_digest_type", "Digest Type", FieldType::U8).optional(),
1188    FieldDescriptor::new("rdata_digest", "Digest", FieldType::Bytes).optional(),
1189    // -- RRSIG --
1190    FieldDescriptor::new("rdata_type_covered", "Type Covered", FieldType::U16).optional(),
1191    FieldDescriptor::new("rdata_labels", "Labels", FieldType::U8).optional(),
1192    FieldDescriptor::new("rdata_original_ttl", "Original TTL", FieldType::U32).optional(),
1193    FieldDescriptor::new(
1194        "rdata_signature_expiration",
1195        "Signature Expiration",
1196        FieldType::U32,
1197    )
1198    .optional(),
1199    FieldDescriptor::new(
1200        "rdata_signature_inception",
1201        "Signature Inception",
1202        FieldType::U32,
1203    )
1204    .optional(),
1205    FieldDescriptor::new("rdata_signer_name", "Signer's Name", FieldType::Str).optional(),
1206    FieldDescriptor::new("rdata_signature", "Signature", FieldType::Bytes).optional(),
1207    // -- NSEC --
1208    FieldDescriptor::new("rdata_next_domain_name", "Next Domain Name", FieldType::Str).optional(),
1209    FieldDescriptor::new("rdata_type_bitmaps", "Type Bit Maps", FieldType::Bytes).optional(),
1210    // -- DNSKEY / CDNSKEY --
1211    FieldDescriptor::new("rdata_protocol", "Protocol", FieldType::U8).optional(),
1212    FieldDescriptor::new("rdata_public_key", "Public Key", FieldType::Bytes).optional(),
1213    // -- NSEC3 / NSEC3PARAM --
1214    FieldDescriptor::new("rdata_hash_algorithm", "Hash Algorithm", FieldType::U8).optional(),
1215    FieldDescriptor::new("rdata_iterations", "Iterations", FieldType::U16).optional(),
1216    FieldDescriptor::new("rdata_salt_length", "Salt Length", FieldType::U8).optional(),
1217    FieldDescriptor::new("rdata_salt", "Salt", FieldType::Bytes).optional(),
1218    FieldDescriptor::new("rdata_hash_length", "Hash Length", FieldType::U8).optional(),
1219    FieldDescriptor::new(
1220        "rdata_next_hashed_owner",
1221        "Next Hashed Owner Name",
1222        FieldType::Bytes,
1223    )
1224    .optional(),
1225    // -- TLSA --
1226    FieldDescriptor::new("rdata_cert_usage", "Certificate Usage", FieldType::U8).optional(),
1227    FieldDescriptor::new("rdata_selector", "Selector", FieldType::U8).optional(),
1228    FieldDescriptor::new("rdata_matching_type", "Matching Type", FieldType::U8).optional(),
1229    FieldDescriptor::new(
1230        "rdata_cert_assoc_data",
1231        "Certificate Association Data",
1232        FieldType::Bytes,
1233    )
1234    .optional(),
1235    // -- CAA --
1236    FieldDescriptor::new("rdata_tag", "Tag", FieldType::Str).optional(),
1237    FieldDescriptor::new("rdata_value", "Value", FieldType::Str).optional(),
1238    // -- SVCB / HTTPS --
1239    FieldDescriptor::new("rdata_params", "SvcParams", FieldType::Bytes).optional(),
1240    // -- mDNS: cache-flush bit (top bit of rrclass) --
1241    // RFC 6762, Section 18.13 / 10.2 — <https://www.rfc-editor.org/rfc/rfc6762#section-10.2>
1242    // Emitted by the mDNS parsing path for non-OPT records only.
1243    FieldDescriptor::new("cache_flush", "Cache Flush", FieldType::U8).optional(),
1244];
1245
1246/// Generates the common DNS header + section field descriptors shared by both
1247/// the UDP and TCP variants.  The `$tcp_length_optional` parameter controls
1248/// whether the leading `tcp_length` field is marked optional (UDP schema, where
1249/// it is included only for `bask fields` completeness) or required (TCP schema).
1250macro_rules! dns_field_descriptors {
1251    (tcp_length_optional: $opt:expr) => {
1252        &[
1253            FieldDescriptor {
1254                name: "tcp_length",
1255                display_name: "TCP Length",
1256                field_type: FieldType::U16,
1257                optional: $opt,
1258                children: None,
1259                display_fn: None,
1260                format_fn: None,
1261            },
1262            FieldDescriptor::new("id", "Transaction ID", FieldType::U16),
1263            FieldDescriptor {
1264                name: "qr",
1265                display_name: "QR",
1266                field_type: FieldType::U8,
1267                optional: false,
1268                children: None,
1269                display_fn: Some(|v, _siblings| match v {
1270                    FieldValue::U8(0) => Some("Query"),
1271                    FieldValue::U8(1) => Some("Response"),
1272                    _ => None,
1273                }),
1274                format_fn: None,
1275            },
1276            FieldDescriptor {
1277                name: "opcode",
1278                display_name: "Opcode",
1279                field_type: FieldType::U8,
1280                optional: false,
1281                children: None,
1282                display_fn: Some(|v, _siblings| match v {
1283                    FieldValue::U8(o) => dns_opcode_name(*o),
1284                    _ => None,
1285                }),
1286                format_fn: None,
1287            },
1288            FieldDescriptor::new("aa", "Authoritative Answer", FieldType::U8),
1289            FieldDescriptor::new("tc", "Truncation", FieldType::U8),
1290            FieldDescriptor::new("rd", "Recursion Desired", FieldType::U8),
1291            FieldDescriptor::new("ra", "Recursion Available", FieldType::U8),
1292            FieldDescriptor::new("z", "Reserved", FieldType::U8),
1293            FieldDescriptor::new("ad", "Authentic Data", FieldType::U8),
1294            FieldDescriptor::new("cd", "Checking Disabled", FieldType::U8),
1295            FieldDescriptor {
1296                name: "rcode",
1297                display_name: "Response Code",
1298                field_type: FieldType::U8,
1299                optional: false,
1300                children: None,
1301                display_fn: Some(|v, _siblings| match v {
1302                    FieldValue::U8(r) => dns_rcode_name(*r),
1303                    _ => None,
1304                }),
1305                format_fn: None,
1306            },
1307            FieldDescriptor::new("qdcount", "Question Count", FieldType::U16),
1308            FieldDescriptor::new("ancount", "Answer Count", FieldType::U16),
1309            FieldDescriptor::new("nscount", "Authority Count", FieldType::U16),
1310            FieldDescriptor::new("arcount", "Additional Count", FieldType::U16),
1311            FieldDescriptor::new("questions", "Questions", FieldType::Array)
1312                .optional()
1313                .with_children(QUESTION_CHILD_FIELDS),
1314            FieldDescriptor::new("answers", "Answer Records", FieldType::Array)
1315                .optional()
1316                .with_children(RR_CHILD_FIELDS),
1317            FieldDescriptor::new("authorities", "Authority Records", FieldType::Array)
1318                .optional()
1319                .with_children(RR_CHILD_FIELDS),
1320            FieldDescriptor::new("additionals", "Additional Records", FieldType::Array)
1321                .optional()
1322                .with_children(RR_CHILD_FIELDS),
1323        ]
1324    };
1325}
1326
1327/// Field descriptors for [`DnsDissector`] (DNS over UDP).
1328///
1329/// Includes the `tcp_length` field (as optional) so that `bask fields dns`
1330/// shows the full superset of fields for both UDP and TCP variants.
1331static DNS_FIELD_DESCRIPTORS: &[FieldDescriptor] =
1332    dns_field_descriptors!(tcp_length_optional: true);
1333
1334/// Field descriptors for [`DnsTcpDissector`] (DNS over TCP).
1335///
1336/// Includes the 2-byte TCP length prefix field followed by the standard DNS fields.
1337/// TCP stream reassembly is handled centrally by the registry; the TCP layer's
1338/// `reassembly_in_progress` and `segment_count` fields indicate reassembly status,
1339/// and the `stream_id` field correlates segments belonging to the same stream.
1340static DNS_TCP_FIELD_DESCRIPTORS: &[FieldDescriptor] =
1341    dns_field_descriptors!(tcp_length_optional: false);
1342
1343impl Dissector for DnsDissector {
1344    fn name(&self) -> &'static str {
1345        "Domain Name System"
1346    }
1347
1348    fn short_name(&self) -> &'static str {
1349        "DNS"
1350    }
1351
1352    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
1353        DNS_FIELD_DESCRIPTORS
1354    }
1355
1356    fn dissect<'pkt>(
1357        &self,
1358        data: &'pkt [u8],
1359        buf: &mut DissectBuffer<'pkt>,
1360        offset: usize,
1361    ) -> Result<DissectResult, PacketError> {
1362        // DNS (RFC 1035) — standard parsing without mDNS bit reinterpretation.
1363        dissect_dns_core(data, buf, offset, self.short_name(), false)
1364    }
1365}
1366
1367/// Parse a Multicast DNS (RFC 6762) message.
1368///
1369/// This is the same DNS message parser used by [`DnsDissector`] but with
1370/// RFC 6762 reinterpretation applied to the class fields:
1371///
1372/// - Each question gets a `qu` child field carrying the top bit of qclass
1373///   (the unicast-response bit, RFC 6762, Section 18.12 — <https://www.rfc-editor.org/rfc/rfc6762#section-18.12>);
1374///   the `class` field carries only the lower 15 bits.
1375/// - Each non-OPT resource record gets a `cache_flush` child field carrying
1376///   the top bit of rrclass (RFC 6762, Section 18.13 / 10.2 —
1377///   <https://www.rfc-editor.org/rfc/rfc6762#section-10.2>); the `class`
1378///   field carries only the lower 15 bits.
1379/// - OPT pseudo-RRs (RFC 6891) are left untouched: their rrclass field is
1380///   the full 16-bit EDNS0 UDP payload size, as required by RFC 6762,
1381///   Section 10.2.
1382///
1383/// The produced layer is labelled `"mDNS"`. All other fields are identical
1384/// to the DNS parsing output.
1385pub fn dissect_as_mdns<'pkt>(
1386    data: &'pkt [u8],
1387    buf: &mut DissectBuffer<'pkt>,
1388    offset: usize,
1389) -> Result<DissectResult, PacketError> {
1390    dissect_dns_core(data, buf, offset, "mDNS", true)
1391}
1392
1393/// Shared DNS / mDNS message parser.
1394///
1395/// `layer_name` is the short name pushed onto the layer (e.g., `"DNS"` or
1396/// `"mDNS"`). When `mdns_mode` is true, the top bit of each question's
1397/// qclass is emitted as a separate `qu` field, and the top bit of each
1398/// non-OPT record's rrclass is emitted as a separate `cache_flush` field,
1399/// per RFC 6762, Sections 18.12, 18.13 and 10.2.
1400fn dissect_dns_core<'pkt>(
1401    data: &'pkt [u8],
1402    buf: &mut DissectBuffer<'pkt>,
1403    offset: usize,
1404    layer_name: &'static str,
1405    mdns_mode: bool,
1406) -> Result<DissectResult, PacketError> {
1407    if data.len() < HEADER_SIZE {
1408        return Err(PacketError::Truncated {
1409            expected: HEADER_SIZE,
1410            actual: data.len(),
1411        });
1412    }
1413
1414    // RFC 1035, Section 4.1.1 — Header
1415    let id = read_be_u16(data, 0)?;
1416    let flags = read_be_u16(data, 2)?;
1417
1418    let qr = ((flags >> 15) & 1) as u8;
1419    let opcode = ((flags >> 11) & 0x0F) as u8;
1420    let aa = ((flags >> 10) & 1) as u8;
1421    let tc = ((flags >> 9) & 1) as u8;
1422    let rd = ((flags >> 8) & 1) as u8;
1423    let ra = ((flags >> 7) & 1) as u8;
1424    // RFC 1035, Section 4.1.1 — Z reserved bit (must be zero)
1425    let z = ((flags >> 6) & 1) as u8;
1426    // RFC 4035 — AD and CD flags (formerly Z bits)
1427    let ad = ((flags >> 5) & 1) as u8;
1428    let cd = ((flags >> 4) & 1) as u8;
1429    let rcode = (flags & 0x0F) as u8;
1430
1431    let qdcount = read_be_u16(data, 4)?;
1432    let ancount = read_be_u16(data, 6)?;
1433    let nscount = read_be_u16(data, 8)?;
1434    let arcount = read_be_u16(data, 10)?;
1435
1436    buf.begin_layer(
1437        layer_name,
1438        None,
1439        DNS_FIELD_DESCRIPTORS,
1440        offset..offset + data.len(),
1441    );
1442
1443    buf.push_field(
1444        &DNS_FIELD_DESCRIPTORS[FD_ID],
1445        FieldValue::U16(id),
1446        offset..offset + 2,
1447    );
1448    buf.push_field(
1449        &DNS_FIELD_DESCRIPTORS[FD_QR],
1450        FieldValue::U8(qr),
1451        offset + 2..offset + 4,
1452    );
1453    buf.push_field(
1454        &DNS_FIELD_DESCRIPTORS[FD_OPCODE],
1455        FieldValue::U8(opcode),
1456        offset + 2..offset + 4,
1457    );
1458    buf.push_field(
1459        &DNS_FIELD_DESCRIPTORS[FD_AA],
1460        FieldValue::U8(aa),
1461        offset + 2..offset + 4,
1462    );
1463    buf.push_field(
1464        &DNS_FIELD_DESCRIPTORS[FD_TC],
1465        FieldValue::U8(tc),
1466        offset + 2..offset + 4,
1467    );
1468    buf.push_field(
1469        &DNS_FIELD_DESCRIPTORS[FD_RD],
1470        FieldValue::U8(rd),
1471        offset + 2..offset + 4,
1472    );
1473    buf.push_field(
1474        &DNS_FIELD_DESCRIPTORS[FD_RA],
1475        FieldValue::U8(ra),
1476        offset + 2..offset + 4,
1477    );
1478    buf.push_field(
1479        &DNS_FIELD_DESCRIPTORS[FD_Z],
1480        FieldValue::U8(z),
1481        offset + 2..offset + 4,
1482    );
1483    buf.push_field(
1484        &DNS_FIELD_DESCRIPTORS[FD_AD],
1485        FieldValue::U8(ad),
1486        offset + 2..offset + 4,
1487    );
1488    buf.push_field(
1489        &DNS_FIELD_DESCRIPTORS[FD_CD],
1490        FieldValue::U8(cd),
1491        offset + 2..offset + 4,
1492    );
1493    buf.push_field(
1494        &DNS_FIELD_DESCRIPTORS[FD_RCODE],
1495        FieldValue::U8(rcode),
1496        offset + 2..offset + 4,
1497    );
1498    buf.push_field(
1499        &DNS_FIELD_DESCRIPTORS[FD_QDCOUNT],
1500        FieldValue::U16(qdcount),
1501        offset + 4..offset + 6,
1502    );
1503    buf.push_field(
1504        &DNS_FIELD_DESCRIPTORS[FD_ANCOUNT],
1505        FieldValue::U16(ancount),
1506        offset + 6..offset + 8,
1507    );
1508    buf.push_field(
1509        &DNS_FIELD_DESCRIPTORS[FD_NSCOUNT],
1510        FieldValue::U16(nscount),
1511        offset + 8..offset + 10,
1512    );
1513    buf.push_field(
1514        &DNS_FIELD_DESCRIPTORS[FD_ARCOUNT],
1515        FieldValue::U16(arcount),
1516        offset + 10..offset + 12,
1517    );
1518
1519    let mut pos = HEADER_SIZE;
1520
1521    // RFC 1035, Section 4.1.2 — Question Section
1522    let questions_start = pos;
1523    let questions_count = qdcount as usize;
1524    let questions_array_idx = if questions_count > 0 {
1525        Some(buf.begin_container(
1526            &DNS_FIELD_DESCRIPTORS[FD_QUESTIONS],
1527            FieldValue::Array(0..0),
1528            offset + questions_start..offset + questions_start,
1529        ))
1530    } else {
1531        None
1532    };
1533    for _i in 0..questions_count {
1534        let name_len = parse_name(data, pos)?;
1535        let name_start = pos;
1536        pos += name_len;
1537
1538        if pos + 4 > data.len() {
1539            return Err(PacketError::Truncated {
1540                expected: pos + 4,
1541                actual: data.len(),
1542            });
1543        }
1544
1545        let qtype = read_be_u16(data, pos)?;
1546        let qclass_raw = read_be_u16(data, pos + 2)?;
1547        // RFC 6762, Section 18.12 / 5.4 — in mDNS the top bit of qclass is the
1548        // unicast-response ("QU") bit, not part of the class value.
1549        // <https://www.rfc-editor.org/rfc/rfc6762#section-18.12>
1550        let (qclass, qu_bit) = if mdns_mode {
1551            (qclass_raw & 0x7FFF, ((qclass_raw >> 15) & 1) as u8)
1552        } else {
1553            (qclass_raw, 0)
1554        };
1555
1556        let obj_idx = buf.begin_container(
1557            &QUESTION_CHILD_FIELDS[QFD_NAME],
1558            FieldValue::Object(0..0),
1559            offset + name_start..offset + pos + 4,
1560        );
1561        buf.push_field(
1562            &QUESTION_CHILD_FIELDS[QFD_NAME],
1563            FieldValue::Bytes(&data[name_start..pos]),
1564            offset + name_start..offset + pos,
1565        );
1566        buf.push_field(
1567            &QUESTION_CHILD_FIELDS[QFD_TYPE],
1568            FieldValue::U16(qtype),
1569            offset + pos..offset + pos + 2,
1570        );
1571        buf.push_field(
1572            &QUESTION_CHILD_FIELDS[QFD_CLASS],
1573            FieldValue::U16(qclass),
1574            offset + pos + 2..offset + pos + 4,
1575        );
1576        if mdns_mode {
1577            buf.push_field(
1578                &QUESTION_CHILD_FIELDS[QFD_QU],
1579                FieldValue::U8(qu_bit),
1580                offset + pos + 2..offset + pos + 4,
1581            );
1582        }
1583        buf.end_container(obj_idx);
1584        pos += 4;
1585    }
1586    if let Some(idx) = questions_array_idx {
1587        // Update the range on the array container
1588        if let Some(field) = buf.field_mut(idx as usize) {
1589            field.range = offset + questions_start..offset + pos;
1590        }
1591        buf.end_container(idx);
1592    }
1593
1594    // RFC 1035, Section 4.1.3 — Resource Records (Answer, Authority, Additional)
1595    let sections: &[(usize, u16)] = &[
1596        (FD_ANSWERS, ancount),
1597        (FD_AUTHORITIES, nscount),
1598        (FD_ADDITIONALS, arcount),
1599    ];
1600
1601    for &(section_fd, count) in sections {
1602        let section_start = pos;
1603        let count = count as usize;
1604        let array_idx = if count > 0 {
1605            Some(buf.begin_container(
1606                &DNS_FIELD_DESCRIPTORS[section_fd],
1607                FieldValue::Array(0..0),
1608                offset + section_start..offset + section_start,
1609            ))
1610        } else {
1611            None
1612        };
1613        for _i in 0..count {
1614            let name_len = parse_name(data, pos)?;
1615            let name_start = pos;
1616            pos += name_len;
1617
1618            // TYPE(2) + CLASS(2) + TTL(4) + RDLENGTH(2) = 10 bytes
1619            if pos + 10 > data.len() {
1620                return Err(PacketError::Truncated {
1621                    expected: pos + 10,
1622                    actual: data.len(),
1623                });
1624            }
1625
1626            let rtype = read_be_u16(data, pos)?;
1627            let rclass_raw = read_be_u16(data, pos + 2)?;
1628            let ttl = read_be_u32(data, pos + 4)?;
1629            let rdlength = read_be_u16(data, pos + 8)? as usize;
1630
1631            if pos + 10 + rdlength > data.len() {
1632                return Err(PacketError::Truncated {
1633                    expected: pos + 10 + rdlength,
1634                    actual: data.len(),
1635                });
1636            }
1637
1638            let rdata = &data[pos + 10..pos + 10 + rdlength];
1639            let record_end = pos + 10 + rdlength;
1640
1641            let obj_idx = buf.begin_container(
1642                &RR_CHILD_FIELDS[RRFD_NAME],
1643                FieldValue::Object(0..0),
1644                offset + name_start..offset + record_end,
1645            );
1646
1647            // RFC 6891 — OPT pseudo-record has different field semantics.
1648            // RFC 6762, Section 10.2 also specifies that the cache-flush bit
1649            // reuse does NOT apply to pseudo-RRs like OPT — the rrclass field
1650            // remains the full 16-bit UDP payload size.
1651            // <https://www.rfc-editor.org/rfc/rfc6762#section-10.2>
1652            if rtype == TYPE_OPT {
1653                let extended_rcode = ((ttl >> 24) & 0xFF) as u8;
1654                let edns_version = ((ttl >> 16) & 0xFF) as u8;
1655                let do_bit = ((ttl >> 15) & 1) as u8;
1656                buf.push_field(
1657                    &RR_CHILD_FIELDS[RRFD_NAME],
1658                    FieldValue::Bytes(&data[name_start..pos]),
1659                    offset + name_start..offset + pos,
1660                );
1661                buf.push_field(
1662                    &RR_CHILD_FIELDS[RRFD_TYPE],
1663                    FieldValue::U16(rtype),
1664                    offset + pos..offset + pos + 2,
1665                );
1666                buf.push_field(
1667                    &RR_CHILD_FIELDS[RRFD_UDP_PAYLOAD_SIZE],
1668                    FieldValue::U16(rclass_raw),
1669                    offset + pos + 2..offset + pos + 4,
1670                );
1671                buf.push_field(
1672                    &RR_CHILD_FIELDS[RRFD_EXTENDED_RCODE],
1673                    FieldValue::U8(extended_rcode),
1674                    offset + pos + 4..offset + pos + 8,
1675                );
1676                buf.push_field(
1677                    &RR_CHILD_FIELDS[RRFD_EDNS_VERSION],
1678                    FieldValue::U8(edns_version),
1679                    offset + pos + 4..offset + pos + 8,
1680                );
1681                buf.push_field(
1682                    &RR_CHILD_FIELDS[RRFD_DO_BIT],
1683                    FieldValue::U8(do_bit),
1684                    offset + pos + 4..offset + pos + 8,
1685                );
1686                buf.push_field(
1687                    &RR_CHILD_FIELDS[RRFD_RDLENGTH],
1688                    FieldValue::U16(rdlength as u16),
1689                    offset + pos + 8..offset + pos + 10,
1690                );
1691                let edns_arr_idx = buf.begin_container(
1692                    &RR_CHILD_FIELDS[RRFD_EDNS_OPTIONS],
1693                    FieldValue::Array(0..0),
1694                    offset + pos + 10..offset + pos + 10 + rdlength,
1695                );
1696                parse_edns_options(buf, rdata, offset + pos + 10);
1697                buf.end_container(edns_arr_idx);
1698            } else {
1699                // RFC 6762, Section 18.13 / 10.2 — in mDNS the top bit of
1700                // rrclass is the cache-flush bit on non-OPT records.
1701                // <https://www.rfc-editor.org/rfc/rfc6762#section-18.13>
1702                let (rclass, cache_flush_bit) = if mdns_mode {
1703                    (rclass_raw & 0x7FFF, ((rclass_raw >> 15) & 1) as u8)
1704                } else {
1705                    (rclass_raw, 0)
1706                };
1707                buf.push_field(
1708                    &RR_CHILD_FIELDS[RRFD_NAME],
1709                    FieldValue::Bytes(&data[name_start..pos]),
1710                    offset + name_start..offset + pos,
1711                );
1712                buf.push_field(
1713                    &RR_CHILD_FIELDS[RRFD_TYPE],
1714                    FieldValue::U16(rtype),
1715                    offset + pos..offset + pos + 2,
1716                );
1717                buf.push_field(
1718                    &RR_CHILD_FIELDS[RRFD_CLASS],
1719                    FieldValue::U16(rclass),
1720                    offset + pos + 2..offset + pos + 4,
1721                );
1722                if mdns_mode {
1723                    buf.push_field(
1724                        &RR_CHILD_FIELDS[RRFD_CACHE_FLUSH],
1725                        FieldValue::U8(cache_flush_bit),
1726                        offset + pos + 2..offset + pos + 4,
1727                    );
1728                }
1729                buf.push_field(
1730                    &RR_CHILD_FIELDS[RRFD_TTL],
1731                    FieldValue::U32(ttl),
1732                    offset + pos + 4..offset + pos + 8,
1733                );
1734                buf.push_field(
1735                    &RR_CHILD_FIELDS[RRFD_RDLENGTH],
1736                    FieldValue::U16(rdlength as u16),
1737                    offset + pos + 8..offset + pos + 10,
1738                );
1739                // RFC 1035, Section 3.2 — Parse RDATA based on record type
1740                parse_rdata(buf, data, pos + 10, rdata, rtype, offset + pos + 10);
1741            }
1742
1743            buf.end_container(obj_idx);
1744            pos += 10 + rdlength;
1745        }
1746        if let Some(idx) = array_idx {
1747            if let Some(field) = buf.field_mut(idx as usize) {
1748                field.range = offset + section_start..offset + pos;
1749            }
1750            buf.end_container(idx);
1751        }
1752    }
1753
1754    // Update layer range to actual consumed bytes
1755    if let Some(layer) = buf.last_layer_mut() {
1756        layer.range = offset..offset + pos;
1757    }
1758    buf.end_layer();
1759
1760    Ok(DissectResult::new(pos, DispatchHint::End))
1761}
1762
1763/// Dissect a complete DNS-over-TCP message (length prefix + DNS payload).
1764///
1765/// `msg_data` must start with the 2-byte length prefix followed by the DNS message.
1766/// `offset` sets the base for all produced field and layer ranges. Pass the real
1767/// packet byte offset for single-segment (stateless) parsing, or `0` for
1768/// reassembled messages so that ranges are expressed in the reassembly buffer's
1769/// coordinate space rather than in original-packet byte positions.
1770fn dissect_dns_tcp_message<'pkt>(
1771    msg_data: &'pkt [u8],
1772    buf: &mut DissectBuffer<'pkt>,
1773    offset: usize,
1774) -> Result<DissectResult, PacketError> {
1775    let msg_len = read_be_u16(msg_data, 0)? as usize;
1776
1777    // Push tcp_length field BEFORE calling the inner DNS dissector,
1778    // so it appears as the first field in the DNS layer.
1779    // We record the field index so we can include it in the layer's field_range.
1780    let tcp_len_field_idx = buf.field_count();
1781    buf.push_field(
1782        &DNS_TCP_FIELD_DESCRIPTORS[0], // tcp_length is the first descriptor
1783        FieldValue::U16(msg_len as u16),
1784        offset..offset + 2,
1785    );
1786
1787    let result = DnsDissector.dissect(&msg_data[2..2 + msg_len], buf, offset + 2)?;
1788
1789    // Extend the DNS layer range to include the 2-byte TCP length prefix
1790    // and the tcp_length field we pushed before the DNS dissect call.
1791    if let Some(layer) = buf.last_layer_mut() {
1792        layer.range = offset..layer.range.end;
1793        layer.field_range.start = tcp_len_field_idx;
1794        layer.field_descriptors = DNS_TCP_FIELD_DESCRIPTORS;
1795    }
1796
1797    Ok(DissectResult::new(
1798        2 + result.bytes_consumed,
1799        DispatchHint::End,
1800    ))
1801}
1802
1803/// Stateless DNS over TCP dissector.
1804///
1805/// Handles the 2-byte length prefix used for DNS messages over TCP
1806/// (RFC 1035, Section 4.2.2 — <https://www.rfc-editor.org/rfc/rfc1035#section-4.2.2>,
1807/// updated by RFC 7766 — <https://www.rfc-editor.org/rfc/rfc7766>).
1808/// TCP stream reassembly is handled centrally by the registry;
1809/// this dissector only parses complete DNS messages.
1810pub struct DnsTcpDissector;
1811
1812impl Dissector for DnsTcpDissector {
1813    fn name(&self) -> &'static str {
1814        "DNS over TCP"
1815    }
1816
1817    fn short_name(&self) -> &'static str {
1818        "DNS"
1819    }
1820
1821    fn field_descriptors(&self) -> &'static [FieldDescriptor] {
1822        DNS_TCP_FIELD_DESCRIPTORS
1823    }
1824
1825    fn dissect<'pkt>(
1826        &self,
1827        data: &'pkt [u8],
1828        buf: &mut DissectBuffer<'pkt>,
1829        offset: usize,
1830    ) -> Result<DissectResult, PacketError> {
1831        // RFC 1035, Section 4.2.2 (updated by RFC 7766) — TCP messages are
1832        // prefixed with a 2-byte length.
1833        // <https://www.rfc-editor.org/rfc/rfc7766#section-8>
1834        if data.len() < 2 {
1835            return Err(PacketError::Truncated {
1836                expected: 2,
1837                actual: data.len(),
1838            });
1839        }
1840
1841        let msg_len = read_be_u16(data, 0)? as usize;
1842        let total_len = 2 + msg_len;
1843
1844        if data.len() < total_len {
1845            return Err(PacketError::Truncated {
1846                expected: total_len,
1847                actual: data.len(),
1848            });
1849        }
1850
1851        dissect_dns_tcp_message(&data[..total_len], buf, offset)
1852    }
1853}
1854
1855#[cfg(test)]
1856mod tests {
1857    use super::*;
1858    use packet_dissector_core::field::Field;
1859
1860    // # RFC Coverage (DNS dissector)
1861    //
1862    // | RFC / Section          | Description                         | Test                              |
1863    // |------------------------|-------------------------------------|-----------------------------------|
1864    // | RFC 1035 §4.1.1        | Header layout & flag extraction     | parse_header_flags                |
1865    // | RFC 1035 §4.1.1        | Truncated DNS header (<12 bytes)    | parse_header_truncated            |
1866    // | RFC 1035 §4.1.2        | Question section (A/IN)             | parse_question_a_in               |
1867    // | RFC 1035 §4.1.3/§3.4.1 | A record RDATA                      | parse_a_record                    |
1868    // | RFC 1035 §3.3.1/§3.3.11/§3.3.12 | CNAME / NS / PTR           | parse_cname_ns_ptr_record         |
1869    // | RFC 1035 §3.3.9        | MX record                           | parse_mx_record                   |
1870    // | RFC 1035 §3.3.13       | SOA record                          | parse_soa_record                  |
1871    // | RFC 1035 §3.3.14       | TXT record                          | parse_txt_record                  |
1872    // | RFC 1035 §2.3.4/§3.1   | Name length > 255 octets rejected   | reject_name_over_255_octets       |
1873    // | RFC 1035 §4.1.4        | Name compression pointer loop       | reject_name_pointer_loop          |
1874    // | RFC 1035 §3.1          | Reserved label type (10)            | reject_reserved_label_type        |
1875    // | RFC 1035 §4.2.2 / 7766 | TCP 2-byte length prefix            | parse_tcp_length_prefix           |
1876    // | RFC 3596               | AAAA record                         | parse_aaaa_record                 |
1877    // | RFC 2782               | SRV record                          | parse_srv_record                  |
1878    // | RFC 3403               | NAPTR record                        | parse_naptr_record                |
1879    // | RFC 3403               | NAPTR parsing is zero-allocation    | naptr_dissect_zero_alloc          |
1880    // | RFC 4035 §3.1.6        | AD / CD flag bit positions          | parse_header_flags                |
1881    // | RFC 4034 §2.1          | DNSKEY record                       | parse_dnskey_record               |
1882    // | RFC 4034 §3.1          | RRSIG record                        | parse_rrsig_record                |
1883    // | RFC 4034 §4.1          | NSEC record                         | parse_nsec_record                 |
1884    // | RFC 4034 §5.1          | DS record                           | parse_ds_record                   |
1885    // | RFC 4255               | SSHFP record                        | parse_sshfp_record                |
1886    // | RFC 5155 §3.2          | NSEC3 record                        | parse_nsec3_record                |
1887    // | RFC 5155 §4.2          | NSEC3PARAM record                   | parse_nsec3param_record           |
1888    // | RFC 6672               | DNAME record                        | parse_cname_ns_ptr_record         |
1889    // | RFC 6698               | TLSA record                         | parse_tlsa_record                 |
1890    // | RFC 6891 §6.1.2/§6.1.3 | OPT pseudo-RR (UDP size, DO bit)    | parse_opt_record_edns0            |
1891    // | RFC 7344               | CDS / CDNSKEY records               | parse_cds_record                  |
1892    // | RFC 7828 §3            | EDNS0 TCP Keepalive option          | parse_edns_tcp_keepalive          |
1893    // | RFC 8659 §4.1          | CAA record RDATA layout             | parse_caa_record                  |
1894    // | RFC 9460 §2.2          | SVCB / HTTPS record                 | parse_svcb_record                 |
1895    // | —                      | Opcode / RCODE / TYPE / CLASS names | type_class_opcode_rcode_names     |
1896    // | —                      | Dispatch hint is End                | dispatch_hint_is_end              |
1897    // | —                      | `write_dns_name` formats output     | write_dns_name_formats_output     |
1898
1899    /// Shared `DissectBuffer` for tests that only need a fresh buffer.
1900    fn buf() -> DissectBuffer<'static> {
1901        DissectBuffer::new()
1902    }
1903
1904    /// Look up a child field by name within the nested range of `parent`.
1905    fn find_child<'a, 'pkt>(
1906        buf: &'a DissectBuffer<'pkt>,
1907        parent: &Field<'pkt>,
1908        name: &str,
1909    ) -> Option<&'a Field<'pkt>> {
1910        let range = match &parent.value {
1911            FieldValue::Object(r) | FieldValue::Array(r) => r.clone(),
1912            _ => return None,
1913        };
1914        buf.nested_fields(&range).iter().find(|f| f.name() == name)
1915    }
1916
1917    /// Return the first Object placeholder within an Array field.
1918    ///
1919    /// Flat-storage note: `nested_fields(array_range)` returns all fields
1920    /// between the Array's `begin_container` and `end_container`, i.e. both
1921    /// the per-entry Object placeholders AND their flattened children.
1922    /// Tests that only look at a single RR use this helper to locate that
1923    /// first Object directly.
1924    fn first_array_entry<'a, 'pkt>(
1925        buf: &'a DissectBuffer<'pkt>,
1926        array: &Field<'pkt>,
1927    ) -> &'a Field<'pkt> {
1928        let range = match &array.value {
1929            FieldValue::Array(r) => r.clone(),
1930            _ => panic!("expected Array field"),
1931        };
1932        buf.nested_fields(&range)
1933            .iter()
1934            .find(|f| matches!(f.value, FieldValue::Object(_)))
1935            .expect("array must have at least one Object entry")
1936    }
1937
1938    /// Encode a domain name in wire format (no compression).
1939    fn wire_name(name: &str) -> Vec<u8> {
1940        let mut out = Vec::new();
1941        if !name.is_empty() {
1942            for label in name.split('.') {
1943                out.push(label.len() as u8);
1944                out.extend_from_slice(label.as_bytes());
1945            }
1946        }
1947        out.push(0); // root terminator
1948        out
1949    }
1950
1951    /// Assemble a DNS header (ID=0, flags=0x0000, counts = provided).
1952    fn header(qd: u16, an: u16, ns: u16, ar: u16) -> Vec<u8> {
1953        let mut h = Vec::with_capacity(12);
1954        h.extend_from_slice(&0u16.to_be_bytes()); // ID
1955        h.extend_from_slice(&0u16.to_be_bytes()); // Flags
1956        h.extend_from_slice(&qd.to_be_bytes());
1957        h.extend_from_slice(&an.to_be_bytes());
1958        h.extend_from_slice(&ns.to_be_bytes());
1959        h.extend_from_slice(&ar.to_be_bytes());
1960        h
1961    }
1962
1963    // ---- RFC 1035 §4.1.1 — header & flag extraction ----------------------
1964
1965    #[test]
1966    fn parse_header_flags() {
1967        // All flags set (including AD/CD from RFC 4035) with opcode=UPDATE(5),
1968        // rcode=REFUSED(5).
1969        // bits: QR=1 Opcode=5 AA=1 TC=1 RD=1 RA=1 Z=1 AD=1 CD=1 RCODE=5
1970        // 1 0101 1 1 1 1 1 1 1 0101 = 0xAFF5
1971        let flags: u16 = (1 << 15) // QR
1972            | (5 << 11) // Opcode=UPDATE
1973            | (1 << 10) // AA
1974            | (1 << 9)  // TC
1975            | (1 << 8)  // RD
1976            | (1 << 7)  // RA
1977            | (1 << 6)  // Z
1978            | (1 << 5)  // AD
1979            | (1 << 4)  // CD
1980            | 5; // RCODE=REFUSED
1981
1982        let mut data = Vec::new();
1983        data.extend_from_slice(&0xABCDu16.to_be_bytes());
1984        data.extend_from_slice(&flags.to_be_bytes());
1985        data.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]); // zeros for counts
1986
1987        let mut b = buf();
1988        DnsDissector.dissect(&data, &mut b, 0).unwrap();
1989
1990        let layer = &b.layers()[0];
1991        let get = |name: &str| b.field_by_name(layer, name).unwrap().value.clone();
1992
1993        assert_eq!(get("id"), FieldValue::U16(0xABCD));
1994        assert_eq!(get("qr"), FieldValue::U8(1));
1995        assert_eq!(get("opcode"), FieldValue::U8(5));
1996        assert_eq!(get("aa"), FieldValue::U8(1));
1997        assert_eq!(get("tc"), FieldValue::U8(1));
1998        assert_eq!(get("rd"), FieldValue::U8(1));
1999        assert_eq!(get("ra"), FieldValue::U8(1));
2000        assert_eq!(get("z"), FieldValue::U8(1));
2001        assert_eq!(get("ad"), FieldValue::U8(1));
2002        assert_eq!(get("cd"), FieldValue::U8(1));
2003        assert_eq!(get("rcode"), FieldValue::U8(5));
2004    }
2005
2006    #[test]
2007    fn parse_header_truncated() {
2008        // RFC 1035 §4.1.1 — minimum header is 12 bytes.
2009        let mut b = buf();
2010        let err = DnsDissector.dissect(&[0u8; 11], &mut b, 0).unwrap_err();
2011        assert!(matches!(
2012            err,
2013            PacketError::Truncated {
2014                expected: 12,
2015                actual: 11,
2016            }
2017        ));
2018    }
2019
2020    // ---- RFC 1035 §4.1.2 — question & §3.4.1 A RDATA ---------------------
2021
2022    #[test]
2023    fn parse_question_a_in() {
2024        let mut data = header(1, 0, 0, 0);
2025        data.extend_from_slice(&wire_name("example.com"));
2026        data.extend_from_slice(&1u16.to_be_bytes()); // QTYPE=A
2027        data.extend_from_slice(&1u16.to_be_bytes()); // QCLASS=IN
2028
2029        let mut b = buf();
2030        let res = DnsDissector.dissect(&data, &mut b, 0).unwrap();
2031        assert_eq!(res.bytes_consumed, data.len());
2032
2033        let layer = &b.layers()[0];
2034        let questions = b.field_by_name(layer, "questions").unwrap();
2035        let FieldValue::Array(ref q_range) = questions.value else {
2036            panic!("questions should be an Array");
2037        };
2038        let q_list = b.nested_fields(q_range);
2039
2040        let q = &q_list[0];
2041        assert_eq!(find_child(&b, q, "type").unwrap().value, FieldValue::U16(1));
2042        assert_eq!(
2043            find_child(&b, q, "class").unwrap().value,
2044            FieldValue::U16(1)
2045        );
2046    }
2047
2048    #[test]
2049    fn parse_a_record() {
2050        // 1 answer RR: example.com. IN A 192.0.2.1
2051        let mut data = header(0, 1, 0, 0);
2052        data.extend_from_slice(&wire_name("example.com"));
2053        data.extend_from_slice(&1u16.to_be_bytes()); // TYPE=A
2054        data.extend_from_slice(&1u16.to_be_bytes()); // CLASS=IN
2055        data.extend_from_slice(&3600u32.to_be_bytes()); // TTL
2056        data.extend_from_slice(&4u16.to_be_bytes()); // RDLENGTH
2057        data.extend_from_slice(&[192, 0, 2, 1]);
2058
2059        let mut b = buf();
2060        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2061        let layer = &b.layers()[0];
2062        let answers = b.field_by_name(layer, "answers").unwrap();
2063        let rr = first_array_entry(&b, answers);
2064        let rdata = find_child(&b, rr, "rdata").unwrap();
2065        assert_eq!(rdata.value, FieldValue::Ipv4Addr([192, 0, 2, 1]));
2066    }
2067
2068    // ---- RFC 3596 — AAAA -------------------------------------------------
2069
2070    #[test]
2071    fn parse_aaaa_record() {
2072        let addr: [u8; 16] = [
2073            0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01,
2074        ];
2075        let mut data = header(0, 1, 0, 0);
2076        data.extend_from_slice(&wire_name("example.com"));
2077        data.extend_from_slice(&TYPE_AAAA.to_be_bytes());
2078        data.extend_from_slice(&1u16.to_be_bytes()); // IN
2079        data.extend_from_slice(&3600u32.to_be_bytes());
2080        data.extend_from_slice(&16u16.to_be_bytes());
2081        data.extend_from_slice(&addr);
2082
2083        let mut b = buf();
2084        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2085        let layer = &b.layers()[0];
2086        let answers = b.field_by_name(layer, "answers").unwrap();
2087        let rr = first_array_entry(&b, answers);
2088        let rdata = find_child(&b, rr, "rdata").unwrap();
2089        assert_eq!(rdata.value, FieldValue::Ipv6Addr(addr));
2090    }
2091
2092    // ---- RFC 1035 §3.3.{1,9,11,12,13,14} / RFC 6672 ----------------------
2093
2094    #[test]
2095    fn parse_cname_ns_ptr_record() {
2096        // A single CNAME record: owner "a.test" → target "b.test"
2097        for rtype in [TYPE_CNAME, TYPE_NS, TYPE_PTR, TYPE_DNAME] {
2098            let mut data = header(0, 1, 0, 0);
2099            data.extend_from_slice(&wire_name("a.test"));
2100            data.extend_from_slice(&rtype.to_be_bytes());
2101            data.extend_from_slice(&1u16.to_be_bytes()); // IN
2102            data.extend_from_slice(&0u32.to_be_bytes());
2103            let target = wire_name("b.test");
2104            data.extend_from_slice(&(target.len() as u16).to_be_bytes());
2105            data.extend_from_slice(&target);
2106
2107            let mut b = buf();
2108            DnsDissector.dissect(&data, &mut b, 0).unwrap();
2109            let layer = &b.layers()[0];
2110            let answers = b.field_by_name(layer, "answers").unwrap();
2111            let rr = first_array_entry(&b, answers);
2112            let rdata = find_child(&b, rr, "rdata").unwrap();
2113            // RDATA is stored as raw bytes pointing into the wire format name.
2114            assert_eq!(rdata.value, FieldValue::Bytes(&target));
2115        }
2116    }
2117
2118    #[test]
2119    fn parse_mx_record() {
2120        let mut data = header(0, 1, 0, 0);
2121        data.extend_from_slice(&wire_name("ex.test"));
2122        data.extend_from_slice(&TYPE_MX.to_be_bytes());
2123        data.extend_from_slice(&1u16.to_be_bytes());
2124        data.extend_from_slice(&0u32.to_be_bytes());
2125        let exch = wire_name("mail.ex.test");
2126        let rdlen = (2 + exch.len()) as u16;
2127        data.extend_from_slice(&rdlen.to_be_bytes());
2128        data.extend_from_slice(&10u16.to_be_bytes()); // preference
2129        data.extend_from_slice(&exch);
2130
2131        let mut b = buf();
2132        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2133        let layer = &b.layers()[0];
2134        let answers = b.field_by_name(layer, "answers").unwrap();
2135        let rr = first_array_entry(&b, answers);
2136        assert_eq!(
2137            find_child(&b, rr, "rdata_preference").unwrap().value,
2138            FieldValue::U16(10)
2139        );
2140        assert_eq!(
2141            find_child(&b, rr, "rdata_exchange").unwrap().value,
2142            FieldValue::Bytes(&exch)
2143        );
2144    }
2145
2146    #[test]
2147    fn parse_soa_record() {
2148        let mname = wire_name("ns1.ex.test");
2149        let rname = wire_name("hostmaster.ex.test");
2150        let mut rdata = Vec::new();
2151        rdata.extend_from_slice(&mname);
2152        rdata.extend_from_slice(&rname);
2153        rdata.extend_from_slice(&20_240_101u32.to_be_bytes()); // SERIAL
2154        rdata.extend_from_slice(&3600u32.to_be_bytes()); // REFRESH
2155        rdata.extend_from_slice(&1800u32.to_be_bytes()); // RETRY
2156        rdata.extend_from_slice(&604_800u32.to_be_bytes()); // EXPIRE
2157        rdata.extend_from_slice(&300u32.to_be_bytes()); // MINIMUM
2158
2159        let mut data = header(0, 1, 0, 0);
2160        data.extend_from_slice(&wire_name("ex.test"));
2161        data.extend_from_slice(&TYPE_SOA.to_be_bytes());
2162        data.extend_from_slice(&1u16.to_be_bytes());
2163        data.extend_from_slice(&0u32.to_be_bytes());
2164        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2165        data.extend_from_slice(&rdata);
2166
2167        let mut b = buf();
2168        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2169        let layer = &b.layers()[0];
2170        let answers = b.field_by_name(layer, "answers").unwrap();
2171        let rr = first_array_entry(&b, answers);
2172
2173        assert_eq!(
2174            find_child(&b, rr, "rdata_serial").unwrap().value,
2175            FieldValue::U32(20_240_101)
2176        );
2177        assert_eq!(
2178            find_child(&b, rr, "rdata_refresh").unwrap().value,
2179            FieldValue::U32(3600)
2180        );
2181        assert_eq!(
2182            find_child(&b, rr, "rdata_retry").unwrap().value,
2183            FieldValue::U32(1800)
2184        );
2185        assert_eq!(
2186            find_child(&b, rr, "rdata_expire").unwrap().value,
2187            FieldValue::U32(604_800)
2188        );
2189        assert_eq!(
2190            find_child(&b, rr, "rdata_minimum").unwrap().value,
2191            FieldValue::U32(300)
2192        );
2193    }
2194
2195    #[test]
2196    fn parse_txt_record() {
2197        let rdata: &[u8] = &[3, b'a', b'b', b'c', 2, b'd', b'e'];
2198        let mut data = header(0, 1, 0, 0);
2199        data.extend_from_slice(&wire_name("ex.test"));
2200        data.extend_from_slice(&TYPE_TXT.to_be_bytes());
2201        data.extend_from_slice(&1u16.to_be_bytes());
2202        data.extend_from_slice(&0u32.to_be_bytes());
2203        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2204        data.extend_from_slice(rdata);
2205
2206        let mut b = buf();
2207        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2208        let layer = &b.layers()[0];
2209        let answers = b.field_by_name(layer, "answers").unwrap();
2210        let rr = first_array_entry(&b, answers);
2211        assert_eq!(
2212            find_child(&b, rr, "rdata").unwrap().value,
2213            FieldValue::Bytes(rdata)
2214        );
2215    }
2216
2217    // ---- RFC 2782 — SRV --------------------------------------------------
2218
2219    #[test]
2220    fn parse_srv_record() {
2221        let target = wire_name("sip.ex.test");
2222        let mut rdata = Vec::new();
2223        rdata.extend_from_slice(&10u16.to_be_bytes()); // priority
2224        rdata.extend_from_slice(&20u16.to_be_bytes()); // weight
2225        rdata.extend_from_slice(&5060u16.to_be_bytes()); // port
2226        rdata.extend_from_slice(&target);
2227
2228        let mut data = header(0, 1, 0, 0);
2229        data.extend_from_slice(&wire_name("_sip._udp.ex.test"));
2230        data.extend_from_slice(&TYPE_SRV.to_be_bytes());
2231        data.extend_from_slice(&1u16.to_be_bytes());
2232        data.extend_from_slice(&0u32.to_be_bytes());
2233        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2234        data.extend_from_slice(&rdata);
2235
2236        let mut b = buf();
2237        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2238        let layer = &b.layers()[0];
2239        let answers = b.field_by_name(layer, "answers").unwrap();
2240        let rr = first_array_entry(&b, answers);
2241        assert_eq!(
2242            find_child(&b, rr, "rdata_priority").unwrap().value,
2243            FieldValue::U16(10)
2244        );
2245        assert_eq!(
2246            find_child(&b, rr, "rdata_weight").unwrap().value,
2247            FieldValue::U16(20)
2248        );
2249        assert_eq!(
2250            find_child(&b, rr, "rdata_port").unwrap().value,
2251            FieldValue::U16(5060)
2252        );
2253        assert_eq!(
2254            find_child(&b, rr, "rdata_target").unwrap().value,
2255            FieldValue::Bytes(&target)
2256        );
2257    }
2258
2259    // ---- RFC 3403 — NAPTR ------------------------------------------------
2260
2261    fn build_naptr_packet() -> Vec<u8> {
2262        let replacement = wire_name("ex.test");
2263        let mut rdata = Vec::new();
2264        rdata.extend_from_slice(&100u16.to_be_bytes()); // order
2265        rdata.extend_from_slice(&10u16.to_be_bytes()); // preference
2266        // flags char-string "s"
2267        rdata.extend_from_slice(&[1, b's']);
2268        // services "SIP+D2U"
2269        let svc = b"SIP+D2U";
2270        rdata.push(svc.len() as u8);
2271        rdata.extend_from_slice(svc);
2272        // regexp (empty)
2273        rdata.push(0);
2274        // replacement name
2275        rdata.extend_from_slice(&replacement);
2276
2277        let mut data = header(0, 1, 0, 0);
2278        data.extend_from_slice(&wire_name("ex.test"));
2279        data.extend_from_slice(&TYPE_NAPTR.to_be_bytes());
2280        data.extend_from_slice(&1u16.to_be_bytes());
2281        data.extend_from_slice(&0u32.to_be_bytes());
2282        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2283        data.extend_from_slice(&rdata);
2284        data
2285    }
2286
2287    #[test]
2288    fn parse_naptr_record() {
2289        let data = build_naptr_packet();
2290        let mut b = buf();
2291        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2292        let layer = &b.layers()[0];
2293        let answers = b.field_by_name(layer, "answers").unwrap();
2294        let rr = first_array_entry(&b, answers);
2295        assert_eq!(
2296            find_child(&b, rr, "rdata_order").unwrap().value,
2297            FieldValue::U16(100)
2298        );
2299        assert_eq!(
2300            find_child(&b, rr, "rdata_preference").unwrap().value,
2301            FieldValue::U16(10)
2302        );
2303        // flags and services character-strings include the leading length byte
2304        // in the stored bytes (they are emitted as raw RDATA slices).
2305        let flags = find_child(&b, rr, "rdata_flags").unwrap();
2306        assert_eq!(flags.value, FieldValue::Bytes(&[1, b's']));
2307        let services = find_child(&b, rr, "rdata_services").unwrap();
2308        let mut expected_svc = vec![7u8];
2309        expected_svc.extend_from_slice(b"SIP+D2U");
2310        assert_eq!(services.value, FieldValue::Bytes(&expected_svc));
2311    }
2312
2313    // ---- RFC 6891 — EDNS0 OPT --------------------------------------------
2314
2315    #[test]
2316    fn parse_opt_record_edns0() {
2317        // OPT pseudo-RR with UDP payload size 4096, DO=1, extended_rcode=0,
2318        // version=0, and a COOKIE option.
2319        let mut rdata = Vec::new();
2320        rdata.extend_from_slice(&10u16.to_be_bytes()); // OPTION-CODE = COOKIE
2321        rdata.extend_from_slice(&8u16.to_be_bytes()); // OPTION-LENGTH = 8
2322        rdata.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0x00, 0x11, 0x22, 0x33]);
2323
2324        let mut data = header(0, 0, 0, 1);
2325        // OPT NAME MUST be root.
2326        data.push(0);
2327        data.extend_from_slice(&TYPE_OPT.to_be_bytes());
2328        data.extend_from_slice(&4096u16.to_be_bytes()); // CLASS = UDP payload size
2329        // TTL: extended-rcode(0) | version(0) | DO=1 | Z=0
2330        // DO bit = high bit of byte 2 → 0x8000_0000 in 16-bit lower half.
2331        data.extend_from_slice(&0x0000_8000u32.to_be_bytes());
2332        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2333        data.extend_from_slice(&rdata);
2334
2335        let mut b = buf();
2336        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2337        let layer = &b.layers()[0];
2338        let additionals = b.field_by_name(layer, "additionals").unwrap();
2339        let rr = first_array_entry(&b, additionals);
2340        assert_eq!(
2341            find_child(&b, rr, "udp_payload_size").unwrap().value,
2342            FieldValue::U16(4096)
2343        );
2344        assert_eq!(
2345            find_child(&b, rr, "extended_rcode").unwrap().value,
2346            FieldValue::U8(0)
2347        );
2348        assert_eq!(
2349            find_child(&b, rr, "edns_version").unwrap().value,
2350            FieldValue::U8(0)
2351        );
2352        assert_eq!(
2353            find_child(&b, rr, "do_bit").unwrap().value,
2354            FieldValue::U8(1)
2355        );
2356
2357        let opts = find_child(&b, rr, "edns_options").unwrap();
2358        let FieldValue::Array(ref opt_range) = opts.value else {
2359            unreachable!()
2360        };
2361        let opt_list = b.nested_fields(opt_range);
2362        assert_eq!(
2363            find_child(&b, &opt_list[0], "code").unwrap().value,
2364            FieldValue::U16(10) // COOKIE
2365        );
2366        assert_eq!(
2367            find_child(&b, &opt_list[0], "length").unwrap().value,
2368            FieldValue::U16(8)
2369        );
2370    }
2371
2372    #[test]
2373    fn edns_option_container_resolves_to_option_name() {
2374        // OPT RR carrying a single COOKIE option so the container label
2375        // should resolve to "COOKIE" rather than duplicating "Code".
2376        let mut rdata = Vec::new();
2377        rdata.extend_from_slice(&10u16.to_be_bytes()); // code = COOKIE
2378        rdata.extend_from_slice(&8u16.to_be_bytes()); // length = 8
2379        rdata.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0x00, 0x11, 0x22, 0x33]);
2380
2381        let mut data = header(0, 0, 0, 1);
2382        data.push(0);
2383        data.extend_from_slice(&TYPE_OPT.to_be_bytes());
2384        data.extend_from_slice(&4096u16.to_be_bytes());
2385        data.extend_from_slice(&0u32.to_be_bytes());
2386        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2387        data.extend_from_slice(&rdata);
2388
2389        let mut b = buf();
2390        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2391
2392        let (opt_idx, opt_field) = b
2393            .fields()
2394            .iter()
2395            .enumerate()
2396            .find(|(_, f)| f.name() == "edns_option")
2397            .expect("edns_option container not found");
2398        assert!(matches!(opt_field.value, FieldValue::Object(_)));
2399        assert_eq!(opt_field.display_name(), "EDNS Option");
2400        assert_eq!(
2401            b.resolve_container_display_name(opt_idx as u32),
2402            Some("COOKIE")
2403        );
2404    }
2405
2406    // ---- RFC 7828 — EDNS TCP Keepalive -----------------------------------
2407
2408    #[test]
2409    fn parse_edns_tcp_keepalive() {
2410        // OPT RR carrying a TCP-KEEPALIVE option with timeout = 300 (30 s).
2411        let mut rdata = Vec::new();
2412        rdata.extend_from_slice(&EDNS_OPT_TCP_KEEPALIVE.to_be_bytes()); // code = 11
2413        rdata.extend_from_slice(&2u16.to_be_bytes()); // length = 2
2414        rdata.extend_from_slice(&300u16.to_be_bytes()); // 30.0 seconds
2415
2416        let mut data = header(0, 0, 0, 1);
2417        data.push(0);
2418        data.extend_from_slice(&TYPE_OPT.to_be_bytes());
2419        data.extend_from_slice(&1232u16.to_be_bytes());
2420        data.extend_from_slice(&0u32.to_be_bytes());
2421        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2422        data.extend_from_slice(&rdata);
2423
2424        let mut b = buf();
2425        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2426        let layer = &b.layers()[0];
2427        let additionals = b.field_by_name(layer, "additionals").unwrap();
2428        let rr = first_array_entry(&b, additionals);
2429        let opts = find_child(&b, rr, "edns_options").unwrap();
2430        let opt = first_array_entry(&b, opts);
2431        assert_eq!(
2432            find_child(&b, opt, "timeout").unwrap().value,
2433            FieldValue::U16(300)
2434        );
2435    }
2436
2437    // ---- RFC 4255 — SSHFP ------------------------------------------------
2438
2439    #[test]
2440    fn parse_sshfp_record() {
2441        // algorithm=2 (DSS), fingerprint_type=1 (SHA-1), fingerprint = 20 bytes.
2442        let rdata: Vec<u8> = {
2443            let mut v = vec![2u8, 1u8];
2444            v.extend_from_slice(&[0u8; 20]);
2445            v
2446        };
2447        let mut data = header(0, 1, 0, 0);
2448        data.extend_from_slice(&wire_name("ex.test"));
2449        data.extend_from_slice(&TYPE_SSHFP.to_be_bytes());
2450        data.extend_from_slice(&1u16.to_be_bytes());
2451        data.extend_from_slice(&0u32.to_be_bytes());
2452        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2453        data.extend_from_slice(&rdata);
2454
2455        let mut b = buf();
2456        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2457        let layer = &b.layers()[0];
2458        let answers = b.field_by_name(layer, "answers").unwrap();
2459        let rr = first_array_entry(&b, answers);
2460        assert_eq!(
2461            find_child(&b, rr, "rdata_algorithm").unwrap().value,
2462            FieldValue::U8(2)
2463        );
2464        assert_eq!(
2465            find_child(&b, rr, "rdata_fingerprint_type").unwrap().value,
2466            FieldValue::U8(1)
2467        );
2468        assert_eq!(
2469            find_child(&b, rr, "rdata_fingerprint").unwrap().value,
2470            FieldValue::Bytes(&[0u8; 20])
2471        );
2472    }
2473
2474    // ---- RFC 6698 — TLSA -------------------------------------------------
2475
2476    #[test]
2477    fn parse_tlsa_record() {
2478        // usage=3 (DANE-EE), selector=1 (SPKI), matching_type=1 (SHA-256),
2479        // 32-byte SHA-256 hash.
2480        let rdata: Vec<u8> = {
2481            let mut v = vec![3u8, 1u8, 1u8];
2482            v.extend_from_slice(&[0xAAu8; 32]);
2483            v
2484        };
2485        let mut data = header(0, 1, 0, 0);
2486        data.extend_from_slice(&wire_name("_443._tcp.ex.test"));
2487        data.extend_from_slice(&TYPE_TLSA.to_be_bytes());
2488        data.extend_from_slice(&1u16.to_be_bytes());
2489        data.extend_from_slice(&0u32.to_be_bytes());
2490        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2491        data.extend_from_slice(&rdata);
2492
2493        let mut b = buf();
2494        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2495        let layer = &b.layers()[0];
2496        let answers = b.field_by_name(layer, "answers").unwrap();
2497        let rr = first_array_entry(&b, answers);
2498        assert_eq!(
2499            find_child(&b, rr, "rdata_cert_usage").unwrap().value,
2500            FieldValue::U8(3)
2501        );
2502        assert_eq!(
2503            find_child(&b, rr, "rdata_selector").unwrap().value,
2504            FieldValue::U8(1)
2505        );
2506        assert_eq!(
2507            find_child(&b, rr, "rdata_matching_type").unwrap().value,
2508            FieldValue::U8(1)
2509        );
2510        assert_eq!(
2511            find_child(&b, rr, "rdata_cert_assoc_data").unwrap().value,
2512            FieldValue::Bytes(&[0xAAu8; 32])
2513        );
2514    }
2515
2516    // ---- RFC 4034 §5.1 — DS ---------------------------------------------
2517
2518    #[test]
2519    fn parse_ds_record() {
2520        let digest = [0x11u8; 20]; // SHA-1 digest
2521        let rdata: Vec<u8> = {
2522            let mut v = Vec::new();
2523            v.extend_from_slice(&12345u16.to_be_bytes()); // key tag
2524            v.push(8); // RSASHA256
2525            v.push(1); // SHA-1
2526            v.extend_from_slice(&digest);
2527            v
2528        };
2529        let mut data = header(0, 1, 0, 0);
2530        data.extend_from_slice(&wire_name("ex.test"));
2531        data.extend_from_slice(&TYPE_DS.to_be_bytes());
2532        data.extend_from_slice(&1u16.to_be_bytes());
2533        data.extend_from_slice(&0u32.to_be_bytes());
2534        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2535        data.extend_from_slice(&rdata);
2536
2537        let mut b = buf();
2538        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2539        let layer = &b.layers()[0];
2540        let answers = b.field_by_name(layer, "answers").unwrap();
2541        let rr = first_array_entry(&b, answers);
2542        assert_eq!(
2543            find_child(&b, rr, "rdata_key_tag").unwrap().value,
2544            FieldValue::U16(12345)
2545        );
2546        assert_eq!(
2547            find_child(&b, rr, "rdata_algorithm").unwrap().value,
2548            FieldValue::U8(8)
2549        );
2550        assert_eq!(
2551            find_child(&b, rr, "rdata_digest_type").unwrap().value,
2552            FieldValue::U8(1)
2553        );
2554        assert_eq!(
2555            find_child(&b, rr, "rdata_digest").unwrap().value,
2556            FieldValue::Bytes(&digest)
2557        );
2558    }
2559
2560    // ---- RFC 7344 — CDS / CDNSKEY share parsing with DS / DNSKEY ---------
2561
2562    #[test]
2563    fn parse_cds_record() {
2564        let digest = [0x22u8; 20];
2565        let rdata: Vec<u8> = {
2566            let mut v = Vec::new();
2567            v.extend_from_slice(&65535u16.to_be_bytes());
2568            v.push(13); // ECDSAP256SHA256
2569            v.push(2); // SHA-256
2570            v.extend_from_slice(&digest);
2571            v
2572        };
2573        let mut data = header(0, 1, 0, 0);
2574        data.extend_from_slice(&wire_name("ex.test"));
2575        data.extend_from_slice(&TYPE_CDS.to_be_bytes());
2576        data.extend_from_slice(&1u16.to_be_bytes());
2577        data.extend_from_slice(&0u32.to_be_bytes());
2578        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2579        data.extend_from_slice(&rdata);
2580
2581        let mut b = buf();
2582        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2583        let layer = &b.layers()[0];
2584        let answers = b.field_by_name(layer, "answers").unwrap();
2585        let rr = first_array_entry(&b, answers);
2586        assert_eq!(
2587            find_child(&b, rr, "rdata_key_tag").unwrap().value,
2588            FieldValue::U16(65535)
2589        );
2590        assert_eq!(
2591            find_child(&b, rr, "rdata_digest").unwrap().value,
2592            FieldValue::Bytes(&digest)
2593        );
2594    }
2595
2596    // ---- RFC 4034 §2.1 — DNSKEY ------------------------------------------
2597
2598    #[test]
2599    fn parse_dnskey_record() {
2600        let pubkey = [0x33u8; 64];
2601        let rdata: Vec<u8> = {
2602            let mut v = Vec::new();
2603            v.extend_from_slice(&256u16.to_be_bytes()); // flags: ZONE=1 (bit 7)
2604            v.push(3); // protocol MUST be 3
2605            v.push(13); // algorithm ECDSAP256SHA256
2606            v.extend_from_slice(&pubkey);
2607            v
2608        };
2609        let mut data = header(0, 1, 0, 0);
2610        data.extend_from_slice(&wire_name("ex.test"));
2611        data.extend_from_slice(&TYPE_DNSKEY.to_be_bytes());
2612        data.extend_from_slice(&1u16.to_be_bytes());
2613        data.extend_from_slice(&0u32.to_be_bytes());
2614        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2615        data.extend_from_slice(&rdata);
2616
2617        let mut b = buf();
2618        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2619        let layer = &b.layers()[0];
2620        let answers = b.field_by_name(layer, "answers").unwrap();
2621        let rr = first_array_entry(&b, answers);
2622        assert_eq!(
2623            find_child(&b, rr, "rdata_flags").unwrap().value,
2624            FieldValue::U16(256)
2625        );
2626        assert_eq!(
2627            find_child(&b, rr, "rdata_protocol").unwrap().value,
2628            FieldValue::U8(3)
2629        );
2630        assert_eq!(
2631            find_child(&b, rr, "rdata_algorithm").unwrap().value,
2632            FieldValue::U8(13)
2633        );
2634        assert_eq!(
2635            find_child(&b, rr, "rdata_public_key").unwrap().value,
2636            FieldValue::Bytes(&pubkey)
2637        );
2638    }
2639
2640    // ---- RFC 4034 §3.1 — RRSIG ------------------------------------------
2641
2642    #[test]
2643    fn parse_rrsig_record() {
2644        let signer = wire_name("ex.test");
2645        let signature = [0x55u8; 64];
2646        let mut rdata = Vec::new();
2647        rdata.extend_from_slice(&TYPE_A.to_be_bytes()); // type covered
2648        rdata.push(13); // algorithm
2649        rdata.push(2); // labels
2650        rdata.extend_from_slice(&3600u32.to_be_bytes()); // original TTL
2651        rdata.extend_from_slice(&2_000_000_000u32.to_be_bytes()); // expiration
2652        rdata.extend_from_slice(&1_000_000_000u32.to_be_bytes()); // inception
2653        rdata.extend_from_slice(&4321u16.to_be_bytes()); // key tag
2654        rdata.extend_from_slice(&signer);
2655        rdata.extend_from_slice(&signature);
2656
2657        let mut data = header(0, 1, 0, 0);
2658        data.extend_from_slice(&wire_name("ex.test"));
2659        data.extend_from_slice(&TYPE_RRSIG.to_be_bytes());
2660        data.extend_from_slice(&1u16.to_be_bytes());
2661        data.extend_from_slice(&0u32.to_be_bytes());
2662        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2663        data.extend_from_slice(&rdata);
2664
2665        let mut b = buf();
2666        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2667        let layer = &b.layers()[0];
2668        let answers = b.field_by_name(layer, "answers").unwrap();
2669        let rr = first_array_entry(&b, answers);
2670        assert_eq!(
2671            find_child(&b, rr, "rdata_type_covered").unwrap().value,
2672            FieldValue::U16(TYPE_A)
2673        );
2674        assert_eq!(
2675            find_child(&b, rr, "rdata_labels").unwrap().value,
2676            FieldValue::U8(2)
2677        );
2678        assert_eq!(
2679            find_child(&b, rr, "rdata_original_ttl").unwrap().value,
2680            FieldValue::U32(3600)
2681        );
2682        assert_eq!(
2683            find_child(&b, rr, "rdata_signature_expiration")
2684                .unwrap()
2685                .value,
2686            FieldValue::U32(2_000_000_000)
2687        );
2688        assert_eq!(
2689            find_child(&b, rr, "rdata_signature_inception")
2690                .unwrap()
2691                .value,
2692            FieldValue::U32(1_000_000_000)
2693        );
2694        assert_eq!(
2695            find_child(&b, rr, "rdata_key_tag").unwrap().value,
2696            FieldValue::U16(4321)
2697        );
2698        assert_eq!(
2699            find_child(&b, rr, "rdata_signature").unwrap().value,
2700            FieldValue::Bytes(&signature)
2701        );
2702    }
2703
2704    // ---- RFC 4034 §4.1 — NSEC -------------------------------------------
2705
2706    #[test]
2707    fn parse_nsec_record() {
2708        // NSEC with next-domain-name = "next.ex.test" and a type bitmap
2709        // window 0 indicating A and AAAA are present.
2710        let next_name = wire_name("next.ex.test");
2711        // Bitmap window 0, length 4, bitmap covers bits for types 1 (A) and
2712        // 28 (AAAA). bit 1 in byte 0 → 0x40, bit 28 in byte 3 → 0x08.
2713        let bitmap = [0u8, 0, 0, 0x08, 0x40];
2714        // Actually build the bitmap dynamically for clarity.
2715        let mut bitmaps = Vec::new();
2716        bitmaps.push(0u8); // window block 0
2717        bitmaps.push(4u8); // bitmap length (covers bytes 0..4 → types 0..31)
2718        bitmaps.extend_from_slice(&[0x40, 0, 0, 0x08]); // A (1), AAAA (28)
2719        let _ = bitmap;
2720
2721        let mut rdata = Vec::new();
2722        rdata.extend_from_slice(&next_name);
2723        rdata.extend_from_slice(&bitmaps);
2724
2725        let mut data = header(0, 1, 0, 0);
2726        data.extend_from_slice(&wire_name("ex.test"));
2727        data.extend_from_slice(&TYPE_NSEC.to_be_bytes());
2728        data.extend_from_slice(&1u16.to_be_bytes());
2729        data.extend_from_slice(&0u32.to_be_bytes());
2730        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2731        data.extend_from_slice(&rdata);
2732
2733        let mut b = buf();
2734        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2735        let layer = &b.layers()[0];
2736        let answers = b.field_by_name(layer, "answers").unwrap();
2737        let rr = first_array_entry(&b, answers);
2738        assert_eq!(
2739            find_child(&b, rr, "rdata_next_domain_name").unwrap().value,
2740            FieldValue::Bytes(&next_name)
2741        );
2742        assert_eq!(
2743            find_child(&b, rr, "rdata_type_bitmaps").unwrap().value,
2744            FieldValue::Bytes(&bitmaps)
2745        );
2746    }
2747
2748    // ---- RFC 5155 §3.2 — NSEC3 ------------------------------------------
2749
2750    #[test]
2751    fn parse_nsec3_record() {
2752        let salt = [0xCAu8, 0xFE];
2753        let next_hash = [0x11u8; 20];
2754        let bitmaps = [0u8, 1u8, 0x40u8]; // window 0, length 1, bit 1 (A)
2755        let mut rdata = Vec::new();
2756        rdata.push(1); // hash algorithm = SHA-1
2757        rdata.push(0); // flags
2758        rdata.extend_from_slice(&10u16.to_be_bytes()); // iterations
2759        rdata.push(salt.len() as u8);
2760        rdata.extend_from_slice(&salt);
2761        rdata.push(next_hash.len() as u8);
2762        rdata.extend_from_slice(&next_hash);
2763        rdata.extend_from_slice(&bitmaps);
2764
2765        let mut data = header(0, 1, 0, 0);
2766        data.extend_from_slice(&wire_name("ex.test"));
2767        data.extend_from_slice(&TYPE_NSEC3.to_be_bytes());
2768        data.extend_from_slice(&1u16.to_be_bytes());
2769        data.extend_from_slice(&0u32.to_be_bytes());
2770        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2771        data.extend_from_slice(&rdata);
2772
2773        let mut b = buf();
2774        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2775        let layer = &b.layers()[0];
2776        let answers = b.field_by_name(layer, "answers").unwrap();
2777        let rr = first_array_entry(&b, answers);
2778        assert_eq!(
2779            find_child(&b, rr, "rdata_hash_algorithm").unwrap().value,
2780            FieldValue::U8(1)
2781        );
2782        assert_eq!(
2783            find_child(&b, rr, "rdata_iterations").unwrap().value,
2784            FieldValue::U16(10)
2785        );
2786        assert_eq!(
2787            find_child(&b, rr, "rdata_salt_length").unwrap().value,
2788            FieldValue::U8(2)
2789        );
2790        assert_eq!(
2791            find_child(&b, rr, "rdata_salt").unwrap().value,
2792            FieldValue::Bytes(&salt)
2793        );
2794        assert_eq!(
2795            find_child(&b, rr, "rdata_hash_length").unwrap().value,
2796            FieldValue::U8(20)
2797        );
2798        assert_eq!(
2799            find_child(&b, rr, "rdata_next_hashed_owner").unwrap().value,
2800            FieldValue::Bytes(&next_hash)
2801        );
2802        assert_eq!(
2803            find_child(&b, rr, "rdata_type_bitmaps").unwrap().value,
2804            FieldValue::Bytes(&bitmaps)
2805        );
2806    }
2807
2808    // ---- RFC 5155 §4.2 — NSEC3PARAM -------------------------------------
2809
2810    #[test]
2811    fn parse_nsec3param_record() {
2812        let salt = [0x01u8, 0x02, 0x03];
2813        let mut rdata = Vec::new();
2814        rdata.push(1); // hash algorithm
2815        rdata.push(0); // flags
2816        rdata.extend_from_slice(&5u16.to_be_bytes()); // iterations
2817        rdata.push(salt.len() as u8);
2818        rdata.extend_from_slice(&salt);
2819
2820        let mut data = header(0, 1, 0, 0);
2821        data.extend_from_slice(&wire_name("ex.test"));
2822        data.extend_from_slice(&TYPE_NSEC3PARAM.to_be_bytes());
2823        data.extend_from_slice(&1u16.to_be_bytes());
2824        data.extend_from_slice(&0u32.to_be_bytes());
2825        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2826        data.extend_from_slice(&rdata);
2827
2828        let mut b = buf();
2829        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2830        let layer = &b.layers()[0];
2831        let answers = b.field_by_name(layer, "answers").unwrap();
2832        let rr = first_array_entry(&b, answers);
2833        assert_eq!(
2834            find_child(&b, rr, "rdata_hash_algorithm").unwrap().value,
2835            FieldValue::U8(1)
2836        );
2837        assert_eq!(
2838            find_child(&b, rr, "rdata_iterations").unwrap().value,
2839            FieldValue::U16(5)
2840        );
2841        assert_eq!(
2842            find_child(&b, rr, "rdata_salt").unwrap().value,
2843            FieldValue::Bytes(&salt)
2844        );
2845    }
2846
2847    // ---- RFC 8659 §4.1 — CAA --------------------------------------------
2848
2849    #[test]
2850    fn parse_caa_record() {
2851        // Flags = 0x80 (Issuer Critical), tag = "issue", value = "ca.example.net".
2852        let tag = b"issue";
2853        let value = b"ca.example.net";
2854        let mut rdata = Vec::new();
2855        rdata.push(0x80); // critical flag
2856        rdata.push(tag.len() as u8);
2857        rdata.extend_from_slice(tag);
2858        rdata.extend_from_slice(value);
2859
2860        let mut data = header(0, 1, 0, 0);
2861        data.extend_from_slice(&wire_name("ex.test"));
2862        data.extend_from_slice(&TYPE_CAA.to_be_bytes());
2863        data.extend_from_slice(&1u16.to_be_bytes());
2864        data.extend_from_slice(&0u32.to_be_bytes());
2865        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2866        // Remember absolute offset of the RDATA for range verification.
2867        let rdata_abs = data.len();
2868        data.extend_from_slice(&rdata);
2869
2870        let mut b = buf();
2871        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2872        let layer = &b.layers()[0];
2873        let answers = b.field_by_name(layer, "answers").unwrap();
2874        let rr = first_array_entry(&b, answers);
2875
2876        assert_eq!(
2877            find_child(&b, rr, "rdata_flags").unwrap().value,
2878            FieldValue::U8(0x80)
2879        );
2880
2881        // Tag value and range. Range MUST cover exactly the tag bytes
2882        // (rdata[2..2+tag_len]) per RFC 8659 §4.1, not include the length
2883        // byte at offset 1.
2884        let tag_field = find_child(&b, rr, "rdata_tag").unwrap();
2885        assert_eq!(tag_field.value, FieldValue::Bytes(tag));
2886        assert_eq!(
2887            tag_field.range,
2888            (rdata_abs + 2)..(rdata_abs + 2 + tag.len())
2889        );
2890        assert_eq!(tag_field.range.len(), tag.len());
2891
2892        let value_field = find_child(&b, rr, "rdata_value").unwrap();
2893        assert_eq!(value_field.value, FieldValue::Bytes(value));
2894        assert_eq!(
2895            value_field.range,
2896            (rdata_abs + 2 + tag.len())..(rdata_abs + 2 + tag.len() + value.len())
2897        );
2898    }
2899
2900    // ---- RFC 9460 — SVCB / HTTPS -----------------------------------------
2901
2902    #[test]
2903    fn parse_svcb_record() {
2904        // ServiceMode priority=1, target=svc.ex.test, empty SvcParams.
2905        let target = wire_name("svc.ex.test");
2906        let mut rdata = Vec::new();
2907        rdata.extend_from_slice(&1u16.to_be_bytes()); // priority
2908        rdata.extend_from_slice(&target);
2909        // No SvcParams.
2910
2911        let mut data = header(0, 1, 0, 0);
2912        data.extend_from_slice(&wire_name("ex.test"));
2913        data.extend_from_slice(&TYPE_HTTPS.to_be_bytes());
2914        data.extend_from_slice(&1u16.to_be_bytes());
2915        data.extend_from_slice(&0u32.to_be_bytes());
2916        data.extend_from_slice(&(rdata.len() as u16).to_be_bytes());
2917        data.extend_from_slice(&rdata);
2918
2919        let mut b = buf();
2920        DnsDissector.dissect(&data, &mut b, 0).unwrap();
2921        let layer = &b.layers()[0];
2922        let answers = b.field_by_name(layer, "answers").unwrap();
2923        let rr = first_array_entry(&b, answers);
2924        assert_eq!(
2925            find_child(&b, rr, "rdata_priority").unwrap().value,
2926            FieldValue::U16(1)
2927        );
2928        assert_eq!(
2929            find_child(&b, rr, "rdata_target").unwrap().value,
2930            FieldValue::Bytes(&target)
2931        );
2932    }
2933
2934    // ---- RFC 1035 §2.3.4 / §3.1 / §4.1.4 — name limits ------------------
2935
2936    #[test]
2937    fn reject_name_over_255_octets() {
2938        // Build a 256-octet name (just exceeds the 255 limit) by stringing
2939        // together labels of 63 bytes + 63 + 63 + 62 + terminator.
2940        let label_63: Vec<u8> = core::iter::once(63u8)
2941            .chain(std::iter::repeat_n(b'a', 63))
2942            .collect();
2943        let label_62: Vec<u8> = core::iter::once(62u8)
2944            .chain(std::iter::repeat_n(b'a', 62))
2945            .collect();
2946        let mut name = Vec::new();
2947        name.extend_from_slice(&label_63);
2948        name.extend_from_slice(&label_63);
2949        name.extend_from_slice(&label_63);
2950        name.extend_from_slice(&label_62);
2951        name.push(0);
2952        assert_eq!(name.len(), 64 * 3 + 63 + 1); // 256
2953
2954        let mut data = header(1, 0, 0, 0);
2955        data.extend_from_slice(&name);
2956        data.extend_from_slice(&1u16.to_be_bytes()); // QTYPE
2957        data.extend_from_slice(&1u16.to_be_bytes()); // QCLASS
2958
2959        let mut b = buf();
2960        let err = DnsDissector.dissect(&data, &mut b, 0).unwrap_err();
2961        assert!(matches!(err, PacketError::InvalidHeader(_)));
2962    }
2963
2964    #[test]
2965    fn reject_name_pointer_loop() {
2966        // Construct a QNAME that is a single 2-byte pointer referencing itself.
2967        // Pointer at offset 12 (== HEADER_SIZE) points back to offset 12.
2968        let mut data = header(1, 0, 0, 0);
2969        data.push(0xC0);
2970        data.push(HEADER_SIZE as u8); // pointer to self
2971        data.extend_from_slice(&1u16.to_be_bytes());
2972        data.extend_from_slice(&1u16.to_be_bytes());
2973
2974        let mut b = buf();
2975        let err = DnsDissector.dissect(&data, &mut b, 0).unwrap_err();
2976        assert!(matches!(err, PacketError::InvalidHeader(_)));
2977    }
2978
2979    #[test]
2980    fn reject_reserved_label_type() {
2981        // 01 / 10 label-type prefixes are reserved (RFC 1035 §4.1.4).
2982        let mut data = header(1, 0, 0, 0);
2983        data.push(0x80); // starts with bits 10 — reserved
2984        data.extend_from_slice(&1u16.to_be_bytes());
2985        data.extend_from_slice(&1u16.to_be_bytes());
2986
2987        let mut b = buf();
2988        let err = DnsDissector.dissect(&data, &mut b, 0).unwrap_err();
2989        assert!(matches!(err, PacketError::InvalidHeader(_)));
2990    }
2991
2992    // ---- RFC 1035 §4.2.2 / RFC 7766 §8 — TCP length prefix ---------------
2993
2994    #[test]
2995    fn parse_tcp_length_prefix() {
2996        let mut dns = header(1, 0, 0, 0);
2997        dns.extend_from_slice(&wire_name("ex.test"));
2998        dns.extend_from_slice(&1u16.to_be_bytes()); // QTYPE=A
2999        dns.extend_from_slice(&1u16.to_be_bytes()); // QCLASS=IN
3000        let mut framed = Vec::new();
3001        framed.extend_from_slice(&(dns.len() as u16).to_be_bytes());
3002        framed.extend_from_slice(&dns);
3003
3004        let mut b = buf();
3005        let res = DnsTcpDissector.dissect(&framed, &mut b, 0).unwrap();
3006        assert_eq!(res.bytes_consumed, framed.len());
3007
3008        let layer = &b.layers()[0];
3009        assert_eq!(layer.name, "DNS");
3010        let tcp_len = b.field_by_name(layer, "tcp_length").unwrap();
3011        assert_eq!(tcp_len.value, FieldValue::U16(dns.len() as u16));
3012        assert_eq!(tcp_len.range, 0..2);
3013    }
3014
3015    #[test]
3016    fn tcp_truncated_length_prefix() {
3017        let mut b = buf();
3018        let err = DnsTcpDissector.dissect(&[0u8], &mut b, 0).unwrap_err();
3019        assert!(matches!(err, PacketError::Truncated { .. }));
3020    }
3021
3022    // ---- Name lookup helpers --------------------------------------------
3023
3024    #[test]
3025    fn type_class_opcode_rcode_names() {
3026        assert_eq!(dns_type_name(TYPE_A), Some("A"));
3027        assert_eq!(dns_type_name(TYPE_AAAA), Some("AAAA"));
3028        assert_eq!(dns_type_name(TYPE_OPT), Some("OPT"));
3029        assert_eq!(dns_type_name(TYPE_HTTPS), Some("HTTPS"));
3030        assert_eq!(dns_type_name(9999), None);
3031
3032        assert_eq!(dns_class_name(1), Some("IN"));
3033        assert_eq!(dns_class_name(255), Some("ANY"));
3034        assert_eq!(dns_class_name(7), None);
3035
3036        assert_eq!(dns_opcode_name(0), Some("QUERY"));
3037        assert_eq!(dns_opcode_name(5), Some("UPDATE"));
3038        assert_eq!(dns_opcode_name(15), None);
3039
3040        assert_eq!(dns_rcode_name(0), Some("NOERROR"));
3041        assert_eq!(dns_rcode_name(3), Some("NXDOMAIN"));
3042        assert_eq!(dns_rcode_name(15), None);
3043
3044        assert_eq!(edns_option_code_name(10), Some("COOKIE"));
3045        assert_eq!(
3046            edns_option_code_name(EDNS_OPT_TCP_KEEPALIVE),
3047            Some("TCP-KEEPALIVE")
3048        );
3049        assert_eq!(edns_option_code_name(9999), None);
3050    }
3051
3052    #[test]
3053    fn dispatch_hint_is_end() {
3054        // DNS is terminal: dispatch key must be End.
3055        let mut data = header(0, 0, 0, 0);
3056        data.extend_from_slice(&[]);
3057        let mut b = buf();
3058        let res = DnsDissector.dissect(&data, &mut b, 0).unwrap();
3059        assert!(matches!(res.next, DispatchHint::End));
3060    }
3061
3062    #[test]
3063    fn write_dns_name_formats_output() {
3064        // Build a minimal DNS message where the QNAME is a single compressed
3065        // pointer to "example.com." stored earlier in the buffer.
3066        let target = wire_name("example.com");
3067        let mut data = Vec::new();
3068        data.extend_from_slice(&[0xBE, 0xEF, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]);
3069        // Place target name right after the header to make pointer offsets easy.
3070        let name_off = data.len();
3071        data.extend_from_slice(&target);
3072        data.extend_from_slice(&1u16.to_be_bytes()); // QTYPE
3073        data.extend_from_slice(&1u16.to_be_bytes()); // QCLASS
3074
3075        let ctx = FormatContext {
3076            packet_data: &data,
3077            scratch: &[],
3078            layer_range: 0..data.len() as u32,
3079            field_range: name_off as u32..(name_off + target.len()) as u32,
3080        };
3081        let mut out = Vec::new();
3082        write_dns_name(&FieldValue::Bytes(&target), &ctx, &mut out).unwrap();
3083        assert_eq!(&out, b"\"example.com\"");
3084    }
3085}