code wiki / _hdl_build / nx_home.nx

nx_home.nx source

↩ module page · 206 lines · 12709 B

1// nx_home.nx -- SOVEREIGN home-control server (H0 of the IoT workstream). The brain a control surface (Roku 2// "Home" tab / web / phone) POSTs to; it holds the device registry + live state and applies commands. H0 uses a 3// SIM driver (a command just updates state) -- real IP-hub drivers (Matter/Hue/MQTT) plug in behind this later. 4// Sovereign socket HTTP server (no python/shell). REST: 5// GET /home/state -> {"devices":[{id,name,room,type,on,brightness},...]} 6// POST /home/cmd?id=<id>&on=<0|1>&bri=<0-100> -> the device's new state (404 if unknown id, 400 if no id) 7// POST /home/shutdown -> {"ok":1} then the server exits (managed teardown; auth-gate in prod) 8// Idempotent (#10), structured errors (#6), graceful (#14). Devices embedded for H0; H1 makes them data-driven 9// (#11, TSV/seg_store) + persisted. license_tier: ORIGINAL 10// nx_home <port> 11import "nx_syscalls.nx" 12import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 13const K_MAGIC_65536: i64 = 65536 14const K_MAGIC_16384: i64 = 16384 15const K_MAGIC_1024: i64 = 1024 16const K_MAGIC_131072: i64 = 131072 17 18func h_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 19// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 20// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 21// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 22// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 23func h_n(v: i64) -> i64 { nxi_out(v); return 0 } 24func h_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 25func h_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[off+i]=s[i]; i=i+1 } return off+i } 26func h_catb(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { var i: i64=0; while i<n { dst[off+i]=src[i]; i=i+1 } return off+n } 27func h_catn(dst: *u8, off: i64, v: i64) -> i64 { var m: i64=v; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var o: i64=off; var i: i64=0; while i<k{dst[o]=t[k-1-i];o=o+1;i=i+1} return o } 28func h_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 } 29func h_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 30func h_find(hay: *u8, n: i64, needle: *u8) -> i64 { 31 let nl: i64=h_strlen(needle); if nl==0 { return 0-1 } 32 var i: i64=0 33 while i+nl<=n { var j: i64=0; var hit: i64=1; while j<nl { if hay[i+j]!=needle[j] { hit=0; j=nl } else { j=j+1 } } if hit==1 { return i+nl } i=i+1 } 34 return 0-1 35} 36func h_has(hay: *u8, n: i64, needle: *u8) -> i64 { if h_find(hay,n,needle)>=0 { return 1 } return 0 } 37func h_extract(hay: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 { 38 let start: i64=h_find(hay, n, key); if start<0 { return 0-1 } 39 var i: i64=start; var o: i64=0 40 while i<n { 41 let c: i64=hay[i] as i64; var stop: i64=0 42 if c==38 { stop=1 } if c==32 { stop=1 } if c==13 { stop=1 } if c==10 { stop=1 } 43 if stop==1 { i=n } else { if o<cap { out[o]=hay[i]; o=o+1 } i=i+1 } 44 } 45 out[o]=0 as u8; return o 46} 47func h_sockaddr(buf: *u8, port: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 48 var i: i64=0; while i<16 { buf[i]=0 as u8; i=i+1 } 49 buf[0]=2 as u8; buf[2]=((port>>8)&0xff) as u8; buf[3]=(port&0xff) as u8 50 buf[4]=a as u8; buf[5]=b as u8; buf[6]=c as u8; buf[7]=d as u8; return 0 51} 52func h_bind_listen(port: i64) -> i64 { 53 let lfd: i64=sys_socket(AF_INET, SOCK_STREAM, 0); if lfd<0 { return 0-1 } 54 let one: *i64=sys_mmap(8) as *i64; one[0]=1 55 sys_setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, one as *u8, 4) 56 let addr: *u8=sys_mmap(16); h_sockaddr(addr, port, 127, 0, 0, 1) 57 if sys_bind(lfd, addr, 16)!=0 { sys_close(lfd); return 0-2 } 58 if sys_listen(lfd, 16)!=0 { sys_close(lfd); return 0-3 } 59 return lfd 60} 61func h_recv(cfd: i64, buf: *u8, cap: i64) -> i64 { 62 var total: i64=0; var done: i64=0 63 while done==0 { 64 let r: i64=sys_read(cfd, ((buf as i64)+total) as *u8, cap-total) 65 if r<=0 { done=1 } else { total=total+r; if h_find(buf, total, "\r\n\r\n" as *u8)>=0 { done=1 } if total>=cap { done=1 } } 66 } 67 return total 68} 69func h_send(cfd: i64, status: *u8, body: *u8, blen: i64) -> i64 { 70 let b: *u8=sys_mmap(K_MAGIC_65536); var o: i64=0 71 o=h_cat(b,o,"HTTP/1.1 " as *u8); o=h_cat(b,o,status) 72 o=h_cat(b,o,"\r\nContent-Type: application/json\r\nContent-Length: " as *u8); o=h_catn(b,o,blen) 73 o=h_cat(b,o,"\r\nConnection: close\r\n\r\n" as *u8); o=h_catb(b,o,body,blen) 74 sys_write(cfd, b, o); return 0 75} 76func find_dev(ids: *i64, ndev: i64, id: *u8) -> i64 { 77 var i: i64=0; while i<ndev { if h_streq(ids[i] as *u8, id)==1 { return i } i=i+1 } return 0-1 78} 79func j_dev(buf: *u8, off: i64, ids: *i64, names: *i64, rooms: *i64, kinds: *i64, pwr: *i64, bri: *i64, i: i64) -> i64 { 80 var o: i64=off 81 o=h_cat(buf,o,"{\"id\":\"" as *u8); o=h_cat(buf,o,ids[i] as *u8) 82 o=h_cat(buf,o,"\",\"name\":\"" as *u8); o=h_cat(buf,o,names[i] as *u8) 83 o=h_cat(buf,o,"\",\"room\":\"" as *u8); o=h_cat(buf,o,rooms[i] as *u8) 84 o=h_cat(buf,o,"\",\"type\":\"" as *u8); o=h_cat(buf,o,kinds[i] as *u8) 85 o=h_cat(buf,o,"\",\"on\":" as *u8); o=h_catn(buf,o,pwr[i]) 86 o=h_cat(buf,o,",\"brightness\":" as *u8); o=h_catn(buf,o,bri[i]) 87 o=h_cat(buf,o,"}" as *u8); return o 88} 89func handle(cfd: i64, req: *u8, n: i64, ids: *i64, names: *i64, rooms: *i64, kinds: *i64, pwr: *i64, bri: *i64, ndev: i64) -> i64 { 90 if h_has(req,n,"POST /home/shutdown" as *u8)==1 { 91 let body: *u8="{\"ok\":1,\"msg\":\"shutting down\"}" as *u8 92 h_send(cfd, "200 OK" as *u8, body, h_strlen(body)); return 1 93 } 94 if h_has(req,n,"GET /home/state" as *u8)==1 { 95 let body: *u8=sys_mmap(K_MAGIC_16384); var o: i64=0 96 o=h_cat(body,o,"{\"devices\":[" as *u8) 97 var i: i64=0; while i<ndev { if i>0 { o=h_cat(body,o,"," as *u8) } o=j_dev(body,o, ids,names,rooms,kinds,pwr,bri, i); i=i+1 } 98 o=h_cat(body,o,"]}" as *u8) 99 h_send(cfd, "200 OK" as *u8, body, o); return 0 100 } 101 if h_has(req,n,"POST /home/cmd" as *u8)==1 { 102 let idbuf: *u8=sys_mmap(128); let idl: i64=h_extract(req,n,"id=" as *u8,idbuf,127) 103 if idl<0 { let e: *u8="{\"error\":\"missing id\"}" as *u8; h_send(cfd,"400 Bad Request" as *u8,e,h_strlen(e)); return 0 } 104 let idx: i64=find_dev(ids,ndev,idbuf) 105 if idx<0 { 106 let e: *u8=sys_mmap(256); var eo: i64=0; eo=h_cat(e,eo,"{\"error\":\"unknown device\",\"id\":\"" as *u8); eo=h_cat(e,eo,idbuf); eo=h_cat(e,eo,"\"}" as *u8) 107 h_send(cfd,"404 Not Found" as *u8, e, eo); return 0 108 } 109 let onbuf: *u8=sys_mmap(64); let onl: i64=h_extract(req,n,"on=" as *u8,onbuf,63) 110 if onl>=0 { var v: i64=h_atoi(onbuf); if v!=0 { pwr[idx]=1 } else { pwr[idx]=0 } } 111 let brbuf: *u8=sys_mmap(64); let brl: i64=h_extract(req,n,"bri=" as *u8,brbuf,63) 112 if brl>=0 { var v2: i64=h_atoi(brbuf); if v2<0 { v2=0 } if v2>100 { v2=100 } bri[idx]=v2 } 113 let body: *u8=sys_mmap(K_MAGIC_1024); let bl: i64=j_dev(body,0, ids,names,rooms,kinds,pwr,bri, idx) 114 h_send(cfd,"200 OK" as *u8, body, bl); return 0 115 } 116 let e: *u8="{\"error\":\"not found\"}" as *u8; h_send(cfd,"404 Not Found" as *u8, e, h_strlen(e)); return 0 117} 118 119// copy buf[s..e) (minus any \r) into pool as a NUL-terminated string; return its address; advance pool cursor 120func tsv_field(pool: *u8, pip: *i64, buf: *u8, s: i64, e: i64) -> i64 { 121 let base: i64=(pool as i64)+pip[0]; let p: *u8=base as *u8 122 var k: i64=0; var j: i64=s; while j<e { if buf[j]!=(13 as u8) { p[k]=buf[j]; k=k+1 } j=j+1 } p[k]=0 as u8 123 pip[0]=pip[0]+k+1; return base 124} 125// data-driven registry (#11): one device per line, TAB-separated id<TAB>name<TAB>room<TAB>type; return ndev (0 = none/missing -> caller uses embedded defaults) 126func load_tsv(path: *u8, ids: *i64, names: *i64, rooms: *i64, kinds: *i64, pwr: *i64, bri: *i64) -> i64 { 127 let szp: *i64=sys_mmap(16) as *i64 128 let buf: *u8=sys_read_file(path, szp) 129 if (buf as i64)==0 { return 0 } 130 let n: i64=szp[0] 131 let pool: *u8=sys_mmap(K_MAGIC_131072); let pip: *i64=sys_mmap(8) as *i64; pip[0]=0 132 let fp: *i64=sys_mmap(8*8) as *i64 133 var ndev: i64=0; var i: i64=0 134 while i<n { 135 if ndev>=64 { i=n } else { 136 var fnum: i64=0; var fs: i64=i; var done: i64=0 137 while done==0 { 138 if i>=n { 139 if i>fs { if fnum<4 { fp[fnum]=tsv_field(pool,pip,buf,fs,i) } fnum=fnum+1 } 140 done=1 141 } else { 142 let c: i64=buf[i] as i64 143 if c==9 { if fnum<4 { fp[fnum]=tsv_field(pool,pip,buf,fs,i) } fnum=fnum+1; i=i+1; fs=i } 144 else { if c==10 { if i>fs { if fnum<4 { fp[fnum]=tsv_field(pool,pip,buf,fs,i) } fnum=fnum+1 } i=i+1; done=1 } 145 else { i=i+1 } } 146 } 147 } 148 if fnum>=4 { ids[ndev]=fp[0]; names[ndev]=fp[1]; rooms[ndev]=fp[2]; kinds[ndev]=fp[3]; pwr[ndev]=0; bri[ndev]=0; ndev=ndev+1 } 149 } 150 } 151 return ndev 152} 153 154// operator-only auth: request must carry "Authorization: Bearer <token>" exactly matching the configured token 155func check_auth(req: *u8, n: i64, token: *u8) -> i64 { 156 let p: i64=h_find(req, n, "Authorization: Bearer " as *u8); if p<0 { return 0 } 157 var i: i64=0 158 while token[i]!=(0 as u8) { if p+i>=n { return 0 } if req[p+i]!=token[i] { return 0 } i=i+1 } 159 if p+i<n { let c: i64=req[p+i] as i64; if c!=13 { if c!=10 { if c!=32 { return 0 } } } } // exact match, no prefix 160 return 1 161} 162 163func main(argc: i64, argv: *i64) -> i64 { 164 if argc<2 { h_p("usage: nx_home <port> [registry.tsv] [tokenfile]\n" as *u8); sys_exit(2); return 2 } 165 let port: i64=h_atoi(argv[1] as *u8) 166 let ids: *i64=sys_mmap(8*64) as *i64; let names: *i64=sys_mmap(8*64) as *i64 167 let rooms: *i64=sys_mmap(8*64) as *i64; let kinds: *i64=sys_mmap(8*64) as *i64 168 let pwr: *i64=sys_mmap(8*64) as *i64; let bri: *i64=sys_mmap(8*64) as *i64 169 var ndev: i64=0 170 if argc>=3 { ndev=load_tsv(argv[2] as *u8, ids, names, rooms, kinds, pwr, bri) } // data-driven registry (#11) 171 if ndev==0 { // graceful fallback to embedded defaults (#17 config hierarchy: file > defaults) 172 ids[0]="living_room" as *u8 as i64; names[0]="Living Room" as *u8 as i64; rooms[0]="living" as *u8 as i64; kinds[0]="light" as *u8 as i64; pwr[0]=0; bri[0]=0 173 ids[1]="bedroom" as *u8 as i64; names[1]="Bedroom" as *u8 as i64; rooms[1]="bedroom" as *u8 as i64; kinds[1]="light" as *u8 as i64; pwr[1]=0; bri[1]=0 174 ids[2]="kitchen" as *u8 as i64; names[2]="Kitchen" as *u8 as i64; rooms[2]="kitchen" as *u8 as i64; kinds[2]="light" as *u8 as i64; pwr[2]=0; bri[2]=0 175 ndev=3 176 } 177 var tokenset: i64=0; let token: *u8=sys_mmap(256) 178 if argc>=4 { // operator-only auth when a token file is given (#12 defensive at boundary; #17 env/config-driven) 179 let tszp: *i64=sys_mmap(16) as *i64; let tb: *u8=sys_read_file(argv[3] as *u8, tszp) 180 if (tb as i64)!=0 { 181 var tn: i64=tszp[0]; var trimming: i64=1 182 while trimming==1 { if tn<=0 { trimming=0 } else { if tb[tn-1]==(10 as u8) { tn=tn-1 } else { if tb[tn-1]==(13 as u8) { tn=tn-1 } else { trimming=0 } } } } 183 var k: i64=0; while k<tn { token[k]=tb[k]; k=k+1 } token[tn]=0 as u8 184 if tn>0 { tokenset=1 } 185 } 186 } 187 let lfd: i64=h_bind_listen(port); if lfd<0 { h_p("nx_home BIND-FAIL rc=" as *u8); h_n(lfd); h_p("\n" as *u8); sys_exit(1); return 1 } 188 h_p("nx_home listening port=" as *u8); h_n(port); h_p(" devices=" as *u8); h_n(ndev); h_p(" (sim driver)\n" as *u8) 189 let req: *u8=sys_mmap(K_MAGIC_65536); var run: i64=1 190 while run==1 { 191 let cfd: i64=sys_accept(lfd) 192 if cfd<0 { run=0 } else { 193 let n: i64=h_recv(cfd, req, K_MAGIC_65536) 194 var authok: i64=1 195 if tokenset==1 { if check_auth(req, n, token)==0 { authok=0 } } 196 if authok==0 { 197 let e: *u8="{\"error\":\"unauthorized\"}" as *u8; h_send(cfd, "401 Unauthorized" as *u8, e, h_strlen(e)); sys_close(cfd) 198 } else { 199 let sd: i64=handle(cfd, req, n, ids,names,rooms,kinds,pwr,bri, ndev) 200 sys_close(cfd) 201 if sd==1 { run=0 } 202 } 203 } 204 } 205 sys_close(lfd); h_p("nx_home stopped\n" as *u8); sys_exit(0); return 0 206}