nx_content_get.nx source
↩ module page · 305 lines · 13384 B
1// nx_content_get.nx -- CHUNKED CONTENT DOWNLOAD SOURCE: the sovereign door for NAS->laptop bytes.
2//
3// WHY THIS EXISTS (operator standing order 2026-09-03: "dont build python ... build nishi and nishi estate
4// should have this capability and if it doesnt lets build from the first byte up these capabilities").
5// nx_content_put calls itself "the sovereign door for laptop->NAS bytes" and there was NO MIRROR. Measured
6// the same day: a NAS-side source that had legitimately diverged from its laptop twin could not be brought
7// back without a seat retyping 38,414 bytes through a chat transcript -- which is the hand-transcription
8// defect this estate builds organs to avoid. The absence was named as a gap before it was built; this is it.
9//
10// nx_content_get begin <src> -> CG-BEGIN src= total_bytes= sha256= chunk_raw= nchunks=
11// nx_content_get chunk <src> <index> -> CG-CHUNK OK ... chunk_sha256= b64=<data>
12// nx_content_get probe -> the derived chunk size and every term of its derivation
13//
14// IT IS A MIRROR, NOT A COPY, AND THE ASYMMETRIES ARE THE DESIGN:
15//
16// 1. STATELESS BY CONSTRUCTION. nx_content_put needs staging, an index and a stale-reaper because the
17// bytes ACCUMULATE ON THE RECEIVER. In a get they accumulate on the CLIENT, so there is no server-side
18// transfer state to stage, index, resume or reap. Half of the incumbent machinery is therefore
19// deliberately ABSENT rather than mirrored -- carrying it would be complexity with no invariant to
20// protect, and a reaper with nothing to reap is a beat that can only ever be wrong.
21//
22// 2. THE GENERATION CHECK REPLACES CAS. The incumbent refuses a DIFFERENT body for an index that already
23// landed, so a blind retry can never corrupt. A get has the opposite exposure: the SOURCE can change
24// under a multi-call read, and reassembling chunks from two generations yields a franken-file that is
25// byte-valid and semantically nonsense. So begin declares the WHOLE-FILE sha256 and the client MUST
26// verify its reassembly against it. A file edited mid-transfer fails that check loudly.
27// Per-chunk sha is carried too, and here is exactly what each can and cannot do: the per-chunk digest
28// detects a CORRUPTED chunk, and it CANNOT detect a consistent read of a CHANGED file. Only the
29// whole-file digest can, which is why begin pays one full read to compute it.
30// A SIZE COMPARISON WOULD NOT DO: a same-size rewrite passes it, and "size is never an identity" is a
31// law this estate has been bitten by three separate times.
32//
33// 3. THE CHUNK SIZE IS DERIVED FROM THE *RESPONSE* CAP, NOT THE REQUEST CAP. This is the term a symmetric
34// copy would get wrong. The incumbent sizes its chunk against the 65,536-byte request-body cap it
35// measured at the edge; a get rides the RESPONSE path, whose observed capture cap is 163,840 -- the
36// bound nx_fs prints as capture_cap when it truncates. Reserve = the receipt skeleton MEASURED FROM THE
37// LITERAL AT RUNTIME, never hand-counted, plus a full path, plus the digits and hex digest it carries.
38// The raw size is floored to a multiple of 3 so base64 never emits interior padding.
39// MEASURED 2026-09-03: reserve=1249 (66+1024+95+64), chunk_raw=121941, exactly divisible by 3.
40//
41// 4. A POSITIVE ALLOWLIST, NOT A DENY-LIST. A deny-list is defeated by the path nobody thought of; an
42// allowlist fails closed. This is the same choice cp_dest_ok made for the write direction.
43// DELIBERATE DIVERGENCE FROM THE INCUMBENT fs DENY-LIST, ON MEASURED GROUNDS: that list matches the
44// substring "secret" anywhere in the path, which the estate has ALREADY RECORDED as a defect -- it
45// blocks reading the secret scanner's own source, and so blocks fixing the detector. Copying that rule
46// here would replicate a known defect. Credential MATERIAL is refused by name instead, and that
47// refusal is belt-and-braces BEHIND the allowlist, never the primary control.
48//
49// PROVEN LOCALLY 2026-09-03 BEFORE LANDING, against INDEPENDENT oracles (sha256sum, wc -c, base64 -d):
50// whole-file digest identical to sha256sum on two subjects; round-trip of a 554,696-byte source across
51// FIVE chunks reassembled to a byte-identical digest, with the chunk sizes summing exactly to the total
52// (4 x 121941 + 66932 = 554696) and offsets stepping 0/121941/243882/365823/487764.
53// The single-chunk case was NOT accepted as proof of the multi-chunk path -- a subject large enough to
54// force nchunks>1 was selected deliberately and the fixture asserts nchunks>1 before judging anything.
55//
56// exit: 0 ok | 2 io | 3 usage | 4 refused-src | 5 range
57// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
58import "nx_syscalls.nx"
59import "nx_base64.nx"
60import "nx_sha256.nx"
61
62const CG_RESP_CAP: i64 = 163840
63const CG_DIGEST: i64 = 32
64const CG_SHAHEX: i64 = 64
65const CG_PATHCAP: i64 = 1024
66const CG_I64_DIGITS: i64 = 19
67const CG_RECEIPT_FIELDS: i64 = 5
68const CG_SEEK_SET: i64 = 0
69const CG_SEEK_END: i64 = 2
70const CG_READBUF: i64 = 65536
71// the exact receipt a chunk call rides in; its length is MEASURED at runtime, never counted.
72const CG_SKEL: *u8 = "CG-CHUNK OK src= index= off= raw= total_bytes= chunk_sha256= b64=\n" as *u8
73
74func cg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
75func cg_out(s: *u8) -> i64 { sys_write(1, s, cg_slen(s)); return 0 }
76func cg_eo(s: *u8) -> i64 { sys_write(2, s, cg_slen(s)); return 0 }
77func cg_num(v: i64) -> i64 {
78 let b: *u8 = sys_mmap(32)
79 var m: i64 = v
80 var i: i64 = 31
81 if m == 0 { i = i - 1; b[i] = 48 as u8 }
82 while m > 0 { i = i - 1; b[i] = (48 + (m % 10)) as u8; m = m / 10 }
83 sys_write(1, (b as i64 + i) as *u8, 31 - i)
84 return 0
85}
86func cg_hex_into(d: *u8, out: *u8) -> i64 {
87 var i: i64 = 0
88 while i < CG_DIGEST {
89 let v: i64 = d[i] as i64
90 let hi: i64 = (v >> 4) & 15
91 let lo: i64 = v & 15
92 if hi < 10 { out[i * 2] = (48 + hi) as u8 } else { out[i * 2] = (87 + hi) as u8 }
93 if lo < 10 { out[i * 2 + 1] = (48 + lo) as u8 } else { out[i * 2 + 1] = (87 + lo) as u8 }
94 i = i + 1
95 }
96 out[CG_SHAHEX] = 0 as u8
97 return 0
98}
99func cg_sha_hex_of(buf: *u8, n: i64, outhex: *u8) -> i64 {
100 let d: *u8 = sys_mmap(CG_DIGEST + 8)
101 sha256_digest(buf, n, d)
102 cg_hex_into(d, outhex)
103 return 0
104}
105func cg_starts(p: *u8, pre: *u8) -> i64 {
106 var i: i64 = 0
107 while pre[i] != (0 as u8) {
108 if p[i] != pre[i] { return 0 }
109 i = i + 1
110 }
111 return 1
112}
113func cg_has(p: *u8, needle: *u8) -> i64 {
114 let n: i64 = cg_slen(needle)
115 let m: i64 = cg_slen(p)
116 if n == 0 { return 0 }
117 var i: i64 = 0
118 while i + n <= m {
119 var j: i64 = 0
120 var same: i64 = 1
121 while j < n {
122 if p[i + j] != needle[j] { same = 0; j = n } else { j = j + 1 }
123 }
124 if same == 1 { return 1 }
125 i = i + 1
126 }
127 return 0
128}
129
130// ---- SOURCE ALLOWLIST (positive; see header note 4) -------------------------------------------
131func cg_src_ok(p: *u8) -> i64 {
132 if cg_has(p, ".." as *u8) == 1 { return 0 }
133 // credential MATERIAL by name -- belt-and-braces behind the allowlist, never the primary control
134 if cg_has(p, ".cap" as *u8) == 1 { return 0 }
135 if cg_has(p, ".key" as *u8) == 1 { return 0 }
136 if cg_has(p, "tokens" as *u8) == 1 { return 0 }
137 if cg_starts(p, "buildroot/runtime/" as *u8) == 1 { return 1 }
138 if cg_starts(p, "buildroot/knowledge/" as *u8) == 1 { return 1 }
139 if cg_starts(p, "runtime/" as *u8) == 1 { return 1 }
140 if cg_starts(p, "knowledge/" as *u8) == 1 { return 1 }
141 if cg_starts(p, "/tmp/" as *u8) == 1 { return 1 }
142 return 0
143}
144
145// ---- THE DERIVED CHUNK SIZE (see header note 3) -----------------------------------------------
146// Every term is named and measured; nothing here is a tuned constant.
147func cg_reserve() -> i64 {
148 return cg_slen(CG_SKEL) + CG_PATHCAP + (CG_RECEIPT_FIELDS * CG_I64_DIGITS) + CG_SHAHEX
149}
150func cg_chunk_raw() -> i64 {
151 let avail: i64 = CG_RESP_CAP - cg_reserve()
152 if avail <= 0 { return 0 }
153 var raw: i64 = (avail * 3) / 4
154 raw = raw - (raw % 3)
155 return raw
156}
157
158func cg_fsize(path: *u8) -> i64 {
159 let fd: i64 = sys_openat_rd(path)
160 if fd < 0 { return 0 - 1 }
161 let n: i64 = sys_lseek(fd, 0, CG_SEEK_END)
162 sys_close(fd)
163 return n
164}
165
166// whole-file sha256, streamed -- the generation witness begin pays one full read for
167func cg_file_sha(path: *u8, outhex: *u8) -> i64 {
168 let fd: i64 = sys_openat_rd(path)
169 if fd < 0 { return 0 - 1 }
170 let c: *Sha256 = sys_mmap(512) as *Sha256
171 sha256_init(c)
172 let buf: *u8 = sys_mmap(CG_READBUF)
173 var go: i64 = 1
174 var total: i64 = 0
175 while go == 1 {
176 let r: i64 = sys_read(fd, buf, CG_READBUF)
177 if r <= 0 { go = 0 } else {
178 sha256_update(c, buf, r)
179 total = total + r
180 }
181 }
182 sys_close(fd)
183 let d: *u8 = sys_mmap(CG_DIGEST + 8)
184 sha256_final(c, d)
185 cg_hex_into(d, outhex)
186 return total
187}
188
189func cg_do_begin(src: *u8) -> i64 {
190 let sz: i64 = cg_fsize(src)
191 if sz < 0 { cg_eo("CG-IO: cannot open source: " as *u8); cg_eo(src); cg_eo("\n" as *u8); return 2 }
192 let hex: *u8 = sys_mmap(CG_SHAHEX + 8)
193 let n: i64 = cg_file_sha(src, hex)
194 if n < 0 { cg_eo("CG-IO: cannot read source for digest\n" as *u8); return 2 }
195 let raw: i64 = cg_chunk_raw()
196 var nch: i64 = 0
197 if raw > 0 { nch = (n + raw - 1) / raw }
198 cg_out("CG-BEGIN src=" as *u8); cg_out(src)
199 cg_out(" total_bytes=" as *u8); cg_num(n)
200 cg_out(" sha256=" as *u8); cg_out(hex)
201 cg_out(" chunk_raw=" as *u8); cg_num(raw)
202 cg_out(" nchunks=" as *u8); cg_num(nch)
203 cg_out("\n" as *u8)
204 return 0
205}
206
207func cg_do_chunk(src: *u8, index: i64) -> i64 {
208 let raw: i64 = cg_chunk_raw()
209 if raw <= 0 { cg_eo("CG-IO: derived chunk size is zero\n" as *u8); return 2 }
210 let sz: i64 = cg_fsize(src)
211 if sz < 0 { cg_eo("CG-IO: cannot open source\n" as *u8); return 2 }
212 let off: i64 = index * raw
213 if off >= sz {
214 if sz > 0 { cg_eo("CG-RANGE: index past end of file\n" as *u8); return 5 }
215 }
216 var want: i64 = raw
217 if off + want > sz { want = sz - off }
218 let fd: i64 = sys_openat_rd(src)
219 if fd < 0 { cg_eo("CG-IO: cannot open source\n" as *u8); return 2 }
220 sys_lseek(fd, off, CG_SEEK_SET)
221 let buf: *u8 = sys_mmap(raw + 8)
222 var got: i64 = 0
223 var go: i64 = 1
224 while go == 1 {
225 if got >= want { go = 0 } else {
226 let r: i64 = sys_read(fd, (buf as i64 + got) as *u8, want - got)
227 if r <= 0 { go = 0 } else { got = got + r }
228 }
229 }
230 sys_close(fd)
231 let hex: *u8 = sys_mmap(CG_SHAHEX + 8)
232 cg_sha_hex_of(buf, got, hex)
233 let enc: *u8 = sys_mmap(((got + 2) / 3) * 4 + 16)
234 let elen: i64 = b64_encode(buf, got, enc)
235 cg_out("CG-CHUNK OK src=" as *u8); cg_out(src)
236 cg_out(" index=" as *u8); cg_num(index)
237 cg_out(" off=" as *u8); cg_num(off)
238 cg_out(" raw=" as *u8); cg_num(got)
239 cg_out(" total_bytes=" as *u8); cg_num(sz)
240 cg_out(" chunk_sha256=" as *u8); cg_out(hex)
241 cg_out(" b64=" as *u8)
242 sys_write(1, enc, elen)
243 cg_out("\n" as *u8)
244 return 0
245}
246
247func cg_do_probe() -> i64 {
248 cg_out("CG-PROBE resp_cap=" as *u8); cg_num(CG_RESP_CAP)
249 cg_out(" skel_measured=" as *u8); cg_num(cg_slen(CG_SKEL))
250 cg_out(" pathcap=" as *u8); cg_num(CG_PATHCAP)
251 cg_out(" digit_fields=" as *u8); cg_num(CG_RECEIPT_FIELDS * CG_I64_DIGITS)
252 cg_out(" shahex=" as *u8); cg_num(CG_SHAHEX)
253 cg_out(" reserve=" as *u8); cg_num(cg_reserve())
254 cg_out(" chunk_raw=" as *u8); cg_num(cg_chunk_raw())
255 cg_out("\n" as *u8)
256 return 0
257}
258
259func cg_atoi(s: *u8) -> i64 {
260 var v: i64 = 0
261 var i: i64 = 0
262 while s[i] != (0 as u8) {
263 let c: i64 = s[i] as i64
264 if c < 48 { return 0 - 1 }
265 if c > 57 { return 0 - 1 }
266 v = v * 10 + (c - 48)
267 i = i + 1
268 }
269 if i == 0 { return 0 - 1 }
270 return v
271}
272func cg_streq(a: *u8, b: *u8) -> i64 {
273 var i: i64 = 0
274 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
275 if b[i] != (0 as u8) { return 0 }
276 return 1
277}
278
279func main(argc: i64, argv: *i64) -> i64 {
280 if argc < 2 {
281 cg_eo("usage: nx_content_get begin <src> | chunk <src> <index> | probe\n" as *u8)
282 return 3
283 }
284 let op: *u8 = argv[1] as *u8
285 if cg_streq(op, "probe" as *u8) == 1 { return cg_do_probe() }
286 if argc < 3 {
287 cg_eo("usage: nx_content_get begin <src> | chunk <src> <index> | probe\n" as *u8)
288 return 3
289 }
290 let src: *u8 = argv[2] as *u8
291 if cg_src_ok(src) == 0 {
292 cg_eo("CG-REFUSED-SRC: source must start buildroot/runtime/ buildroot/knowledge/ runtime/ knowledge/ /tmp/ carry no dotdot and name no credential material -- got: " as *u8)
293 cg_eo(src); cg_eo("\n" as *u8)
294 return 4
295 }
296 if cg_streq(op, "begin" as *u8) == 1 { return cg_do_begin(src) }
297 if cg_streq(op, "chunk" as *u8) == 1 {
298 if argc < 4 { cg_eo("usage: nx_content_get chunk <src> <index>\n" as *u8); return 3 }
299 let idx: i64 = cg_atoi(argv[3] as *u8)
300 if idx < 0 { cg_eo("CG-USAGE: index must be a non-negative integer\n" as *u8); return 3 }
301 return cg_do_chunk(src, idx)
302 }
303 cg_eo("CG-USAGE: unknown verb\n" as *u8)
304 return 3
305}