code wiki / (root) / nx_mdns_test.nx

nx_mdns_test.nx source

↩ module page · 118 lines · 5484 B

1// nx_mdns_test.nx -- offline gate for the mDNS / DNS-SD printer-discovery parser (nx_mdns). 2// 3// Hand-builds a realistic mDNS `_ipp._tcp` response -- PTR + SRV + TXT + A, with NAME-COMPRESSION POINTERS 4// on the SRV/TXT/A owner names (exactly what real printers emit) -- then proves nx_mdns extracts the printer 5// tuple. Unique exit codes per invariant: 6// 1-10 extract port=631 (SRV) + IPv4 192.168.8.127 (A) + TXT rp=ipp/print, through compression pointers 7// 20-24 query builder emits a well-formed _ipp._tcp.local PTR question 8// 30-31 truncated response -> sealed MALFORMED (NEVER a silent success) 9// 40 absent record type -> NOT_FOUND (no false hit) 10// expect_exit: 0 ; license_tier: ORIGINAL ; genealogy_id: project-printer-management-ipp-sclass-2026-06-20 11 12import "nx_syscalls.nx" 13import "nx_dns.nx" 14import "nx_mdns.nx" 15 16func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 17 18func ap_u16(b: *u8, o: i64, v: i64) -> i64 { return nx_dns_put_u16_be(b, o, v) } 19func ap_byte(b: *u8, o: i64, v: i64) -> i64 { b[o] = v as u8; return o + 1 } 20func ap_label(b: *u8, o: i64, s: *u8, slen: i64) -> i64 { 21 b[o] = slen as u8 22 var i: i64 = 0 23 while i < slen { b[o + 1 + i] = s[i]; i = i + 1 } 24 return o + 1 + slen 25} 26func ap_raw(b: *u8, o: i64, s: *u8, slen: i64) -> i64 { 27 var i: i64 = 0 28 while i < slen { b[o + i] = s[i]; i = i + 1 } 29 return o + slen 30} 31 32func main() -> i64 { 33 let b: *u8 = sys_mmap(512) 34 var o: i64 = 0 35 // ---- header: id=0, flags=0x8400 (QR=1, AA=1), qd=0, an=1 (PTR), ns=0, ar=3 (SRV,TXT,A) ---- 36 o = ap_u16(b, o, 0) 37 o = ap_u16(b, o, 0x8400) 38 o = ap_u16(b, o, 0) 39 o = ap_u16(b, o, 1) 40 o = ap_u16(b, o, 0) 41 o = ap_u16(b, o, 3) 42 // ---- service name `_ipp._tcp.local` at offset 12 (the PTR owner; compression pointers target this) ---- 43 o = ap_label(b, o, "_ipp" as *u8, 4) 44 o = ap_label(b, o, "_tcp" as *u8, 4) 45 o = ap_label(b, o, "local" as *u8, 5) 46 o = ap_byte(b, o, 0) 47 // PTR rr: type=12 class=IN ttl=4500 rdlen=8 rdata=[5]nishi + ptr->12 48 o = ap_u16(b, o, NX_DNS_TYPE_PTR) 49 o = ap_u16(b, o, NX_DNS_CLASS_IN) 50 o = ap_u16(b, o, 0); o = ap_u16(b, o, 4500) 51 o = ap_u16(b, o, 8) 52 o = ap_label(b, o, "nishi" as *u8, 5) 53 o = ap_byte(b, o, 0xc0); o = ap_byte(b, o, 12) 54 // SRV rr: owner=ptr->12 ; type=33 class=IN ttl=120 rdlen=7 rdata= prio0 weight0 port631 target=root 55 o = ap_byte(b, o, 0xc0); o = ap_byte(b, o, 12) 56 o = ap_u16(b, o, NX_DNSSD_TYPE_SRV) 57 o = ap_u16(b, o, NX_DNS_CLASS_IN) 58 o = ap_u16(b, o, 0); o = ap_u16(b, o, 120) 59 o = ap_u16(b, o, 7) 60 o = ap_u16(b, o, 0) 61 o = ap_u16(b, o, 0) 62 o = ap_u16(b, o, 631) 63 o = ap_byte(b, o, 0) 64 // TXT rr: owner=ptr->12 ; type=16 class=IN ttl=4500 rdlen=13 rdata=[12]"rp=ipp/print" 65 o = ap_byte(b, o, 0xc0); o = ap_byte(b, o, 12) 66 o = ap_u16(b, o, NX_DNS_TYPE_TXT) 67 o = ap_u16(b, o, NX_DNS_CLASS_IN) 68 o = ap_u16(b, o, 0); o = ap_u16(b, o, 4500) 69 o = ap_u16(b, o, 13) 70 o = ap_byte(b, o, 12) 71 o = ap_raw(b, o, "rp=ipp/print" as *u8, 12) 72 // A rr: owner=ptr->12 ; type=1 class=IN ttl=120 rdlen=4 rdata=192.168.8.127 73 o = ap_byte(b, o, 0xc0); o = ap_byte(b, o, 12) 74 o = ap_u16(b, o, NX_DNS_TYPE_A) 75 o = ap_u16(b, o, NX_DNS_CLASS_IN) 76 o = ap_u16(b, o, 0); o = ap_u16(b, o, 120) 77 o = ap_u16(b, o, 4) 78 o = ap_byte(b, o, 192); o = ap_byte(b, o, 168); o = ap_byte(b, o, 8); o = ap_byte(b, o, 127) 79 let total: i64 = o 80 81 // ===== KAT 1: extract the printer through the compression pointers ===== 82 let out5: *i64 = sys_mmap(64) as *i64 83 let rp: *u8 = sys_mmap(64) 84 let rplen: *i64 = sys_mmap(8) as *i64 85 let sc2: *i64 = sys_mmap(16) as *i64 86 if nx_mdns_extract_printer(b, total, out5, rp, 64, rplen, sc2) != NX_MDNS_OK { return 1 } 87 if out5[0] != 631 { return 2 } 88 if out5[1] != 192 { return 3 } 89 if out5[2] != 168 { return 4 } 90 if out5[3] != 8 { return 5 } 91 if out5[4] != 127 { return 6 } 92 if rplen[0] != 9 { return 7 } 93 if (rp[0] as i64) != 105 { return 8 } // 'i' 94 if (rp[3] as i64) != 47 { return 9 } // '/' in ipp/print (index 3) 95 if (rp[8] as i64) != 116 { return 10 } // 't' 96 97 // ===== KAT 2: query builder emits a well-formed _ipp._tcp.local PTR question ===== 98 let q: *u8 = sys_mmap(128) 99 let qlen: i64 = nx_mdns_build_ipp_query(q, 128, 0) 100 if qlen < 0 { return 20 } 101 if nx_dns_get_u16_be(q, 4) != 1 { return 21 } // QDCOUNT=1 102 if (q[12] as i64) != 4 { return 22 } // first label length "_ipp" 103 if (q[13] as i64) != 95 { return 23 } // '_' 104 if nx_dns_get_u16_be(q, 29) != NX_DNS_TYPE_PTR { return 24 } // QTYPE=PTR after the 17-byte qname 105 106 // ===== KAT 3: truncated response -> sealed MALFORMED (never a silent success) ===== 107 let v2: i64 = nx_mdns_extract_printer(b, 20, out5, rp, 64, rplen, sc2) 108 if v2 == NX_MDNS_OK { return 30 } 109 if nx_mdns_verdict_is_valid(v2) != 1 { return 31 } 110 111 // ===== KAT 4: absent record type -> NOT_FOUND (no false hit) ===== 112 let rdo: *i64 = sys_mmap(8) as *i64 113 let rdl: *i64 = sys_mmap(8) as *i64 114 if nx_mdns_find_rr(b, total, NX_DNS_TYPE_MX, rdo, rdl) != NX_MDNS_NOT_FOUND { return 40 } 115 116 t_puts("nx_mdns: PASS DNS-SD _ipp._tcp -> SRV port=631 + A 192.168.8.127 + TXT rp=ipp/print (through name compression); PTR query built; truncated->sealed MALFORMED; absent-type->NOT_FOUND\n") 117 return 0 118}