code wiki / _hdl_build / nx_route_ctl_gate.nx
nx_route_ctl_gate.nx source
↩ module page · 108 lines · 7853 B
1// nx_route_ctl_gate.nx -- proves the API-pure route-control primitives (md_route_valid + md_route_append) BEFORE
2// they can touch the live edge proxy table. NEVER-BRICK RELEVANT: a malformed proxy_routes.conf row is edge-wide,
3// so validation is fail-closed by construction and the upsert must preserve every UNRELATED row. Exercised only
4// against a /tmp fixture -- never the real proxy_routes.conf. license_tier: ORIGINAL
5import "nx_mgmt_data.nx"
6
7func rg_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
8func rg_wn(v: i64) -> i64 { let b: *u8 = sys_mmap(32); let o: i64 = md_catn(b, 0, v); b[o] = 10 as u8; sys_write(1, b, o + 1); return 0 }
9
10// assert got==want; returns 1 on pass, 0 on fail (main accumulates).
11func rg_eq(name: *u8, got: i64, want: i64) -> i64 {
12 if got == want { rg_w(" ok " as *u8); rg_w(name); rg_w("\n" as *u8); return 1 }
13 rg_w(" FAIL " as *u8); rg_w(name); rg_w(" got=" as *u8); rg_wn(got); return 0
14}
15
16func rg_seed(path: *u8, s: *u8) -> i64 {
17 var n: i64 = 0
18 while s[n] != (0 as u8) { n = n + 1 }
19 let fd: i64 = sys_openat_wr(path, 0x1a4)
20 if fd < 0 { return 0 }
21 sys_write(fd, s, n)
22 sys_close(fd)
23 return 1
24}
25func rg_countlines(buf: *u8, n: i64) -> i64 {
26 var c: i64 = 0
27 var i: i64 = 0
28 while i < n { if (buf[i] as i64) == 10 { c = c + 1 } i = i + 1 }
29 return c
30}
31func rg_has(path: *u8, needle: *u8) -> i64 {
32 let szp: *i64 = sys_mmap(16) as *i64
33 szp[0] = 0
34 let buf: *u8 = sys_read_file(path, szp)
35 if (buf as i64) == 0 { return 0 }
36 var nl: i64 = 0
37 while needle[nl] != (0 as u8) { nl = nl + 1 }
38 return md_pk_contains(buf, szp[0], needle, nl)
39}
40func rg_lines(path: *u8) -> i64 {
41 let szp: *i64 = sys_mmap(16) as *i64
42 szp[0] = 0
43 let buf: *u8 = sys_read_file(path, szp)
44 if (buf as i64) == 0 { return 0 }
45 return rg_countlines(buf, szp[0])
46}
47
48func main(argc: i64, argv: *i64) -> i64 {
49 rg_w("== nx_route_ctl_gate ==\n" as *u8)
50 var pass: i64 = 0
51 var total: i64 = 0
52
53 // ---- md_route_valid: accept the good, reject every bad class (fail-closed) ----
54 rg_w("- md_route_valid\n" as *u8)
55 total = total + 1; pass = pass + rg_eq("T1 accept andelinwest.com /site 18466 buffered" as *u8, md_route_valid("andelinwest.com" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 1)
56 total = total + 1; pass = pass + rg_eq("T2 accept nested prefix + stream" as *u8, md_route_valid("nishifamily.com" as *u8, "/site/ed" as *u8, 18466, "stream" as *u8), 1)
57 total = total + 1; pass = pass + rg_eq("T3 accept gated mode" as *u8, md_route_valid("a.b.com" as *u8, "/x" as *u8, 8027, "gated" as *u8), 1)
58 total = total + 1; pass = pass + rg_eq("T4 reject host without a dot" as *u8, md_route_valid("localhost" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 0)
59 total = total + 1; pass = pass + rg_eq("T5 reject uppercase host" as *u8, md_route_valid("Andelin.com" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 0)
60 total = total + 1; pass = pass + rg_eq("T6 reject leading-dot host" as *u8, md_route_valid(".com" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 0)
61 total = total + 1; pass = pass + rg_eq("T7 reject double-dot host" as *u8, md_route_valid("a..b.com" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 0)
62 total = total + 1; pass = pass + rg_eq("T8 reject prefix missing leading slash" as *u8, md_route_valid("a.com" as *u8, "site" as *u8, 18466, "buffered" as *u8), 0)
63 total = total + 1; pass = pass + rg_eq("T9 reject prefix path-traversal .." as *u8, md_route_valid("a.com" as *u8, "/a/../b" as *u8, 18466, "buffered" as *u8), 0)
64 total = total + 1; pass = pass + rg_eq("T10 reject prefix double-slash" as *u8, md_route_valid("a.com" as *u8, "//x" as *u8, 18466, "buffered" as *u8), 0)
65 total = total + 1; pass = pass + rg_eq("T11 reject prefix with space (injection)" as *u8, md_route_valid("a.com" as *u8, "/a b" as *u8, 18466, "buffered" as *u8), 0)
66 total = total + 1; pass = pass + rg_eq("T12 reject privileged port <1024" as *u8, md_route_valid("a.com" as *u8, "/x" as *u8, 80, "buffered" as *u8), 0)
67 total = total + 1; pass = pass + rg_eq("T13 reject port >65535" as *u8, md_route_valid("a.com" as *u8, "/x" as *u8, 70000, "buffered" as *u8), 0)
68 total = total + 1; pass = pass + rg_eq("T14 reject unknown mode" as *u8, md_route_valid("a.com" as *u8, "/x" as *u8, 8443, "raw" as *u8), 0)
69
70 // ---- md_route_append: upsert preserves unrelated rows, replaces the match, appends the new ----
71 rg_w("- md_route_append (fixture /tmp/nx_route_gate.conf)\n" as *u8)
72 let fx: *u8 = "/tmp/nx_route_gate.conf" as *u8
73 rg_seed(fx, "example.com / 9001 buffered\nother.com /api 9002 stream\n" as *u8)
74
75 // (a) append a genuinely new route
76 total = total + 1; pass = pass + rg_eq("T15 append returns ok" as *u8, md_route_append(fx, "andelinwest.com" as *u8, "/site" as *u8, 18466, "buffered" as *u8), 1)
77 total = total + 1; pass = pass + rg_eq("T16 new row present" as *u8, rg_has(fx, "andelinwest.com /site 18466 buffered" as *u8), 1)
78 total = total + 1; pass = pass + rg_eq("T17 unrelated row example preserved" as *u8, rg_has(fx, "example.com / 9001 buffered" as *u8), 1)
79 total = total + 1; pass = pass + rg_eq("T18 unrelated row other preserved" as *u8, rg_has(fx, "other.com /api 9002 stream" as *u8), 1)
80 total = total + 1; pass = pass + rg_eq("T19 line count == 3" as *u8, rg_lines(fx), 3)
81
82 // (b) UPSERT: same host+prefix -> replace in place (old value gone), count unchanged
83 total = total + 1; pass = pass + rg_eq("T20 upsert returns ok" as *u8, md_route_append(fx, "example.com" as *u8, "/" as *u8, 9999, "gated" as *u8), 1)
84 total = total + 1; pass = pass + rg_eq("T21 new value present" as *u8, rg_has(fx, "example.com / 9999 gated" as *u8), 1)
85 total = total + 1; pass = pass + rg_eq("T22 old value gone (no 9001)" as *u8, rg_has(fx, "9001" as *u8), 0)
86 total = total + 1; pass = pass + rg_eq("T23 sibling other.com still present" as *u8, rg_has(fx, "other.com /api 9002 stream" as *u8), 1)
87 total = total + 1; pass = pass + rg_eq("T24 line count still 3 (no dup)" as *u8, rg_lines(fx), 3)
88
89 // (c) NEVER-BRICK: an empty/unreadable table is REFUSED (a transient read-fail must never let one row
90 // replace the populated edge table -> /api self-lockout). 0-byte file via open(O_CREAT|O_TRUNC)+close;
91 // NOT rg_seed(path,"") -- an empty "" literal aliases the constant pool (documented nx_cc gotcha).
92 let fx2: *u8 = "/tmp/nx_route_gate2.conf" as *u8
93 let tfd: i64 = sys_openat_wr(fx2, 0x1a4)
94 sys_close(tfd)
95 total = total + 1; pass = pass + rg_eq("T25 empty table REFUSED (fail-closed)" as *u8, md_route_append(fx2, "fresh.com" as *u8, "/z" as *u8, 12000, "buffered" as *u8), 0)
96 total = total + 1; pass = pass + rg_eq("T26 empty file untouched (0 lines)" as *u8, rg_lines(fx2), 0)
97 total = total + 1; pass = pass + rg_eq("T27 absent file REFUSED" as *u8, md_route_append("/tmp/nx_route_gate_absent.conf" as *u8, "fresh.com" as *u8, "/z" as *u8, 12000, "buffered" as *u8), 0)
98 // (d) NEVER-BRICK: .prev banks the PRE-edit table (recovery = cp .prev back). After the T20 upsert
99 // (example.com / 9001->9999), .prev must hold the OLD 9001 row and NOT the new 9999 value.
100 total = total + 1; pass = pass + rg_eq("T28 .prev holds pre-edit row (9001)" as *u8, rg_has("/tmp/nx_route_gate.conf.prev" as *u8, "example.com / 9001 buffered" as *u8), 1)
101 total = total + 1; pass = pass + rg_eq("T29 .prev lacks the new value (9999)" as *u8, rg_has("/tmp/nx_route_gate.conf.prev" as *u8, "9999" as *u8), 0)
102
103 rg_w("== result " as *u8); rg_wn(pass)
104 rg_w(" total=" as *u8); rg_wn(total)
105 if pass == total { rg_w("ROUTE_CTL_GATE PASS\n" as *u8); return 0 }
106 rg_w("ROUTE_CTL_GATE FAIL\n" as *u8)
107 return 1
108}