nx_telemetry.nx source
↩ module page · 212 lines · 10854 B
1// nx_telemetry.nx -- CONSENT-GATED DEVICE TELEMETRY (X-DRV-F1).
2//
3// A spore that has brought up its hardware (X-DRV-W2) can CHOOSE to contribute its device
4// learnings (device-id + working driver-spec + bring-up status) to the federated registry --
5// but ONLY by explicit opt-in. The CARDINAL property: DEFAULT = LOCAL-ONLY. Nothing leaves the
6// device unless a consent row says SHARED; sharing is per-device-class and REVOCABLE (flip back to
7// LOCAL and the next run shares nothing new). Shared payloads are GENUINELY ed25519-SIGNED with
8// the node's identity key (composing nx_ed25519_signature -- DRY), carrying the node public key as
9// provenance so the federated side can verify authenticity. Privacy-first, sovereign.
10//
11// nx_telemetry [consent_path] [devmap_path]
12// consent_path : telemetry_consent.tsv (deviceid<TAB>LOCAL|SHARED; ABSENT row = LOCAL-ONLY)
13// devmap_path : emu_devmap.tsv (the probed devices)
14// node key : knowledge/registry/node_ed25519_seed.bin (32-byte node identity priv key)
15// LOCAL records -> knowledge/status/telemetry_local.log (retained on-device, NEVER shared)
16// SHARED records -> knowledge/status/telemetry_shared.log (opt-in only; signed + pubkey)
17// Sovereign (syscalls + the ed25519 organ), no gcc/.sh. license_tier: ORIGINAL
18import "nx_syscalls.nx"
19import "nx_ed25519_signature.nx"
20const TM_MAGIC_65536: i64 = 65536
21const TM_MAGIC_1024: i64 = 1024
22
23const TM_REG: *u8 = "knowledge/registry/driver_registry.tsv"
24const TM_SEED: *u8 = "knowledge/registry/node_ed25519_seed.bin"
25const TM_LOCALLOG: *u8 = "knowledge/status/telemetry_local.log"
26const TM_SHAREDLOG:*u8 = "knowledge/status/telemetry_shared.log"
27
28func tm_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
29func tm_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
30func tm_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
31func tm_n(v: i64) -> i64 { tm_fn(1, v); return 0 }
32
33func tm_read(path: *u8, buf: *u8, cap: i64) -> i64 {
34 let fd: i64 = sys_openat_rd(path)
35 if fd < 0 { return 0 - 1 }
36 var n: i64 = 0
37 var go: i64 = 1
38 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap - 1 { go = 0 } }
39 sys_close(fd)
40 return n
41}
42
43func tm_parse_num(buf: *u8, p: i64, le: i64) -> i64 {
44 var q: i64 = p
45 var val: i64 = 0
46 if q + 1 < le { if buf[q] == (48 as u8) { if buf[q+1] == (120 as u8) {
47 q = q + 2
48 var go: i64 = 1
49 while go == 1 { if q >= le { go = 0 } else { let c: i64 = buf[q] as i64; var d: i64 = 0-1; if c>=48 { if c<=57 { d=c-48 } } if c>=97 { if c<=102 { d=c-87 } } if c>=65 { if c<=70 { d=c-55 } } if d<0 { go=0 } else { val=val*16+d; q=q+1 } } }
50 return val
51 }}}
52 var go2: i64 = 1
53 while go2 == 1 { if q >= le { go2 = 0 } else { let c: i64 = buf[q] as i64; if c>=48 { if c<=57 { val=val*10+(c-48); q=q+1 } else { go2=0 } } else { go2=0 } } }
54 return val
55}
56
57// start offset of the idx-th TAB field in line [ls,le), or -1.
58func tm_field(buf: *u8, ls: i64, le: i64, idx: i64) -> i64 {
59 if idx == 0 { return ls }
60 var f: i64 = 0; var p: i64 = ls
61 while p < le { if buf[p] == (9 as u8) { f = f + 1; if f == idx { return p + 1 } } p = p + 1 }
62 return 0 - 1
63}
64
65// copy field text (until TAB/eol) from start s into out; return length.
66func tm_copy_field(buf: *u8, s: i64, le: i64, out: *u8) -> i64 {
67 var c: i64 = 0; var q: i64 = s
68 while q < le { if buf[q] == (9 as u8) { q = le } else { out[c] = buf[q]; c = c + 1; q = q + 1 } }
69 out[c] = 0 as u8
70 return c
71}
72
73// look up deviceid -> spec in the driver registry buffer; copy into out; return 1 if found.
74func tm_reg_spec(reg: *u8, rn: i64, did: i64, out: *u8) -> i64 {
75 var ls: i64 = 0
76 while ls < rn {
77 var le: i64 = ls
78 var sc: i64 = 1
79 while sc == 1 { if le >= rn { sc = 0 } else { if reg[le] == (10 as u8) { sc = 0 } else { le = le + 1 } } }
80 if reg[ls] != (35 as u8) {
81 let f0: i64 = tm_field(reg, ls, le, 0)
82 let f1: i64 = tm_field(reg, ls, le, 1)
83 if f0 >= 0 { if f1 >= 0 {
84 if tm_parse_num(reg, f0, le) == did { tm_copy_field(reg, f1, le, out); return 1 }
85 } }
86 }
87 ls = le + 1
88 }
89 return 0
90}
91
92// look up deviceid -> consent in the consent buffer; 1 = SHARED, 0 = LOCAL (default if absent).
93func tm_consent(con: *u8, cn: i64, did: i64) -> i64 {
94 var ls: i64 = 0
95 while ls < cn {
96 var le: i64 = ls
97 var sc: i64 = 1
98 while sc == 1 { if le >= cn { sc = 0 } else { if con[le] == (10 as u8) { sc = 0 } else { le = le + 1 } } }
99 if con[ls] != (35 as u8) {
100 let f0: i64 = tm_field(con, ls, le, 0)
101 let f1: i64 = tm_field(con, ls, le, 1)
102 if f0 >= 0 { if f1 >= 0 {
103 if tm_parse_num(con, f0, le) == did {
104 // SHARED iff field1 starts with 'S'
105 if con[f1] == (83 as u8) { return 1 }
106 return 0
107 }
108 } }
109 }
110 ls = le + 1
111 }
112 return 0 // default LOCAL-ONLY (no-share-by-default)
113}
114
115// hex-encode n bytes of src into dst (2*n chars + NUL).
116func tm_hex(src: *u8, n: i64, dst: *u8) -> i64 {
117 var i: i64 = 0
118 while i < n {
119 let b: i64 = src[i] as i64
120 let hi: i64 = (b >> 4) & 0xf
121 let lo: i64 = b & 0xf
122 if hi < 10 { dst[i*2] = (48 + hi) as u8 } else { dst[i*2] = (87 + hi) as u8 }
123 if lo < 10 { dst[i*2+1] = (48 + lo) as u8 } else { dst[i*2+1] = (87 + lo) as u8 }
124 i = i + 1
125 }
126 dst[n*2] = 0 as u8
127 return n * 2
128}
129
130// append NUL-terminated s into dst at off; return new off.
131func tm_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
132func tm_catn(dst: *u8, off: i64, v: i64) -> i64 { let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{dst[off+i]=t[k-1-i];i=i+1}; return off+k }
133
134func main(argc: i64, argv: *i64) -> i64 {
135 var consent_path: *u8 = "knowledge/registry/telemetry_consent.tsv" as *u8
136 var devmap_path: *u8 = "knowledge/registry/emu_devmap.tsv" as *u8
137 if argc >= 2 { consent_path = argv[1] as *u8 }
138 if argc >= 3 { devmap_path = argv[2] as *u8 }
139
140 tm_p("=== nx_telemetry: consent-gated device telemetry (DEFAULT LOCAL-ONLY; opt-in SHARED is ed25519-signed) ===\n" as *u8)
141
142 // node identity key (priv 32B); derive pub. Production generates a random key per node; the
143 // gate writes a fixed test seed for reproducibility -- the SIGNING is real ed25519 either way.
144 let lb: *i64 = sys_mmap(16) as *i64
145 let priv32: *u8 = sys_read_file(TM_SEED, lb)
146 if (priv32 as i64) == 0 { tm_p("TELEMETRY verdict=RED reason=node-key-missing\n" as *u8); sys_exit(1); return 1 }
147 if lb[0] != 32 { tm_p("TELEMETRY verdict=RED reason=node-key-bad-size\n" as *u8); sys_exit(1); return 1 }
148 let pub32: *u8 = sys_mmap(32)
149 if ed25519_pub_from_priv(priv32, pub32) != 0 { tm_p("TELEMETRY verdict=RED reason=pub-derive-fail\n" as *u8); sys_exit(1); return 1 }
150 let pubhex: *u8 = sys_mmap(80)
151 tm_hex(pub32, 32, pubhex)
152
153 let reg: *u8 = sys_mmap(TM_MAGIC_65536); let rn: i64 = tm_read(TM_REG, reg, TM_MAGIC_65536)
154 let con: *u8 = sys_mmap(TM_MAGIC_65536); var cn: i64 = tm_read(consent_path, con, TM_MAGIC_65536)
155 if cn < 0 { cn = 0 } // absent consent file = everything LOCAL-ONLY (no-share-by-default)
156 let dev: *u8 = sys_mmap(TM_MAGIC_65536); let dn: i64 = tm_read(devmap_path, dev, TM_MAGIC_65536)
157 if dn <= 0 { tm_p("TELEMETRY verdict=RED reason=devmap-missing\n" as *u8); sys_exit(1); return 1 }
158
159 let lfd: i64 = sys_openat_append(TM_LOCALLOG, 0x1a4)
160 let sfd: i64 = sys_openat_append(TM_SHAREDLOG, 0x1a4)
161
162 let spec: *u8 = sys_mmap(512)
163 let payload: *u8 = sys_mmap(TM_MAGIC_1024)
164 let sig: *u8 = sys_mmap(64)
165 let sighex: *u8 = sys_mmap(160)
166
167 var shared: i64 = 0
168 var local: i64 = 0
169
170 var ls: i64 = 0
171 while ls < dn {
172 var le: i64 = ls
173 var sc: i64 = 1
174 while sc == 1 { if le >= dn { sc = 0 } else { if dev[le] == (10 as u8) { sc = 0 } else { le = le + 1 } } }
175 if dev[ls] != (35 as u8) {
176 let f0: i64 = tm_field(dev, ls, le, 0)
177 let f1: i64 = tm_field(dev, ls, le, 1)
178 if f0 >= 0 { if f1 >= 0 {
179 let did: i64 = tm_parse_num(dev, f1, le) // emu_devmap: base<TAB>deviceid
180 spec[0] = 0 as u8
181 tm_reg_spec(reg, rn, did, spec)
182 // build the telemetry payload (the learning this node would contribute)
183 var po: i64 = 0
184 po = tm_cat(payload, po, "node-telemetry deviceid=" as *u8); po = tm_catn(payload, po, did)
185 po = tm_cat(payload, po, " spec=" as *u8); po = tm_cat(payload, po, spec)
186 po = tm_cat(payload, po, " status=brought-up-working" as *u8)
187 payload[po] = 0 as u8
188 let consent: i64 = tm_consent(con, cn, did)
189 if consent == 1 {
190 // OPT-IN: SHARE -- ed25519-SIGN the payload with the node identity key.
191 if ed25519_sign_full(priv32, payload, po, sig) == 0 {
192 tm_hex(sig, 64, sighex)
193 if sfd >= 0 { tm_fp(sfd, "SHARED " as *u8); tm_fp(sfd, payload); tm_fp(sfd, " sig=" as *u8); tm_fp(sfd, sighex); tm_fp(sfd, " pub=" as *u8); tm_fp(sfd, pubhex); tm_fp(sfd, "\n" as *u8) }
194 shared = shared + 1
195 tm_p(" deviceid=" as *u8); tm_n(did); tm_p(" consent=SHARED -> signed payload to shared outbox\n" as *u8)
196 }
197 } else {
198 // DEFAULT LOCAL-ONLY: retained on-device, NOTHING leaves.
199 if lfd >= 0 { tm_fp(lfd, "LOCAL " as *u8); tm_fp(lfd, payload); tm_fp(lfd, " (retained on-device, not shared)\n" as *u8) }
200 local = local + 1
201 tm_p(" deviceid=" as *u8); tm_n(did); tm_p(" consent=LOCAL-ONLY -> retained on-device (not shared)\n" as *u8)
202 }
203 } }
204 }
205 ls = le + 1
206 }
207 if lfd >= 0 { sys_close(lfd) }
208 if sfd >= 0 { sys_close(sfd) }
209 tm_p("TELEMETRY summary shared=" as *u8); tm_n(shared); tm_p(" local_only=" as *u8); tm_n(local); tm_p(" (default=LOCAL-ONLY, share requires opt-in consent)\n" as *u8)
210 sys_exit(0)
211 return 0
212}