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}