code wiki / (root) / nx_gzip_wrap.nx

nx_gzip_wrap.nx source

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