code wiki / (root) / nx_content_get.nx

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}