code wiki / _hdl_build / nx_ad_botfilter.nx
nx_ad_botfilter.nx source
↩ module page · 100 lines · 3918 B
1// nx_ad_botfilter.nx -- LIB (PURE, no I/O): aggregate INVALID-TRAFFIC / bot classifier for the
2// "Brought to you by" performance ad engine (ADS-019, CALLOUT-004). Applied BEFORE counting so
3// HONEST billing never charges for bot traffic (nx_ad_meter ADS-007 applies it; nx_ad_conversion
4// ADS-018 reuses it -- DRY rule 15).
5//
6// PRIVACY BY CONSTRUCTION: inputs are AGGREGATE CLASS-BUCKET counts only -- each bucket =
7// (ua_class, src_class, js_class, n). No visitor id / IP / fingerprint exists in the data model,
8// so "filter bots" can never become "track people".
9//
10// PURE: this lib does NO file/store I/O. Rules arrive as a parsed pair-array. The SOURCE of rules
11// is the sovereign store (nx_ad_store -> adcfg:botrules), NOT a TSV file -- "pure Nishi ecosystem",
12// no flat-file registries. `bf_parse_rules` turns store bytes into rules; the store read lives in
13// nx_ad_store so this classifier stays a pure leaf. license_tier: ORIGINAL
14import "nx_syscalls.nx"
15
16const BF_MAXRULES: i64 = 32
17
18// parse a non-negative int starting at p (consumes [0-9]); writes value to *out; returns the
19// index of the first non-digit (or end).
20func bf_int(buf: *u8, p: i64, n: i64, out: *i64) -> i64 {
21 var v: i64 = 0
22 var i: i64 = p
23 var go: i64 = 1
24 while go == 1 {
25 if i >= n { go = 0 } else {
26 let c: i64 = buf[i] as i64
27 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); i = i + 1 } else { go = 0 } } else { go = 0 }
28 }
29 }
30 out[0] = v
31 return i
32}
33
34// parse rules from store bytes: data lines `field<sep>bad_code...` (any trailing fields ignored).
35// Non-digit-leading lines are skipped. rules filled flat [field0,bad0, field1,bad1, ...]; returns
36// the pair count. Data-driven (rule 11): the shipped rule set, never hardcoded signatures.
37func bf_parse_rules(buf: *u8, n: i64, rules: *i64, maxr: i64) -> i64 {
38 let fp: *i64 = sys_mmap(8) as *i64
39 let bp: *i64 = sys_mmap(8) as *i64
40 var cnt: i64 = 0
41 var i: i64 = 0
42 while i < n {
43 var le: i64 = i
44 var go: i64 = 1
45 while go == 1 { if le >= n { go = 0 } else { if buf[le] == (10 as u8) { go = 0 } else { le = le + 1 } } }
46 if i < le {
47 let c0: i64 = buf[i] as i64
48 if c0 >= 48 { if c0 <= 57 {
49 if cnt < maxr {
50 var p: i64 = bf_int(buf, i, le, fp)
51 if p < le { p = p + 1 }
52 p = bf_int(buf, p, le, bp)
53 rules[cnt * 2] = fp[0]
54 rules[cnt * 2 + 1] = bp[0]
55 cnt = cnt + 1
56 }
57 } }
58 }
59 i = le + 1
60 }
61 return cnt
62}
63
64// pick aggregate class field by index (0=ua 1=src 2=js). No identity, only class codes.
65func bf_field(ua: i64, src: i64, js: i64, field: i64) -> i64 {
66 if field == 0 { return ua }
67 if field == 1 { return src }
68 return js
69}
70
71// 1 if this class-bucket is invalid traffic (matches any rule), else 0.
72func bf_invalid(ua: i64, src: i64, js: i64, rules: *i64, nrules: i64) -> i64 {
73 var i: i64 = 0
74 while i < nrules {
75 let f: i64 = rules[i * 2]
76 let bad: i64 = rules[i * 2 + 1]
77 if bf_field(ua, src, js, f) == bad { return 1 }
78 i = i + 1
79 }
80 return 0
81}
82
83// tally valid vs invalid event COUNTS over m buckets (flat [ua,src,js,n] per bucket). Writes
84// valid-n to *ov, invalid-n to *oi. Aggregate-only; returns total n. The meter counts only valid.
85func bf_tally(ev: *i64, m: i64, rules: *i64, nrules: i64, ov: *i64, oi: *i64) -> i64 {
86 var v: i64 = 0
87 var iv: i64 = 0
88 var b: i64 = 0
89 while b < m {
90 let ua: i64 = ev[b * 4]
91 let src: i64 = ev[b * 4 + 1]
92 let js: i64 = ev[b * 4 + 2]
93 let cnt: i64 = ev[b * 4 + 3]
94 if bf_invalid(ua, src, js, rules, nrules) == 1 { iv = iv + cnt } else { v = v + cnt }
95 b = b + 1
96 }
97 ov[0] = v
98 oi[0] = iv
99 return v + iv
100}