384 lines
14 KiB
TypeScript
384 lines
14 KiB
TypeScript
/**
|
|
* Comprehensive Fuzzing & Differential Stress Harness for scripts/png.ts
|
|
*
|
|
* Implements the 3-component pattern from the solution-stress-testing playbook:
|
|
* 1. Generator: produces systematic boundary & pseudo-random inputs across unaligned widths & strides
|
|
* 2. Oracle: independent W3C PNG specification parser & unfilter engine
|
|
* 3. Harness: orchestrates 1,000+ test iterations, asserts exact bit-for-bit parity, benchmarks speed
|
|
*/
|
|
import { inflateSync } from 'node:zlib'
|
|
import { encodeIndexedPng, encodeRgbaPng, type IndexedImage, type RgbaImage } from './png'
|
|
|
|
interface DecodedResult {
|
|
readonly width: number
|
|
readonly height: number
|
|
readonly colorType: number
|
|
readonly pixels: Uint8Array
|
|
readonly filterTypes: number[]
|
|
}
|
|
|
|
const PNG_SIG = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
|
|
|
const CRC_TBL = new Uint32Array(256)
|
|
for (let n = 0; n < 256; n += 1) {
|
|
let c = n
|
|
for (let k = 0; k < 8; k += 1) c = (c & 1) !== 0 ? (0xedb88320 ^ (c >>> 1)) >>> 0 : c >>> 1
|
|
CRC_TBL[n] = c >>> 0
|
|
}
|
|
|
|
function checkCrc(bytes: Uint8Array, expected: number): boolean {
|
|
let c = 0xffffffff
|
|
for (const byte of bytes) c = (CRC_TBL[(c ^ byte) & 0xff]! ^ (c >>> 8)) >>> 0
|
|
return ((c ^ 0xffffffff) >>> 0) === expected
|
|
}
|
|
|
|
function oracleDecode(bytes: Uint8Array): DecodedResult {
|
|
for (let i = 0; i < 8; i += 1) {
|
|
if (bytes[i] !== PNG_SIG[i]) throw new Error(`Invalid signature at byte ${i}`)
|
|
}
|
|
|
|
let offset = 8
|
|
let width = 0
|
|
let height = 0
|
|
let colorType = 0
|
|
const idatParts: Uint8Array[] = []
|
|
|
|
while (offset < bytes.length) {
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 8)
|
|
const len = view.getUint32(0)
|
|
const type = String.fromCharCode(bytes[offset + 4]!, bytes[offset + 5]!, bytes[offset + 6]!, bytes[offset + 7]!)
|
|
const payload = bytes.subarray(offset + 8, offset + 8 + len)
|
|
const crc = new DataView(bytes.buffer, bytes.byteOffset + offset + 8 + len, 4).getUint32(0)
|
|
|
|
if (!checkCrc(bytes.subarray(offset + 4, offset + 8 + len), crc)) {
|
|
throw new Error(`CRC error in chunk ${type}`)
|
|
}
|
|
|
|
if (type === 'IHDR') {
|
|
const hv = new DataView(payload.buffer, payload.byteOffset, 13)
|
|
width = hv.getUint32(0)
|
|
height = hv.getUint32(4)
|
|
colorType = payload[9]!
|
|
} else if (type === 'IDAT') {
|
|
idatParts.push(payload)
|
|
} else if (type === 'IEND') {
|
|
break
|
|
}
|
|
offset += 12 + len
|
|
}
|
|
|
|
const stride = colorType === 3 ? 1 : 4
|
|
const rowBytes = width * stride
|
|
const totalIdat = idatParts.reduce((sum, p) => sum + p.length, 0)
|
|
const idatMerged = new Uint8Array(totalIdat)
|
|
let pOff = 0
|
|
for (const p of idatParts) {
|
|
idatMerged.set(p, pOff)
|
|
pOff += p.length
|
|
}
|
|
|
|
const decomp = new Uint8Array(inflateSync(idatMerged))
|
|
if (decomp.length !== (rowBytes + 1) * height) {
|
|
throw new Error(`Unexpected raw decompressed size: expected ${(rowBytes + 1) * height}, got ${decomp.length}`)
|
|
}
|
|
|
|
const pixels = new Uint8Array(width * height * stride)
|
|
const filterTypes: number[] = []
|
|
let at = 0
|
|
|
|
for (let y = 0; y < height; y += 1) {
|
|
const filter = decomp[at]!
|
|
filterTypes.push(filter)
|
|
at += 1
|
|
|
|
const row = decomp.subarray(at, at + rowBytes)
|
|
const outRow = pixels.subarray(y * rowBytes, (y + 1) * rowBytes)
|
|
const priorRow = y === 0 ? null : pixels.subarray((y - 1) * rowBytes, y * rowBytes)
|
|
|
|
if (filter === 0) {
|
|
outRow.set(row)
|
|
} else if (filter === 1) {
|
|
for (let x = 0; x < stride; x += 1) outRow[x] = row[x]!
|
|
for (let x = stride; x < rowBytes; x += 1) outRow[x] = (row[x]! + outRow[x - stride]!) & 0xff
|
|
} else if (filter === 2) {
|
|
for (let x = 0; x < rowBytes; x += 1) {
|
|
const up = priorRow ? priorRow[x]! : 0
|
|
outRow[x] = (row[x]! + up) & 0xff
|
|
}
|
|
} else {
|
|
throw new Error(`Unsupported PNG filter type: ${filter}`)
|
|
}
|
|
at += rowBytes
|
|
}
|
|
|
|
return { width, height, colorType, pixels, filterTypes }
|
|
}
|
|
|
|
function verifyParity(expected: Uint8Array, actual: Uint8Array, context: string): void {
|
|
if (expected.length !== actual.length) {
|
|
throw new Error(`[${context}] Length mismatch: expected ${expected.length}, got ${actual.length}`)
|
|
}
|
|
for (let i = 0; i < expected.length; i += 1) {
|
|
if (expected[i] !== actual[i]) {
|
|
throw new Error(`[${context}] Byte mismatch at offset ${i}: expected ${expected[i]}, got ${actual[i]}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// PRNG
|
|
function splitmix64(seed: bigint) {
|
|
let s = seed
|
|
return () => {
|
|
s = (s + 0x9e3779b97f4a7c15n) & 0xffffffffffffffffn
|
|
let z = s
|
|
z = ((z ^ (z >> 30n)) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn
|
|
z = ((z ^ (z >> 27n)) * 0x94d049bb133111ebn) & 0xffffffffffffffffn
|
|
return Number((z ^ (z >> 31n)) & 0xffffffffn)
|
|
}
|
|
}
|
|
|
|
// Palette
|
|
const PALETTE_768 = new Uint8Array(768)
|
|
for (let i = 0; i < 256; i += 1) {
|
|
PALETTE_768[i * 3 + 0] = (i * 11) & 0xff
|
|
PALETTE_768[i * 3 + 1] = (i * 23) & 0xff
|
|
PALETTE_768[i * 3 + 2] = (i * 37) & 0xff
|
|
}
|
|
|
|
console.log('=== STARTING EMPIRICAL DIFFERENTIAL STRESS TEST ===')
|
|
let totalCasesTested = 0
|
|
|
|
// PHASE 1: Exhaustive minimal dimensions (1x1 to 4x4)
|
|
console.log('\n--- Phase 1: Exhaustive Minimal Dimensions Sweep ---')
|
|
for (let w = 1; w <= 4; w += 1) {
|
|
for (let h = 1; h <= 4; h += 1) {
|
|
// 1. All zeros
|
|
const zIdx = new Uint8Array(w * h)
|
|
const encZIdx = encodeIndexedPng({ width: w, height: h, pixels: zIdx, palette: PALETTE_768 })
|
|
verifyParity(zIdx, oracleDecode(encZIdx).pixels, `Indexed zero ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
|
|
const zRgba = new Uint8Array(w * h * 4)
|
|
const encZRgba = encodeRgbaPng({ width: w, height: h, pixels: zRgba })
|
|
verifyParity(zRgba, oracleDecode(encZRgba).pixels, `RGBA zero ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
|
|
// 2. Solid color
|
|
const sIdx = new Uint8Array(w * h).fill(42)
|
|
const encSIdx = encodeIndexedPng({ width: w, height: h, pixels: sIdx, palette: PALETTE_768 })
|
|
verifyParity(sIdx, oracleDecode(encSIdx).pixels, `Indexed solid ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
|
|
const sRgba = new Uint8Array(w * h * 4).fill(128)
|
|
const encSRgba = encodeRgbaPng({ width: w, height: h, pixels: sRgba })
|
|
verifyParity(sRgba, oracleDecode(encSRgba).pixels, `RGBA solid ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
|
|
// 3. Incrementing values
|
|
const iIdx = new Uint8Array(w * h)
|
|
for (let i = 0; i < iIdx.length; i += 1) iIdx[i] = (i + 1) & 0xff
|
|
const encIIdx = encodeIndexedPng({ width: w, height: h, pixels: iIdx, palette: PALETTE_768 })
|
|
verifyParity(iIdx, oracleDecode(encIIdx).pixels, `Indexed ramp ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
|
|
const iRgba = new Uint8Array(w * h * 4)
|
|
for (let i = 0; i < iRgba.length; i += 1) iRgba[i] = (i * 3 + 7) & 0xff
|
|
const encIRgba = encodeRgbaPng({ width: w, height: h, pixels: iRgba })
|
|
verifyParity(iRgba, oracleDecode(encIRgba).pixels, `RGBA ramp ${w}x${h}`)
|
|
totalCasesTested += 1
|
|
}
|
|
}
|
|
console.log(`Phase 1 passed: ${totalCasesTested} cases executed.`)
|
|
|
|
// PHASE 2: Random Small Dimensions (1,000 iterations fuzzing)
|
|
console.log('\n--- Phase 2: Differential Fuzzing on Small Inputs (1,000 iterations) ---')
|
|
const rngSmall = splitmix64(123456789n)
|
|
const phase2Start = Date.now()
|
|
|
|
for (let iter = 0; iter < 1000; iter += 1) {
|
|
const w = (rngSmall() % 23) + 1 // 1..23
|
|
const h = (rngSmall() % 23) + 1 // 1..23
|
|
const patternType = rngSmall() % 6
|
|
const compLvl = (rngSmall() % 10) // 0..9
|
|
|
|
if (iter % 2 === 0) {
|
|
// Indexed
|
|
const pixels = new Uint8Array(w * h)
|
|
if (patternType === 0) {
|
|
// all zero
|
|
} else if (patternType === 1) {
|
|
pixels.fill(rngSmall() & 0xff)
|
|
} else if (patternType === 2) {
|
|
// identical rows
|
|
const baseRow = new Uint8Array(w)
|
|
for (let x = 0; x < w; x += 1) baseRow[x] = rngSmall() & 0xff
|
|
for (let y = 0; y < h; y += 1) pixels.set(baseRow, y * w)
|
|
} else if (patternType === 3) {
|
|
// gradient
|
|
for (let y = 0; y < h; y += 1) {
|
|
for (let x = 0; x < w; x += 1) pixels[y * w + x] = (x + y * 3) & 0xff
|
|
}
|
|
} else if (patternType === 4) {
|
|
// sparse
|
|
for (let i = 0; i < pixels.length; i += 1) {
|
|
if ((rngSmall() % 10) === 0) pixels[i] = rngSmall() & 0xff
|
|
}
|
|
} else {
|
|
// high entropy noise
|
|
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngSmall() & 0xff
|
|
}
|
|
|
|
const enc = encodeIndexedPng({
|
|
width: w,
|
|
height: h,
|
|
pixels,
|
|
palette: PALETTE_768,
|
|
transparentIndex: rngSmall() % 2 === 0 ? 0 : undefined,
|
|
compressionLevel: compLvl,
|
|
})
|
|
const dec = oracleDecode(enc)
|
|
verifyParity(pixels, dec.pixels, `Phase 2 Indexed iter ${iter} (${w}x${h} pattern ${patternType})`)
|
|
} else {
|
|
// RGBA
|
|
const pixels = new Uint8Array(w * h * 4)
|
|
if (patternType === 0) {
|
|
// all zero
|
|
} else if (patternType === 1) {
|
|
const color = rngSmall()
|
|
for (let i = 0; i < w * h; i += 1) {
|
|
pixels[i * 4 + 0] = color & 0xff
|
|
pixels[i * 4 + 1] = (color >> 8) & 0xff
|
|
pixels[i * 4 + 2] = (color >> 16) & 0xff
|
|
pixels[i * 4 + 3] = (color >> 24) & 0xff
|
|
}
|
|
} else if (patternType === 2) {
|
|
// identical rows
|
|
const baseRow = new Uint8Array(w * 4)
|
|
for (let x = 0; x < w * 4; x += 1) baseRow[x] = rngSmall() & 0xff
|
|
for (let y = 0; y < h; y += 1) pixels.set(baseRow, y * w * 4)
|
|
} else {
|
|
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngSmall() & 0xff
|
|
}
|
|
|
|
const enc = encodeRgbaPng({
|
|
width: w,
|
|
height: h,
|
|
pixels,
|
|
compressionLevel: compLvl,
|
|
})
|
|
const dec = oracleDecode(enc)
|
|
verifyParity(pixels, dec.pixels, `Phase 2 RGBA iter ${iter} (${w}x${h} pattern ${patternType})`)
|
|
}
|
|
totalCasesTested += 1
|
|
}
|
|
console.log(`Phase 2 passed: 1000 fuzzing iterations in ${Date.now() - phase2Start} ms. Total cases: ${totalCasesTested}.`)
|
|
|
|
// PHASE 3: Random Medium Dimensions (100 iterations, N = 50..300)
|
|
console.log('\n--- Phase 3: Differential Fuzzing on Medium Inputs (100 iterations) ---')
|
|
const rngMedium = splitmix64(987654321n)
|
|
const phase3Start = Date.now()
|
|
|
|
for (let iter = 0; iter < 100; iter += 1) {
|
|
const w = (rngMedium() % 250) + 50 // 50..299 (unaligned)
|
|
const h = (rngMedium() % 250) + 50 // 50..299
|
|
const patternType = rngMedium() % 5
|
|
|
|
const pixels = new Uint8Array(w * h)
|
|
if (patternType === 0) {
|
|
// 50% transparent rows, 50% content
|
|
for (let y = 0; y < h; y += 2) {
|
|
for (let x = 0; x < w; x += 1) pixels[y * w + x] = (x ^ y) & 0xff
|
|
}
|
|
} else if (patternType === 1) {
|
|
// Repeated block bands (tests Fast-Path 2 over sequences of rows)
|
|
const bandHeight = 5
|
|
for (let y = 0; y < h; y += bandHeight) {
|
|
const rowPattern = new Uint8Array(w)
|
|
for (let x = 0; x < w; x += 1) rowPattern[x] = rngMedium() & 0xff
|
|
for (let b = 0; b < bandHeight && y + b < h; b += 1) {
|
|
pixels.set(rowPattern, (y + b) * w)
|
|
}
|
|
}
|
|
} else if (patternType === 2) {
|
|
// Gradients
|
|
for (let y = 0; y < h; y += 1) {
|
|
for (let x = 0; x < w; x += 1) pixels[y * w + x] = ((x * 3) + (y * 7)) & 0xff
|
|
}
|
|
} else {
|
|
// Noise
|
|
for (let i = 0; i < pixels.length; i += 1) pixels[i] = rngMedium() & 0xff
|
|
}
|
|
|
|
const enc = encodeIndexedPng({
|
|
width: w,
|
|
height: h,
|
|
pixels,
|
|
palette: PALETTE_768,
|
|
transparentIndex: 0,
|
|
compressionLevel: 6,
|
|
})
|
|
const dec = oracleDecode(enc)
|
|
verifyParity(pixels, dec.pixels, `Phase 3 Medium iter ${iter} (${w}x${h})`)
|
|
totalCasesTested += 1
|
|
}
|
|
console.log(`Phase 3 passed: 100 medium iterations in ${Date.now() - phase3Start} ms. Total cases: ${totalCasesTested}.`)
|
|
|
|
// PHASE 4: Performance & Memory Benchmark (Atlas Scale 2048x1808)
|
|
console.log('\n--- Phase 4: Performance & Benchmark Profiling ---')
|
|
const W = 2048
|
|
const H = 1808
|
|
const atlasPixels = new Uint8Array(W * H)
|
|
// Simulate realistic Diablo 2 sprite sheet:
|
|
// ~60% transparent empty space, ~20% repeating edge runs, ~20% sprite pixel islands
|
|
for (let y = 200; y < 700; y += 1) {
|
|
for (let x = 200; x < 900; x += 1) {
|
|
atlasPixels[y * W + x] = ((x ^ y) & 0x7f) + 1
|
|
}
|
|
}
|
|
for (let y = 900; y < 1500; y += 1) {
|
|
for (let x = 800; x < 1600; x += 1) {
|
|
atlasPixels[y * W + x] = ((x * 13 + y * 7) & 0xff) || 1
|
|
}
|
|
}
|
|
|
|
// Warm up
|
|
encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 })
|
|
|
|
// Level 6 benchmark
|
|
const trials6: number[] = []
|
|
let len6 = 0
|
|
for (let t = 0; t < 5; t += 1) {
|
|
const t0 = performance.now()
|
|
const out6 = encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 })
|
|
trials6.push(performance.now() - t0)
|
|
len6 = out6.length
|
|
}
|
|
const avg6 = trials6.reduce((a, b) => a + b, 0) / trials6.length
|
|
|
|
// Level 9 benchmark
|
|
const trials9: number[] = []
|
|
let len9 = 0
|
|
for (let t = 0; t < 5; t += 1) {
|
|
const t0 = performance.now()
|
|
const out9 = encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 9 })
|
|
trials9.push(performance.now() - t0)
|
|
len9 = out9.length
|
|
}
|
|
const avg9 = trials9.reduce((a, b) => a + b, 0) / trials9.length
|
|
|
|
console.log(`2048x1808 Atlas Benchmark Results:`)
|
|
console.log(`- Deflate Level 6 (New Default): ${avg6.toFixed(2)} ms (Size: ${(len6 / 1024).toFixed(1)} KB)`)
|
|
console.log(`- Deflate Level 9 (Old Baseline): ${avg9.toFixed(2)} ms (Size: ${(len9 / 1024).toFixed(1)} KB)`)
|
|
console.log(`- Speedup: ${(avg9 / avg6).toFixed(2)}x faster!`)
|
|
console.log(`- Compression size delta: ${(((len6 - len9) / len9) * 100).toFixed(2)}% difference`)
|
|
|
|
// Verify bit-for-bit parity on the large atlas
|
|
const decAtlas6 = oracleDecode(encodeIndexedPng({ width: W, height: H, pixels: atlasPixels, palette: PALETTE_768, compressionLevel: 6 }))
|
|
verifyParity(atlasPixels, decAtlas6.pixels, '2048x1808 Atlas Parity')
|
|
totalCasesTested += 1
|
|
|
|
console.log(`\n======================================================`)
|
|
console.log(`ALL TESTS PASSED: ${totalCasesTested} total verification cases.`)
|
|
console.log(`100% BIT-FOR-BIT PIXEL PARITY EMPIRICALLY CONFIRMED.`)
|
|
console.log(`======================================================\n`)
|