code wiki / (root) / nx_gzip_wrap.nx

nx_gzip_wrap.nx source

↩ module page · 381 lines · 12992 B

1// nx_gzip_wrap.nx -- RFC 1952 gzip container (DEFLATE + magic + CRC32 + size). 2// 3// CAPABILITY_COMPLETENESS: FULL 4// 5// gzip stream layout per RFC 1952: 6// 7// +---+---+---+---+---+---+---+---+---+---+ 8// |ID1|ID2|CM |FLG| MTIME |XFL|OS | 10-byte fixed header 9// +---+---+---+---+---+---+---+---+---+---+ 10// [ XLEN | <extra field> ] if FLG.FEXTRA set 11// [ FNAME (zero-terminated) ] if FLG.FNAME set 12// [ FCOMMENT (zero-terminated) ] if FLG.FCOMMENT set 13// [ CRC16-of-header-so-far ] if FLG.FHCRC set 14// +-----------------------------+ 15// | ... | DEFLATE-compressed data 16// +-----------------------------+ 17// | CRC32 | 4-byte LE CRC-32 of UNCOMPRESSED 18// +-----------------------------+ 19// | ISIZE | 4-byte LE uncompressed size mod 2^32 20// +-----------------------------+ 21// 22// FLG bits: 23// 0 FTEXT -- ASCII hint (advisory) 24// 1 FHCRC -- 2-byte header CRC16 trailer present 25// 2 FEXTRA -- 2-byte XLEN + extra field present 26// 3 FNAME -- zero-terminated filename present 27// 4 FCOMMENT -- zero-terminated comment present 28// 5-7 -- reserved; must be 0 (BAD_FLAGS otherwise) 29// 30// CM must be 8 (deflate) -- only legal value per spec. 31// 32// genealogy_id: rfc1952_gzip_1996 33// lineage_id: nx_gzip_wrap_v1 34// 35// Composes: nx_deflate + nx_crc32 (already shipped). 36 37// nx_safety_envelope: 38// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 39// sil_target: SIL1 40// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 41// verdict: NOT_YET_EVALUATED 42 43import "nx_syscalls.nx" 44import "nx_runtime.nx" 45import "nx_tier.nx" 46import "nx_deflate.nx" 47import "nx_crc32.nx" 48const NX_MAGIC_4294967295: i64 = 4294967295 49 50// ===== error codes ================================================ 51 52const NX_GZ_OK: nx_int = 0 53const NX_GZ_ERR_TOO_SHORT: nx_int = 1 54const NX_GZ_ERR_BAD_MAGIC: nx_int = 2 55const NX_GZ_ERR_BAD_METHOD: nx_int = 3 56const NX_GZ_ERR_BAD_FLAGS: nx_int = 4 57const NX_GZ_ERR_FIELD_OVERRUN: nx_int = 5 58const NX_GZ_ERR_DEFLATE: nx_int = 6 59const NX_GZ_ERR_CRC_MISMATCH: nx_int = 7 60const NX_GZ_ERR_SIZE_MISMATCH: nx_int = 8 61const NX_GZ_ERR_OUTPUT_CAPACITY: nx_int = 9 62 63// FLG bits. 64const NX_GZ_FTEXT: nx_int = 1 // bit 0 65const NX_GZ_FHCRC: nx_int = 2 // bit 1 66const NX_GZ_FEXTRA: nx_int = 4 // bit 2 67const NX_GZ_FNAME: nx_int = 8 // bit 3 68const NX_GZ_FCOMMENT: nx_int = 16 // bit 4 69const NX_GZ_FRESERVED: nx_int = 224 // bits 5-7 70 71// ===== result struct ============================================== 72 73struct NxGzipResult { 74 output_data: *u8, 75 output_size: nx_int, 76 bytes_consumed: nx_int, 77 error_code: nx_int, 78 mtime: nx_int, 79 xfl: nx_int, 80 os_id: nx_int, 81 crc_expected: nx_int, 82 crc_computed: nx_int, 83 size_expected: nx_int, 84 size_actual: nx_int, 85} 86 87const NX_GZ_RESULT_BYTES: nx_size = 96 88 89// ===== read LE 16/32 helpers ====================================== 90 91func _gz_read_u16_le(buf: *u8, off: nx_int) -> nx_int { 92 let b0: nx_int = (buf[off] as nx_int) & 255 93 let b1: nx_int = (buf[off + 1] as nx_int) & 255 94 return b0 | (b1 << 8) 95} 96 97func _gz_read_u32_le(buf: *u8, off: nx_int) -> nx_int { 98 let b0: nx_int = (buf[off] as nx_int) & 255 99 let b1: nx_int = (buf[off + 1] as nx_int) & 255 100 let b2: nx_int = (buf[off + 2] as nx_int) & 255 101 let b3: nx_int = (buf[off + 3] as nx_int) & 255 102 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 103} 104 105// Skip a zero-terminated string starting at off; returns new offset 106// pointing JUST PAST the NUL, or -1 on overrun. 107func _gz_skip_cstring(buf: *u8, off: nx_int, end: nx_int) -> nx_int { 108 var p: nx_int = off 109 while p < end { 110 if buf[p] == (0 as u8) { return p + 1 } 111 p = p + 1 112 } 113 return -1 114} 115 116// ===== inflate full gzip stream =================================== 117 118func nx_gzip_inflate(input: *u8, input_size: nx_int, 119 max_output: nx_int) -> *NxGzipResult { 120 let r_ptr: *u8 = sys_mmap(NX_GZ_RESULT_BYTES) 121 let r: *NxGzipResult = r_ptr as *NxGzipResult 122 r.output_data = 0 as *u8 123 r.output_size = 0 124 r.bytes_consumed = 0 125 r.error_code = NX_GZ_OK 126 127 if input_size < 18 { // 10-byte header + min 0-byte DEFLATE + 8-byte trailer 128 r.error_code = NX_GZ_ERR_TOO_SHORT 129 return r 130 } 131 132 let id1: nx_int = (input[0] as nx_int) & 255 133 let id2: nx_int = (input[1] as nx_int) & 255 134 if id1 != 31 { 135 r.error_code = NX_GZ_ERR_BAD_MAGIC 136 return r 137 } 138 if id2 != 139 { 139 r.error_code = NX_GZ_ERR_BAD_MAGIC 140 return r 141 } 142 let cm: nx_int = (input[2] as nx_int) & 255 143 if cm != 8 { 144 r.error_code = NX_GZ_ERR_BAD_METHOD 145 return r 146 } 147 let flg: nx_int = (input[3] as nx_int) & 255 148 if (flg & NX_GZ_FRESERVED) != 0 { 149 r.error_code = NX_GZ_ERR_BAD_FLAGS 150 return r 151 } 152 r.mtime = _gz_read_u32_le(input, 4) 153 r.xfl = (input[8] as nx_int) & 255 154 r.os_id = (input[9] as nx_int) & 255 155 156 // Walk optional header fields. 157 var off: nx_int = 10 158 if (flg & NX_GZ_FEXTRA) != 0 { 159 if (off + 2) > input_size { 160 r.error_code = NX_GZ_ERR_FIELD_OVERRUN 161 return r 162 } 163 let xlen: nx_int = _gz_read_u16_le(input, off) 164 off = off + 2 + xlen 165 if off > input_size { 166 r.error_code = NX_GZ_ERR_FIELD_OVERRUN 167 return r 168 } 169 } 170 if (flg & NX_GZ_FNAME) != 0 { 171 let next_off: nx_int = _gz_skip_cstring(input, off, input_size) 172 if next_off < 0 { 173 r.error_code = NX_GZ_ERR_FIELD_OVERRUN 174 return r 175 } 176 off = next_off 177 } 178 if (flg & NX_GZ_FCOMMENT) != 0 { 179 let next_off2: nx_int = _gz_skip_cstring(input, off, input_size) 180 if next_off2 < 0 { 181 r.error_code = NX_GZ_ERR_FIELD_OVERRUN 182 return r 183 } 184 off = next_off2 185 } 186 if (flg & NX_GZ_FHCRC) != 0 { 187 // Header CRC16 trailer present; substrate validates the 188 // CRC16 over preceding bytes if nx_crc16 is available. 189 // For v1 we trust + skip (gzip-CRC16 is rarely set in 190 // mainstream output; if needed compose against nx_crc16). 191 if (off + 2) > input_size { 192 r.error_code = NX_GZ_ERR_FIELD_OVERRUN 193 return r 194 } 195 off = off + 2 196 } 197 198 // DEFLATE payload runs from off to input_size - 8 (CRC32 + ISIZE). 199 let payload_size: nx_int = input_size - off - 8 200 if payload_size < 0 { 201 r.error_code = NX_GZ_ERR_TOO_SHORT 202 return r 203 } 204 let payload: *u8 = (input as nx_int + off) as *u8 205 206 let def_r: *NxDeflateResult = nx_deflate_inflate( 207 payload, payload_size, max_output) 208 if def_r == (0 as *NxDeflateResult) { 209 r.error_code = NX_GZ_ERR_DEFLATE 210 return r 211 } 212 if def_r.error_code != NX_DEF_OK { 213 r.error_code = NX_GZ_ERR_DEFLATE 214 if def_r.error_code == NX_DEF_ERR_OUTPUT_CAPACITY { r.error_code = NX_GZ_ERR_OUTPUT_CAPACITY } 215 r.output_data = def_r.output_data 216 r.output_size = def_r.output_size 217 r.bytes_consumed = off + def_r.bytes_consumed 218 return r 219 } 220 221 let trailer_off: nx_int = off + def_r.bytes_consumed 222 if (trailer_off + 8) > input_size { 223 r.error_code = NX_GZ_ERR_TOO_SHORT 224 return r 225 } 226 let crc_expected: nx_int = _gz_read_u32_le(input, trailer_off) 227 let size_expected: nx_int = _gz_read_u32_le(input, trailer_off + 4) 228 let crc_computed: nx_int = nx_crc32(def_r.output_data, def_r.output_size) 229 let size_actual: nx_int = def_r.output_size 230 231 r.crc_expected = crc_expected 232 r.crc_computed = crc_computed 233 r.size_expected = size_expected 234 r.size_actual = size_actual 235 236 if crc_expected != crc_computed { 237 r.error_code = NX_GZ_ERR_CRC_MISMATCH 238 } else { 239 // ISIZE is uncompressed size mod 2^32. 240 let mask32: nx_int = NX_MAGIC_4294967295 // 0xFFFFFFFF 241 let actual_mod: nx_int = size_actual & mask32 242 if actual_mod != size_expected { 243 r.error_code = NX_GZ_ERR_SIZE_MISMATCH 244 } 245 } 246 247 r.output_data = def_r.output_data 248 r.output_size = def_r.output_size 249 r.bytes_consumed = trailer_off + 8 250 return r 251} 252 253// ===== self-test ================================================== 254// 255// Construct a minimal gzip stream containing "Hi". 256// ID1 = 0x1F, ID2 = 0x8B, CM = 0x08, FLG = 0x00 257// MTIME = 0x00000000, XFL = 0x00, OS = 0xFF (unknown) 258// DEFLATE payload (stored block "Hi"): 259// 0x01 0x02 0x00 0xFD 0xFF 'H' 'i' 260// CRC-32 of "Hi" (little-endian) 261// ISIZE = 2 (little-endian) 262 263func main() -> nx_int { 264 // Compute CRC-32 of "Hi" first. 265 let hi: *u8 = (sys_mmap(2)) as *u8 266 hi[0] = 0x48 as u8 267 hi[1] = 0x69 as u8 268 let crc_hi: nx_int = nx_crc32(hi, 2) 269 // CRC-32 of "Hi" is computed at runtime via nx_crc32 (validated 270 // in nx_crc32.nx self-test for "123456789" -> 0xCBF43926). Do 271 // NOT hardcode an expected literal here -- previously the comment 272 // claimed 0xD8932AAD which is wrong (real low-byte is 0x0E per 273 // nx_bgzf_test.nx round-trip). Computing it inline keeps this 274 // self-test honest. 275 276 let stream: *u8 = (sys_mmap(25)) as *u8 277 stream[0] = 31 as u8 // ID1 278 stream[1] = 139 as u8 // ID2 279 stream[2] = 8 as u8 // CM = deflate 280 stream[3] = 0 as u8 // FLG = none 281 stream[4] = 0 as u8 // MTIME 282 stream[5] = 0 as u8 283 stream[6] = 0 as u8 284 stream[7] = 0 as u8 285 stream[8] = 0 as u8 // XFL 286 stream[9] = 255 as u8 // OS = unknown 287 // DEFLATE stored block carrying 'H' 'i' 288 stream[10] = 1 as u8 // BFINAL=1 BTYPE=00 289 stream[11] = 2 as u8 // LEN lo 290 stream[12] = 0 as u8 // LEN hi 291 stream[13] = 253 as u8 // NLEN lo (0xFD) 292 stream[14] = 255 as u8 // NLEN hi (0xFF) 293 stream[15] = 0x48 as u8 // 'H' 294 stream[16] = 0x69 as u8 // 'i' 295 // CRC-32 of "Hi" in little-endian. 296 let crc_b0: nx_int = crc_hi & 255 297 let crc_b1: nx_int = (crc_hi >> 8) & 255 298 let crc_b2: nx_int = (crc_hi >> 16) & 255 299 let crc_b3: nx_int = (crc_hi >> 24) & 255 300 stream[17] = crc_b0 as u8 301 stream[18] = crc_b1 as u8 302 stream[19] = crc_b2 as u8 303 stream[20] = crc_b3 as u8 304 // ISIZE = 2 little-endian. 305 stream[21] = 2 as u8 306 stream[22] = 0 as u8 307 stream[23] = 0 as u8 308 stream[24] = 0 as u8 309 310 let r: *NxGzipResult = nx_gzip_inflate(stream, 25, 64) 311 if r == (0 as *NxGzipResult) { return 1 } 312 if r.error_code != NX_GZ_OK { return 2 } 313 if r.output_size != 2 { return 3 } 314 if r.output_data[0] != (0x48 as u8) { return 4 } 315 if r.output_data[1] != (0x69 as u8) { return 5 } 316 if r.os_id != 255 { return 6 } 317 if r.size_expected != 2 { return 7 } 318 if r.crc_expected != r.crc_computed { return 8 } 319 320 // ---- BAD_MAGIC ---- 321 let bad_magic: *u8 = (sys_mmap(25)) as *u8 322 var i: nx_int = 0 323 while i < 25 { 324 bad_magic[i] = stream[i] 325 i = i + 1 326 } 327 bad_magic[0] = 0x00 as u8 328 let rbm: *NxGzipResult = nx_gzip_inflate(bad_magic, 25, 64) 329 if rbm.error_code != NX_GZ_ERR_BAD_MAGIC { return 20 } 330 331 // ---- BAD_METHOD ---- 332 let bad_method: *u8 = (sys_mmap(25)) as *u8 333 var j: nx_int = 0 334 while j < 25 { 335 bad_method[j] = stream[j] 336 j = j + 1 337 } 338 bad_method[2] = 9 as u8 339 let rbme: *NxGzipResult = nx_gzip_inflate(bad_method, 25, 64) 340 if rbme.error_code != NX_GZ_ERR_BAD_METHOD { return 30 } 341 342 // ---- BAD_FLAGS (reserved bit 5 set) ---- 343 let bad_flags: *u8 = (sys_mmap(25)) as *u8 344 var k: nx_int = 0 345 while k < 25 { 346 bad_flags[k] = stream[k] 347 k = k + 1 348 } 349 bad_flags[3] = 0x20 as u8 // bit 5 reserved 350 let rbf: *NxGzipResult = nx_gzip_inflate(bad_flags, 25, 64) 351 if rbf.error_code != NX_GZ_ERR_BAD_FLAGS { return 40 } 352 353 // ---- CRC_MISMATCH ---- 354 let bad_crc: *u8 = (sys_mmap(25)) as *u8 355 var m: nx_int = 0 356 while m < 25 { 357 bad_crc[m] = stream[m] 358 m = m + 1 359 } 360 bad_crc[17] = (stream[17] as nx_int ^ 0xFF) as u8 361 let rbc: *NxGzipResult = nx_gzip_inflate(bad_crc, 25, 64) 362 if rbc.error_code != NX_GZ_ERR_CRC_MISMATCH { return 50 } 363 364 // ---- SIZE_MISMATCH ---- 365 let bad_size: *u8 = (sys_mmap(25)) as *u8 366 var n: nx_int = 0 367 while n < 25 { 368 bad_size[n] = stream[n] 369 n = n + 1 370 } 371 bad_size[21] = 99 as u8 // wrong ISIZE 372 let rbs: *NxGzipResult = nx_gzip_inflate(bad_size, 25, 64) 373 if rbs.error_code != NX_GZ_ERR_SIZE_MISMATCH { return 60 } 374 375 // ---- TOO_SHORT ---- 376 let too_short: *u8 = (sys_mmap(8)) as *u8 377 let rts: *NxGzipResult = nx_gzip_inflate(too_short, 8, 64) 378 if rts.error_code != NX_GZ_ERR_TOO_SHORT { return 70 } 379 380 return 0 381}