Exercise 1 solution import struct import zlib SIG = b"\x89PNG\r\n\x1a\n" def chunk(ctype, data=b""): body = ctype + data return struct.pack(">I", len(data)) + body + struct.pack(">I", zlib.crc32(body)) def list_chunks(raw): if raw[:8] != SIG: raise ValueError("not a PNG: bad signature") pos, out = 8, [] while pos < len(raw): if pos + 8 > len(raw): raise ValueError("truncated chunk header") length, ctype = struct.unpack_from(">I4s", raw, pos) if pos + 12 + length > len(raw): raise ValueError(f"chunk {ctype!r} runs past end of file") out.append((ctype, length)) pos += 12 + length return out ihdr = struct.pack(">IIBBBBB", 3, 1, 8, 0, 0, 0, 0) idat = zlib.compress(b"\x00\x0a\x14\x1e") png = SIG + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat) + chunk(b"IEND") print(list_chunks(png)) for label, raw in (("bad signature", b"GIF89a" + bytes(30)), ("cut mid-chunk", png[:45]), ("cut in header", png[:-5])): try: list_chunks(raw) except ValueError as e: print(label + ":", e) # [(b'IHDR', 13), (b'IDAT', 12), (b'IEND', 0)] # bad signature: not a PNG: bad signature # cut mid-chunk: chunk b'IDAT' runs past end of file # cut in header: truncated chunk header (Run as written; the output matched the comments.) Explanation: Every chunk is length (4 bytes, big-endian), type (4 bytes), data, then a 4-byte CRC, so the next chunk starts at pos + 12 + length. The walker never trusts a length beyond the end of the data. The last two cases fail differently: one chunk header is whole but its data is cut off, the other has too few bytes left to hold a header.