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}