nx_wiki_https_daemon_mv.nx source
↩ module page · 454 lines · 20045 B
1// nx_wiki_https_daemon_mv.nx -- V-HOST-2.5: multi-vhost wiki HTTPS daemon.
2//
3// One substrate process serves N sovereign wiki sites
4// (e.g., nishifamily.com + andelinwest.com + jasonewest.com).
5//
6// Composes:
7// wiki/nx_wiki_vhost_table -- per-host wiki state registry
8// wiki/nx_wiki_https_daemon -- single-vhost reference for per-conn pattern
9// All existing TLS / wiki / search / archive / artifact primitives
10//
11// Per Cardinal 19 (API stability): NEW file parallel to single-vhost
12// daemon. Existing nx_wiki_https_daemon unchanged. Operator picks which
13// to boot based on site count.
14//
15// V-HOST-2.5 SCOPE:
16// - Multi-SAN cert (one cert covering all hosted domains; SAN list
17// per V-HOST-4d nx_cert_gen)
18// - Per-Host-header dispatch via NxWikiVHostTable
19// - Per-site doc_store + archive_store + artifact_store
20// - Shared admin_cfg + search_flow (V+1: per-site)
21// - Fallback vhost on no match (or 404)
22//
23// V-HOST-2.5 NON-SCOPE (queued):
24// - SNI-aware cert selection during TLS handshake (V-HOST-2.5.5)
25// - Per-site cert (V-HOST-2.5.5)
26// - Per-site admin auth (V-HOST-2.5.7)
27// - Hot-reload vhost table (V-PROV-3)
28//
29// Status: V-HOST-2.5. 2026-05-27.
30
31import "nx_syscalls.nx"
32import "nx_csprng.nx"
33import "nx_http_server.nx"
34import "nx_http_io.nx"
35import "nx_http_header_find.nx"
36import "nx_tls13_server_session.nx"
37import "nx_tls13_server_session_run.nx"
38import "nx_tls13_server_session_app_data.nx"
39import "hub/nx_modern_auth_flow.nx"
40import "hub/nx_user_account_store.nx"
41import "hub/nx_search_handler_flow.nx"
42import "nx_kv_store.nx"
43import "nx_search_inverted.nx"
44import "wiki/nx_wiki_routes.nx"
45import "wiki/nx_wiki_index_builder.nx"
46import "wiki/nx_wiki_archive_router.nx"
47import "wiki/nx_artifact_store.nx"
48import "wiki/nx_wiki_vhost_table.nx"
49import "wiki/nx_wiki_content_loader.nx"
50
51// ===== Sealed verdict surface (codes 3830-3839) =================================================
52const NX_MV_OK: i64 = 0
53const NX_MV_BAD_INPUT: i64 = 3830
54const NX_MV_CERT_LOAD_FAILED: i64 = 3831
55const NX_MV_BIND_FAILED: i64 = 3832
56const NX_MV_HS_FAILED: i64 = 3833
57const NX_MV_RECV_FAILED: i64 = 3834
58const NX_MV_PARSE_FAILED: i64 = 3835
59const NX_MV_NO_VHOST: i64 = 3836
60const NX_MV_DISPATCH_FAILED: i64 = 3837
61const NX_MV_SEND_FAILED: i64 = 3838
62
63// ===== Named constants (M7) =================================================
64const NX_MV_LISTEN_PORT: i64 = 8443
65const NX_MV_BACKLOG: i64 = 32
66const NX_MV_REQ_REC_CAP: i64 = 16384
67const NX_MV_PLAIN_CAP: i64 = 16384
68const NX_MV_RESP_CAP: i64 = 1048576
69const NX_MV_OUT_REC_CAP: i64 = 1048768
70const NX_MV_REQUEST_BUDGET: i64 = 100000
71
72// Persistent, user-writable on Synology (/var is not writable without
73// root and may not persist). Matches the nx_dns deploy convention.
74// V-PROV will make this env/config-driven.
75const NX_MV_DEFAULT_CERT_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/multi_san_cert.der" as *u8
76const NX_MV_DEFAULT_PRIV_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/multi_san_priv.bin" as *u8
77
78// V-MODAUTH-2/4 auth state (same persistent volume convention as certs).
79const NX_MV_AUTH_DIR: *u8 = "/volume1/homes/elderwesto/nishihost/auth" as *u8
80const NX_MV_AUTH_KEYS_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/auth/nishi_auth.keys.v1" as *u8
81const NX_MV_AUTH_STORE_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/auth/nishi_auth_accounts.v1.log" as *u8
82
83const NX_MV_REALM: *u8 = "Nishi Wiki" as *u8
84const NX_MV_REALM_N: i64 = 10
85const NX_MV_ADMIN_USER: *u8 = "elderwesto" as *u8
86const NX_MV_ADMIN_USER_N: i64 = 10
87
88// Per-site content kinds (per NISHI_WIKI_CONTENT_INDEX section 4
89// per-site visibility curation). Passed to nx_mv_setup_site to decide
90// which loader to invoke before the index is finalized.
91const NX_MV_CONTENT_MINIMAL: i64 = 0 // landing only (placeholder sites)
92const NX_MV_CONTENT_FULL: i64 = 1 // every charter (nishifamily.com)
93const NX_MV_CONTENT_PORTFOLIO: i64 = 2 // curated 6-charter subset (jasonewest.com)
94
95// ===== 404 response (when Host header has no matching vhost AND no fallback) =================================================
96
97func _nx_mv_emit_404_response(out: *u8, out_n: *i64, cap: i64) -> i64 {
98 let body: *u8 = "<!DOCTYPE html>\n<html><head><title>404 No Vhost Match</title></head><body><h1>404 No Vhost Match</h1><p>The Host header did not match any configured vhost on this substrate.</p></body></html>\n" as *u8
99 let body_n: i64 = 200
100 let hdr: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 200\r\n\r\n" as *u8
101 let hdr_n: i64 = 89
102 if hdr_n + body_n > cap { return 0 - NX_MV_SEND_FAILED }
103 var i: i64 = 0
104 while i < hdr_n { out[i] = hdr[i]; i = i + 1 }
105 var j: i64 = 0
106 while j < body_n { out[hdr_n + j] = body[j]; j = j + 1 }
107 out_n[0] = hdr_n + body_n
108 return NX_MV_OK
109}
110
111// ===== Per-conn handle =================================================
112//
113// V-HOST-2.5 flow:
114// 1. TLS handshake (shared multi-SAN cert; client accepted any
115// vhost in cert SAN list during handshake)
116// 2. Decrypt request
117// 3. Parse HTTP: extract path + method + Host header
118// 4. Lookup vhost in NxWikiVHostTable by Host header
119// 5. If match: dispatch to that vhost's wiki state via nx_wiki_route_dispatch
120// 6. If no match + no fallback: 404 + send
121// 7. Encrypt response + send
122
123func nx_mv_handle_one(
124 client_fd: i64,
125 cert_der: *u8, cert_der_len: i64,
126 ed25519_priv: *u8,
127 server_random: *u8, server_x25519_priv: *u8,
128 vhost_table: *NxWikiVHostTable,
129 doc_names: *u8,
130 random_16: *u8, now_s: i64
131) -> i64 {
132 // 1. TLS handshake (Ed25519 sig per V-HOST-2+1)
133 let s_raw: i64 = nx_tls13_server_session_run_ed25519(
134 client_fd,
135 server_random, server_x25519_priv,
136 cert_der, cert_der_len,
137 ed25519_priv)
138 if s_raw <= 0 { return 0 - NX_MV_HS_FAILED }
139 let s: *Tls13ServerSession = s_raw as *Tls13ServerSession
140
141 // 2. Read + decrypt request
142 let rec_buf: *u8 = sys_mmap(NX_MV_REQ_REC_CAP)
143 let rec_n: i64 = sys_read(client_fd, rec_buf, NX_MV_REQ_REC_CAP)
144 if rec_n <= 0 { return 0 - NX_MV_RECV_FAILED }
145 let plain: *u8 = sys_mmap(NX_MV_PLAIN_CAP)
146 let plain_n: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rec_n,
147 plain, NX_MV_PLAIN_CAP)
148 if plain_n <= 0 { return 0 - NX_MV_RECV_FAILED }
149
150 // 3. Parse HTTP
151 let om: *i64 = (sys_mmap(8)) as *i64
152 let opo: *i64 = (sys_mmap(8)) as *i64
153 let opl: *i64 = (sys_mmap(8)) as *i64
154 let headers_end: *i64 = (sys_mmap(8)) as *i64
155 let parse_rc: i64 = nx_http_parse_request(plain, plain_n,
156 om, opo, opl, headers_end)
157 if parse_rc != NXS_OK { return 0 - NX_MV_PARSE_FAILED }
158 let path_ptr: *u8 = (plain as i64 + opo[0]) as *u8
159 let path_n: i64 = opl[0]
160
161 // 4. Extract Host header value
162 let host_off: *i64 = (sys_mmap(8)) as *i64
163 let host_len: *i64 = (sys_mmap(8)) as *i64
164 host_off[0] = 0
165 host_len[0] = 0
166 nx_http_header_find(plain, headers_end[0], "Host" as *u8, 4, host_off, host_len)
167
168 // 5. Lookup vhost
169 let host_ptr: *u8 = (plain as i64 + host_off[0]) as *u8
170 let vhost_idx: i64 = nx_wiki_vhost_lookup_by_host(vhost_table, host_ptr, host_len[0])
171
172 let resp_buf: *u8 = sys_mmap(NX_MV_RESP_CAP)
173 let resp_n: *i64 = (sys_mmap(8)) as *i64
174 resp_n[0] = 0
175
176 if vhost_idx < 0 {
177 // 6. No match -> 404
178 let rc_404: i64 = _nx_mv_emit_404_response(resp_buf, resp_n, NX_MV_RESP_CAP)
179 if rc_404 != NX_MV_OK { return rc_404 }
180 }
181 if vhost_idx >= 0 {
182 let v: *NxWikiVHost = nx_wiki_vhost_at(vhost_table, vhost_idx)
183 if (v as i64) == 0 { return 0 - NX_MV_NO_VHOST }
184
185 // 5b. Dispatch to this vhost's wiki state
186 let disp_rc: i64 = nx_wiki_route_dispatch(
187 plain, headers_end[0],
188 path_ptr, path_n,
189 om[0],
190 v.admin_cfg,
191 now_s,
192 v.search_flow, v.search_idx, v.doc_store, v.archive_store, v.artifact_store,
193 resp_buf, NX_MV_RESP_CAP,
194 resp_n)
195 if disp_rc != NX_WIKI_ROUTE_OK { return 0 - NX_MV_DISPATCH_FAILED }
196 }
197
198 // 7. Encrypt + send
199 let out_rec: *u8 = sys_mmap(NX_MV_OUT_REC_CAP)
200 let out_n: i64 = nx_tls13_server_session_app_send(
201 s, resp_buf, resp_n[0], out_rec, NX_MV_OUT_REC_CAP)
202 if out_n <= 0 { return 0 - NX_MV_SEND_FAILED }
203 if sys_write(client_fd, out_rec, out_n) != out_n {
204 return 0 - NX_MV_SEND_FAILED
205 }
206
207 return NX_MV_OK
208}
209
210// ===== Per-site state allocator (helper; called N times at startup) =================================================
211//
212// Allocates fresh per-site state structs + seeds with a minimal landing
213// page. Operator extends with real content via NxWikiDocStore.add at startup.
214
215func nx_mv_setup_site(
216 hostname: *u8, hostname_n: i64,
217 landing_summary: *u8, landing_summary_n: i64,
218 content_kind: i64,
219 admin_cfg: *NxAuthContext,
220 out_search_flow: *i64,
221 out_search_idx: *i64,
222 out_doc_store: *i64,
223 out_archive_store: *i64,
224 out_artifact_store: *i64
225) -> i64 {
226 let search_flow: *NxSearchFlow = (sys_mmap(256)) as *NxSearchFlow
227 nx_search_flow_init(search_flow, 100000, 100, 10000)
228 out_search_flow[0] = search_flow as i64
229
230 let doc_store: *NxWikiDocStore = (sys_mmap(256)) as *NxWikiDocStore
231 nx_wiki_doc_store_init(doc_store, 100, 65536, 65536, 4194304)
232 out_doc_store[0] = doc_store as i64
233
234 let builder: *NxWikiIndexBuilder = (sys_mmap(64)) as *NxWikiIndexBuilder
235 nx_wiki_index_builder_init(builder, doc_store, 100)
236
237 // Landing page (every site gets one).
238 nx_wiki_index_builder_add(builder,
239 hostname, hostname_n,
240 "/" as *u8, 1,
241 landing_summary, landing_summary_n)
242
243 // Per-site content load BEFORE finalize (per
244 // NISHI_WIKI_CONTENT_INDEX section 4 per-site visibility curation).
245 if content_kind == NX_MV_CONTENT_FULL {
246 nx_wcl_load_full_manifest(builder)
247 }
248 if content_kind == NX_MV_CONTENT_PORTFOLIO {
249 nx_wcl_load_jasonewest_portfolio(builder)
250 }
251 // NX_MV_CONTENT_MINIMAL: no extra loads (landing only)
252
253 nx_wiki_index_builder_finalize(builder)
254 out_search_idx[0] = (nx_wiki_index_builder_get_index(builder)) as i64
255
256 let archive_store: *NxArchiveStore = (sys_mmap(256)) as *NxArchiveStore
257 nx_archive_store_init(archive_store, 100)
258 out_archive_store[0] = archive_store as i64
259
260 let artifact_store: *NxArtifactStore = (sys_mmap(512)) as *NxArtifactStore
261 nx_artifact_store_init(artifact_store, 100)
262 out_artifact_store[0] = artifact_store as i64
263
264 return NX_MV_OK
265}
266
267// ===== Main daemon =================================================
268
269func main() -> i64 {
270 // 1. Load multi-SAN cert + priv
271 let cert_len_box: *i64 = (sys_mmap(8)) as *i64
272 cert_len_box[0] = 0
273 let cert_der: *u8 = sys_read_file(NX_MV_DEFAULT_CERT_PATH, cert_len_box)
274 if (cert_der as i64) == 0 { return NX_MV_CERT_LOAD_FAILED }
275 let cert_der_len: i64 = cert_len_box[0]
276
277 let priv_len_box: *i64 = (sys_mmap(8)) as *i64
278 priv_len_box[0] = 0
279 let ed25519_priv: *u8 = sys_read_file(NX_MV_DEFAULT_PRIV_PATH, priv_len_box)
280 if (ed25519_priv as i64) == 0 { return NX_MV_CERT_LOAD_FAILED }
281 if priv_len_box[0] != 32 { return NX_MV_CERT_LOAD_FAILED }
282
283 // 2. server_random + x25519 priv
284 let server_random: *u8 = sys_mmap(32)
285 let server_x25519_priv: *u8 = sys_mmap(32)
286 nx_csprng_fill(server_random, 32)
287 nx_csprng_fill(server_x25519_priv, 32)
288
289 // 3. Shared admin config (V1; per-site V+1)
290 // V2 (2026-06-10): OPAQUE ARMED. Persistent V-MODAUTH-4 key bundle
291 // (oprf_seed + Ed25519 session keys; created 0600 on first boot,
292 // idempotent after) + durable account store, on the same persistent
293 // user-writable volume as the certs (nx_dns deploy convention).
294 // If the bundle cannot be created/read (e.g., dev box without the
295 // volume), ctx stays UNARMED: session validation still works with
296 // ephemeral keys and POST /wiki/admin/login serves the honest 503.
297 sys_mkdir(NX_MV_AUTH_DIR, 0x1C0) // 0700, best-effort (exists = fine)
298 let oprf_seed: *u8 = sys_mmap(32)
299 let ake_priv_unused: *u8 = sys_mmap(32)
300 let ake_pub_unused: *u8 = sys_mmap(33)
301 let server_ed25519_priv: *u8 = sys_mmap(NX_MAUTH_ED25519_PRIV_BYTES)
302 let server_ed25519_pub: *u8 = sys_mmap(NX_MAUTH_ED25519_PUB_BYTES)
303 var auth_armed: i64 = 1
304 if nx_uas_server_keys_load_or_init(NX_MV_AUTH_KEYS_PATH, oprf_seed,
305 ake_priv_unused, ake_pub_unused,
306 server_ed25519_priv, server_ed25519_pub) != NX_UAS_OK {
307 auth_armed = 0
308 nx_csprng_fill(server_ed25519_priv, NX_MAUTH_ED25519_PRIV_BYTES)
309 nx_csprng_fill(server_ed25519_pub, NX_MAUTH_ED25519_PUB_BYTES)
310 }
311 var auth_store_i64: i64 = 0
312 var oprf_seed_arg: *u8 = (0 as i64) as *u8
313 if auth_armed == 1 {
314 auth_store_i64 = NX_MV_AUTH_STORE_PATH as i64
315 oprf_seed_arg = oprf_seed
316 }
317
318 let admin_cfg: *NxAuthContext = (sys_mmap(256)) as *NxAuthContext
319 nx_auth_context_init(admin_cfg,
320 NX_MV_REALM, NX_MV_REALM_N,
321 NX_MV_REALM, NX_MV_REALM_N,
322 auth_store_i64, // account_store path (V-MODAUTH-4)
323 oprf_seed_arg, // oprf_seed (V-MODAUTH-2 OPAQUE)
324 server_ed25519_priv,
325 server_ed25519_pub,
326 NX_MAUTH_DEFAULT_SESSION_TTL_S,
327 32768, // argon2id m = 32 MiB (vault KDF v2 measured budget)
328 NX_MAUTH_DEFAULT_ARGON2ID_T_COST,
329 1, // argon2id p = 1 (substrate single-lane RFC 9106)
330 NX_MAUTH_DEFAULT_RATE_LIMIT_PER_MIN,
331 1) // allow_recovery (M3 NOT_IMPLEMENTED yet)
332
333 // 4. Build vhost table
334 let vhost_table: *NxWikiVHostTable = (sys_mmap(64)) as *NxWikiVHostTable
335 nx_wiki_vhost_table_init(vhost_table)
336
337 // Allocate per-site state for 3 demo sites:
338 // nishifamily.com
339 // andelinwest.com
340 // jasonewest.com
341 let sf1: *i64 = (sys_mmap(8)) as *i64
342 let si1: *i64 = (sys_mmap(8)) as *i64
343 let ds1: *i64 = (sys_mmap(8)) as *i64
344 let as1: *i64 = (sys_mmap(8)) as *i64
345 let ar1: *i64 = (sys_mmap(8)) as *i64
346 // nishifamily.com -- family hub + Nishi sovereign substrate wiki
347 // Loads FULL charter manifest (every charter from
348 // NISHI_WIKI_CONTENT_INDEX section 1 becomes a /wiki/<slug> page).
349 nx_mv_setup_site(
350 "nishifamily.com" as *u8, 15,
351 "Nishi Family hub. Hosts the Nishi sovereign ecosystem wiki + dashboards. See /wiki/ for substrate documentation, /wiki/pipeline for living artifact status, /wiki/dashboard for product-ops view." as *u8, 218,
352 NX_MV_CONTENT_FULL,
353 admin_cfg, sf1, si1, ds1, as1, ar1)
354 nx_wiki_vhost_add(vhost_table,
355 "nishifamily.com" as *u8, 15,
356 sf1[0] as *NxSearchFlow, si1[0] as *NxInvIndex,
357 ds1[0] as *NxWikiDocStore, as1[0] as *NxArchiveStore,
358 ar1[0] as *NxArtifactStore, admin_cfg)
359
360 let sf2: *i64 = (sys_mmap(8)) as *i64
361 let si2: *i64 = (sys_mmap(8)) as *i64
362 let ds2: *i64 = (sys_mmap(8)) as *i64
363 let as2: *i64 = (sys_mmap(8)) as *i64
364 let ar2: *i64 = (sys_mmap(8)) as *i64
365 // andelinwest.com -- LAWYER PRACTICE site (compliance-critical; see NISHI_SITES_INVENTORY.md ยง2)
366 // PLACEHOLDER landing only -- DO NOT DEPLOY PUBLIC until per-state bar advertising
367 // rules audit + ADA scan + operator-attorney content review complete.
368 // andelinwest.com -- LAWYER PRACTICE site: NO substrate-internal
369 // charters surfaced by default (per NISHI_WIKI_CONTENT_INDEX
370 // section 4; operator-attorney decides if a curated subset like
371 // privacy + consent + ADA charters is appropriate via V+1 config).
372 nx_mv_setup_site(
373 "andelinwest.com" as *u8, 15,
374 "Andelin West Law. PLACEHOLDER -- this site requires legal-firm UX + per-state bar-association advertising-rule compliance + ADA accessibility audit + operator-attorney content review before public deployment. See NISHI_SITES_INVENTORY.md section 2 for compliance checklist." as *u8, 269,
375 NX_MV_CONTENT_MINIMAL,
376 admin_cfg, sf2, si2, ds2, as2, ar2)
377 nx_wiki_vhost_add(vhost_table,
378 "andelinwest.com" as *u8, 15,
379 sf2[0] as *NxSearchFlow, si2[0] as *NxInvIndex,
380 ds2[0] as *NxWikiDocStore, as2[0] as *NxArchiveStore,
381 ar2[0] as *NxArtifactStore, admin_cfg)
382
383 let sf3: *i64 = (sys_mmap(8)) as *i64
384 let si3: *i64 = (sys_mmap(8)) as *i64
385 let ds3: *i64 = (sys_mmap(8)) as *i64
386 let as3: *i64 = (sys_mmap(8)) as *i64
387 let ar3: *i64 = (sys_mmap(8)) as *i64
388 // jasonewest.com -- operator's PORTFOLIO site (job + consulting business-driver)
389 // Loads CURATED PORTFOLIO subset (6 charters showing operator's
390 // substrate work as portfolio evidence per NISHI_WIKI_CONTENT_INDEX
391 // section 4: personal-data-sovereignty + modern-auth + sovereign-
392 // hosting + superiority-bar + bench-winner-standard + silicon-z2a).
393 nx_mv_setup_site(
394 "jasonewest.com" as *u8, 14,
395 "Jason E. West. Portfolio for consulting + employment opportunities. Hosting work on the Nishi sovereign substrate ecosystem -- substrate-bits-up engineering across compilers, TLS, search, distributed systems, web platform. See projects + case studies + contact for engagement inquiries." as *u8, 287,
396 NX_MV_CONTENT_PORTFOLIO,
397 admin_cfg, sf3, si3, ds3, as3, ar3)
398 nx_wiki_vhost_add(vhost_table,
399 "jasonewest.com" as *u8, 14,
400 sf3[0] as *NxSearchFlow, si3[0] as *NxInvIndex,
401 ds3[0] as *NxWikiDocStore, as3[0] as *NxArchiveStore,
402 ar3[0] as *NxArtifactStore, admin_cfg)
403
404 // Fallback: first vhost (nishifamily.com); operator can change to NX_WVT_FALLBACK_NONE
405 // to enforce strict Host header matching (404 on mismatch).
406 nx_wiki_vhost_set_fallback(vhost_table, 0)
407
408 // 5. Shared per-conn helpers
409 let doc_names: *u8 = sys_mmap(4096)
410 let random_16: *u8 = sys_mmap(16)
411
412 // 6. Bind listen
413 let addr_buf: *u8 = sys_mmap(16)
414 nx_http_server_addr_any(addr_buf, NX_MV_LISTEN_PORT)
415 let lv: *i64 = (sys_mmap(8)) as *i64
416 let lfd: i64 = nx_http_server_listen(addr_buf, NX_MV_BACKLOG, lv)
417 if lfd < 0 { return NX_MV_BIND_FAILED }
418
419 let banner: *u8 = "nx_wiki_https_daemon_mv: 3 vhosts (nishifamily/andelinwest/jasonewest); 0.0.0.0:8443\n" as *u8
420 sys_write(1, banner, 84)
421
422 // 7. Accept loop
423 var n_served: i64 = 0
424 var n_errors: i64 = 0
425 while n_served < NX_MV_REQUEST_BUDGET {
426 let av: *i64 = (sys_mmap(8)) as *i64
427 let cfd: i64 = nx_http_server_accept_one(lfd, av)
428 if cfd < 0 {
429 n_errors = n_errors + 1
430 n_served = n_served + 1
431 }
432 if cfd >= 0 {
433 let now_ms: i64 = sys_now_ms()
434 let now_s: i64 = now_ms / 1000
435 nx_csprng_fill(random_16, 16)
436
437 let rc: i64 = nx_mv_handle_one(
438 cfd,
439 cert_der, cert_der_len,
440 ed25519_priv,
441 server_random, server_x25519_priv,
442 vhost_table,
443 doc_names, random_16, now_s)
444
445 if rc != NX_MV_OK { n_errors = n_errors + 1 }
446
447 sys_close(cfd)
448 n_served = n_served + 1
449 }
450 }
451
452 sys_close(lfd)
453 return NX_MV_OK
454}