code wiki / _hdl_build / nx_site_lock_lib.nx

nx_site_lock_lib.nx source

↩ module page · 128 lines · 6608 B

1// nx_site_lock_lib.nx -- DATA-DRIVEN multi-level access-control lock resolver. ONE reusable primitive 2// 2026-07-29 seq1232 (P2 safe-subset): per-call scratch -> lazy statics (no-munmap page leak class). 3static slk_scr_fs: *i64 4static slk_scr_fe: *i64 5// that locks at MULTI-SITE, SITE, or SITE-AREA granularity from a single registry (the hub-primitive / 6// thin-per-site pattern applied to auth). A serving daemon calls slk_resolve(site, path) per request to 7// learn "is this locked, into which OPAQUE realm, at what min auth level?" -- so locking an area is ONE 8// registry line (one-click) with NO code redeploy and NO daemon restart => applying a lock can NEVER 9// crash hosting (config change, not a rollout). Most-specific-wins: area > site > multi. 10// 11// Registry line schema (TAB-separated): scope <TAB> pattern <TAB> realm <TAB> min_level 12// scope=multi pattern=* (ignored) -> every site, every path (spec 0) 13// scope=site pattern=<site> -> all paths on that site (spec 100) 14// scope=area pattern=<site>/<path-prefix> -> paths under that prefix (spec 1000+preflen) 15// A path with NO matching row is PUBLIC (explicit-lock model: public by default, lock what you name). 16// Sovereign: nx_syscalls only. license_tier: ORIGINAL 17import "nx_syscalls.nx" 18 19func slk_at(buf: *u8, off: i64) -> *u8 { return ((buf as i64) + off) as *u8 } 20func slk_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 21 22// byte-range equality: a[0..an) == b[0..bn) 23func slk_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 24 if an != bn { return 0 } 25 var i: i64 = 0 26 while i < an { if a[i] != b[i] { return 0 } i = i + 1 } 27 return 1 28} 29 30func slk_line_end(buf: *u8, len: i64, s: i64) -> i64 { 31 var e: i64 = s 32 while e < len { if buf[e] == (10 as u8) { return e } e = e + 1 } 33 return len 34} 35 36// TAB-field k of line [s,e): write [start,end) into fs[0]/fe[0]; return 1 if present. 37func slk_field(buf: *u8, s: i64, e: i64, k: i64, fs: *i64, fe: *i64) -> i64 { 38 var field: i64 = 0 39 var fstart: i64 = s 40 var i: i64 = s 41 while i < e { 42 if buf[i] == (9 as u8) { 43 if field == k { fs[0] = fstart; fe[0] = i; return 1 } 44 field = field + 1 45 fstart = i + 1 46 } 47 i = i + 1 48 } 49 if field == k { fs[0] = fstart; fe[0] = e; return 1 } 50 return 0 51} 52 53func slk_atoi(buf: *u8, s: i64, e: i64) -> i64 { 54 var v: i64 = 0; var i: i64 = s 55 while i < e { let c: i64 = buf[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } 56 return v 57} 58 59// does `path` (request path, leading '/' optional) start with `pref[0..preflen)` at a segment boundary? 60// boundary = end-of-path OR next char is '/' or '.' (so "wiki/board" matches /wiki/board.html and 61// /wiki/board/x but NOT /wiki/boardroom). pref is a *u8 over the registry buffer. 62func slk_path_match(path: *u8, pathlen: i64, pref: *u8, preflen: i64) -> i64 { 63 var ps: i64 = 0 64 if pathlen > 0 { if path[0] == (47 as u8) { ps = 1 } } // skip one leading '/' 65 if pathlen - ps < preflen { return 0 } 66 var i: i64 = 0 67 while i < preflen { if path[ps + i] != pref[i] { return 0 } i = i + 1 } 68 let nxt: i64 = ps + preflen 69 if nxt >= pathlen { return 1 } 70 let c: i64 = path[nxt] as i64 71 if c == 47 { return 1 } // '/' 72 if c == 46 { return 1 } // '.' 73 return 0 74} 75 76// THE RESOLVER. Scan the registry; among all rows matching (site, path), pick the MOST SPECIFIC. 77// Returns 1 = LOCKED (fills out_realm NUL-terminated + out_level[0]); 0 = PUBLIC (no match). 78func slk_resolve(reg: *u8, reglen: i64, site: *u8, sitelen: i64, path: *u8, pathlen: i64, 79 out_realm: *u8, out_realm_cap: i64, out_level: *i64) -> i64 { 80 var best_spec: i64 = 0 - 1 81 var best_rs: i64 = 0 82 var best_re: i64 = 0 83 var best_level: i64 = 0 84 if (slk_scr_fs as i64) == 0 { slk_scr_fs = sys_mmap(8) as *i64 } 85 if (slk_scr_fe as i64) == 0 { slk_scr_fe = sys_mmap(8) as *i64 } 86 let fs: *i64 = slk_scr_fs; let fe: *i64 = slk_scr_fe 87 var ls: i64 = 0 88 while ls < reglen { 89 let le: i64 = slk_line_end(reg, reglen, ls) 90 if le > ls { 91 if reg[ls] != (35 as u8) { // skip '#' comment lines 92 if slk_field(reg, ls, le, 3, fs, fe) == 1 { // require a 4th field => well-formed row 93 slk_field(reg, ls, le, 0, fs, fe); let s0: i64 = fs[0]; let e0: i64 = fe[0] 94 slk_field(reg, ls, le, 1, fs, fe); let s1: i64 = fs[0]; let e1: i64 = fe[0] 95 slk_field(reg, ls, le, 2, fs, fe); let s2: i64 = fs[0]; let e2: i64 = fe[0] 96 slk_field(reg, ls, le, 3, fs, fe); let s3: i64 = fs[0]; let e3: i64 = fe[0] 97 var spec: i64 = 0 - 1 98 if slk_eq(slk_at(reg, s0), e0 - s0, "multi" as *u8, 5) == 1 { spec = 0 } 99 if slk_eq(slk_at(reg, s0), e0 - s0, "site" as *u8, 4) == 1 { 100 if slk_eq(slk_at(reg, s1), e1 - s1, site, sitelen) == 1 { spec = 100 } 101 } 102 if slk_eq(slk_at(reg, s0), e0 - s0, "area" as *u8, 4) == 1 { 103 var slash: i64 = 0 - 1 // first '/' in the pattern 104 var pi: i64 = s1 105 while pi < e1 { if reg[pi] == (47 as u8) { if slash < 0 { slash = pi } } pi = pi + 1 } 106 if slash > 0 { 107 let sp_len: i64 = slash - s1 // sitepart length 108 if slk_eq(slk_at(reg, s1), sp_len, site, sitelen) == 1 { 109 let pref: *u8 = slk_at(reg, slash + 1) 110 let preflen: i64 = e1 - (slash + 1) 111 if slk_path_match(path, pathlen, pref, preflen) == 1 { spec = 1000 + preflen } 112 } 113 } 114 } 115 if spec > best_spec { best_spec = spec; best_rs = s2; best_re = e2; best_level = slk_atoi(reg, s3, e3) } 116 } 117 } 118 } 119 ls = le + 1 120 } 121 if best_spec < 0 { return 0 } // no match => PUBLIC 122 var o: i64 = 0 123 let rn: i64 = best_re - best_rs 124 while o < rn { if o < out_realm_cap - 1 { out_realm[o] = reg[best_rs + o] } o = o + 1 } 125 if rn < out_realm_cap { out_realm[rn] = 0 as u8 } else { out_realm[out_realm_cap - 1] = 0 as u8 } 126 out_level[0] = best_level 127 return 1 128}