png_header.nx source
↩ module page · 165 lines · 5827 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
32import "syscalls.nx"
33
34const PNG_ERR_FORMAT: i64 = -1
35const PNG_ERR_SHORT: i64 = -2
36const PNG_ERR_IHDR: i64 = -3
37
38struct PngIhdr {
39 width: i64,
40 height: i64,
41 bit_depth: i64,
42 color_type: i64,
43 compression: i64,
44 filter: i64,
45 interlace: i64,
46}
47
48// Read a big-endian u32.
49func png_read_u32_be(buf: *u8, off: i64) -> i64 {
50 let b0: i64 = buf[off]
51 let b1: i64 = buf[off + 1]
52 let b2: i64 = buf[off + 2]
53 let b3: i64 = buf[off + 3]
54 return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
55}
56
57// Check PNG signature at buf[0..8]. Returns 0 on match.
58func png_check_sig(buf: *u8, n: i64) -> i64 {
59 if n < 8 { return PNG_ERR_SHORT }
60 if buf[0] != 0x89 { return PNG_ERR_FORMAT }
61 if buf[1] != 0x50 { return PNG_ERR_FORMAT } // 'P'
62 if buf[2] != 0x4E { return PNG_ERR_FORMAT } // 'N'
63 if buf[3] != 0x47 { return PNG_ERR_FORMAT } // 'G'
64 if buf[4] != 0x0D { return PNG_ERR_FORMAT }
65 if buf[5] != 0x0A { return PNG_ERR_FORMAT }
66 if buf[6] != 0x1A { return PNG_ERR_FORMAT }
67 if buf[7] != 0x0A { return PNG_ERR_FORMAT }
68 return 0
69}
70
71// Parse the IHDR chunk that must follow the signature. Returns
72// 0 on success, negative on error.
73func png_parse_ihdr(buf: *u8, n: i64, out: *PngIhdr) -> i64 {
74 // Chunk starts right after signature at offset 8.
75 if n < 8 + 8 + 13 + 4 { return PNG_ERR_SHORT }
76 let len: i64 = png_read_u32_be(buf, 8)
77 if len != 13 { return PNG_ERR_IHDR }
78 // Type tag must be "IHDR" = 49 48 44 52.
79 if buf[12] != 0x49 { return PNG_ERR_IHDR }
80 if buf[13] != 0x48 { return PNG_ERR_IHDR }
81 if buf[14] != 0x44 { return PNG_ERR_IHDR }
82 if buf[15] != 0x52 { return PNG_ERR_IHDR }
83
84 out.width = png_read_u32_be(buf, 16)
85 out.height = png_read_u32_be(buf, 20)
86 out.bit_depth = buf[24]
87 out.color_type = buf[25]
88 out.compression = buf[26]
89 out.filter = buf[27]
90 out.interlace = buf[28]
91 return 0
92}
93
94// Locate the next chunk after offset. Returns offset of chunk
95// start (length field) or -1 if no more chunks / malformed.
96// `cur_off` is the current chunk's length field; we advance past
97// it + type + data + crc = 4 + 4 + length + 4.
98func png_next_chunk(buf: *u8, n: i64, cur_off: i64) -> i64 {
99 if n < cur_off + 8 { return -1 }
100 let len: i64 = png_read_u32_be(buf, cur_off)
101 let next: i64 = cur_off + 4 + 4 + len + 4
102 if next + 8 > n { return -1 }
103 return next
104}
105
106// Copy a chunk's 4-byte type tag to out[0..4]. Returns 0 on ok.
107func png_chunk_type(buf: *u8, n: i64, off: i64, out: *u8) -> i64 {
108 if n < off + 8 { return PNG_ERR_SHORT }
109 out[0] = buf[off + 4]
110 out[1] = buf[off + 5]
111 out[2] = buf[off + 6]
112 out[3] = buf[off + 7]
113 return 0
114}
115
116// Test if a chunk at `off` has the four-letter type `t0..t3`.
117// Cheaper than copying the tag when caller just wants to check.
118func png_chunk_is(buf: *u8, n: i64, off: i64,
119 t0: i64, t1: i64, t2: i64, t3: i64) -> i64 {
120 if n < off + 8 { return 0 }
121 if buf[off + 4] != t0 { return 0 }
122 if buf[off + 5] != t1 { return 0 }
123 if buf[off + 6] != t2 { return 0 }
124 if buf[off + 7] != t3 { return 0 }
125 return 1
126}
127
128// Compile-only smoke.
129func main() -> i64 {
130 let raw: *u8 = sys_mmap(128)
131 var i: i64 = 0
132 while i < 128 { raw[i] = 0; i = i + 1 }
133
134 // Signature.
135 raw[0] = 0x89; raw[1] = 0x50; raw[2] = 0x4E; raw[3] = 0x47
136 raw[4] = 0x0D; raw[5] = 0x0A; raw[6] = 0x1A; raw[7] = 0x0A
137
138 // IHDR chunk length = 13 (big-endian).
139 raw[11] = 13
140 // Type "IHDR"
141 raw[12] = 0x49; raw[13] = 0x48; raw[14] = 0x44; raw[15] = 0x52
142 // Width = 320, Height = 200, bit_depth=8, color_type=2 (RGB)
143 // 320 = 0x00000140, 200 = 0x000000C8
144 raw[16] = 0x00; raw[17] = 0x00; raw[18] = 0x01; raw[19] = 0x40
145 raw[20] = 0x00; raw[21] = 0x00; raw[22] = 0x00; raw[23] = 0xC8
146 raw[24] = 8 // bit_depth
147 raw[25] = 2 // color_type RGB
148 raw[26] = 0; raw[27] = 0; raw[28] = 0
149 // CRC (4 bytes) -- leave zero, we don't verify.
150
151 if png_check_sig(raw, 128) != 0 { return 1 }
152
153 let ih_raw: *u8 = sys_mmap(64)
154 let ih: *PngIhdr = ih_raw as *PngIhdr
155 if png_parse_ihdr(raw, 128, ih) != 0 { return 2 }
156 if ih.width != 320 { return 3 }
157 if ih.height != 200 { return 4 }
158 if ih.bit_depth != 8 { return 5 }
159 if ih.color_type != 2 { return 6 }
160
161 // Bad signature -> PNG_ERR_FORMAT.
162 raw[0] = 0
163 if png_check_sig(raw, 128) != PNG_ERR_FORMAT { return 7 }
164 return 0
165}