nx_png_header.nx source
↩ module page · 171 lines · 5916 B
1// png_header.nx -- parse PNG signature + IHDR + chunk stream.
2//
3// PNG (W3C PNG 2nd ed / ISO 15948):
4// - 8-byte signature: 0x89 'P' 'N' 'G' 0x0D 0x0A 0x1A 0x0A
5// - Series of chunks, each:
6// 4-byte big-endian length
7// 4-byte type tag (ASCII, case encodes flags)
8// <length> bytes data
9// 4-byte CRC-32 over (type || data)
10// - First chunk must be IHDR (13-byte data payload).
11// - Must end with IEND (0-byte data).
12//
13// IHDR layout:
14// width u32 BE
15// height u32 BE
16// bit_depth u8 (1/2/4/8/16)
17// color_type u8 (0 gray, 2 rgb, 3 indexed, 4 gray+alpha, 6 rgba)
18// compression u8 must be 0
19// filter u8 must be 0
20// interlace u8 0=none, 1=Adam7
21//
22// Module does: sig-check, IHDR parse, chunk-walker (type tag +
23// offsets) without CRC validation. Callers handle CRC verification
24// once deflate.nx lands and full decode is viable.
25//
26// Invariants:
27// P1 Signature bytes are exact.
28// P2 IHDR must be first; length must equal 13.
29// P3 Color-type + bit-depth combinations per W3C PNG table 5.12
30// are NOT validated here; caller decides.
31
32// nx_safety_envelope:
33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
34// sil_target: SIL1
35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
36// verdict: NOT_YET_EVALUATED
37
38import "nx_syscalls.nx"
39
40const PNG_ERR_FORMAT: i64 = -1
41const PNG_ERR_SHORT: i64 = -2
42const PNG_ERR_IHDR: i64 = -3
43
44struct PngIhdr {
45 width: i64,
46 height: i64,
47 bit_depth: i64,
48 color_type: i64,
49 compression: i64,
50 filter: i64,
51 interlace: i64,
52}
53
54// Read a big-endian u32.
55func png_read_u32_be(buf: *u8, off: i64) -> i64 {
56 let b0: i64 = buf[off]
57 let b1: i64 = buf[off + 1]
58 let b2: i64 = buf[off + 2]
59 let b3: i64 = buf[off + 3]
60 return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
61}
62
63// Check PNG signature at buf[0..8]. Returns 0 on match.
64func png_check_sig(buf: *u8, n: i64) -> i64 {
65 if n < 8 { return PNG_ERR_SHORT }
66 if buf[0] != 0x89 { return PNG_ERR_FORMAT }
67 if buf[1] != 0x50 { return PNG_ERR_FORMAT } // 'P'
68 if buf[2] != 0x4E { return PNG_ERR_FORMAT } // 'N'
69 if buf[3] != 0x47 { return PNG_ERR_FORMAT } // 'G'
70 if buf[4] != 0x0D { return PNG_ERR_FORMAT }
71 if buf[5] != 0x0A { return PNG_ERR_FORMAT }
72 if buf[6] != 0x1A { return PNG_ERR_FORMAT }
73 if buf[7] != 0x0A { return PNG_ERR_FORMAT }
74 return 0
75}
76
77// Parse the IHDR chunk that must follow the signature. Returns
78// 0 on success, negative on error.
79func png_parse_ihdr(buf: *u8, n: i64, out: *PngIhdr) -> i64 {
80 // Chunk starts right after signature at offset 8.
81 if n < 8 + 8 + 13 + 4 { return PNG_ERR_SHORT }
82 let len: i64 = png_read_u32_be(buf, 8)
83 if len != 13 { return PNG_ERR_IHDR }
84 // Type tag must be "IHDR" = 49 48 44 52.
85 if buf[12] != 0x49 { return PNG_ERR_IHDR }
86 if buf[13] != 0x48 { return PNG_ERR_IHDR }
87 if buf[14] != 0x44 { return PNG_ERR_IHDR }
88 if buf[15] != 0x52 { return PNG_ERR_IHDR }
89
90 out.width = png_read_u32_be(buf, 16)
91 out.height = png_read_u32_be(buf, 20)
92 out.bit_depth = buf[24]
93 out.color_type = buf[25]
94 out.compression = buf[26]
95 out.filter = buf[27]
96 out.interlace = buf[28]
97 return 0
98}
99
100// Locate the next chunk after offset. Returns offset of chunk
101// start (length field) or -1 if no more chunks / malformed.
102// `cur_off` is the current chunk's length field; we advance past
103// it + type + data + crc = 4 + 4 + length + 4.
104func png_next_chunk(buf: *u8, n: i64, cur_off: i64) -> i64 {
105 if n < cur_off + 8 { return -1 }
106 let len: i64 = png_read_u32_be(buf, cur_off)
107 let next: i64 = cur_off + 4 + 4 + len + 4
108 if next + 8 > n { return -1 }
109 return next
110}
111
112// Copy a chunk's 4-byte type tag to out[0..4]. Returns 0 on ok.
113func png_chunk_type(buf: *u8, n: i64, off: i64, out: *u8) -> i64 {
114 if n < off + 8 { return PNG_ERR_SHORT }
115 out[0] = buf[off + 4]
116 out[1] = buf[off + 5]
117 out[2] = buf[off + 6]
118 out[3] = buf[off + 7]
119 return 0
120}
121
122// Test if a chunk at `off` has the four-letter type `t0..t3`.
123// Cheaper than copying the tag when caller just wants to check.
124func png_chunk_is(buf: *u8, n: i64, off: i64,
125 t0: i64, t1: i64, t2: i64, t3: i64) -> i64 {
126 if n < off + 8 { return 0 }
127 if buf[off + 4] != t0 { return 0 }
128 if buf[off + 5] != t1 { return 0 }
129 if buf[off + 6] != t2 { return 0 }
130 if buf[off + 7] != t3 { return 0 }
131 return 1
132}
133
134// Compile-only smoke.
135func main() -> i64 {
136 let raw: *u8 = sys_mmap(128)
137 var i: i64 = 0
138 while i < 128 { raw[i] = 0; i = i + 1 }
139
140 // Signature.
141 raw[0] = 0x89; raw[1] = 0x50; raw[2] = 0x4E; raw[3] = 0x47
142 raw[4] = 0x0D; raw[5] = 0x0A; raw[6] = 0x1A; raw[7] = 0x0A
143
144 // IHDR chunk length = 13 (big-endian).
145 raw[11] = 13
146 // Type "IHDR"
147 raw[12] = 0x49; raw[13] = 0x48; raw[14] = 0x44; raw[15] = 0x52
148 // Width = 320, Height = 200, bit_depth=8, color_type=2 (RGB)
149 // 320 = 0x00000140, 200 = 0x000000C8
150 raw[16] = 0x00; raw[17] = 0x00; raw[18] = 0x01; raw[19] = 0x40
151 raw[20] = 0x00; raw[21] = 0x00; raw[22] = 0x00; raw[23] = 0xC8
152 raw[24] = 8 // bit_depth
153 raw[25] = 2 // color_type RGB
154 raw[26] = 0; raw[27] = 0; raw[28] = 0
155 // CRC (4 bytes) -- leave zero, we don't verify.
156
157 if png_check_sig(raw, 128) != 0 { return 1 }
158
159 let ih_raw: *u8 = sys_mmap(64)
160 let ih: *PngIhdr = ih_raw as *PngIhdr
161 if png_parse_ihdr(raw, 128, ih) != 0 { return 2 }
162 if ih.width != 320 { return 3 }
163 if ih.height != 200 { return 4 }
164 if ih.bit_depth != 8 { return 5 }
165 if ih.color_type != 2 { return 6 }
166
167 // Bad signature -> PNG_ERR_FORMAT.
168 raw[0] = 0
169 if png_check_sig(raw, 128) != PNG_ERR_FORMAT { return 7 }
170 return 0
171}