code wiki / _hdl_build / nx_inventory_serve.nx
nx_inventory_serve.nx source
↩ module page · 394 lines · 20557 B
1// nx_inventory_serve.nx -- nishifamily.com/inventory : paste a storefront URL, get a LIVING CHECKLIST
2// of what in it you already own.
3//
4// REBUILT 2026-08-07 TO MATCH THE KNOWN GOOD (nx_status_daemon), after the first cut shipped three
5// defects the template would have prevented outright:
6// 1. it bound INADDR_ANY, so an UNAUTHENTICATED, SSRF-capable fetcher answered from another host on
7// the LAN the instant it started (measured, then killed). Now: nx_http_server_addr_loopback.
8// 2. it had NO AUTH, and I intended to route it with proxy mode=gated believing that WAS the login
9// gate. It is not -- `gated` means fail-closed 302 when the BACKEND IS DOWN. Site auth is
10// nx_sa_validate over X-Nishi-Session, NO cookies (charter C1). Now every route but /health is
11// validated, so an unauthenticated request cannot make this daemon fetch anything.
12// 3. its handler was welded to a socket, so the only way to test it was curl from a shell script --
13// the exact break-glass shape rule 29 forbids. Now the router is a PURE FUNCTION
14// iv_handle(ctx, req, req_n, ...) -> out_n, so a NishiLang gate drives it IN-PROCESS.
15//
16// THIN BY DESIGN. It orchestrates two binaries already proven rather than reimplementing them:
17// 1. ./nx_https_get_cli2.elf <url> -- the sovereign TLS-1.3 client (own trust store)
18// 2. ./nx_ownlib.elf scan <file> -- the ledger diff, selftest 19/19, match key included
19// The match key (ol_norm) is the correctness-critical part, and a daemon holding its own copy is how
20// two components drift into disagreeing about what "the same game" is. So the daemon owns NO matching
21// logic -- it owns a socket, an auth check, and an HTML wrapper.
22//
23// SSRF: this fetches an operator-supplied URL from INSIDE the estate, where mgmt (:18098) and the tools
24// daemon (:18096) sit on loopback. Auth alone is NOT sufficient -- it only means the attacker must be
25// logged in. So the guard is structural and runs before any socket: https only; loopback/private/
26// link-local refused by construction.
27//
28// nx_inventory_serve <port> <keysfile> <storefile> <realm> <budget>
29// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
30import "nx_syscalls.nx"
31import "nx_http_server.nx"
32import "nx_site_auth.nx"
33import "nx_tool_run.nx" // tr_run_capture_to -- watchdog capture, gate-proven no-leak-after-kill
34
35const IV_MAGIC_4096: i64 = 4096
36const IV_MAGIC_8192: i64 = 8192
37const IV_REQCAP: i64 = 65536
38const IV_PAGECAP: i64 = 4194304 // a bundle page measured 719,027 bytes LIVE; 4 MiB is ~5.8x headroom
39const IV_OUTCAP: i64 = 262144
40const IV_URLCAP: i64 = 2048
41const IV_PATHCAP: i64 = 512
42const IV_SMALL: i64 = 64
43const IV_FETCH_MS: i64 = 45000
44const IV_SCAN_MS: i64 = 30000
45const IV_SESS_TTL: i64 = 900
46const IV_FETCHER: *u8 = "./nx_https_get_cli2.elf" as *u8
47const IV_OWNLIB: *u8 = "./nx_ownlib.elf" as *u8
48
49func iv_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
50func iv_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o+i] = s[i]; i = i + 1 } return o + i }
51func iv_catb(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o+i] = s[i]; i = i + 1 } return o + n }
52func iv_catn(d: *u8, o: i64, v: i64) -> i64 {
53 if v < 0 { d[o] = 45 as u8; return iv_catn(d, o + 1, 0 - v) }
54 // NEGATIVES (2026-08-07). Without this the `while m > 0` loop below never runs for a
55 // negative value and this function emits ZERO CHARACTERS, silently corrupting whatever
56 // format it is writing into. Handled AT THE SIGNATURE so it is independent of which
57 // cursor variable the body happens to use. Non-negative input is byte-identical (rule 19).
58 if v < 0 { d[o] = 45 as u8; return iv_catn(d, o + 1, 0 - v) }
59 let t: *u8 = sys_mmap(IV_SMALL)
60 var m: i64 = v
61 var k: i64 = 0
62 if m == 0 { t[0] = 48 as u8; k = 1 }
63 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
64 var i: i64 = 0
65 while i < k { d[o+i] = t[k-1-i]; i = i + 1 }
66 return o + k
67}
68func iv_lit_at(buf: *u8, n: i64, i: i64, lit: *u8) -> i64 {
69 var k: i64 = 0
70 while lit[k] != (0 as u8) {
71 if i + k >= n { return 0 }
72 if buf[i + k] != lit[k] { return 0 }
73 k = k + 1
74 }
75 return k
76}
77// HTML-escape. The scan output is machine-generated but carries STORE-SUPPLIED TITLES -- bytes a third
78// party chose -- so this is the only thing between a vendor's product name and script execution on a
79// page inside the login.
80func iv_esc(d: *u8, o: i64, s: *u8, n: i64, cap: i64) -> i64 {
81 var i: i64 = 0
82 while i < n {
83 if o + 8 >= cap { return o }
84 let c: i64 = s[i] as i64
85 if c == 60 { o = iv_cat(d, o, "<" as *u8) } else {
86 if c == 62 { o = iv_cat(d, o, ">" as *u8) } else {
87 if c == 38 { o = iv_cat(d, o, "&" as *u8) } else {
88 if c == 34 { o = iv_cat(d, o, """ as *u8) } else {
89 d[o] = c as u8; o = o + 1
90 }
91 }
92 }
93 }
94 i = i + 1
95 }
96 return o
97}
98func iv_hexval(c: i64) -> i64 {
99 if c >= 48 { if c <= 57 { return c - 48 } }
100 if c >= 97 { if c <= 102 { return c - 87 } }
101 if c >= 65 { if c <= 70 { return c - 55 } }
102 return 0 - 1
103}
104// percent-decode a query value ('+' is a space). TERMINATES at '&', SPACE or '#'.
105// THE SPACE TERMINATOR IS LOAD-BEARING. Without it the decode runs past the request-line URI and
106// swallows " HTTP/1.1\r\nHost: ..." into the url, tripping the guard's control-byte rule -- so EVERY
107// url refuses, and because every SSRF negative test expects a refusal, ALL OF THEM PASS ANYWAY.
108// A GUARD THAT REFUSES EVERYTHING PASSES EVERY NEGATIVE TEST. Only a POSITIVE control sees it.
109// This regressed once when a sibling edit dropped it; the gate now asserts WHICH rule fired.
110func iv_urldec(src: *u8, sn: i64, i0: i64, dst: *u8, cap: i64) -> i64 {
111 var o: i64 = 0
112 var i: i64 = i0
113 while i < sn {
114 let c: i64 = src[i] as i64
115 if c == 38 { i = sn } else {
116 if c == 32 { i = sn } else {
117 if c == 35 { i = sn } else {
118 if o + 2 >= cap { return o }
119 if c == 43 { dst[o] = 32 as u8; o = o + 1; i = i + 1 } else {
120 if c == 37 {
121 if i + 2 < sn {
122 let h1: i64 = iv_hexval(src[i+1] as i64)
123 let h2: i64 = iv_hexval(src[i+2] as i64)
124 if h1 >= 0 { if h2 >= 0 { dst[o] = (h1 * 16 + h2) as u8; o = o + 1; i = i + 3 } else { dst[o] = c as u8; o = o + 1; i = i + 1 } } else { dst[o] = c as u8; o = o + 1; i = i + 1 }
125 } else { dst[o] = c as u8; o = o + 1; i = i + 1 }
126 } else { dst[o] = c as u8; o = o + 1; i = i + 1 }
127 }
128 }
129 }
130 }
131 }
132 dst[o] = 0 as u8
133 return o
134}
135// ---- THE SSRF GUARD ------------------------------------------------------------------------------
136func iv_host_start(url: *u8, n: i64) -> i64 {
137 var i: i64 = 0
138 while i + 2 < n {
139 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { return i + 3 } } }
140 i = i + 1
141 }
142 return 0 - 1
143}
144func iv_url_ok(url: *u8, n: i64, whyout: *i64) -> i64 {
145 whyout[0] = 0
146 if n < 12 { whyout[0] = 1; return 0 }
147 if n >= IV_URLCAP { whyout[0] = 2; return 0 }
148 if iv_lit_at(url, n, 0, "https://" as *u8) == 0 { whyout[0] = 3; return 0 }
149 var i: i64 = 0
150 while i < n {
151 let c: i64 = url[i] as i64
152 if c <= 32 { whyout[0] = 4; return 0 }
153 if c == 127 { whyout[0] = 4; return 0 }
154 i = i + 1
155 }
156 let hs: i64 = iv_host_start(url, n)
157 if hs < 0 { whyout[0] = 3; return 0 }
158 var j: i64 = hs
159 while j < n {
160 let c2: i64 = url[j] as i64
161 if c2 == 47 { j = n } else {
162 if c2 == 64 { whyout[0] = 5; return 0 }
163 j = j + 1
164 }
165 }
166 if iv_lit_at(url, n, hs, "localhost" as *u8) > 0 { whyout[0] = 6; return 0 }
167 if iv_lit_at(url, n, hs, "127." as *u8) > 0 { whyout[0] = 6; return 0 }
168 if iv_lit_at(url, n, hs, "0." as *u8) > 0 { whyout[0] = 6; return 0 }
169 if iv_lit_at(url, n, hs, "10." as *u8) > 0 { whyout[0] = 6; return 0 }
170 if iv_lit_at(url, n, hs, "192.168." as *u8) > 0 { whyout[0] = 6; return 0 }
171 if iv_lit_at(url, n, hs, "169.254." as *u8) > 0 { whyout[0] = 6; return 0 }
172 if iv_lit_at(url, n, hs, "[" as *u8) > 0 { whyout[0] = 6; return 0 }
173 // 172.16.0.0/12 -- only 16..31 are private, so 172.8. and 172.200. stay ALLOWED (positive control)
174 if iv_lit_at(url, n, hs, "172." as *u8) > 0 {
175 var v: i64 = 0
176 var k: i64 = hs + 4
177 var seen: i64 = 0
178 while k < n {
179 let c3: i64 = url[k] as i64
180 if c3 >= 48 { if c3 <= 57 { v = v * 10 + (c3 - 48); seen = 1; k = k + 1 } else { k = n } } else { k = n }
181 }
182 if seen == 1 { if v >= 16 { if v <= 31 { whyout[0] = 6; return 0 } } }
183 }
184 return 1
185}
186func iv_why(w: i64) -> *u8 {
187 if w == 1 { return "url too short" as *u8 }
188 if w == 2 { return "url too long" as *u8 }
189 if w == 3 { return "only https:// urls are fetched" as *u8 }
190 if w == 4 { return "url contains control or whitespace bytes" as *u8 }
191 if w == 5 { return "url carries userinfo (@) -- refused rather than parsed" as *u8 }
192 if w == 6 { return "host is loopback/private/link-local -- refused: this daemon runs INSIDE the estate" as *u8 }
193 return "refused" as *u8
194}
195// ---- page furniture ------------------------------------------------------------------------------
196func iv_head(d: *u8, o: i64) -> i64 {
197 o = iv_cat(d, o, "<!DOCTYPE html><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\">" as *u8)
198 o = iv_cat(d, o, "<title>Inventory — Nishi</title><style>html{-webkit-text-size-adjust:100%}body{font-family:-apple-system,Segoe UI,sans-serif;max-width:760px;margin:3vh auto;padding:0 16px;color:#1c1c1e}" as *u8)
199 o = iv_cat(d, o, "h1{font-size:1.2rem;letter-spacing:-.02em}h1 span{color:#0a6}form{display:flex;gap:8px;margin:1rem 0}input[name=url]{flex:1;min-width:0;padding:12px;border:1px solid #ccc;border-radius:10px;font-size:16px}" as *u8)
200 o = iv_cat(d, o, "button{min-height:44px;padding:10px 20px;border:0;border-radius:10px;background:#0a6;color:#fff;font-size:16px}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f6f8f7;border:1px solid #e2e8e5;border-radius:12px;padding:14px;line-height:1.5;font-size:.92rem}" as *u8)
201 o = iv_cat(d, o, ".hint{color:#666;font-size:.88rem}.err{border:1px solid #e6b3b3;background:#fdf5f5;border-radius:12px;padding:12px}" as *u8)
202 o = iv_cat(d, o, "@media(prefers-color-scheme:dark){body{background:#111214;color:#e8e8ea}input[name=url]{background:#1c1c1e;color:#e8e8ea;border-color:#3a3a3c}pre{background:#17191a;border-color:#2c2c2e}.hint{color:#8e8e93}.err{background:#241617;border-color:#4a2626}}</style>" as *u8)
203 o = iv_cat(d, o, "<h1>Nishi <span>Inventory</span></h1>" as *u8)
204 o = iv_cat(d, o, "<form action=/inventory method=get><input name=url placeholder=\"Paste a bundle or store page URL\" autofocus><button>Check</button></form>" as *u8)
205 return o
206}
207func iv_foot(d: *u8, o: i64) -> i64 {
208 o = iv_cat(d, o, "<p class=hint>Diffed against your ownership ledger. <b>UNREDEEMED</b> means you hold a key no storefront can see — redeem it, do not buy. <b>NEW</b> means nothing in the ledger matches; a near match is shown for review rather than claimed as owned.</p>" as *u8)
209 return o
210}
211func iv_resp(out: *u8, status: *u8, body: *u8, blen: i64) -> i64 {
212 var h: i64 = 0
213 h = iv_cat(out, h, "HTTP/1.1 " as *u8)
214 h = iv_cat(out, h, status)
215 h = iv_cat(out, h, "\r\nContent-Type: text/html; charset=utf-8\r\nCache-Control: no-store\r\nX-Content-Type-Options: nosniff\r\nReferrer-Policy: no-referrer\r\nConnection: close\r\nContent-Length: " as *u8)
216 h = iv_catn(out, h, blen)
217 h = iv_cat(out, h, "\r\n\r\n" as *u8)
218 h = iv_catb(out, h, body, blen)
219 return h
220}
221// 401 in the charter's shape: NEVER a Set-Cookie, NEVER WWW-Authenticate Basic.
222func iv_401(out: *u8) -> i64 {
223 var h: i64 = 0
224 h = iv_cat(out, h, "HTTP/1.1 401 Unauthorized\r\nContent-Type: application/json\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: 24\r\n\r\n" as *u8)
225 h = iv_cat(out, h, "{\"error\":\"unauthorized\"}" as *u8)
226 return h
227}
228// ---- the fetch+diff leg --------------------------------------------------------------------------
229func iv_check_url(url: *u8, un: i64, seq: i64, page: *u8, scan: *u8, body: *u8, out: *u8) -> i64 {
230 var o: i64 = iv_head(body, 0)
231 let whyb: *i64 = sys_mmap(IV_SMALL) as *i64
232 if iv_url_ok(url, un, whyb) == 0 {
233 o = iv_cat(body, o, "<div class=err><b>Refused.</b> " as *u8)
234 o = iv_cat(body, o, iv_why(whyb[0]))
235 o = iv_cat(body, o, "</div>" as *u8)
236 o = iv_foot(body, o)
237 return iv_resp(out, "400 Bad Request" as *u8, body, o)
238 }
239 let plen: *i64 = sys_mmap(IV_SMALL) as *i64
240 plen[0] = 0
241 let av: *i64 = sys_mmap(IV_SMALL) as *i64
242 av[0] = IV_FETCHER as i64
243 av[1] = url as i64
244 av[2] = 0
245 tr_run_capture_to(IV_FETCHER, av, page, IV_PAGECAP - 1, plen, IV_FETCH_MS)
246 if plen[0] <= 0 {
247 o = iv_cat(body, o, "<div class=err><b>Fetch returned nothing.</b> The page may require a logged-in session. If this is unexpected, check the fetcher binary EXISTS -- an absent artifact looks exactly like a dead remote server (exit 127).</div>" as *u8)
248 o = iv_foot(body, o)
249 return iv_resp(out, "502 Bad Gateway" as *u8, body, o)
250 }
251 let tmp: *u8 = sys_mmap(IV_PATHCAP)
252 var t: i64 = iv_cat(tmp, 0, "/tmp/inv_" as *u8)
253 t = iv_catn(tmp, t, seq)
254 t = iv_cat(tmp, t, ".html" as *u8)
255 tmp[t] = 0 as u8
256 let fd: i64 = sys_openat_wr(tmp, 0x1a4)
257 if fd < 0 {
258 o = iv_cat(body, o, "<div class=err><b>Could not stage the fetched page.</b></div>" as *u8)
259 o = iv_foot(body, o)
260 return iv_resp(out, "500 Internal Server Error" as *u8, body, o)
261 }
262 sys_write(fd, page, plen[0])
263 sys_close(fd)
264 let slen: *i64 = sys_mmap(IV_SMALL) as *i64
265 slen[0] = 0
266 let av2: *i64 = sys_mmap(IV_SMALL) as *i64
267 av2[0] = IV_OWNLIB as i64
268 av2[1] = "scan" as *u8 as i64
269 av2[2] = tmp as i64
270 av2[3] = 0
271 tr_run_capture_to(IV_OWNLIB, av2, scan, IV_OUTCAP - 1, slen, IV_SCAN_MS)
272 o = iv_cat(body, o, "<p class=hint>source: " as *u8)
273 o = iv_esc(body, o, url, un, IV_OUTCAP)
274 o = iv_cat(body, o, " · " as *u8)
275 o = iv_catn(body, o, plen[0])
276 o = iv_cat(body, o, " bytes fetched</p><pre>" as *u8)
277 if slen[0] > 0 { o = iv_esc(body, o, scan, slen[0], IV_OUTCAP - IV_MAGIC_4096) } else {
278 o = iv_cat(body, o, "the scanner produced no output" as *u8)
279 }
280 o = iv_cat(body, o, "</pre>" as *u8)
281 o = iv_foot(body, o)
282 return iv_resp(out, "200 OK" as *u8, body, o)
283}
284// ---- THE PURE ROUTER: request bytes in, response bytes out, NO SOCKET ------------------------------
285// This shape is the whole reason a NishiLang gate can drive it IN-PROCESS instead of shell + curl.
286// AUTH IS CHECKED HERE, not at the proxy: `mode=gated` means fail-closed-when-backend-down, and
287// mistaking it for a login gate is how an SSRF-capable fetcher gets published to the world.
288func iv_handle(ctx: *NxAuthContext, req: *u8, req_n: i64, seq: i64, page: *u8, scan: *u8, body: *u8, out: *u8) -> i64 {
289 if req_n <= 0 { return iv_401(out) }
290 // /inventory/health is the ONLY unauthenticated route: the deploy path port-connects, and a health
291 // check needing a session would make every restart depend on a credential.
292 if iv_lit_at(req, req_n, 0, "GET /inventory/health" as *u8) > 0 {
293 return iv_resp(out, "200 OK" as *u8, "OK" as *u8, 2)
294 }
295 let now_s: i64 = sys_now_realtime_sec()
296 if nx_sa_validate(ctx, req, req_n, now_s) != NX_MAUTH_OK { return iv_401(out) }
297 // find "url=" inside the REQUEST LINE only -- bounded at the first CR/LF so a header or body can
298 // never smuggle in the parameter that decides what this daemon fetches.
299 var q: i64 = 0
300 var found: i64 = 0
301 var i: i64 = 0
302 while i < req_n {
303 if req[i] == (13 as u8) { i = req_n } else {
304 if req[i] == (10 as u8) { i = req_n } else {
305 if found == 0 { if iv_lit_at(req, req_n, i, "url=" as *u8) > 0 { q = i + 4; found = 1 } }
306 i = i + 1
307 }
308 }
309 }
310 if found == 0 {
311 var o: i64 = iv_head(body, 0)
312 o = iv_cat(body, o, "<p class=hint>Paste a bundle page (Humble, Fanatical, a store listing) and this checks every item in it against what you already own — including keys you hold but never redeemed, which is the case no storefront can warn you about.</p>" as *u8)
313 o = iv_foot(body, o)
314 return iv_resp(out, "200 OK" as *u8, body, o)
315 }
316 let url: *u8 = sys_mmap(IV_URLCAP)
317 let un: i64 = iv_urldec(req, req_n, q, url, IV_URLCAP - 2)
318 return iv_check_url(url, un, seq, page, scan, body, out)
319}
320func iv_atoi(s: *u8) -> i64 {
321 var v: i64 = 0
322 var i: i64 = 0
323 while s[i] != (0 as u8) {
324 let c: i64 = s[i] as i64
325 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
326 i = i + 1
327 }
328 return v
329}
330func main(argc: i64, argv: *i64) -> i64 {
331 // NO ARGS => usage, and DO NOT BIND, so a build smoke can never hang on accept.
332 if argc < 6 {
333 sys_write(1, "usage: nx_inventory_serve <port> <keysfile> <storefile> <realm> <budget>\n" as *u8, 72)
334 sys_exit(0)
335 return 0
336 }
337 let port: i64 = iv_atoi(argv[1] as *u8)
338 let keysfile: *u8 = argv[2] as *u8
339 let storefile: *u8 = argv[3] as *u8
340 let realm: *u8 = argv[4] as *u8
341 let budget: i64 = iv_atoi(argv[5] as *u8)
342 let realm_n: i64 = iv_slen(realm)
343
344 // fail-fast: arm the realm context at STARTUP (Rule 20) -- a daemon that starts must be ready.
345 let oprf_seed: *u8 = sys_mmap(32)
346 let akp: *u8 = sys_mmap(32)
347 let akb: *u8 = sys_mmap(33)
348 let edp: *u8 = sys_mmap(32)
349 let edb: *u8 = sys_mmap(32)
350 if nx_uas_server_keys_load_or_init(keysfile, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { sys_write(2, "FATAL: server-key bundle\n" as *u8, 24); sys_exit(2); return 2 }
351 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
352 if nx_auth_context_init(ctx, realm, realm_n, realm, realm_n, storefile as i64, oprf_seed, edp, edb, IV_SESS_TTL, IV_MAGIC_8192, 1, 1, 5, 1) != NX_MAUTH_OK { sys_write(2, "FATAL: context init\n" as *u8, 20); sys_exit(3); return 3 }
353
354 // LOOPBACK ONLY. The first cut bound INADDR_ANY and was answering from another host on the LAN
355 // within seconds of starting -- unauthenticated, with an SSRF-capable fetcher behind it.
356 let addr: *u8 = sys_mmap(16)
357 if nx_http_server_addr_loopback(addr, port) != 16 { sys_write(2, "FATAL: addr\n" as *u8, 12); sys_exit(4); return 4 }
358 let lv: *i64 = sys_mmap(8) as *i64
359 let lfd: i64 = nx_http_server_listen(addr, 64, lv)
360 if lfd < 0 { sys_write(2, "FATAL: listen\n" as *u8, 14); sys_exit(4); return 4 }
361 sys_write(1, "INVENTORY-UP\n" as *u8, 13)
362
363 // Buffers allocated ONCE and reused, not per request: a 4 MiB page buffer mmap'd per connection is
364 // how a long-lived daemon grows without bound. nx_status_daemon's shape, for the same reason.
365 let req: *u8 = sys_mmap(IV_REQCAP)
366 let out: *u8 = sys_mmap(IV_OUTCAP)
367 let page: *u8 = sys_mmap(IV_PAGECAP)
368 let scan: *u8 = sys_mmap(IV_OUTCAP)
369 let body: *u8 = sys_mmap(IV_OUTCAP)
370 var served: i64 = 0
371 while served < budget {
372 let av: *i64 = sys_mmap(8) as *i64
373 let cfd: i64 = nx_http_server_accept_one(lfd, av)
374 if cfd < 0 { served = served + 1 }
375 if cfd >= 0 {
376 let om: *i64 = sys_mmap(8) as *i64
377 let opo: *i64 = sys_mmap(8) as *i64
378 let opl: *i64 = sys_mmap(8) as *i64
379 let ocl: *i64 = sys_mmap(8) as *i64
380 let obo: *i64 = sys_mmap(8) as *i64
381 let orn: *i64 = sys_mmap(8) as *i64
382 let rrc: i64 = nx_http_server_read_request(cfd, req, IV_REQCAP, om, opo, opl, ocl, obo, orn)
383 if rrc == NXS_OK {
384 let o: i64 = iv_handle(ctx, req, orn[0], served, page, scan, body, out)
385 nx_http_server_send_response_nokeep_close(cfd, out, o)
386 }
387 sys_close(cfd)
388 served = served + 1
389 }
390 }
391 sys_close(lfd)
392 sys_exit(0)
393 return 0
394}