code wiki / _hdl_build / nx_iot_ctl_gate.nx

nx_iot_ctl_gate.nx source

↩ module page · 316 lines · 14360 B

1// nx_iot_ctl_gate.nx -- gate for the sovereign home-electronics organ. 2// 3// Every tooth is designed to FAIL if the claim is false, not to co-sign it: 4// 5// T1 manifest defaults load, and ORDER IS PRECEDENCE (kasa:9999 outranks 6// http:80) -- the exact inversion that makes the live survey call a 7// smart plug a "web-device" 8// T2 conf parsing is real: a conf image REPLACES the defaults, with the 9// parsed values readable back (not merely "a file was opened") 10// T3 fail-safe: a MALFORMED / absent conf degrades to defaults, never to 11// an empty manifest (an organ that silently sees nothing is worse than 12// one that sees the default set) 13// T4 the UDP blind spot is DECLARED: wiz/38899 is present in the manifest, 14// marked udp, and excluded from the swept set 15// T5 NEVER-BRICK: no write verb exists; on/off/set/flash/provision/gcode 16// all resolve to BAD, and the verb map has exactly 4 read-only entries 17// T6 REAL loopback sweep: a live listener on 127.0.0.1 is found through the 18// SHARED lscan_sweep primitive (the same bytes nx_printer_ctl runs) 19// T7 REAL end-to-end classification: a fork'd fake Kasa device on loopback 20// is swept, and the emitted JSON says smart-plug + kasa -- NOT web-device 21// T8 REAL Kasa probe roundtrip: framed+autokey-encrypted get_sysinfo over 22// the kernel TCP stack, reply decrypted, vendor decided by REPLY signal 23// T9 NEGATIVE CONTROL for T7/T8: an HTTP-only listener must NOT be called a 24// kasa plug (a classifier that says "kasa" to everything passes T7) 25// 26// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 27import "nx_syscalls.nx" 28import "nx_tls13_io.nx" 29import "nx_lan_scan.nx" 30import "nx_iot_ctl_lib.nx" 31import "nx_gate_verdict.nx" 32 33func ig_contains(buf: *u8, len: i64, needle: *u8) -> i64 { 34 var nlen: i64 = 0 35 while needle[nlen] != (0 as u8) { nlen = nlen + 1 } 36 if nlen == 0 { return 1 } 37 if len < nlen { return 0 } 38 var i: i64 = 0 39 while i <= len - nlen { 40 var j: i64 = 0 41 var matched: i64 = 1 42 while j < nlen { 43 if buf[i + j] != needle[j] { matched = 0; j = nlen } 44 j = j + 1 45 } 46 if matched == 1 { return 1 } 47 i = i + 1 48 } 49 return 0 50} 51 52// A fake Kasa device: accept one connection, read the framed request, reply 53// with a framed + autokey-encrypted sysinfo body (real bytes, real cipher). 54func ig_fake_kasa(listen_fd: i64) -> i64 { 55 let cfd: i64 = sys_accept(listen_fd) 56 if cfd < 0 { return 0 } 57 let req: *u8 = sys_mmap(4096) 58 sys_read(cfd, req, 4096) 59 let body: *u8 = "{\"system\":{\"get_sysinfo\":{\"sw_ver\":\"1.0.6\",\"model\":\"HS210(US)\",\"relay_state\":1,\"mic_type\":\"IOT.SMARTPLUGSWITCH\"}}}" 60 var bn: i64 = 0 61 while body[bn] != (0 as u8) { bn = bn + 1 } 62 let frame: *u8 = sys_mmap(4096) 63 let vp: *i64 = sys_mmap(8) as *i64 64 vp[0] = 0 65 let fn2: i64 = nx_iot_kasa_frame_tcp(body, bn, frame, 4096, vp) 66 if fn2 > 0 { sys_write(cfd, frame, fn2) } 67 sys_close(cfd) 68 return 1 69} 70 71func main() -> i64 { 72 let ctr: *i64 = gv_ctr() 73 gv_head("nx_iot_ctl_gate -- sovereign home-electronics identification (data-driven manifest, declared blind spots, real loopback)" as *u8) 74 75 // ---- T1: defaults + manifest order IS precedence ---- 76 let m1: *u8 = iotm_new() 77 iotm_defaults(m1) 78 var t1: i64 = 0 79 if iotm_count(m1) > 6 { 80 // kasa:9999 must sit at a LOWER index than http:80, else a plug that 81 // also serves a web UI gets called a web-device (the live defect). 82 var i_kasa: i64 = 0 - 1 83 var i_http: i64 = 0 - 1 84 var i: i64 = 0 85 while i < iotm_count(m1) { 86 if iotm_port(m1, i) == 9999 { if i_kasa < 0 { i_kasa = i } } 87 if iotm_port(m1, i) == 80 { if i_http < 0 { i_http = i } } 88 i = i + 1 89 } 90 if i_kasa >= 0 { 91 if i_http > i_kasa { 92 // and the resolver must actually honour that order 93 let bits: i64 = (1 << i_kasa) | (1 << i_http) 94 if iotc_mask_row(m1, bits) == i_kasa { 95 if iotc_streq(iotc_kind_name(iotm_kind(m1, i_kasa)), "smart-plug" as *u8) == 1 { t1 = 1 } 96 } 97 } 98 } 99 } 100 gv_check("T1 defaults load; manifest ORDER=precedence (kasa:9999 outranks http:80 -> smart-plug not web-device)" as *u8, t1, ctr) 101 102 // ---- T2: conf parsing REPLACES defaults, values read back ---- 103 let m2: *u8 = iotm_new() 104 let conf: *u8 = "# comment line\n4711 tcp yeelight bulb lab-bulb\n1234 udp wiz bulb lab-wiz\n" 105 var cn: i64 = 0 106 while conf[cn] != (0 as u8) { cn = cn + 1 } 107 let got2: i64 = iotm_parse(m2, conf, cn) 108 var t2: i64 = 0 109 if got2 == 2 { 110 if iotm_count(m2) == 2 { 111 if iotm_port(m2, 0) == 4711 { 112 if iotm_proto(m2, 0) == IOTC_PROTO_TCP { 113 if iotm_vendor(m2, 0) == NX_IOT_VENDOR_YEELIGHT { 114 if iotm_kind(m2, 0) == IOTC_KIND_BULB { 115 if iotc_streq(iotm_label(m2, 0), "lab-bulb" as *u8) == 1 { 116 if iotm_port(m2, 1) == 1234 { 117 if iotm_proto(m2, 1) == IOTC_PROTO_UDP { t2 = 1 } 118 } 119 } 120 } 121 } 122 } 123 } 124 } 125 } 126 gv_check("T2 conf rows parse + read back (port/proto/vendor/kind/label); '#' comment skipped" as *u8, t2, ctr) 127 128 // ---- T3: malformed conf -> defaults, never an empty manifest ---- 129 let m3: *u8 = iotm_new() 130 let junk: *u8 = "not a row at all\n### \nzzz qqq\n" 131 var jn: i64 = 0 132 while junk[jn] != (0 as u8) { jn = jn + 1 } 133 let got3: i64 = iotm_parse(m3, junk, jn) 134 var t3: i64 = 0 135 if got3 == 0 { 136 if iotm_count(m3) == 0 { 137 // this is the path iotm_load takes: no usable rows -> defaults 138 iotm_defaults(m3) 139 if iotm_count(m3) > 6 { t3 = 1 } 140 } 141 } 142 gv_check("T3 fail-safe: malformed conf yields 0 rows -> compiled defaults, never a blind empty manifest" as *u8, t3, ctr) 143 144 // ---- T4: the UDP blind spot is declared, not hidden ---- 145 var t4: i64 = 0 146 var wiz_i: i64 = 0 - 1 147 var k: i64 = 0 148 while k < iotm_count(m1) { 149 if iotm_port(m1, k) == 38899 { if wiz_i < 0 { wiz_i = k } } 150 k = k + 1 151 } 152 if wiz_i >= 0 { 153 if iotm_proto(m1, wiz_i) == IOTC_PROTO_UDP { 154 if iotm_vendor(m1, wiz_i) == NX_IOT_VENDOR_WIZ { 155 // and a scan emission must name it as unswept 156 let mask4: *i64 = sys_mmap(256 * 8) as *i64 157 let out4: *u8 = sys_mmap(IOTC_OUT) 158 let n4: i64 = iotc_emit_scan(out4, m1, "10.0.0" as *u8, 0, mask4, 0) 159 if ig_contains(out4, n4, "\"udp_ports_unswept\":[38899]" as *u8) == 1 { 160 if ig_contains(out4, n4, "NOT evidence of absence" as *u8) == 1 { t4 = 1 } 161 } 162 } 163 } 164 } 165 gv_check("T4 blind spot DECLARED: wiz/38899 is udp, excluded from the sweep, and named in udp_ports_unswept" as *u8, t4, ctr) 166 167 // ---- T5: NEVER-BRICK -- no write verb is reachable ---- 168 var t5: i64 = 1 169 if iotc_verb_id("on" as *u8) != IOTC_V_BAD { t5 = 0 } 170 if iotc_verb_id("off" as *u8) != IOTC_V_BAD { t5 = 0 } 171 if iotc_verb_id("set" as *u8) != IOTC_V_BAD { t5 = 0 } 172 if iotc_verb_id("flash" as *u8) != IOTC_V_BAD { t5 = 0 } 173 if iotc_verb_id("ota" as *u8) != IOTC_V_BAD { t5 = 0 } 174 if iotc_verb_id("provision" as *u8) != IOTC_V_BAD { t5 = 0 } 175 if iotc_verb_id("set_stainfo" as *u8) != IOTC_V_BAD { t5 = 0 } 176 if iotc_verb_id("gcode" as *u8) != IOTC_V_BAD { t5 = 0 } 177 if iotc_verb_id("reboot" as *u8) != IOTC_V_BAD { t5 = 0 } 178 // and the four read verbs DO resolve (a map that rejects everything also 179 // passes the negatives above -- this is the non-vacuity control) 180 if iotc_verb_id("services" as *u8) != IOTC_V_SERVICES { t5 = 0 } 181 if iotc_verb_id("scan" as *u8) != IOTC_V_SCAN { t5 = 0 } 182 if iotc_verb_id("probe" as *u8) != IOTC_V_PROBE { t5 = 0 } 183 if iotc_verb_id("contract" as *u8) != IOTC_V_CONTRACT { t5 = 0 } 184 gv_check("T5 never-brick: no write/flash/provision verb resolves; the 4 read verbs do (non-vacuous)" as *u8, t5, ctr) 185 186 // ---- T6: the SHARED sweep finds a real listener ---- 187 var t6: i64 = 0 188 let lfd6: i64 = tcp_listen_loopback(19311) 189 if lfd6 >= 0 { 190 let base: i64 = lscan_parse_prefix("127.0.0" as *u8) 191 let ips: *i64 = sys_mmap(LSCAN_HOSTS * 8) as *i64 192 let cnt: i64 = lscan_sweep(base, 19311, 120, ips, LSCAN_HOSTS) 193 let want: i64 = ((127 << 24) | 1) 194 if cnt >= 1 { if ips[0] == want { t6 = 1 } } 195 sys_close(lfd6) 196 } 197 gv_check("T6 shared lscan_sweep (the one nx_printer_ctl also uses) finds a real 127.0.0.1 listener" as *u8, t6, ctr) 198 199 // ---- T7: real sweep -> emitted JSON says smart-plug + kasa ---- 200 var t7: i64 = 0 201 let lfd7: i64 = tcp_listen_loopback(9999) 202 if lfd7 >= 0 { 203 let m7: *u8 = iotm_new() 204 iotm_defaults(m7) 205 let mask7: *i64 = sys_mmap(256 * 8) as *i64 206 iotc_scan(m7, lscan_parse_prefix("127.0.0" as *u8), 60, mask7) 207 let out7: *u8 = sys_mmap(IOTC_OUT) 208 let n7: i64 = iotc_emit_scan(out7, m7, "127.0.0" as *u8, lscan_parse_prefix("127.0.0" as *u8), mask7, 0) 209 if ig_contains(out7, n7, "\"ip\":\"127.0.0.1\"" as *u8) == 1 { 210 if ig_contains(out7, n7, "\"kind\":\"smart-plug\"" as *u8) == 1 { 211 if ig_contains(out7, n7, "\"vendor\":\"kasa\"" as *u8) == 1 { t7 = 1 } 212 } 213 } 214 sys_close(lfd7) 215 } 216 gv_check("T7 real sweep of a :9999 listener emits kind=smart-plug vendor=kasa (NOT web-device)" as *u8, t7, ctr) 217 218 // ---- T8: real Kasa probe roundtrip, vendor decided by REPLY ---- 219 var t8: i64 = 0 220 let lfd8: i64 = tcp_listen_loopback(19399) 221 if lfd8 >= 0 { 222 let pid: i64 = sys_fork() 223 if pid == 0 { 224 // child = the production client path 225 var spin: i64 = 0 226 while spin < 80000 { spin = spin + 1 } 227 let plain: *u8 = sys_mmap(IOTC_RESP) 228 let pn: i64 = iotc_kasa_probe((127 << 24) | 1, 19399, plain, IOTC_RESP) 229 if pn > 0 { 230 if nx_iot_kasa_looks_like_sysinfo(plain, pn) == 1 { 231 if nx_iot_classify_by_reply(plain, pn) == NX_IOT_VENDOR_KASA { sys_exit(0) } 232 } 233 } 234 sys_exit(1) 235 } 236 if pid > 0 { 237 ig_fake_kasa(lfd8) 238 sys_close(lfd8) 239 let st: *i64 = sys_mmap(8) as *i64 240 st[0] = 0 241 sys_wait4(pid, st, 0) 242 if ((st[0] >> 8) & 0xff) == 0 { t8 = 1 } 243 } 244 } 245 gv_check("T8 real Kasa probe: framed+autokey get_sysinfo over loopback TCP, reply decrypted, vendor by REPLY signal" as *u8, t8, ctr) 246 247 // ---- T9: negative control -- an HTTP-only host is NOT a kasa plug ---- 248 var t9: i64 = 0 249 let lfd9: i64 = tcp_listen_loopback(80) 250 if lfd9 >= 0 { 251 let m9: *u8 = iotm_new() 252 iotm_defaults(m9) 253 let mask9: *i64 = sys_mmap(256 * 8) as *i64 254 iotc_scan(m9, lscan_parse_prefix("127.0.0" as *u8), 60, mask9) 255 let out9: *u8 = sys_mmap(IOTC_OUT) 256 let n9: i64 = iotc_emit_scan(out9, m9, "127.0.0" as *u8, lscan_parse_prefix("127.0.0" as *u8), mask9, 0) 257 if ig_contains(out9, n9, "\"kind\":\"web-device\"" as *u8) == 1 { 258 if ig_contains(out9, n9, "\"vendor\":\"kasa\"" as *u8) == 0 { t9 = 1 } 259 } 260 sys_close(lfd9) 261 } else { 262 // binding :80 needs privilege; a skipped control must NOT count as a 263 // pass -- prove the classifier discriminates on a synthetic mask instead 264 let mA: *u8 = iotm_new() 265 iotm_defaults(mA) 266 var i_http: i64 = 0 - 1 267 var q: i64 = 0 268 while q < iotm_count(mA) { 269 if iotm_port(mA, q) == 80 { if i_http < 0 { i_http = q } } 270 q = q + 1 271 } 272 if i_http >= 0 { 273 let bits: i64 = 1 << i_http 274 if iotc_streq(iotc_kind_name(iotm_kind(mA, iotc_mask_row(mA, bits))), "web-device" as *u8) == 1 { 275 if iotc_mask_vendor(mA, bits) == NX_IOT_VENDOR_UNKNOWN { t9 = 1 } 276 } 277 } 278 } 279 gv_check("T9 negative control: an HTTP-only host classifies web-device/unknown, never kasa" as *u8, t9, ctr) 280 281 // ---- T10: COMPOSITE -- one real host answering on TWO manifest ports ---- 282 // T7 binds a single port, so it can pass even if precedence is inverted 283 // (proven: a mutation that reordered the defaults left T7 green while T1 284 // went red). The live case is a host that answers on several ports at once, 285 // so bind BOTH a kasa port and a lower-precedence printer port on the same 286 // loopback host and require the higher-precedence row to win. 287 var t10: i64 = 0 288 let lfdA: i64 = tcp_listen_loopback(9999) 289 let lfdB: i64 = tcp_listen_loopback(10088) 290 if lfdA >= 0 { 291 if lfdB >= 0 { 292 let mB: *u8 = iotm_new() 293 iotm_defaults(mB) 294 let maskB: *i64 = sys_mmap(256 * 8) as *i64 295 let baseB: i64 = lscan_parse_prefix("127.0.0" as *u8) 296 iotc_scan(mB, baseB, 60, maskB) 297 // the host must have BOTH bits set, else this is not a composite test 298 let outB: *u8 = sys_mmap(IOTC_OUT) 299 let nB: i64 = iotc_emit_scan(outB, mB, "127.0.0" as *u8, baseB, maskB, 0) 300 if ig_contains(outB, nB, "9999" as *u8) == 1 { 301 if ig_contains(outB, nB, "10088" as *u8) == 1 { 302 if ig_contains(outB, nB, "\"kind\":\"smart-plug\"" as *u8) == 1 { 303 if ig_contains(outB, nB, "\"kind\":\"3d-printer\"" as *u8) == 0 { t10 = 1 } 304 } 305 } 306 } 307 sys_close(lfdB) 308 } 309 sys_close(lfdA) 310 } 311 gv_check("T10 composite: a host open on BOTH 9999 and 10088 resolves smart-plug (higher-precedence row wins)" as *u8, t10, ctr) 312 313 gv_verdict("nx_iot_ctl_gate" as *u8, ctr, 314 "sovereign home-electronics identification: data-driven manifest, declared UDP blind spot, never-brick read-only surface, real loopback classification" as *u8) 315 return 0 316}