code wiki / bin / nx_sites_daemon.nx

nx_sites_daemon.nx source

↩ module page · 751 lines · 46494 B

1// nx_sites_daemon.nx -- sovereign multi-vhost HTTPS daemon (pure Nishi, 2// silicon-up). Serves andelinwest.com + nishifamily.com (+ /wiki) by 3// Host-header / path dispatch over the PROVEN TLS 1.3 + Ed25519 path. 4// 5// Operator 2026-05-28 (full autonomy): "get andelinwest.com and 6// nishifamily.com/wiki up ... near hundred percent site uptime." 7// 8// Reuses only the proven primitives (run_ed25519 handshake + app_recv + 9// app_send) the ed25519 test daemon validated end-to-end. Avoids the 10// mv daemon's nx_wiki_route_dispatch (which returns empty on the NAS). 11// Dispatch is simple substring matching on the decrypted request -- 12// reliable for placeholders. Real wiki engine swaps in later behind 13// the same vhost routing. 14// 15// M6 anti-pretend-stub: each page SELF-LABELS as a launch placeholder 16// and shows real structure (andelinwest names its compliance blockers; 17// the wiki lists the actual shipped substrate components). 18 19import "nx_syscalls.nx" 20import "nx_csprng.nx" 21import "nx_http_server.nx" 22import "nx_tls13_server_session.nx" 23import "nx_tls13_server_session_run.nx" 24import "nx_tls13_server_session_app_data.nx" 25import "nx_sites_telemetry.nx" 26import "wiki/nx_wiki_status.nx" 27import "wiki/nx_wiki_article.nx" 28import "wiki/nx_wiki_queue.nx" 29import "_hdl_build/nx_host_router.nx" 30// Route B: SNI -> per-domain wildcard-cert selection (shared with the gate). 31import "nx_sni_cert_select.nx" 32// L5 dynamic: reverse-proxy /research -> the sovereign library daemon (:8095). 33import "nx_http_proxy.nx" 34 35const NX_SD_PORT: i64 = 8443 36const NX_SD_BUDGET: i64 = 100000000 37// Fork-per-connection concurrency cap. Bounds live handler children so a 38// scanner flood can't fork-bomb the box; the parent block-reaps one child 39// when at the cap before forking again. 40const NX_SD_MAX_CHILDREN: i64 = 64 41// Browser-trusted Let's Encrypt ECDSA P-256 chain (SAN andelinwest.com + 42// nishifamily.com) + its P-256 private key. Replaces the Ed25519 43// self-signed cert that all browsers reject (Ed25519 server certs are 44// unsupported by Chrome/Firefox/Safari -> "SSL protocol error"). 45// CERT_PATH is the CONCATENATED-DER fullchain (leaf + LE intermediates), 46// leaf first; the TLS run splits it into one CertificateEntry per cert 47// so clients can build a path to a root in their trust store. 48const NX_SD_CERT_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_fullchain.der" as *u8 49const NX_SD_PRIV_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_ecdsa_key.bin" as *u8 50// Route B (SNI-aware TLS): TWO per-domain wildcard chains loaded at startup, one chosen 51// per handshake by the ClientHello SNI. Each is a concat-DER fullchain (leaf-first) + a 52// 32-byte P-256 ECDSA private scalar -- same on-disk format as the legacy pair above. 53// nishifamily = the DEFAULT (SAN *.nishifamily.com + nishifamily.com) 54// andelinwest = SAN *.andelinwest.com + andelinwest.com 55// Operator deploys the four files at these paths; absent files degrade fail-safe (see main). 56const NX_SD_CERT_NISHI: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_nishifamily_fullchain.der" as *u8 57const NX_SD_PRIV_NISHI: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_nishifamily_ecdsa_key.bin" as *u8 58const NX_SD_CERT_ANDELIN: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_andelinwest_fullchain.der" as *u8 59const NX_SD_PRIV_ANDELIN: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_andelinwest_ecdsa_key.bin" as *u8 60// MSG_PEEK (recvfrom flag 0x2): read the cleartext ClientHello WITHOUT consuming it, so the 61// proven handshake re-reads the same bytes -> the SNI peek is invisible to nx_tls13_server_session_run. 62const NX_SD_MSG_PEEK: i64 = 2 63const NX_SD_SNI_PEEK_CAP: i64 = 8192 64// Sovereign hosting sites table (Host -> doc root). Per-request file serve = HOT content. 65const NX_SD_SITES_CONF: *u8 = "/volume1/homes/elderwesto/nishihost/sites.conf" as *u8 66// SENSE layer: append-only per-connection telemetry log (self-improving 67// system). Opened once at startup; written fire-and-forget per request. 68const NX_SD_TELEMETRY_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/sites_telemetry.log" as *u8 69// Keep-alive: max requests served on one TLS session before we close it 70// (bounds a single client's hold on a forked child); response build cap. 71const NX_SD_MAX_REQ_PER_CONN: i64 = 64 72const NX_SD_OUTCAP: i64 = 16384 73// Hoisted buffer for the live /wiki/status scorecard response (rendered 74// once at startup by the real wiki engine; served verbatim). 75const NX_SD_STATUS_CAP: i64 = 65536 76// Hoisted buffers for the Gitea x Wikipedia component articles + index. 77const NX_SD_ART_CAP: i64 = 65536 78// Per-request buffer for the DYNAMIC routes (work queue) rendered in the child. 79const NX_SD_DYN_CAP: i64 = 65536 80// Favicon: answer instantly with 204 (no body) so the browser's parallel 81// favicon fetch doesn't cost a second handshake / hang. 82const RESP_FAVICON: *u8 = "HTTP/1.1 204 No Content\r\nConnection: keep-alive\r\n\r\n" as *u8 83 84const BODY_ANDELIN: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Andelin West Law - Coming Soon</title><style>body{font-family:Georgia,serif;max-width:680px;margin:8vh auto;padding:0 24px;color:#1a1a2e;line-height:1.6}h1{font-size:2rem}.tag{color:#555}.note{margin-top:2rem;padding:16px 20px;background:#f4f4f8;border-left:4px solid #2a4d8f;font-size:.95rem}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Andelin West Law</h1><p class=\"tag\">A modern legal practice. Site launching soon.</p><div class=\"note\"><strong>Placeholder page.</strong> Served by the sovereign Nishi substrate. The full site is pending attorney review of state-bar advertising rules, ADA accessibility conformance, and the privacy notice before any client-facing or intake content goes live.</div><p class=\"foot\">Served bits-up by the Nishi stack &mdash; TLS 1.3 + Ed25519, no third-party web server.</p></body></html>\n" as *u8 85 86const BODY_WIKI: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Nishi Ecosystem Wiki</title><style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:760px;margin:6vh auto;padding:0 24px;color:#161622;line-height:1.65}h1{font-size:2.1rem}h2{font-size:1.1rem;margin-top:2rem;color:#2a4d8f}ul{padding-left:1.2rem}li{margin:.35rem 0}.note{padding:14px 18px;background:#eef2fb;border-left:4px solid #2a4d8f;border-radius:4px;font-size:.95rem}code{background:#f0f0f4;padding:1px 5px;border-radius:3px;font-size:.9em}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Nishi Ecosystem Wiki</h1><p class=\"note\"><strong>Launch placeholder &mdash; the live wiki engine is coming online.</strong> What you are reading is itself served bits-up by the substrate below: no nginx, no OpenSSL, no libc.</p><p style=\"margin:1.2rem 0\"><a href=\"/wiki/status\"><strong>&#9656; Live stack scorecard &rarr;</strong></a> &nbsp; <a href=\"/wiki/components\"><strong>&#9656; Component articles (Gitea&times;Wikipedia) &rarr;</strong></a></p><h2>Shipped substrate</h2><ul><li>Self-hosting NishiLang compiler (RV64 + x86_64), bits-up from C bootstrap</li><li>TLS 1.3 server + Ed25519 signatures (RFC 8446 / 8032) &mdash; serving this page now</li><li>SHA-256/512, ChaCha20-Poly1305, AES-GCM, HKDF, Argon2id &mdash; all KAT-verified</li><li>Sovereign secrets vault (HashiCorp-class, ChaCha20-Poly1305 at rest)</li><li>Multi-vhost HTTPS daemon with DHCP-stable routing + auto-healing watchdog</li></ul><h2>In progress</h2><ul><li>Full wiki engine (search index, doc store, markdown render)</li><li>Modern auth (OPAQUE PAKE + BIP39 recovery, no cookies)</li><li>Multi-agent concurrent editing (CRDT, sovereign)</li></ul><p class=\"foot\">nishifamily.com/wiki &mdash; bits-up sovereign hosting on the west NAS.</p></body></html>\n" as *u8 87 88const BODY_FAMILY: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Nishi Family</title><style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:680px;margin:8vh auto;padding:0 24px;color:#161622;line-height:1.65;text-align:center}h1{font-size:2.3rem}.tag{color:#555;font-size:1.05rem}a{color:#2a4d8f}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Nishi Family</h1><p class=\"tag\">The family hub &mdash; home of the Nishi sovereign ecosystem.</p><p><a href=\"/wiki\">Explore the Nishi Ecosystem Wiki &rarr;</a></p><p class=\"foot\">Served bits-up by the Nishi stack &mdash; TLS 1.3 + Ed25519, no third-party web server.</p></body></html>\n" as *u8 89 90func sd_strlen(s: *u8) -> i64 { 91 var n: i64 = 0 92 while s[n] != 0 { n = n + 1 } 93 return n 94} 95 96// substring search: returns 1 if needle (nlen) occurs in hay[0..hlen). 97func sd_contains(hay: *u8, hlen: i64, needle: *u8, nlen: i64) -> i64 { 98 if nlen <= 0 { return 0 } 99 if hlen < nlen { return 0 } 100 var i: i64 = 0 101 let last: i64 = hlen - nlen 102 while i <= last { 103 var j: i64 = 0 104 var ok: i64 = 1 105 while j < nlen { 106 if hay[i + j] != needle[j] { ok = 0; j = nlen } else { j = j + 1 } 107 } 108 if ok == 1 { return 1 } 109 i = i + 1 110 } 111 return 0 112} 113 114// Return the index just past the first CRLFCRLF (HTTP header/body split), or 115// hlen if absent. Used to locate a POST body in the decrypted request. 116func sd_body_start(hay: *u8, hlen: i64) -> i64 { 117 if hlen < 4 { return hlen } 118 var i: i64 = 0 119 let last: i64 = hlen - 4 120 while i <= last { 121 if hay[i] == (0x0D as u8) { if hay[i + 1] == (0x0A as u8) { if hay[i + 2] == (0x0D as u8) { if hay[i + 3] == (0x0A as u8) { 122 return i + 4 123 } } } } 124 i = i + 1 125 } 126 return hlen 127} 128 129// --- HTTP/1.1 response construction (keep-alive requires Content-Length 130// so the client knows where the body ends and can reuse the connection) --- 131func sd_u64_to_dec(n: i64, buf: *u8) -> i64 { 132 if n == 0 { buf[0] = 48; return 1 } 133 var d: i64 = 0 134 var x: i64 = n 135 while x > 0 { d = d + 1; x = x / 10 } 136 var i: i64 = d - 1 137 x = n 138 while i >= 0 { 139 buf[i] = (48 + (x % 10)) as u8 140 x = x / 10 141 i = i - 1 142 } 143 return d 144} 145 146func sd_append(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { 147 var i: i64 = 0 148 while i < n { dst[off + i] = src[i]; i = i + 1 } 149 return off + n 150} 151 152func sd_append_str(dst: *u8, off: i64, s: *u8) -> i64 { 153 return sd_append(dst, off, s, sd_strlen(s)) 154} 155 156// Build a complete HTTP/1.1 keep-alive response (status + headers + 157// Content-Length + body) into out; returns total length. Built ONCE per 158// page at startup into a hoisted buffer, then sent verbatim per request. 159func sd_build_resp(out: *u8, ctype: *u8, body: *u8, body_len: i64) -> i64 { 160 var w: i64 = 0 161 w = sd_append_str(out, w, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8) 162 w = sd_append_str(out, w, ctype) 163 w = sd_append_str(out, w, "\r\nContent-Length: " as *u8) 164 let dbuf: *u8 = sys_mmap(24) 165 let dn: i64 = sd_u64_to_dec(body_len, dbuf) 166 w = sd_append(out, w, dbuf, dn) 167 w = sd_append_str(out, w, "\r\nConnection: keep-alive\r\nX-Served-By: nishi-substrate\r\n\r\n" as *u8) 168 w = sd_append(out, w, body, body_len) 169 return w 170} 171 172// Load a concatenated-DER fullchain; returns the buffer (or 0 on failure) and writes its 173// length into len_box. Validates a sane minimum size (a real LE leaf is ~900+ bytes). 174func sd_load_chain(path: *u8, len_box: *i64) -> *u8 { 175 len_box[0] = 0 176 let p: *u8 = sys_read_file(path, len_box) 177 if (p as i64) == 0 { return 0 as *u8 } 178 if len_box[0] < 100 { return 0 as *u8 } 179 return p 180} 181 182// Load a 32-byte P-256 ECDSA private scalar; returns the buffer or 0 if absent / wrong size. 183func sd_load_key(path: *u8) -> *u8 { 184 let lb: *i64 = (sys_mmap(8)) as *i64 185 lb[0] = 0 186 let p: *u8 = sys_read_file(path, lb) 187 if (p as i64) == 0 { return 0 as *u8 } 188 if lb[0] != 32 { return 0 as *u8 } 189 return p 190} 191 192// ---- DATA-DRIVEN SNI cert table (R1b 2026-07-15) ------------------------------------------------- 193// Every domain we serve HTTPS for is one manifest row (suffix + fullchain-der + ecdsa-key), so adding 194// a domain = drop its le_<domain>_* + a line, NEVER an edge recompile. Row 0 = the DEFAULT cert. 195const NX_SD_CERT_MANIFEST: *u8 = "/volume1/homes/elderwesto/nishihost/certs/sni_certs.manifest" as *u8 196const NX_SD_CERT_MAX: i64 = 32 197 198// Parse a positive decimal from a NUL-terminated string (stops at first non-digit). Used for argv port. 199func sd_atoi(s: *u8) -> i64 { 200 var v: i64 = 0 201 var i: i64 = 0 202 var go: i64 = 1 203 while go == 1 { 204 let c: i64 = s[i] as i64 205 if c < 48 { go = 0 } 206 else { if c > 57 { go = 0 } 207 else { v = v * 10 + (c - 48); i = i + 1 } } 208 } 209 return v 210} 211 212// Copy one whitespace-delimited field from buf[pos..end) into out (NUL-terminated). Skips leading 213// spaces/tabs. Returns the position just past the field, or -1 if the line has no field (blank/'#'/EOL). 214func sd_field(buf: *u8, pos: i64, end: i64, out: *u8, out_cap: i64) -> i64 { 215 var p: i64 = pos 216 var skip: i64 = 1 217 while skip == 1 { 218 if p >= end { skip = 0 } 219 else { 220 let c: i64 = buf[p] as i64 221 if c == 32 { p = p + 1 } 222 else { if c == 9 { p = p + 1 } else { skip = 0 } } 223 } 224 } 225 if p >= end { return 0 - 1 } 226 let c0: i64 = buf[p] as i64 227 if c0 == 10 { return 0 - 1 } 228 if c0 == 13 { return 0 - 1 } 229 if c0 == 35 { return 0 - 1 } 230 var o: i64 = 0 231 var cp: i64 = 1 232 while cp == 1 { 233 if p >= end { cp = 0 } 234 else { 235 let c: i64 = buf[p] as i64 236 if c == 32 { cp = 0 } 237 else { if c == 9 { cp = 0 } 238 else { if c == 10 { cp = 0 } 239 else { if c == 13 { cp = 0 } 240 else { 241 if o < out_cap - 1 { out[o] = buf[p] as u8; o = o + 1 } 242 p = p + 1 243 } } } } 244 } 245 } 246 out[o] = 0 as u8 247 return p 248} 249 250// Advance past the current line (to the byte after the next '\n', or to end). 251func sd_next_line(buf: *u8, pos: i64, end: i64) -> i64 { 252 var p: i64 = pos 253 var go: i64 = 1 254 while go == 1 { 255 if p >= end { go = 0 } 256 else { if (buf[p] as i64) == 10 { p = p + 1; go = 0 } else { p = p + 1 } } 257 } 258 return p 259} 260 261// Parse the manifest into parallel arrays; loads each row's cert+key. Rows whose cert/key fail to load 262// are skipped (fail-safe). Returns the number of rows loaded (0 => caller uses the legacy fallback). 263func sd_load_cert_table(man: *u8, man_n: i64, 264 suf_ptrs: *i64, suf_lens: *i64, 265 cert_ptrs: *i64, cert_lens: *i64, key_ptrs: *i64, 266 max: i64) -> i64 { 267 var count: i64 = 0 268 var pos: i64 = 0 269 while pos < man_n { 270 if count >= max { pos = man_n } 271 else { 272 let sufb: *u8 = sys_mmap(256) 273 let p1: i64 = sd_field(man, pos, man_n, sufb, 256) 274 if p1 < 0 { pos = sd_next_line(man, pos, man_n) } 275 else { 276 let certp: *u8 = sys_mmap(512) 277 let p2: i64 = sd_field(man, p1, man_n, certp, 512) 278 if p2 < 0 { pos = sd_next_line(man, pos, man_n) } 279 else { 280 let keyp: *u8 = sys_mmap(512) 281 let p3: i64 = sd_field(man, p2, man_n, keyp, 512) 282 if p3 < 0 { pos = sd_next_line(man, pos, man_n) } 283 else { 284 let clb: *i64 = (sys_mmap(8)) as *i64 285 clb[0] = 0 286 let cbuf: *u8 = sd_load_chain(certp, clb) 287 let kbuf: *u8 = sd_load_key(keyp) 288 if (cbuf as i64) == 0 { pos = sd_next_line(man, pos, man_n) } 289 else { if (kbuf as i64) == 0 { pos = sd_next_line(man, pos, man_n) } 290 else { 291 var sl: i64 = 0 292 while sufb[sl] != (0 as u8) { sl = sl + 1 } 293 suf_ptrs[count] = sufb as i64 294 suf_lens[count] = sl 295 cert_ptrs[count] = cbuf as i64 296 cert_lens[count] = clb[0] 297 key_ptrs[count] = kbuf as i64 298 count = count + 1 299 pos = sd_next_line(man, pos, man_n) 300 } } 301 } 302 } 303 } 304 } 305 } 306 return count 307} 308 309func main(argc: i64, argv: *i64) -> i64 { 310 // ---- Route B: load BOTH per-domain wildcard chains at startup ---- 311 // The NISHI (nishifamily) pair is the DEFAULT served for any host that does not match a 312 // known domain (and whenever the SNI is absent/garbled). Fail-safe: if the new 313 // nishifamily files are not deployed yet, fall back to the LEGACY single chain so the 314 // existing single-domain handshake never regresses (rule 20 fail-fast only if NOTHING loads). 315 let cert_len_box: *i64 = (sys_mmap(8)) as *i64 316 var cert_nishi: *u8 = sd_load_chain(NX_SD_CERT_NISHI, cert_len_box) 317 var ecdsa_nishi: *u8 = sd_load_key(NX_SD_PRIV_NISHI) 318 if (cert_nishi as i64) == 0 { cert_nishi = sd_load_chain(NX_SD_CERT_PATH, cert_len_box) } 319 if (ecdsa_nishi as i64) == 0 { ecdsa_nishi = sd_load_key(NX_SD_PRIV_PATH) } 320 if (cert_nishi as i64) == 0 { return 2 } 321 if (ecdsa_nishi as i64) == 0 { return 3 } 322 let cert_nishi_len: i64 = cert_len_box[0] 323 324 // The ANDELIN (andelinwest) pair. Fail-safe: if its files are absent, serve the default 325 // pair instead (a valid TLS handshake still completes -- the cert just won't cover an 326 // andelinwest host -- rather than crash or drop the connection). 327 let andelin_len_box: *i64 = (sys_mmap(8)) as *i64 328 var cert_andelin: *u8 = sd_load_chain(NX_SD_CERT_ANDELIN, andelin_len_box) 329 var ecdsa_andelin: *u8 = sd_load_key(NX_SD_PRIV_ANDELIN) 330 var cert_andelin_len: i64 = andelin_len_box[0] 331 if (cert_andelin as i64) == 0 { cert_andelin = cert_nishi; cert_andelin_len = cert_nishi_len; ecdsa_andelin = ecdsa_nishi } 332 if (ecdsa_andelin as i64) == 0 { cert_andelin = cert_nishi; cert_andelin_len = cert_nishi_len; ecdsa_andelin = ecdsa_nishi } 333 334 // ---- DATA-DRIVEN cert table: load the manifest (row 0 = default). Fall back to the legacy 335 // nishi+andelin pair if the manifest is absent/empty so a fresh box never regresses (rule 20). ---- 336 let cert_suf_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 337 let cert_suf_lens: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 338 let cert_buf_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 339 let cert_buf_lens: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 340 let cert_key_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 341 let man_box: *i64 = (sys_mmap(8)) as *i64 342 man_box[0] = 0 343 let man_buf: *u8 = sys_read_file(NX_SD_CERT_MANIFEST, man_box) 344 var cert_count: i64 = 0 345 if (man_buf as i64) != 0 { 346 cert_count = sd_load_cert_table(man_buf, man_box[0], cert_suf_ptrs, cert_suf_lens, cert_buf_ptrs, cert_buf_lens, cert_key_ptrs, NX_SD_CERT_MAX) 347 } 348 if cert_count == 0 { 349 cert_suf_ptrs[0] = ("nishifamily.com" as *u8) as i64 350 cert_suf_lens[0] = 15 351 cert_buf_ptrs[0] = cert_nishi as i64 352 cert_buf_lens[0] = cert_nishi_len 353 cert_key_ptrs[0] = ecdsa_nishi as i64 354 cert_suf_ptrs[1] = ("andelinwest.com" as *u8) as i64 355 cert_suf_lens[1] = 15 356 cert_buf_ptrs[1] = cert_andelin as i64 357 cert_buf_lens[1] = cert_andelin_len 358 cert_key_ptrs[1] = ecdsa_andelin as i64 359 cert_count = 2 360 } 361 sys_write(1, "nishi sites daemon: SNI cert table rows=" as *u8, 40) 362 let ccb: *u8 = sys_mmap(24) 363 let ccn: i64 = sd_u64_to_dec(cert_count, ccb) 364 sys_write(1, ccb, ccn) 365 sys_write(1, "\n" as *u8, 1) 366 367 // Load the sovereign hosting sites table once at startup. Graceful: if absent, 368 // sites_cfg_n stays 0 and every host uses the legacy baked routing (no regression). 369 let sd_cfg_box: *i64 = (sys_mmap(8)) as *i64 370 sd_cfg_box[0] = 0 371 let sites_cfg: *u8 = sys_read_file(NX_SD_SITES_CONF, sd_cfg_box) 372 var sites_cfg_n: i64 = 0 373 if (sites_cfg as i64) != 0 { sites_cfg_n = sd_cfg_box[0] } 374 375 // Bind port: argv[1] overrides the default (lets a test instance run on :8543 beside the live :8443). 376 var port: i64 = NX_SD_PORT 377 if argc > 1 { let ap: i64 = sd_atoi(argv[1] as *u8); if ap > 0 { port = ap } } 378 let addr_buf: *u8 = sys_mmap(16) 379 nx_http_server_addr_any(addr_buf, port) 380 let lv: *i64 = (sys_mmap(8)) as *i64 381 let lfd: i64 = nx_http_server_listen(addr_buf, 16, lv) 382 if lfd < 0 { return 4 } 383 sys_write(1, "nishi sites daemon: data-driven SNI cert table + sites.conf docroot, listening\n" as *u8, 78) 384 385 // Build full HTTP/1.1 keep-alive responses ONCE into hoisted buffers 386 // (constant memory; sent verbatim for every matching request). 387 let ctype_html: *u8 = "text/html; charset=utf-8" as *u8 388 let nb_andelin: i64 = sd_strlen(BODY_ANDELIN) 389 let nb_wiki: i64 = sd_strlen(BODY_WIKI) 390 let nb_family: i64 = sd_strlen(BODY_FAMILY) 391 let resp_andelin: *u8 = sys_mmap(nb_andelin + 256) 392 let resp_wiki: *u8 = sys_mmap(nb_wiki + 256) 393 let resp_family: *u8 = sys_mmap(nb_family + 256) 394 let n_andelin: i64 = sd_build_resp(resp_andelin, ctype_html, BODY_ANDELIN, nb_andelin) 395 let n_wiki: i64 = sd_build_resp(resp_wiki, ctype_html, BODY_WIKI, nb_wiki) 396 let n_family: i64 = sd_build_resp(resp_family, ctype_html, BODY_FAMILY, nb_family) 397 let n_favicon: i64 = sd_strlen(RESP_FAVICON) 398 399 // /wiki/status -- the LIVE sovereign scorecard, rendered ONCE at startup 400 // by the real wiki engine (nx_wiki_status_handle) into a hoisted buffer 401 // (constant memory; served verbatim per request). Self-contained: needs 402 // NO doc-store, so it works on the NAS where the content store is empty 403 // (the reason the full dispatcher returned empty here before). Graceful: 404 // on render failure, falls back to the static wiki placeholder. 405 var resp_wstatus: *u8 = sys_mmap(NX_SD_STATUS_CAP) 406 let wstatus_box: *i64 = (sys_mmap(8)) as *i64 407 wstatus_box[0] = 0 408 let wstatus_rc: i64 = nx_wiki_status_handle(resp_wstatus, NX_SD_STATUS_CAP, wstatus_box) 409 var n_wstatus: i64 = wstatus_box[0] 410 if wstatus_rc != 0 { resp_wstatus = resp_wiki; n_wstatus = n_wiki } 411 if n_wstatus <= 0 { resp_wstatus = resp_wiki; n_wstatus = n_wiki } 412 413 // Component articles (Gitea x Wikipedia) + index, each rendered ONCE at 414 // startup by the wiki engine into a hoisted buffer (constant memory; 415 // served verbatim). Self-contained -- no doc-store needed. Graceful 416 // fallback to the wiki placeholder on any render error. 417 var resp_art_crawler: *u8 = sys_mmap(NX_SD_ART_CAP) 418 let acr_box: *i64 = (sys_mmap(8)) as *i64 419 acr_box[0] = 0 420 let acr_rc: i64 = nx_wiki_article_handle("crawler" as *u8, 7, resp_art_crawler, NX_SD_ART_CAP, acr_box) 421 var n_art_crawler: i64 = acr_box[0] 422 if acr_rc != 0 { resp_art_crawler = resp_wiki; n_art_crawler = n_wiki } 423 if n_art_crawler <= 0 { resp_art_crawler = resp_wiki; n_art_crawler = n_wiki } 424 425 var resp_art_x25519: *u8 = sys_mmap(NX_SD_ART_CAP) 426 let axx_box: *i64 = (sys_mmap(8)) as *i64 427 axx_box[0] = 0 428 let axx_rc: i64 = nx_wiki_article_handle("x25519" as *u8, 6, resp_art_x25519, NX_SD_ART_CAP, axx_box) 429 var n_art_x25519: i64 = axx_box[0] 430 if axx_rc != 0 { resp_art_x25519 = resp_wiki; n_art_x25519 = n_wiki } 431 if n_art_x25519 <= 0 { resp_art_x25519 = resp_wiki; n_art_x25519 = n_wiki } 432 433 var resp_art_ranking: *u8 = sys_mmap(NX_SD_ART_CAP) 434 let ark_box: *i64 = (sys_mmap(8)) as *i64 435 ark_box[0] = 0 436 let ark_rc: i64 = nx_wiki_article_handle("ranking-math" as *u8, 12, resp_art_ranking, NX_SD_ART_CAP, ark_box) 437 var n_art_ranking: i64 = ark_box[0] 438 if ark_rc != 0 { resp_art_ranking = resp_wiki; n_art_ranking = n_wiki } 439 if n_art_ranking <= 0 { resp_art_ranking = resp_wiki; n_art_ranking = n_wiki } 440 441 var resp_art_wiki: *u8 = sys_mmap(NX_SD_ART_CAP) 442 let awe_box: *i64 = (sys_mmap(8)) as *i64 443 awe_box[0] = 0 444 let awe_rc: i64 = nx_wiki_article_handle("wiki-engine" as *u8, 11, resp_art_wiki, NX_SD_ART_CAP, awe_box) 445 var n_art_wiki: i64 = awe_box[0] 446 if awe_rc != 0 { resp_art_wiki = resp_wiki; n_art_wiki = n_wiki } 447 if n_art_wiki <= 0 { resp_art_wiki = resp_wiki; n_art_wiki = n_wiki } 448 449 var resp_components: *u8 = sys_mmap(NX_SD_ART_CAP) 450 let aci_box: *i64 = (sys_mmap(8)) as *i64 451 aci_box[0] = 0 452 let aci_rc: i64 = nx_wiki_components_handle(resp_components, NX_SD_ART_CAP, aci_box) 453 var n_components: i64 = aci_box[0] 454 if aci_rc != 0 { resp_components = resp_wiki; n_components = n_wiki } 455 if n_components <= 0 { resp_components = resp_wiki; n_components = n_wiki } 456 457 // /wiki/projects is discovered live by the standalone nx_pm_discover tool 458 // (the HTTP client hangs the daemon's compile graph). The daemon will 459 // serve the tool's emitted page from a file; until that file-serve is 460 // wired, /wiki/projects falls back to the wiki placeholder. 461 var resp_projects: *u8 = resp_wiki 462 var n_projects: i64 = n_wiki 463 464 // CONSTANT MEMORY: every per-connection buffer is allocated ONCE 465 // here and reused for every connection. NishiLang is GC-free + has 466 // no hidden allocator -- so allocating inside the accept loop would 467 // be a programmer-side leak (the language won't reclaim it). Hoist 468 // everything; the daemon's RSS is then fixed for its whole lifetime 469 // and cannot grow with request count. 470 let server_random: *u8 = sys_mmap(32) 471 let server_x25519_priv: *u8 = sys_mmap(32) 472 let rec_buf: *u8 = sys_mmap(20480) 473 let plain: *u8 = sys_mmap(20480) 474 let out_rec: *u8 = sys_mmap(NX_SD_OUTCAP) 475 // Route B: hoisted SNI-peek buffers (constant memory; children get a COW copy on write). 476 let sni_peek: *u8 = sys_mmap(NX_SD_SNI_PEEK_CAP) 477 let sni_host: *u8 = sys_mmap(256) 478 // Dynamic-route render buffer (work queue / request 303). Hoisted in the 479 // parent; each forked child gets its own COW copy. 480 let dyn_buf: *u8 = sys_mmap(NX_SD_DYN_CAP) 481 // SENSE layer (self-improving system): peer-addr buffer for 482 // LAN/internet classification, a hoisted telemetry line buffer, and 483 // one append-mode log fd held open for the daemon's whole lifetime 484 // (no per-connection open/close in the hot path). A failed open 485 // (tel_fd < 0) simply disables telemetry -- it never blocks serving. 486 let sock_addr: *u8 = sys_mmap(64) 487 let sock_len: *i64 = (sys_mmap(8)) as *i64 488 let tel_buf: *u8 = sys_mmap(256) 489 // ---- HOISTED OUT OF THE CHILD'S PER-REQUEST LOOP (2026-08-16, debt 1786927535) ---- 490 // These five were mmap'd per REQUEST inside `while keep == 1`, and NX_SD_MAX_REQ_PER_CONN is 64, so 491 // ONE connection cost up to 5x64 = 320 kernel VMAs and ~1.3 MB of pages living until the child exits. 492 // At NX_SD_MAX_CHILDREN=64 that is up to 20,480 VMAs against vm.max_map_count 65530 -- the ceiling 493 // nx_syscalls names, past which mmap returns -ENOMEM and callers write through the failed pointer. 494 // ★AN 8-BYTE mmap COSTS A WHOLE PAGE AND A WHOLE VMA: the request size is not what makes it expensive. 495 // ★THE INVARIANT WAS ALREADY WRITTEN DOWN AND VIOLATED -- nx_http_redirect_daemon.nx:15 says verbatim 496 // "nx_sites_daemon (PREVENT: no sys_mmap in the loop)". This restores it; it is not a new rule. 497 // Same fix already shipped in nx_lib_httpd: NEVER ALLOCATE IN A HOT LOOP, HOIST THE BUFFER. 498 // SAFE ON REUSE, CHECKED NOT ASSUMED: dnq/dnq2/rlb/mlb are each written [0]=0 at their use site 499 // before any read. rpath is safe because hr_req_path NUL-TERMINATES UNCONDITIONALLY 500 // (nx_host_router.nx:75 `out[j] = 0`) and the /research probe compares rpath[0..8] then rpath[9]: 501 // when the new path is shorter than 9 bytes the NUL lands at index j, and "/research"[j] is never 502 // NUL, so the compare FAILS AT j and can never match stale bytes left by the previous request on 503 // this connection. A FRESH mmap IS ZERO-FILLED AND A REUSED BUFFER IS NOT -- that difference is 504 // exactly what had to be checked before hoisting, and the terminator is what makes it safe. 505 // PRE-FORK so children inherit them copy-on-write: one page each for the whole process tree 506 // instead of one page per request. 507 let dnq: *i64 = (sys_mmap(8)) as *i64 508 let dnq2: *i64 = (sys_mmap(8)) as *i64 509 let rpath: *u8 = sys_mmap(2048) 510 let rlb: *i64 = (sys_mmap(8)) as *i64 511 let mlb: *i64 = (sys_mmap(8)) as *i64 512 let tel_fd: i64 = sys_openat_append(NX_SD_TELEMETRY_PATH, 420) 513 514 // FORK-PER-CONNECTION: each accepted connection is handled in its own 515 // child, which exits when done. The OS reclaims everything the child 516 // allocated -- so the P-256 field ops' per-operation allocations cannot 517 // accumulate in a long-lived process (the prior OOM -> supervisor- 518 // restart cycle). Bonus: connections are served CONCURRENTLY rather 519 // than one-at-a-time. csprng is fork-safe (getrandom / urandom per 520 // call), so children never share key material. Concurrency is bounded 521 // by NX_SD_MAX_CHILDREN; the parent reaps exited children every accept. 522 // Listen-socket timeout so accept() returns periodically when idle -- 523 // lets the reap loop clear finished children even with no new traffic 524 // (otherwise the parent blocks in accept() while children zombie). 525 sys_set_socket_timeout(lfd, 5) 526 let reap_status: *i64 = (sys_mmap(8)) as *i64 527 var served: i64 = 0 528 var live: i64 = 0 529 while served < NX_SD_BUDGET { 530 // Reap exited children (non-blocking) every iteration -- including 531 // idle accept-timeouts -- so zombies never accumulate. 532 while sys_wait4(0 - 1, reap_status, 1) > 0 { live = live - 1 } 533 534 sock_len[0] = 16 535 let cfd: i64 = sys_accept_with_addr(lfd, sock_addr, sock_len) 536 if cfd < 0 { continue } // accept timeout (idle) or transient error 537 538 // At the cap: block-reap one child before forking another (throttle). 539 if live >= NX_SD_MAX_CHILDREN { 540 if sys_wait4(0 - 1, reap_status, 0) > 0 { live = live - 1 } 541 } 542 543 let pid: i64 = sys_fork() 544 if pid == 0 { 545 // ---- CHILD: handle exactly this connection, then exit ---- 546 sys_close(lfd) 547 // SENSE: classify the access path (observational only). 548 let acc_lan: i64 = nx_access_is_lan(sock_addr) 549 // PUBLIC-EXPOSURE HARDENING: 10s cap on every blocking read/write 550 // so a silent scanner can't pin this child forever. 551 sys_set_socket_timeout(cfd, 10) 552 // TCP_NODELAY (IPPROTO_TCP=6, TCP_NODELAY=1): disable Nagle on the 553 // connection. MEASURED: the PK crypto is only ~9ms/handshake yet 554 // curl saw ~150ms -- the ~140ms gap is Nagle holding our multi- 555 // record handshake flight until the client's delayed ACK (~40ms 556 // each). Without this, every page pays that stall. Per-socket, 557 // not inherited from the listener, so it must be set on cfd. 558 let nodelay: *u8 = sys_mmap(4) 559 nodelay[0] = 1 as u8; nodelay[1] = 0 as u8; nodelay[2] = 0 as u8; nodelay[3] = 0 as u8 560 sys_setsockopt(cfd, 6, 1, nodelay, 4) 561 nx_csprng_fill(server_random, 32) 562 nx_csprng_fill(server_x25519_priv, 32) 563 // ---- Route B: SNI-aware cert selection ---- 564 // Peek the cleartext ClientHello WITHOUT consuming it (MSG_PEEK), pick the 565 // per-domain wildcard pair by the SNI, then run the UNCHANGED handshake serving 566 // that pair. The peek never consumes bytes, so nx_tls13_server_session_run reads 567 // the exact same ClientHello it always did -> zero change to the proven handshake. 568 // Any peek failure / absent / unknown SNI -> the default (nishifamily) pair. 569 let pk_n: i64 = sys_recvfrom(cfd, sni_peek, NX_SD_SNI_PEEK_CAP, NX_SD_MSG_PEEK, 0 as *u8, 0 as *i64) 570 // DATA-DRIVEN: pick the cert-table row from the SNI (row 0 = default; unknown/absent -> 0). 571 var cert_idx: i64 = 0 572 if pk_n > 0 { cert_idx = scs_pick_from_clienthello_table(sni_peek, pk_n, sni_host, 256, cert_suf_ptrs, cert_suf_lens, cert_count) } 573 if cert_idx < 0 { cert_idx = 0 } 574 if cert_idx >= cert_count { cert_idx = 0 } 575 var sel_cert: *u8 = cert_buf_ptrs[cert_idx] as *u8 576 var sel_cert_len: i64 = cert_buf_lens[cert_idx] 577 var sel_key: *u8 = cert_key_ptrs[cert_idx] as *u8 578 let t_hs0: i64 = sys_now_us() 579 let hs_rc: i64 = nx_tls13_server_session_run( 580 cfd, server_random, server_x25519_priv, 581 sel_cert, sel_cert_len, sel_key) 582 let hs_us: i64 = sys_now_us() - t_hs0 583 var vhost_id: i64 = 0 584 var served_bytes: i64 = 0 585 if hs_rc > 0 { 586 let s: *Tls13ServerSession = hs_rc as *Tls13ServerSession 587 // KEEP-ALIVE: serve multiple requests on this one TLS session 588 // (one handshake per page, not one per file). Loop until the 589 // client closes / idle-times-out (cfd has a 10s timeout) or we 590 // hit the per-connection request cap. 591 var keep: i64 = 1 592 var nreq: i64 = 0 593 while keep == 1 { 594 if nreq >= NX_SD_MAX_REQ_PER_CONN { 595 keep = 0 596 } else { 597 let rec_n: i64 = sys_read(cfd, rec_buf, 20480) 598 if rec_n <= 0 { 599 keep = 0 600 } else { 601 let plain_n: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rec_n, plain, 20480) 602 if plain_n <= 0 { 603 keep = 0 604 } else { 605 // SAME EXPERIENCE LAN+INTERNET: dispatch by 606 // Host/path ONLY (never by acc_lan). Favicon 607 // first -- a favicon req to andelinwest.com also 608 // contains "andelinwest". 609 var resp: *u8 = resp_family 610 var resp_n: i64 = n_family 611 let is_favicon: i64 = sd_contains(plain, plain_n, "favicon" as *u8, 7) 612 if is_favicon == 1 { 613 resp = RESP_FAVICON; resp_n = n_favicon; vhost_id = 9 614 } else { 615 // DYNAMIC routes (rendered per-request in this child): the work 616 // queue read + the POST request append. These read/write the 617 // sovereign journal at request time, so they cannot be hoisted. 618 var dyn_done: i64 = 0 619 let is_postreq: i64 = sd_contains(plain, plain_n, "POST /wiki/request" as *u8, 18) 620 let is_queue: i64 = sd_contains(plain, plain_n, "/wiki/queue" as *u8, 11) 621 if is_postreq == 1 { 622 let bstart: i64 = sd_body_start(plain, plain_n) 623 dnq[0] = 0 // hoisted pre-fork; re-zeroed per request 624 let prc: i64 = nx_wiki_request_handle((plain as i64 + bstart) as *u8, plain_n - bstart, dyn_buf, NX_SD_DYN_CAP, dnq) 625 if prc == 0 { resp = dyn_buf; resp_n = dnq[0]; vhost_id = 10; dyn_done = 1 } 626 } 627 if dyn_done == 0 { if is_queue == 1 { 628 dnq2[0] = 0 // hoisted pre-fork; re-zeroed per request 629 let qrc: i64 = nx_wiki_queue_handle(dyn_buf, NX_SD_DYN_CAP, dnq2) 630 if qrc == 0 { resp = dyn_buf; resp_n = dnq2[0]; vhost_id = 11; dyn_done = 1 } 631 } } 632 // SOVEREIGN LIBRARY: /research(/*) -> reverse-proxy to the library 633 // daemon (:8095, 16K-taxon full-text). Dynamic backend, not a docroot. 634 // Additive + fail-safe: any non-/research path falls through unchanged. 635 if dyn_done == 0 { 636 // hoisted pre-fork; hr_req_path NUL-terminates unconditionally, so a 637 // shorter path cannot expose stale bytes to the 9-byte compare below 638 hr_req_path(plain, plain_n, rpath, 2048) 639 let rpre: *u8 = "/research" as *u8 640 var is_research: i64 = 1 641 var ri: i64 = 0 642 while ri < 9 { if rpath[ri] != rpre[ri] { is_research = 0; ri = 9 } else { ri = ri + 1 } } 643 if is_research == 1 { let rc9: i64 = rpath[9] as i64; if rc9 == 0 { is_research = 1 } else { if rc9 == 47 { is_research = 1 } else { is_research = 0 } } } 644 if is_research == 1 { 645 resp = dyn_buf 646 resp_n = nx_http_proxy_relay("127.0.0.1" as *u8, 9, 8095, "/research" as *u8, 9, plain, plain_n, dyn_buf, NX_SD_DYN_CAP) 647 vhost_id = 25 648 dyn_done = 1 649 } 650 } 651 // SOVEREIGN HOSTING: any host in sites.conf is served file-based 652 // (hot content -- push a file, live next request). Hosts NOT in the 653 // table fall through to the legacy baked routing below (the wiki). 654 if dyn_done == 0 { 655 if hr_known_host(sites_cfg, sites_cfg_n, plain, plain_n) == 1 { 656 resp = dyn_buf 657 resp_n = hr_serve(sites_cfg, sites_cfg_n, plain, plain_n, dyn_buf, NX_SD_DYN_CAP) 658 vhost_id = 20 659 dyn_done = 1 660 } 661 } 662 // HOT wiki dashboard: /wiki/roadmap served from a FILE per-request (living -- 663 // re-push the file -> live next request). Checked before the baked /wiki route so it wins. 664 if dyn_done == 0 { 665 if sd_contains(plain, plain_n, "/wiki/roadmap" as *u8, 13) == 1 { 666 rlb[0] = 0 // hoisted pre-fork; re-zeroed per request 667 let rbuf: *u8 = sys_read_file("/volume1/homes/elderwesto/nishihost/wiki/roadmap.html" as *u8, rlb) 668 // FREE THE PER-REQUEST FILE MAP. sys_read_file mmaps the WHOLE file 669 // (8408 B = 3 pages today) and this route is served PER REQUEST, so 670 // without this the child accumulated one fresh mapping and one fresh 671 // kernel VMA per hit until it exited. Safe BY CONSTRUCTION, not by hope: 672 // sd_build_resp COPIES into dyn_buf and resp points at dyn_buf, so rbuf 673 // is dead the moment it returns. Padding arithmetic lives in 674 // sys_free_file beside the +16 it mirrors, never retyped here. 675 if (rbuf as i64) != 0 { resp = dyn_buf; resp_n = sd_build_resp(dyn_buf, ctype_html, rbuf, rlb[0]); vhost_id = 30; dyn_done = 1; sys_free_file(rbuf, rlb[0]) } 676 } 677 } 678 // HOT /maps: nishifamily.com/maps -- the sovereign map surface (nx_geo_mapsvg 679 // renders the SVG world map; no third-party map lib). Served from a FILE per 680 // request (re-render -> live next request), same pattern as /wiki/roadmap. 681 if dyn_done == 0 { 682 if sd_contains(plain, plain_n, "/maps" as *u8, 5) == 1 { 683 mlb[0] = 0 // hoisted pre-fork; re-zeroed per request 684 let mbuf: *u8 = sys_read_file("/volume1/homes/elderwesto/nishihost/sites/nishifamily/maps/index.html" as *u8, mlb) 685 // Same paired free as /wiki/roadmap. This route is DEAD today -- 686 // sites/nishifamily/maps/index.html and its whole directory are ABSENT, 687 // so sys_read_file returns 0, this guard correctly declines, and the 688 // request SILENTLY falls through to the default page. A ROUTE WHOSE 689 // BACKING FILE IS ABSENT FAILS SILENTLY EXACTLY BECAUSE ITS GUARD IS 690 // CORRECT: the guard prevents the crash and thereby prevents the alarm. 691 // Filed as debt 1786927535 -- restore the content or delete the branch; 692 // the free is placed now so a restored file cannot reintroduce the leak. 693 if (mbuf as i64) != 0 { resp = dyn_buf; resp_n = sd_build_resp(dyn_buf, ctype_html, mbuf, mlb[0]); vhost_id = 31; dyn_done = 1; sys_free_file(mbuf, mlb[0]) } 694 } 695 } 696 if dyn_done == 0 { 697 let is_andelin: i64 = sd_contains(plain, plain_n, "andelinwest" as *u8, 11) 698 let is_wiki: i64 = sd_contains(plain, plain_n, "/wiki" as *u8, 5) 699 let is_wstatus: i64 = sd_contains(plain, plain_n, "/wiki/status" as *u8, 12) 700 let is_cidx: i64 = sd_contains(plain, plain_n, "/wiki/components" as *u8, 16) 701 let is_proj: i64 = sd_contains(plain, plain_n, "/wiki/projects" as *u8, 14) 702 let is_cr: i64 = sd_contains(plain, plain_n, "/wiki/component/crawler" as *u8, 23) 703 let is_xx: i64 = sd_contains(plain, plain_n, "/wiki/component/x25519" as *u8, 22) 704 let is_rk: i64 = sd_contains(plain, plain_n, "/wiki/component/ranking-math" as *u8, 28) 705 let is_we: i64 = sd_contains(plain, plain_n, "/wiki/component/wiki-engine" as *u8, 27) 706 // Same-experience LAN+internet: dispatch by Host/path only. The 707 // sequential ifs run least-specific first so the most-specific 708 // path (a component slug) wins the overwrite. 709 if is_andelin == 1 { 710 resp = resp_andelin; resp_n = n_andelin; vhost_id = 1 711 } else { 712 if is_wiki == 1 { resp = resp_wiki; resp_n = n_wiki; vhost_id = 2 } 713 if is_wstatus == 1 { resp = resp_wstatus; resp_n = n_wstatus; vhost_id = 3 } 714 if is_cidx == 1 { resp = resp_components; resp_n = n_components; vhost_id = 8 } 715 if is_proj == 1 { resp = resp_projects; resp_n = n_projects; vhost_id = 12 } 716 if is_cr == 1 { resp = resp_art_crawler; resp_n = n_art_crawler; vhost_id = 4 } 717 if is_xx == 1 { resp = resp_art_x25519; resp_n = n_art_x25519; vhost_id = 5 } 718 if is_rk == 1 { resp = resp_art_ranking; resp_n = n_art_ranking; vhost_id = 6 } 719 if is_we == 1 { resp = resp_art_wiki; resp_n = n_art_wiki; vhost_id = 7 } 720 } 721 } 722 } 723 let out_n: i64 = nx_tls13_server_session_app_send(s, resp, resp_n, out_rec, NX_SD_OUTCAP) 724 if out_n > 0 { 725 sys_write(cfd, out_rec, out_n) 726 served_bytes = served_bytes + resp_n 727 nreq = nreq + 1 728 } else { 729 keep = 0 730 } 731 } 732 } 733 } 734 } 735 } 736 sys_close(cfd) 737 // SENSE: append one additive telemetry record (O_APPEND -> atomic 738 // across concurrent children; fire-and-forget). 739 nx_telemetry_emit(tel_fd, tel_buf, sys_now_us(), acc_lan, vhost_id, 740 hs_us, served_bytes, hs_rc) 741 sys_exit(0) 742 } 743 744 // ---- PARENT: drop our copy of the conn fd; children reaped at loop top ---- 745 sys_close(cfd) 746 if pid > 0 { live = live + 1 } 747 served = served + 1 748 } 749 sys_close(lfd) 750 return 0 751}