code wiki / (root) / nx_inflate.nx

nx_inflate.nx source

↩ module page · 348 lines · 16119 B

1// nx_inflate.nx -- SOVEREIGN DEFLATE DECOMPRESSION (RFC 1951) + gzip (1952) + zlib (1950). 2// 3// WHY THIS EXISTS. Our sovereign HTTPS client could REQUEST gzip and never inflate it. That gap 4// cost us twice: db.bepis.moe and most of the modern web returned bodies we could not read, and 5// worse, the search lane once tokenized COMPRESSED BYTES AS TEXT and poisoned its own index 6// (debt 1785794199) -- a decompressor that is missing does not fail loudly, it corrupts quietly. 7// 8// WHAT WAS ALREADY HERE, AND WHY THIS IS NOT A REBUILD. `_inflate_lib_authored.nx` (builder- 9// generated) already had a structurally correct canonical-Huffman inflate: bit reader, code-length 10// ordering, length/distance tables. That algorithm shape is REUSED here. What it lacked is exactly 11// what makes a decompressor safe to point at the internet: 12// - NO OUTPUT CAPACITY. `out: *u8` with no bound. A hostile or merely large body writes past the 13// allocation -- a heap overflow driven by untrusted network input. 14// - NO DISTANCE VALIDATION. `out[outpos-dist]` with dist > outpos reads BEFORE the buffer. 15// - NO CONTAINER. Raw DEFLATE only, so HTTP Content-Encoding: gzip was still unreadable. 16// - NO INTEGRITY CHECK. gzip carries CRC32 + ISIZE precisely so corruption is detectable. 17// - A DECODE FAILURE READ AS "DONE". An invalid symbol returned -1 and the caller treated it as 18// end-of-block, so a corrupt stream SILENTLY TRUNCATED instead of erroring -- the same 19// quiet-corruption shape as the missing decompressor itself. 20// 21// ★★CORRECTION 2026-08-04 (retraction, one day after shipping): THE PREMISE ABOVE WAS INCOMPLETE. 22// The estate ALREADY HELD a complete, capacity-bounded, CRC-verified gzip inflate: nx_gzip_wrap.nx 23// (nx_gzip_inflate, KAT-proven against real python-gzip streams, ~10 consumers incl the browser, 24// nx_page_ingest, nx_research_engine, the CommonCrawl ingest, nx_libdata) plus nx_zlib_wrap.nx for 25// RFC 1950. The corpus-ask before this build surfaced only _inflate_lib_authored.nx and missed both 26// wraps (they are named *_wrap, the search was too narrow). THIS FILE IS THEREFORE A SECOND 27// IMPLEMENTATION of a capability the estate held. The fetch lane was wired to the INCUMBENT 28// (nx_https_get_cli2 -> nx_gzip_inflate/nx_zlib_inflate, 2026-08-04) so this duplicate gains no 29// consumers; its current consumers are nx_gunzip (CLI) + nx_inflate_gate only. What it has that 30// the incumbent lacks: a raw RFC-1951 entry point (no container) + system-gzip cross-impl gate 31// fixtures. CONVERGENCE DEBT 1785854636: fold those into nx_gzip_wrap and retire this file, or 32// demote it to gate-oracle-only. DO NOT ADD NEW CONSUMERS. 33// ★A DECOMPRESSOR IS A PARSER POINTED AT HOSTILE INPUT: every read bounded, every write bounded, 34// every failure LOUD. Truncating quietly is the one behaviour worse than refusing. 35// 36// ERRORS (all distinct, all negative -- a caller can tell WHICH wall fired): 37// -1 output would exceed capacity -2 input exhausted / truncated stream 38// -3 invalid huffman symbol -4 back-reference distance before buffer start 39// -5 bad block type (BTYPE=3) -6 stored-block LEN/NLEN mismatch 40// -7 not a gzip container -8 unsupported gzip method/flags 41// -9 CRC32 mismatch -10 ISIZE mismatch 42// -11 not a zlib container -12 code-length run overflows the table 43// license_tier: ORIGINAL No hw writes (Rule 26). 44import "nx_syscalls.nx" 45const INF_MAGIC_1025: i64 = 1025 46const INF_MAGIC_1537: i64 = 1537 47const INF_MAGIC_2049: i64 = 2049 48const INF_MAGIC_3073: i64 = 3073 49const INF_MAGIC_4097: i64 = 4097 50const INF_MAGIC_6145: i64 = 6145 51const INF_MAGIC_8193: i64 = 8193 52const INF_MAGIC_12289: i64 = 12289 53const INF_MAGIC_16385: i64 = 16385 54const INF_MAGIC_24577: i64 = 24577 55 56const INF_CRC_POLY: i64 = 0xEDB88320 57const INF_M32: i64 = 0xFFFFFFFF 58 59// ---- CRC32 (IEEE, reflected) -- gzip's own integrity check ----------------------------------- 60func inf_crc32(buf: *u8, n: i64) -> i64 { 61 var crc: i64 = INF_M32 62 var i: i64 = 0 63 while i < n { 64 crc = crc ^ ((buf[i] & 0xff) as i64) 65 var k: i64 = 0 66 while k < 8 { 67 let lsb: i64 = crc & 1 68 crc = (crc >> 1) & 0x7FFFFFFF 69 if lsb == 1 { crc = crc ^ INF_CRC_POLY } 70 k = k + 1 71 } 72 i = i + 1 73 } 74 return (crc ^ INF_M32) & INF_M32 75} 76 77// ---- bit reader: cur[0] = bit position; cur[1] = sticky "ran off the end" flag ---------------- 78func inf_bits(buf: *u8, len: i64, cur: *i64, n: i64) -> i64 { 79 var v: i64 = 0 80 var g: i64 = 0 81 while g < n { 82 let bp: i64 = cur[0] >> 3 83 if bp >= len { cur[1] = 1; return v } 84 let bit: i64 = ((buf[bp] as i64) >> (cur[0] & 7)) & 1 85 v = v | (bit << g) 86 cur[0] = cur[0] + 1 87 g = g + 1 88 } 89 return v 90} 91 92// canonical huffman: counts per length + symbols in canonical order 93func inf_build(ln: *i64, n: i64, ct: *i64, sy: *i64, off: *i64) -> i64 { 94 var i: i64 = 0 95 while i <= 15 { ct[i] = 0; i = i + 1 } 96 i = 0 97 while i < n { ct[ln[i]] = ct[ln[i]] + 1; i = i + 1 } 98 ct[0] = 0 99 off[1] = 0 100 var l: i64 = 1 101 while l < 15 { off[l+1] = off[l] + ct[l]; l = l + 1 } 102 i = 0 103 while i < n { 104 if ln[i] != 0 { sy[off[ln[i]]] = i; off[ln[i]] = off[ln[i]] + 1 } 105 i = i + 1 106 } 107 return 0 108} 109func inf_decode(buf: *u8, len: i64, cur: *i64, ct: *i64, sy: *i64) -> i64 { 110 var code: i64 = 0 111 var first: i64 = 0 112 var idx: i64 = 0 113 var l: i64 = 1 114 while l <= 15 { 115 code = code | inf_bits(buf, len, cur, 1) 116 if cur[1] == 1 { return 0 - 2 } 117 let c: i64 = ct[l] 118 if code - first < c { return sy[idx + (code - first)] } 119 idx = idx + c 120 first = (first + c) << 1 121 code = code << 1 122 l = l + 1 123 } 124 return 0 - 3 125} 126 127// ---- the symbol loop. st[0]=outpos. Returns 0 ok or a negative error. ------------------------ 128func inf_codes(src: *u8, sl: i64, cur: *i64, out: *u8, outcap: i64, st: *i64, 129 lc: *i64, ls: *i64, dc: *i64, ds: *i64, 130 lens: *i64, lext: *i64, dists: *i64, dext: *i64) -> i64 { 131 var outpos: i64 = st[0] 132 var done: i64 = 0 133 while done == 0 { 134 let sym: i64 = inf_decode(src, sl, cur, lc, ls) 135 if sym < 0 { st[0] = outpos; return sym } 136 if sym < 256 { 137 if outpos >= outcap { st[0] = outpos; return 0 - 1 } 138 out[outpos] = sym as u8 139 outpos = outpos + 1 140 } 141 if sym == 256 { done = 1 } 142 if sym > 256 { 143 let s: i64 = sym - 257 144 if s >= 29 { st[0] = outpos; return 0 - 3 } 145 let length: i64 = lens[s] + inf_bits(src, sl, cur, lext[s]) 146 let dsy: i64 = inf_decode(src, sl, cur, dc, ds) 147 if dsy < 0 { st[0] = outpos; return dsy } 148 if dsy >= 30 { st[0] = outpos; return 0 - 3 } 149 let dist: i64 = dists[dsy] + inf_bits(src, sl, cur, dext[dsy]) 150 if cur[1] == 1 { st[0] = outpos; return 0 - 2 } 151 // the back-reference must live INSIDE what we have already produced 152 if dist > outpos { st[0] = outpos; return 0 - 4 } 153 if dist <= 0 { st[0] = outpos; return 0 - 4 } 154 if outpos + length > outcap { st[0] = outpos; return 0 - 1 } 155 var ci: i64 = 0 156 while ci < length { 157 out[outpos] = out[outpos - dist] 158 outpos = outpos + 1 159 ci = ci + 1 160 } 161 } 162 } 163 st[0] = outpos 164 return 0 165} 166 167// ---- raw DEFLATE. Returns output length, or a negative error. -------------------------------- 168func inf_raw(src: *u8, srclen: i64, out: *u8, outcap: i64) -> i64 { 169 let cur: *i64 = sys_mmap(32) as *i64 170 cur[0] = 0 171 cur[1] = 0 172 let st: *i64 = sys_mmap(16) as *i64 173 st[0] = 0 174 var fin: i64 = 0 175 176 let lens: *i64 = sys_mmap(8*29) as *i64 177 lens[0]=3;lens[1]=4;lens[2]=5;lens[3]=6;lens[4]=7;lens[5]=8;lens[6]=9;lens[7]=10 178 lens[8]=11;lens[9]=13;lens[10]=15;lens[11]=17;lens[12]=19;lens[13]=23;lens[14]=27;lens[15]=31 179 lens[16]=35;lens[17]=43;lens[18]=51;lens[19]=59;lens[20]=67;lens[21]=83;lens[22]=99;lens[23]=115 180 lens[24]=131;lens[25]=163;lens[26]=195;lens[27]=227;lens[28]=258 181 let lext: *i64 = sys_mmap(8*29) as *i64 182 var z: i64 = 0 183 while z < 8 { lext[z]=0; z=z+1 } 184 lext[8]=1;lext[9]=1;lext[10]=1;lext[11]=1;lext[12]=2;lext[13]=2;lext[14]=2;lext[15]=2 185 lext[16]=3;lext[17]=3;lext[18]=3;lext[19]=3;lext[20]=4;lext[21]=4;lext[22]=4;lext[23]=4 186 lext[24]=5;lext[25]=5;lext[26]=5;lext[27]=5;lext[28]=0 187 let dists: *i64 = sys_mmap(8*30) as *i64 188 dists[0]=1;dists[1]=2;dists[2]=3;dists[3]=4;dists[4]=5;dists[5]=7;dists[6]=9;dists[7]=13 189 dists[8]=17;dists[9]=25;dists[10]=33;dists[11]=49;dists[12]=65;dists[13]=97;dists[14]=129 190 dists[15]=193;dists[16]=257;dists[17]=385;dists[18]=513;dists[19]=769;dists[20]=INF_MAGIC_1025 191 dists[21]=INF_MAGIC_1537;dists[22]=INF_MAGIC_2049;dists[23]=INF_MAGIC_3073;dists[24]=INF_MAGIC_4097;dists[25]=INF_MAGIC_6145;dists[26]=INF_MAGIC_8193 192 dists[27]=INF_MAGIC_12289;dists[28]=INF_MAGIC_16385;dists[29]=INF_MAGIC_24577 193 let dext: *i64 = sys_mmap(8*30) as *i64 194 dext[0]=0;dext[1]=0;dext[2]=0;dext[3]=0;dext[4]=1;dext[5]=1;dext[6]=2;dext[7]=2 195 dext[8]=3;dext[9]=3;dext[10]=4;dext[11]=4;dext[12]=5;dext[13]=5;dext[14]=6;dext[15]=6 196 dext[16]=7;dext[17]=7;dext[18]=8;dext[19]=8;dext[20]=9;dext[21]=9;dext[22]=10;dext[23]=10 197 dext[24]=11;dext[25]=11;dext[26]=12;dext[27]=12;dext[28]=13;dext[29]=13 198 199 let fll: *i64 = sys_mmap(8*288) as *i64 200 var q: i64 = 0 201 while q < 144 { fll[q]=8; q=q+1 } 202 while q < 256 { fll[q]=9; q=q+1 } 203 while q < 280 { fll[q]=7; q=q+1 } 204 while q < 288 { fll[q]=8; q=q+1 } 205 let fdl: *i64 = sys_mmap(8*30) as *i64 206 q = 0 207 while q < 30 { fdl[q]=5; q=q+1 } 208 let ord: *i64 = sys_mmap(8*19) as *i64 209 ord[0]=16;ord[1]=17;ord[2]=18;ord[3]=0;ord[4]=8;ord[5]=7;ord[6]=9;ord[7]=6;ord[8]=10 210 ord[9]=5;ord[10]=11;ord[11]=4;ord[12]=12;ord[13]=3;ord[14]=13;ord[15]=2;ord[16]=14 211 ord[17]=1;ord[18]=15 212 213 let lc: *i64 = sys_mmap(8*17) as *i64 214 let ls: *i64 = sys_mmap(8*288) as *i64 215 let dc: *i64 = sys_mmap(8*17) as *i64 216 let ds: *i64 = sys_mmap(8*30) as *i64 217 let off: *i64 = sys_mmap(8*17) as *i64 218 let cll: *i64 = sys_mmap(8*19) as *i64 219 let clc: *i64 = sys_mmap(8*17) as *i64 220 let cls: *i64 = sys_mmap(8*19) as *i64 221 let al: *i64 = sys_mmap(8*320) as *i64 222 223 while fin == 0 { 224 let bfinal: i64 = inf_bits(src, srclen, cur, 1) 225 let btype: i64 = inf_bits(src, srclen, cur, 2) 226 if cur[1] == 1 { return 0 - 2 } 227 if bfinal == 1 { fin = 1 } 228 if btype == 3 { return 0 - 5 } 229 if btype == 0 { 230 cur[0] = ((cur[0] + 7) >> 3) << 3 231 let bp: i64 = cur[0] >> 3 232 if bp + 4 > srclen { return 0 - 2 } 233 let bl: i64 = (src[bp] as i64) | (((src[bp+1] & 0xff) as i64) << 8) 234 let nl: i64 = (src[bp+2] as i64) | (((src[bp+3] & 0xff) as i64) << 8) 235 // NLEN is LEN's one's complement -- the format's own corruption check, so honour it 236 if (bl + nl) != 0xFFFF { return 0 - 6 } 237 var p: i64 = bp + 4 238 if p + bl > srclen { return 0 - 2 } 239 if st[0] + bl > outcap { return 0 - 1 } 240 var i2: i64 = 0 241 while i2 < bl { 242 out[st[0]] = src[p] 243 st[0] = st[0] + 1 244 p = p + 1 245 i2 = i2 + 1 246 } 247 cur[0] = p << 3 248 } 249 if btype == 1 { 250 inf_build(fll, 288, lc, ls, off) 251 inf_build(fdl, 30, dc, ds, off) 252 let rc: i64 = inf_codes(src, srclen, cur, out, outcap, st, lc, ls, dc, ds, lens, lext, dists, dext) 253 if rc != 0 { return rc } 254 } 255 if btype == 2 { 256 let hlit: i64 = inf_bits(src, srclen, cur, 5) + 257 257 let hdist: i64 = inf_bits(src, srclen, cur, 5) + 1 258 let hclen: i64 = inf_bits(src, srclen, cur, 4) + 4 259 if cur[1] == 1 { return 0 - 2 } 260 var j: i64 = 0 261 while j < 19 { cll[j] = 0; j = j + 1 } 262 j = 0 263 while j < hclen { cll[ord[j]] = inf_bits(src, srclen, cur, 3); j = j + 1 } 264 inf_build(cll, 19, clc, cls, off) 265 let total: i64 = hlit + hdist 266 if total > 320 { return 0 - 12 } 267 var idx: i64 = 0 268 while idx < total { 269 let s2: i64 = inf_decode(src, srclen, cur, clc, cls) 270 if s2 < 0 { return s2 } 271 if s2 < 16 { al[idx] = s2; idx = idx + 1 } 272 if s2 == 16 { 273 if idx == 0 { return 0 - 3 } 274 var rep: i64 = 3 + inf_bits(src, srclen, cur, 2) 275 let prev: i64 = al[idx-1] 276 if idx + rep > total { return 0 - 12 } 277 while rep > 0 { al[idx] = prev; idx = idx + 1; rep = rep - 1 } 278 } 279 if s2 == 17 { 280 var rep2: i64 = 3 + inf_bits(src, srclen, cur, 3) 281 if idx + rep2 > total { return 0 - 12 } 282 while rep2 > 0 { al[idx] = 0; idx = idx + 1; rep2 = rep2 - 1 } 283 } 284 if s2 == 18 { 285 var rep3: i64 = 11 + inf_bits(src, srclen, cur, 7) 286 if idx + rep3 > total { return 0 - 12 } 287 while rep3 > 0 { al[idx] = 0; idx = idx + 1; rep3 = rep3 - 1 } 288 } 289 if s2 > 18 { return 0 - 3 } 290 if cur[1] == 1 { return 0 - 2 } 291 } 292 inf_build(al, hlit, lc, ls, off) 293 let dl: *i64 = ((al as i64) + hlit*8) as *i64 294 inf_build(dl, hdist, dc, ds, off) 295 let rc2: i64 = inf_codes(src, srclen, cur, out, outcap, st, lc, ls, dc, ds, lens, lext, dists, dext) 296 if rc2 != 0 { return rc2 } 297 } 298 } 299 return st[0] 300} 301 302// ---- gzip container (RFC 1952): header, raw deflate, then VERIFY crc32 + isize --------------- 303func inf_gunzip(src: *u8, srclen: i64, out: *u8, outcap: i64) -> i64 { 304 if srclen < 18 { return 0 - 7 } 305 if (src[0] & 0xff) as i64 != 0x1f { return 0 - 7 } 306 if (src[1] & 0xff) as i64 != 0x8b { return 0 - 7 } 307 if (src[2] & 0xff) as i64 != 8 { return 0 - 8 } 308 let flg: i64 = (src[3] & 0xff) as i64 309 var p: i64 = 10 310 if (flg & 4) != 0 { 311 if p + 2 > srclen { return 0 - 2 } 312 let xl: i64 = ((src[p] & 0xff) as i64) | (((src[p+1] & 0xff) as i64) << 8) 313 p = p + 2 + xl 314 } 315 if (flg & 8) != 0 { 316 while p < srclen { if src[p] == (0 as u8) { p = p + 1; break } p = p + 1 } 317 } 318 if (flg & 16) != 0 { 319 while p < srclen { if src[p] == (0 as u8) { p = p + 1; break } p = p + 1 } 320 } 321 if (flg & 2) != 0 { p = p + 2 } 322 if p >= srclen { return 0 - 2 } 323 // the deflate stream runs to the 8-byte trailer 324 let dlen: i64 = srclen - 8 - p 325 if dlen <= 0 { return 0 - 2 } 326 let n: i64 = inf_raw(((src as i64) + p) as *u8, dlen, out, outcap) 327 if n < 0 { return n } 328 let t: i64 = srclen - 8 329 let want: i64 = ((src[t] & 0xff) as i64) | (((src[t+1] & 0xff) as i64) << 8) 330 | (((src[t+2] & 0xff) as i64) << 16) | (((src[t+3] & 0xff) as i64) << 24) 331 let isz: i64 = ((src[t+4] & 0xff) as i64) | (((src[t+5] & 0xff) as i64) << 8) 332 | (((src[t+6] & 0xff) as i64) << 16) | (((src[t+7] & 0xff) as i64) << 24) 333 if inf_crc32(out, n) != (want & INF_M32) { return 0 - 9 } 334 if (n & INF_M32) != (isz & INF_M32) { return 0 - 10 } 335 return n 336} 337 338// ---- zlib container (RFC 1950): 2-byte header, deflate body ---------------------------------- 339func inf_zlib(src: *u8, srclen: i64, out: *u8, outcap: i64) -> i64 { 340 if srclen < 6 { return 0 - 11 } 341 let cmf: i64 = (src[0] & 0xff) as i64 342 let flg: i64 = (src[1] & 0xff) as i64 343 if (cmf & 15) != 8 { return 0 - 11 } 344 if ((cmf * 256 + flg) % 31) != 0 { return 0 - 11 } 345 var p: i64 = 2 346 if (flg & 32) != 0 { p = p + 4 } 347 return inf_raw(((src as i64) + p) as *u8, srclen - p, out, outcap) 348}