Exercise 2 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 verify(raw): """Return a list of (type, ok) pairs, one per chunk.""" if raw[:8] != SIG: raise ValueError("not a PNG") pos, results = 8, [] while pos + 12 <= len(raw): length, ctype = struct.unpack_from(">I4s", raw, pos) end = pos + 12 + length if end > len(raw): raise ValueError("truncated") data = raw[pos + 8:pos + 8 + length] (stored,) = struct.unpack_from(">I", raw, pos + 8 + length) results.append((ctype.decode("ascii"), stored == zlib.crc32(ctype + data))) pos = end return results png = (SIG + chunk(b"IHDR", struct.pack(">IIBBBBB", 2, 2, 8, 0, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(b"\x00\x01\x02\x00\x03\x04")) + chunk(b"IEND")) print(verify(png)) # IHDR data starts at offset 16; flip one bit of the width bad = bytearray(png) bad[19] ^= 0x01 print(verify(bytes(bad))) # Corrupt the stored CRC itself instead (last byte of IHDR's CRC, offset 32) bad2 = bytearray(png) bad2[32] ^= 0xFF print(verify(bytes(bad2))) # Corrupt the length of IDAT? That breaks framing rather than the CRC. bad3 = bytearray(png) bad3[36] = 0xFF try: verify(bytes(bad3)) except ValueError as e: print("framing:", e) # [('IHDR', True), ('IDAT', True), ('IEND', True)] # [('IHDR', False), ('IDAT', True), ('IEND', True)] # [('IHDR', False), ('IDAT', True), ('IEND', True)] # framing: truncated (Run as written; the output matched the comments.) Explanation: The CRC covers the chunk type and data, not the length. Flipping one bit of the width, or damaging the stored CRC itself, both give a mismatch for that chunk only; the other chunks still verify. Damaging a length field is a different failure: the framing breaks, so the walker runs past the end of the data instead.