nx_iot_kasa_probe.nx source
↩ module page · 154 lines · 5169 B
1// nx_iot_kasa_probe.nx -- SOVEREIGN live Kasa device query.
2//
3// Replaces the non-Nishi shell shim entirely: opens a UDP socket, sends the
4// autokey-encrypted get_sysinfo, reads + decrypts the reply, confirms it is
5// a Kasa sysinfo. The CAPABILITY is this organ; the OS only supplies the
6// socket syscalls -- so the SAME source runs on Linux today (via
7// nx_syscalls_x86_64) and on the optimized NishiOS later (via its syscalls).
8// Nothing PowerShell/netsh on the program path.
9//
10// Composition (DRY, CLAUDE.md #15) -- duplicates nothing:
11// - nx_udp : sovereign UDP socket round-trip
12// - nx_iot_local_kasa : autokey encrypt/decrypt + get_sysinfo builder +
13// looks_like_sysinfo (crypto gate-proven in
14// nx_iot_local_kasa_test)
15//
16// NEVER-BRICK (CLAUDE.md #26): get_sysinfo is READ-ONLY -- it queries device
17// state and writes NOTHING to the device. No firmware path exists here.
18//
19// genealogy_id: NISHI_IOT_HUB_ROADMAP.md Epoch 1/2 + project-iot-discovery-pairing-revival-2026-06-22
20// license_tier: ORIGINAL
21
22import "nx_syscalls_x86_64.nx"
23import "nx_udp.nx"
24import "nx_iot_local_kasa.nx"
25const KP_MAGIC_2048: i64 = 2048
26const KP_MAGIC_9999: i64 = 9999
27const KP_MAGIC_2000: i64 = 2000
28
29// SO_RCVTIMEO so recv returns on timeout instead of blocking forever.
30const KP_SO_RCVTIMEO: i64 = 20
31
32// Sealed verdict for a live probe.
33const KP_UNKNOWN: i64 = 0
34const KP_OK: i64 = 1 // got a Kasa sysinfo reply
35const KP_SOCKET_FAIL: i64 = 2
36const KP_SEND_FAIL: i64 = 3
37const KP_NO_REPLY: i64 = 4 // timeout, no datagram
38const KP_NOT_SYSINFO: i64 = 5 // a reply, but not a Kasa sysinfo
39const KP_BUF_SMALL: i64 = 6
40const KP_N: i64 = 7
41
42func nx_iot_kp_verdict_is_valid(v: i64) -> i64 {
43 if v < 0 { return 0 }
44 if v >= KP_N { return 0 }
45 return 1
46}
47
48// Set SO_RCVTIMEO to `ms` ms (Linux x86_64 timeval: i64 tv_sec; i64 tv_usec).
49func nx_iot_kp_set_rcvtimeo_ms(fd: i64, ms: i64) -> i64 {
50 let tv: *u8 = sys_mmap(16)
51 let sec: i64 = ms / 1000
52 let usec: i64 = (ms - sec * 1000) * 1000
53 tv[0] = sec & 0xff
54 tv[1] = (sec >> 8) & 0xff
55 tv[2] = (sec >> 16) & 0xff
56 tv[3] = (sec >> 24) & 0xff
57 tv[4] = 0
58 tv[5] = 0
59 tv[6] = 0
60 tv[7] = 0
61 tv[8] = usec & 0xff
62 tv[9] = (usec >> 8) & 0xff
63 tv[10] = (usec >> 16) & 0xff
64 tv[11] = (usec >> 24) & 0xff
65 tv[12] = 0
66 tv[13] = 0
67 tv[14] = 0
68 tv[15] = 0
69 return sys_setsockopt(fd, SOL_SOCKET, KP_SO_RCVTIMEO, tv, 16)
70}
71
72// Live query: UDP get_sysinfo to a.b.c.d:port. Decrypted reply -> out.
73// Returns the decrypted byte length (>0) on KP_OK / KP_NOT_SYSINFO, 0 on
74// timeout, -1 on socket/send/buffer failure; verdict via out_verdict.
75func nx_iot_kasa_probe_sysinfo(a: i64, b: i64, c: i64, d: i64, port: i64,
76 out: *u8, out_cap: i64, timeout_ms: i64,
77 out_verdict: *i64) -> i64 {
78 *out_verdict = KP_UNKNOWN
79 let fd: i64 = nx_udp_open()
80 if fd < 0 {
81 *out_verdict = KP_SOCKET_FAIL
82 return -1
83 }
84 nx_iot_kp_set_rcvtimeo_ms(fd, timeout_ms)
85
86 // Build + encrypt get_sysinfo (UDP form: no 4-byte length prefix).
87 let clear: *u8 = sys_mmap(64)
88 let cn: i64 = nx_iot_kasa_pt_sysinfo(clear, 64)
89 let enc: *u8 = sys_mmap(64)
90 nx_iot_kasa_encrypt(clear, cn, enc)
91
92 let dst: *u8 = sys_mmap(16)
93 nx_udp_sockaddr_dest(dst, a, b, c, d, port)
94 let sr: i64 = nx_udp_send(fd, enc, cn, dst)
95 if sr < 0 {
96 sys_close(fd)
97 *out_verdict = KP_SEND_FAIL
98 return -1
99 }
100
101 let rbuf: *u8 = sys_mmap(KP_MAGIC_2048)
102 let pa: *u8 = sys_mmap(16)
103 let plen: *i64 = sys_mmap(16) as *i64
104 plen[0] = 16
105 let r: i64 = nx_udp_recv(fd, rbuf, KP_MAGIC_2048, pa, plen)
106 sys_close(fd)
107 if r <= 0 {
108 *out_verdict = KP_NO_REPLY
109 return 0
110 }
111 if r > out_cap {
112 *out_verdict = KP_BUF_SMALL
113 return -1
114 }
115 nx_iot_kasa_decrypt(rbuf, r, out)
116 if nx_iot_kasa_looks_like_sysinfo(out, r) != 1 {
117 *out_verdict = KP_NOT_SYSINFO
118 return r
119 }
120 *out_verdict = KP_OK
121 return r
122}
123
124// Live runner: query the HS210 on its SoftAP (192.168.0.1:9999) and print
125// the decrypted sysinfo. Exit 0 on KP_OK, else the verdict code.
126func main() -> i64 {
127 let out: *u8 = sys_mmap(KP_MAGIC_2048)
128 let v: *i64 = sys_mmap(8) as *i64
129 let n: i64 = nx_iot_kasa_probe_sysinfo(192, 168, 0, 1, KP_MAGIC_9999, out, KP_MAGIC_2048, KP_MAGIC_2000, v)
130 if v[0] == KP_OK {
131 sys_write(1, "KASA-PROBE OK sysinfo:\n", 23)
132 sys_write(1, out, n)
133 sys_write(1, "\n", 1)
134 return 0
135 }
136 if v[0] == KP_NO_REPLY {
137 sys_write(1, "KASA-PROBE NO_REPLY (timeout)\n", 30)
138 return 4
139 }
140 if v[0] == KP_NOT_SYSINFO {
141 sys_write(1, "KASA-PROBE reply-not-sysinfo\n", 29)
142 return 5
143 }
144 if v[0] == KP_SOCKET_FAIL {
145 sys_write(1, "KASA-PROBE socket-fail\n", 23)
146 return 2
147 }
148 if v[0] == KP_SEND_FAIL {
149 sys_write(1, "KASA-PROBE send-fail\n", 21)
150 return 3
151 }
152 sys_write(1, "KASA-PROBE unknown\n", 19)
153 return 1
154}