code wiki / (root) / nx_png_chunks.nx

nx_png_chunks.nx source

↩ module page · 123 lines · 5795 B

1// nx_png_chunks.nx -- THE PNG CHUNK WALK (one reader, shared): signature, then [length(4, big-endian)][type(4)][data][crc(4)] 2// until IEND. First consumer is companionchat CC2 (a Tavern V2 character card is base64 JSON inside a tEXt chunk whose 3// keyword is `chara`); gen G15 (workflow ingest from a rival's PNG metadata) names the same walk. EXTRACTED here so there is 4// exactly one chunk walker in the estate -- go_png_dim in the orchestrator reads only the IHDR dimensions and stays as is. 5// DECLARED IMPRECISION: CRCs are not verified. This walker EXTRACTS metadata; it does not validate an image, and a wrong CRC 6// on a text chunk cannot change the bytes it returns. A chunk whose declared length overruns the buffer is MALFORMED and the 7// walk stops there rather than reading past the end. license_tier: ORIGINAL 8import "nx_syscalls.nx" 9 10const PNG_SIG_LEN: i64 = 8 11const PNG_CHUNK_HDR_LEN: i64 = 8 12const PNG_CRC_LEN: i64 = 4 13const PNG_TYPE_LEN: i64 = 4 14const PNG_BYTE_MASK: i64 = 255 15const PNG_BITS_PER_BYTE: i64 = 8 16const PNG_CH_NUL: i64 = 0 17// return codes of png_find_text 18const PNG_FOUND: i64 = 1 19const PNG_ABSENT: i64 = 0 20const PNG_NOT_PNG: i64 = 0 - 1 21const PNG_MALFORMED: i64 = 0 - 2 22 23func png_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 24func png_be32(b: *u8, p: i64) -> i64 { 25 var v: i64 = 0 26 var i: i64 = 0 27 while i < PNG_TYPE_LEN { v = (v << PNG_BITS_PER_BYTE) | ((b[p + i] as i64) & PNG_BYTE_MASK); i = i + 1 } 28 return v 29} 30// 1 when buf[0..n) starts with the 8-byte PNG signature 31func png_is(buf: *u8, n: i64) -> i64 { 32 if n < PNG_SIG_LEN { return 0 } 33 if (buf[0] as i64) != 137 { return 0 } 34 if (buf[1] as i64) != 80 { return 0 } 35 if (buf[2] as i64) != 78 { return 0 } 36 if (buf[3] as i64) != 71 { return 0 } 37 if (buf[4] as i64) != 13 { return 0 } 38 if (buf[5] as i64) != 10 { return 0 } 39 if (buf[6] as i64) != 26 { return 0 } 40 if (buf[7] as i64) != 10 { return 0 } 41 return 1 42} 43// does the 4-byte type at buf[p..p+4) spell `t`? 44func png_type_is(buf: *u8, p: i64, t: *u8) -> i64 { 45 var i: i64 = 0 46 while i < PNG_TYPE_LEN { if buf[p + i] != t[i] { return 0 } i = i + 1 } 47 return 1 48} 49// walk every chunk; returns the chunk count up to and including IEND, or PNG_NOT_PNG / PNG_MALFORMED. 50func png_chunk_count(buf: *u8, n: i64) -> i64 { 51 if png_is(buf, n) == 0 { return PNG_NOT_PNG } 52 var p: i64 = PNG_SIG_LEN 53 var count: i64 = 0 54 var walk: i64 = 1 55 var rc: i64 = 0 56 while walk == 1 { 57 if p + PNG_CHUNK_HDR_LEN > n { rc = PNG_MALFORMED; walk = 0 } else { 58 let ln: i64 = png_be32(buf, p) 59 if p + PNG_CHUNK_HDR_LEN + ln + PNG_CRC_LEN > n { rc = PNG_MALFORMED; walk = 0 } else { 60 count = count + 1 61 if png_type_is(buf, p + PNG_TYPE_LEN, "IEND" as *u8) == 1 { rc = count; walk = 0 } else { p = p + PNG_CHUNK_HDR_LEN + ln + PNG_CRC_LEN } 62 } 63 } 64 } 65 return rc 66} 67// find the tEXt chunk whose keyword is `keyword`; on PNG_FOUND out[0] = offset of the text (just past the keyword's NUL), 68// out[1] = its length. PNG_ABSENT when no such chunk precedes IEND; PNG_NOT_PNG / PNG_MALFORMED as named. 69func png_find_text(buf: *u8, n: i64, keyword: *u8, out: *i64) -> i64 { 70 out[0] = 0 71 out[1] = 0 72 if png_is(buf, n) == 0 { return PNG_NOT_PNG } 73 let kl: i64 = png_len(keyword) 74 var p: i64 = PNG_SIG_LEN 75 var walk: i64 = 1 76 var rc: i64 = PNG_ABSENT 77 while walk == 1 { 78 if p + PNG_CHUNK_HDR_LEN > n { rc = PNG_MALFORMED; walk = 0 } else { 79 let ln: i64 = png_be32(buf, p) 80 let dp: i64 = p + PNG_CHUNK_HDR_LEN 81 if dp + ln + PNG_CRC_LEN > n { rc = PNG_MALFORMED; walk = 0 } else { 82 if png_type_is(buf, p + PNG_TYPE_LEN, "IEND" as *u8) == 1 { walk = 0 } else { 83 if png_type_is(buf, p + PNG_TYPE_LEN, "tEXt" as *u8) == 1 { 84 if ln > kl { 85 if (buf[dp + kl] as i64) == PNG_CH_NUL { 86 var same: i64 = 1 87 var i: i64 = 0 88 while i < kl { if buf[dp + i] != keyword[i] { same = 0; i = kl } else { i = i + 1 } } 89 if same == 1 { out[0] = dp + kl + 1; out[1] = ln - kl - 1; rc = PNG_FOUND; walk = 0 } 90 } 91 } 92 } 93 if walk == 1 { p = dp + ln + PNG_CRC_LEN } 94 } 95 } 96 } 97 } 98 return rc 99} 100// WRITER for fixtures and exporters: append one chunk (type + data) at buf[off], returns the new offset. The CRC field is 101// written as zero -- see the header: this reader never checks it, and a fixture that claims a CRC it did not compute would be 102// a second copy of a number nobody verifies. A consumer that needs a browser-valid PNG must compute the CRC itself. 103func png_put_chunk(buf: *u8, off: i64, t: *u8, data: *u8, dlen: i64) -> i64 { 104 var o: i64 = off 105 buf[o] = ((dlen >> 24) & PNG_BYTE_MASK) as u8; buf[o + 1] = ((dlen >> 16) & PNG_BYTE_MASK) as u8 106 buf[o + 2] = ((dlen >> 8) & PNG_BYTE_MASK) as u8; buf[o + 3] = (dlen & PNG_BYTE_MASK) as u8 107 o = o + PNG_TYPE_LEN 108 var i: i64 = 0 109 while i < PNG_TYPE_LEN { buf[o + i] = t[i]; i = i + 1 } 110 o = o + PNG_TYPE_LEN 111 i = 0 112 while i < dlen { buf[o + i] = data[i]; i = i + 1 } 113 o = o + dlen 114 i = 0 115 while i < PNG_CRC_LEN { buf[o + i] = 0 as u8; i = i + 1 } 116 return o + PNG_CRC_LEN 117} 118// the 8-byte signature at buf[0..8); returns 8 119func png_put_sig(buf: *u8) -> i64 { 120 buf[0] = 137 as u8; buf[1] = 80 as u8; buf[2] = 78 as u8; buf[3] = 71 as u8 121 buf[4] = 13 as u8; buf[5] = 10 as u8; buf[6] = 26 as u8; buf[7] = 10 as u8 122 return PNG_SIG_LEN 123}