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}