Very cool, and as usual, I love how lean your implementation is. I recalled seeing something similar a few weeks ago: https://github.com/Fusion/pngsource that I first saw discussed on Hacker News: https://news.ycombinator.com/item?id=29657072 I didn't dig too deeply through the implementation to see what was happening under its hood. It seemed like a lot more to unpack-- yours is much easier to read and understand. I have a down-in-the-weeds question: what motivates the `void *` buffer type in `crc32_update`, when it's `unsigned char *` in other places? (I thought there might be some raw structs getting plugged in somewhere, but I didn't see any, it looks like all of the actual buffers being provided are also `unsigned char` arrays?)
Thanks, Eric! That very project being shared around was what got the gears turning for my project. I was inspired by the headline, skimmed the PNG specification to brush up on the format, went to sleep, and by morning I had a few ideas for how to tackle it. I Initially considered compatibility with pngsource (i.e. the draw.io format), but saw the XML and noped out. > what motivates the `void *` buffer type in `crc32_update` That's just the result of a copy-paste incompletely integrated into the new project. :-) https://github.com/skeeto/scratch/blob/master/checksums/crc32.h The original is C89 — no stdint.h, no for loop variable declaration — and, being agnostic of the surrounding context, accepts a generic block of memory, which as you pointed out could be a struct. I like to avoid even "char *" when accepting raw memory since it's opinionated about the sign of its input. As I worked, I massaged it into the context of pngattach, adapting it to C99 and fixed-width integers. Since I use "unsigned char" consistently throughout the rest of the program as an alias for raw PNG bytes, I probably should have changed that aspect of its interface as well. Side note: In the past when processing string data ("char *") as though unsigned bytes, such as to decode UTF-8 or do table lookups, I've cast "char *" to "unsigned char *". However, I now use "&0xff" on the read value to force it to the equivalent unsigned value without the awkward pointer cast. Every compiler I've tested handles this perfectly (turns it into a zero-extension), so there's no penalty. Technically this requires a two's complement representation, but practically speaking, none of my programs will ever see a computer that isn't so.