code wiki / (root) / nx_mdns.nx

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}