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}