Skip to main content

wireforge_app/mdns/
mod.rs

1//! mDNS (Multicast DNS) parser — RFC 6762.
2//!
3//! mDNS shares the DNS wire format but uses the top bit of qclass for
4//! the unicast-response flag and the cache-flush flag in resource records.
5
6use crate::dns::parser::{DnsPacket, DnsQuestionIter, DnsRecordIter};
7
8/// mDNS packet wrapper. Internally delegates to [`DnsPacket`] since
9/// mDNS uses the identical DNS wire format (RFC 6762 §18).
10#[derive(Debug, Clone)]
11pub struct MdnsPacket<'a> {
12    dns: DnsPacket<'a>,
13}
14
15impl<'a> MdnsPacket<'a> {
16    pub fn new(buf: &'a [u8]) -> Option<Self> {
17        DnsPacket::new(buf).map(|dns| Self { dns })
18    }
19
20    pub fn is_query(&self) -> bool { !self.dns.qr() }
21    pub fn is_response(&self) -> bool { self.dns.qr() }
22
23    /// The unicast-response (QU) bit in the first question's class field.
24    /// When set, responders should send a unicast reply (RFC 6762 §5.4).
25    pub fn unicast_response_requested(&self) -> bool {
26        self.dns.questions().next()
27            .map(|q| u16::from(q.qclass) & 0x8000 != 0)
28            .unwrap_or(false)
29    }
30
31    pub fn questions(&self) -> DnsQuestionIter<'a> { self.dns.questions() }
32    pub fn answers(&self) -> DnsRecordIter<'a> { self.dns.answers() }
33    pub fn authorities(&self) -> DnsRecordIter<'a> { self.dns.authorities() }
34    pub fn additionals(&self) -> DnsRecordIter<'a> { self.dns.additionals() }
35}
36
37/// Check whether the cache-flush bit is set in a record class field
38/// (RFC 6762 §10.2). Top bit of the 16-bit class means "flush cache."
39#[inline]
40pub fn is_cache_flush(class: u16) -> bool {
41    class & 0x8000 != 0
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::dns::builder::DnsPacketBuilder;
48    use crate::dns::types::{DnsClass, DnsType};
49
50    #[test]
51    fn parse_mdns_query() {
52        let q = DnsPacketBuilder::query()
53            .add_question("_http._tcp.local", DnsType::PTR, DnsClass::IN)
54            .build();
55        let p = MdnsPacket::new(&q).unwrap();
56        assert!(p.is_query());
57        assert!(!p.unicast_response_requested());
58        let qs: Vec<_> = p.questions().collect();
59        assert_eq!(qs.len(), 1);
60    }
61
62    #[test]
63    fn parse_mdns_response() {
64        let r = DnsPacketBuilder::response()
65            .add_question("printer.local", DnsType::A, DnsClass::IN)
66            .add_raw_answer("printer.local", DnsType::A, 120, &[192, 168, 1, 100])
67            .build();
68        let p = MdnsPacket::new(&r).unwrap();
69        assert!(p.is_response());
70    }
71
72    #[test]
73    fn unicast_response_bit() {
74        // mDNS queries can set the QU bit (0x8000 in class field)
75        // Build a query with QU bit set via raw class value
76        let qu_class = DnsClass::Unknown(0x8001); // IN(1) with QU bit
77        let q = DnsPacketBuilder::query()
78            .add_question("test.local", DnsType::A, qu_class)
79            .build();
80        let p = MdnsPacket::new(&q).unwrap();
81        assert!(p.unicast_response_requested());
82    }
83}