code wiki / wiki / nx_wiki_vhost_table.nx

nx_wiki_vhost_table.nx source

↩ module page · 199 lines · 7713 B

1// nx_wiki_vhost_table.nx -- V-HOST-2.5: per-vhost wiki state table. 2// 3// Extends V-HOST-2 single-vhost daemon to N-vhost. Operator hosts 4// nishifamily.com + andelinwest.com + jasonewest.com on a single 5// substrate process; per-request Host header picks which site's wiki 6// state to dispatch to. 7// 8// COMPOSES (per "avoid duplicate primitives"): 9// nx_syscalls (caller-allocated structs) 10// hub/nx_admin_login_flow / nx_search_handler_flow / nx_search_inverted 11// wiki/nx_wiki_index_builder + nx_wiki_archive_router + nx_artifact_store 12// 13// COMPOSED BY: 14// wiki/nx_wiki_https_daemon (V-HOST-2.5 multi-vhost dispatch) 15// 16// V-HOST-2.5 SCOPE: 17// - NxWikiVHost struct: hostname + per-site wiki state pointers 18// - NxWikiVHostTable: array of NxWikiVHost; bounded by NX_WVT_MAX_VHOSTS 19// - nx_wiki_vhost_add: register a site at startup 20// - nx_wiki_vhost_lookup_by_host: case-insensitive Host header match 21// - Sealed fallback policy: 0=no fallback (404 on no match); idx=fallback to that vhost 22// 23// V-HOST-2.5 NON-SCOPE (queued): 24// - SNI-aware cert selection during TLS handshake (currently shared 25// multi-SAN cert across all vhosts; V-HOST-2.5.5) 26// - Per-vhost cert (requires extending nx_tls13_server_session_run 27// with cert-picker callback; V-HOST-2.5.5) 28// - Per-vhost admin auth (currently shared admin across vhosts; 29// operator may want per-vhost in V-HOST-2.5.7) 30// 31// Status: V-HOST-2.5. 2026-05-27. 32 33import "nx_syscalls.nx" 34import "hub/nx_modern_auth_flow.nx" 35import "hub/nx_search_handler_flow.nx" 36import "nx_search_inverted.nx" 37import "wiki/nx_wiki_index_builder.nx" 38import "wiki/nx_wiki_archive_router.nx" 39import "wiki/nx_artifact_store.nx" 40 41// ===== Sealed verdict surface (codes 3820-3829) ================================================= 42const NX_WVT_OK: i64 = 0 43const NX_WVT_BAD_INPUT: i64 = 3820 44const NX_WVT_TABLE_FULL: i64 = 3821 45const NX_WVT_NOT_FOUND: i64 = 3822 46const NX_WVT_HOSTNAME_TOO_LONG: i64 = 3823 47 48// ===== Named constants (M7) ================================================= 49const NX_WVT_MAX_VHOSTS: i64 = 16 // bounded; V+1 increases when corpus grows 50const NX_WVT_MAX_HOSTNAME_LEN: i64 = 253 // RFC 1035 per-label * total 51const NX_WVT_FALLBACK_NONE: i64 = 0 - 1 52 53// ===== Per-vhost state (caller-allocated per site) ================================================= 54// 55// Each vhost holds ptrs to its OWN per-site state. The daemon allocates 56// N sets of these at startup; the table indexes them by hostname. 57 58struct NxWikiVHost { 59 hostname: *u8 60 hostname_n: i64 61 search_flow: *NxSearchFlow // per-site search context 62 search_idx: *NxInvIndex // per-site inverted index 63 doc_store: *NxWikiDocStore // per-site doc bodies 64 archive_store: *NxArchiveStore // per-site wayback archive 65 artifact_store: *NxArtifactStore // per-site pipeline state 66 admin_cfg: *NxAuthContext // V-MODAUTH-6 per-site auth context (or shared) 67} 68 69const NX_WVT_VHOST_BYTES: i64 = 72 70 71// ===== Table ================================================= 72 73struct NxWikiVHostTable { 74 vhosts: *NxWikiVHost 75 vhosts_cap: i64 76 vhosts_count: i64 77 fallback_idx: i64 // NX_WVT_FALLBACK_NONE or [0..vhosts_count) 78 valid: i64 79} 80 81func nx_wiki_vhost_table_init(t: *NxWikiVHostTable) -> i64 { 82 if (t as i64) == 0 { return 0 - NX_WVT_BAD_INPUT } 83 t.vhosts = (sys_mmap(NX_WVT_MAX_VHOSTS * NX_WVT_VHOST_BYTES)) as *NxWikiVHost 84 t.vhosts_cap = NX_WVT_MAX_VHOSTS 85 t.vhosts_count = 0 86 t.fallback_idx = NX_WVT_FALLBACK_NONE 87 t.valid = 1 88 return NX_WVT_OK 89} 90 91func nx_wiki_vhost_add(t: *NxWikiVHostTable, 92 hostname: *u8, hostname_n: i64, 93 search_flow: *NxSearchFlow, 94 search_idx: *NxInvIndex, 95 doc_store: *NxWikiDocStore, 96 archive_store: *NxArchiveStore, 97 artifact_store: *NxArtifactStore, 98 admin_cfg: *NxAuthContext) -> i64 { 99 if t.valid != 1 { return 0 - NX_WVT_BAD_INPUT } 100 if t.vhosts_count >= t.vhosts_cap { return 0 - NX_WVT_TABLE_FULL } 101 if hostname_n < 1 { return 0 - NX_WVT_BAD_INPUT } 102 if hostname_n > NX_WVT_MAX_HOSTNAME_LEN { return 0 - NX_WVT_HOSTNAME_TOO_LONG } 103 104 let v: *NxWikiVHost = ((t.vhosts as i64) + t.vhosts_count * NX_WVT_VHOST_BYTES) as *NxWikiVHost 105 v.hostname = hostname 106 v.hostname_n = hostname_n 107 v.search_flow = search_flow 108 v.search_idx = search_idx 109 v.doc_store = doc_store 110 v.archive_store = archive_store 111 v.artifact_store = artifact_store 112 v.admin_cfg = admin_cfg 113 114 let idx: i64 = t.vhosts_count 115 t.vhosts_count = t.vhosts_count + 1 116 return idx 117} 118 119func nx_wiki_vhost_set_fallback(t: *NxWikiVHostTable, idx: i64) -> i64 { 120 if t.valid != 1 { return 0 - NX_WVT_BAD_INPUT } 121 if idx < 0 { t.fallback_idx = NX_WVT_FALLBACK_NONE; return NX_WVT_OK } 122 if idx >= t.vhosts_count { return 0 - NX_WVT_BAD_INPUT } 123 t.fallback_idx = idx 124 return NX_WVT_OK 125} 126 127// ===== Case-insensitive byte compare ================================================= 128// 129// ASCII lowercase fold; substrate convention. Hostnames are ASCII per 130// RFC 1035 ยง2.3.1 (LDH; letters, digits, hyphen; case-insensitive). 131 132func _nx_wvt_lc(b: i64) -> i64 { 133 if b >= 0x41 { if b <= 0x5A { return b + 32 } } 134 return b 135} 136 137func _nx_wvt_eq_ci(a: *u8, a_n: i64, b: *u8, b_n: i64) -> i64 { 138 if a_n != b_n { return 0 } 139 var i: i64 = 0 140 while i < a_n { 141 let ca: i64 = _nx_wvt_lc(a[i] as i64) 142 let cb: i64 = _nx_wvt_lc(b[i] as i64) 143 if ca != cb { return 0 } 144 i = i + 1 145 } 146 return 1 147} 148 149// ===== Lookup by Host header value ================================================= 150// 151// Returns vhost rowid (>= 0) on case-insensitive hostname match; 152// falls back to fallback_idx (or -1) on no match. 153// 154// host_value may include ":port" suffix per HTTP/1.1 spec; we strip 155// before comparison. 156 157func nx_wiki_vhost_lookup_by_host(t: *NxWikiVHostTable, 158 host_value: *u8, host_value_n: i64) -> i64 { 159 if t.valid != 1 { return 0 - NX_WVT_BAD_INPUT } 160 if host_value_n < 1 { 161 if t.fallback_idx >= 0 { return t.fallback_idx } 162 return 0 - NX_WVT_NOT_FOUND 163 } 164 165 // Strip ":port" suffix 166 var name_n: i64 = host_value_n 167 var i: i64 = 0 168 while i < host_value_n { 169 if host_value[i] == (0x3A as u8) { name_n = i; i = host_value_n + 1 } 170 if i <= host_value_n { if i < host_value_n { i = i + 1 } } 171 } 172 173 var v_i: i64 = 0 174 while v_i < t.vhosts_count { 175 let v: *NxWikiVHost = ((t.vhosts as i64) + v_i * NX_WVT_VHOST_BYTES) as *NxWikiVHost 176 if _nx_wvt_eq_ci(v.hostname, v.hostname_n, host_value, name_n) == 1 { 177 return v_i 178 } 179 v_i = v_i + 1 180 } 181 182 // No match -> fallback (or -1) 183 if t.fallback_idx >= 0 { return t.fallback_idx } 184 return 0 - NX_WVT_NOT_FOUND 185} 186 187// ===== Accessor: get NxWikiVHost ptr by rowid ================================================= 188 189func nx_wiki_vhost_at(t: *NxWikiVHostTable, idx: i64) -> *NxWikiVHost { 190 if t.valid != 1 { return 0 as *NxWikiVHost } 191 if idx < 0 { return 0 as *NxWikiVHost } 192 if idx >= t.vhosts_count { return 0 as *NxWikiVHost } 193 return ((t.vhosts as i64) + idx * NX_WVT_VHOST_BYTES) as *NxWikiVHost 194} 195 196func nx_wiki_vhost_count(t: *NxWikiVHostTable) -> i64 { 197 if t.valid != 1 { return 0 } 198 return t.vhosts_count 199}