code wiki / _hdl_build / nx_apistack_ratelimit.nx
nx_apistack_ratelimit.nx source
↩ module page · 55 lines · 2507 B
1// nx_apistack_ratelimit.nx -- CAP-API-RATELIMIT: request quotas over /api (429 on exhaustion). Deterministic
2// token-bucket: refill by (elapsed * rate) capped at cap, then take cost -> pure INTEGER math, so the allow/429
3// decision is byte-reproducible (the sovereign exceed over cloud rate meters). Quotas are DATA (ratelimits.conf:
4// key<TAB>cap<TAB>rate_per_sec) so a per-shard/per-actor limit is a row, not code. The bucket STATE (tokens,
5// last_refill) is held by the caller (per-key), keeping this a pure, gateable step. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_site_lock_lib.nx"
8const K_MAGIC_65536: i64 = 65536
9
10// one token-bucket step. state[0]=tokens, state[1]=last_refill_sec. Returns 1=ALLOW, 0=DENY(429).
11func rl_step(state: *i64, cap: i64, rate: i64, now: i64, cost: i64) -> i64 {
12 let elapsed: i64 = now - state[1]
13 var tok: i64 = state[0]
14 if elapsed > 0 { tok = tok + elapsed * rate }
15 if tok > cap { tok = cap }
16 if tok < 0 { tok = 0 }
17 state[1] = now
18 if tok >= cost { state[0] = tok - cost; return 1 }
19 state[0] = tok
20 return 0
21}
22
23func rl_read_file(path: *u8, out: *u8, cap: i64) -> i64 {
24 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 }
25 var total: i64 = 0; var go: i64 = 1
26 while go == 1 {
27 let tail: *u8 = (out as i64 + total) as *u8
28 let nr: i64 = sys_read(fd, tail, cap - total)
29 if nr <= 0 { go = 0 }
30 if nr > 0 { total = total + nr }
31 if total >= cap { go = 0 }
32 }
33 sys_close(fd); return total
34}
35
36// per-key quota from data (ratelimits.conf: key<TAB>cap<TAB>rate_per_sec). 1 if found (fills out_cap/out_rate), else 0.
37func rl_config(conf_path: *u8, key: *u8, key_n: i64, out_cap: *i64, out_rate: *i64) -> i64 {
38 let buf: *u8 = sys_mmap(K_MAGIC_65536); let n: i64 = rl_read_file(conf_path, buf, K_MAGIC_65536)
39 let fs: *i64 = sys_mmap(8); let fe: *i64 = sys_mmap(8)
40 var ls: i64 = 0
41 while ls < n {
42 let le: i64 = slk_line_end(buf, n, ls)
43 if le > ls { if buf[ls] != (35 as u8) {
44 if slk_field(buf, ls, le, 0, fs, fe) == 1 {
45 if slk_eq(slk_at(buf, fs[0]), fe[0] - fs[0], key, key_n) == 1 {
46 if slk_field(buf, ls, le, 1, fs, fe) == 1 { out_cap[0] = slk_atoi(buf, fs[0], fe[0]) }
47 if slk_field(buf, ls, le, 2, fs, fe) == 1 { out_rate[0] = slk_atoi(buf, fs[0], fe[0]) }
48 return 1
49 }
50 }
51 } }
52 ls = le + 1
53 }
54 return 0
55}