nx_mdns.nx source
↩ module page · 143 lines · 6547 B
1// nx_mdns.nx -- sovereign mDNS / DNS-SD service discovery for IPP printers (RFC 6762 + RFC 6763): the R3
2// "find ANY printer on the LAN" rung. REUSES the proven RFC-1035 substrate nx_dns.nx (bounded, compression-
3// safe name handling -- the hard part, with a 128-hop anti-cycle guard) and adds the DNS-SD layer: from a
4// `_ipp._tcp` response, extract a printer's SRV port, A-record IPv4, and the TXT "rp" (resource path) --
5// exactly the (ip, port, rp) tuple nx_printer_query needs to then talk IPP. PURE parser (no syscalls; the
6// caller owns every buffer) -> never-brick #26 + architecture-neutral, and no_silent_failure (a malformed
7// message yields a sealed MALFORMED verdict, never a false success).
8//
9// LIVE NOTE (honest): mDNS is multicast (224.0.0.251:5353); WSL's NAT does NOT forward multicast to the
10// 192.168.8 LAN, so LIVE discovery needs mirrored networking / a Windows-native radio runner. The parser +
11// the query builder are gateable OFFLINE now (this file); the live send/recv composes nx_udp later.
12//
13// genealogy_id: project-printer-management-ipp-sclass-2026-06-20 ; license_tier: ORIGINAL
14// reuses: nx_dns.nx (international-research-sources/ietf/rfc_1035 + rfc_6763 DNS-SD)
15
16import "nx_dns.nx"
17
18const NX_DNSSD_TYPE_SRV: i64 = 33 // RFC 2782 SRV (not in nx_dns.nx's type table)
19
20const NX_MDNS_OK: i64 = 1
21const NX_MDNS_NOT_FOUND: i64 = 2
22const NX_MDNS_MALFORMED: i64 = 3
23
24func nx_mdns_verdict_is_valid(v: i64) -> i64 {
25 if v < 1 { return 0 }
26 if v > NX_MDNS_MALFORMED { return 0 }
27 return 1
28}
29
30// Find the FIRST resource record of want_type across the answer+authority+additional sections. Writes the
31// RDATA offset + length through out_rdoff/out_rdlen. Every name is skipped via nx_dns_skip_name (compression-
32// safe, 128-hop anti-cycle); every length is bounds-checked against n. Verdict OK / NOT_FOUND / MALFORMED.
33func nx_mdns_find_rr(buf: *u8, n: i64, want_type: i64, out_rdoff: *i64, out_rdlen: *i64) -> i64 {
34 if n < NX_DNS_HEADER_LEN { return NX_MDNS_MALFORMED }
35 let qd: i64 = nx_dns_get_u16_be(buf, 4)
36 let an: i64 = nx_dns_get_u16_be(buf, 6)
37 let ns: i64 = nx_dns_get_u16_be(buf, 8)
38 let ar: i64 = nx_dns_get_u16_be(buf, 10)
39 if qd > NX_DNS_MAX_RECORDS { return NX_MDNS_MALFORMED }
40 let total: i64 = an + ns + ar
41 if total > NX_DNS_MAX_RECORDS { return NX_MDNS_MALFORMED }
42 var o: i64 = NX_DNS_HEADER_LEN
43 var qi: i64 = 0
44 while qi < qd {
45 let e: i64 = nx_dns_skip_name(buf, n, o)
46 if e < 0 { return NX_MDNS_MALFORMED }
47 if e + 4 > n { return NX_MDNS_MALFORMED }
48 o = e + 4
49 qi = qi + 1
50 }
51 var ri: i64 = 0
52 while ri < total {
53 let e2: i64 = nx_dns_skip_name(buf, n, o)
54 if e2 < 0 { return NX_MDNS_MALFORMED }
55 if e2 + 10 > n { return NX_MDNS_MALFORMED }
56 let rtype: i64 = nx_dns_get_u16_be(buf, e2)
57 let rdlen: i64 = nx_dns_get_u16_be(buf, e2 + 8)
58 let rdoff: i64 = e2 + 10
59 if rdoff + rdlen > n { return NX_MDNS_MALFORMED }
60 if rtype == want_type {
61 out_rdoff[0] = rdoff
62 out_rdlen[0] = rdlen
63 return NX_MDNS_OK
64 }
65 o = rdoff + rdlen
66 ri = ri + 1
67 }
68 return NX_MDNS_NOT_FOUND
69}
70
71// SRV rdata = priority(2) weight(2) port(2) target-name. Extract the port.
72func nx_mdns_srv_port(buf: *u8, rdoff: i64, rdlen: i64, out_port: *i64) -> i64 {
73 if rdlen < 7 { return NX_MDNS_MALFORMED }
74 out_port[0] = nx_dns_get_u16_be(buf, rdoff + 4)
75 return NX_MDNS_OK
76}
77
78// A rdata = 4-byte IPv4 -> out4[0..3].
79func nx_mdns_a_ip(buf: *u8, rdoff: i64, rdlen: i64, out4: *i64) -> i64 {
80 if rdlen != 4 { return NX_MDNS_MALFORMED }
81 out4[0] = buf[rdoff] as i64
82 out4[1] = buf[rdoff + 1] as i64
83 out4[2] = buf[rdoff + 2] as i64
84 out4[3] = buf[rdoff + 3] as i64
85 return NX_MDNS_OK
86}
87
88// TXT rdata = a sequence of <len><string>. Find the string "key=value" and copy value (<= cap) to out.
89func nx_mdns_txt_get(buf: *u8, rdoff: i64, rdlen: i64, key: *u8, klen: i64, out: *u8, cap: i64, out_len: *i64) -> i64 {
90 var o: i64 = rdoff
91 let lim: i64 = rdoff + rdlen
92 var keep: i64 = 1
93 var verdict: i64 = NX_MDNS_NOT_FOUND
94 while keep == 1 {
95 if o >= lim { keep = 0 }
96 else {
97 let slen: i64 = buf[o] as i64
98 o = o + 1
99 if o + slen > lim { keep = 0; verdict = NX_MDNS_MALFORMED }
100 else {
101 var hit: i64 = 0
102 if slen > klen {
103 var same: i64 = 1
104 var i: i64 = 0
105 while i < klen { if (buf[o + i] as i64) != (key[i] as i64) { same = 0; i = klen } else { i = i + 1 } }
106 if same == 1 { if (buf[o + klen] as i64) == 61 { hit = 1 } }
107 }
108 if hit == 1 {
109 var w: i64 = 0
110 var j: i64 = klen + 1
111 while j < slen { if w < cap { out[w] = buf[o + j]; w = w + 1 } j = j + 1 }
112 out_len[0] = w
113 keep = 0
114 verdict = NX_MDNS_OK
115 } else { o = o + slen }
116 }
117 }
118 }
119 return verdict
120}
121
122// High-level: extract {port, IPv4} + TXT "rp" from an mDNS `_ipp._tcp` response. out5[0]=port, out5[1..4]=ip.
123// scratch2 = a caller-provided i64[2] work area. Verdict OK / NOT_FOUND / MALFORMED (never a silent success).
124func nx_mdns_extract_printer(buf: *u8, n: i64, out5: *i64, rp_out: *u8, rp_cap: i64, rp_len: *i64, scratch2: *i64) -> i64 {
125 let rdoff: *i64 = scratch2
126 let rdlen: *i64 = ((scratch2 as i64) + 8) as *i64
127 let sv: i64 = nx_mdns_find_rr(buf, n, NX_DNSSD_TYPE_SRV, rdoff, rdlen)
128 if sv != NX_MDNS_OK { return sv }
129 let pv: i64 = nx_mdns_srv_port(buf, rdoff[0], rdlen[0], out5)
130 if pv != NX_MDNS_OK { return pv }
131 let av: i64 = nx_mdns_find_rr(buf, n, NX_DNS_TYPE_A, rdoff, rdlen)
132 if av != NX_MDNS_OK { return av }
133 let iv: i64 = nx_mdns_a_ip(buf, rdoff[0], rdlen[0], ((out5 as i64) + 8) as *i64)
134 if iv != NX_MDNS_OK { return iv }
135 let tv: i64 = nx_mdns_find_rr(buf, n, NX_DNS_TYPE_TXT, rdoff, rdlen)
136 if tv != NX_MDNS_OK { return tv }
137 return nx_mdns_txt_get(buf, rdoff[0], rdlen[0], "rp" as *u8, 2, rp_out, rp_cap, rp_len)
138}
139
140// Build an mDNS PTR query for "_ipp._tcp.local" (the discovery question). Returns length or a negative verdict.
141func nx_mdns_build_ipp_query(out: *u8, out_cap: i64, tx_id: i64) -> i64 {
142 return nx_dns_build_query("_ipp._tcp.local" as *u8, 15, NX_DNS_TYPE_PTR, tx_id, out, out_cap)
143}