Exercise 2 solution import struct, zlib MAGIC = b"TBX1" VERSION = 1 HEADER = struct.Struct("<4sBBH") # magic, version, flags, entry count ENTRY = struct.Struct(" 255: raise FormatError("entry name too long") def write_tbx(entries, compress=False): if len(entries) > MAX_ENTRIES: raise FormatError("too many entries") seen, body = set(), [] for name, data in entries: check_name(name) if name in seen: raise FormatError(f"duplicate name {name!r}") seen.add(name) if len(data) > MAX_ORIGINAL: raise FormatError(f"{name!r} is over {MAX_ORIGINAL} bytes") stored = zlib.compress(data, 9) if compress else data raw_name = name.encode("utf-8") body.append(ENTRY.pack(len(raw_name), len(stored), len(data), zlib.crc32(data))) body.append(raw_name) body.append(stored) flags = FLAG_ZLIB if compress else 0 return HEADER.pack(MAGIC, VERSION, flags, len(entries)) + b"".join(body) class Reader: def __init__(self, buf): self.buf = bytes(buf) self.pos = 0 def remaining(self): return len(self.buf) - self.pos def take(self, n): if n < 0 or n > self.remaining(): raise FormatError(f"need {n} bytes at offset {self.pos}, {self.remaining()} left") chunk = self.buf[self.pos:self.pos + n] self.pos += n return chunk def unpack(self, st): return st.unpack(self.take(st.size)) def inflate(data, expected): d = zlib.decompressobj() try: out = d.decompress(data, expected + 1) except zlib.error as e: raise FormatError(f"corrupt zlib data: {e}") from e if len(out) != expected or not d.eof or d.unused_data: raise FormatError(f"zlib data does not match the declared size {expected}") return out def read_tbx(buf): if len(buf) > MAX_FILE: raise FormatError(f"file over {MAX_FILE} bytes") r = Reader(buf) magic, version, flags, count = r.unpack(HEADER) if magic != MAGIC: raise FormatError(f"bad magic {magic!r}") if version != VERSION: raise FormatError(f"unsupported version {version}") if flags & ~KNOWN_FLAGS: raise FormatError(f"unknown flag bits {flags & ~KNOWN_FLAGS:#04x}") if count > MAX_ENTRIES: raise FormatError(f"entry count {count} over limit {MAX_ENTRIES}") if count * (ENTRY.size + 1) > r.remaining(): raise FormatError(f"entry count {count} cannot fit in {r.remaining()} bytes") out, seen = [], set() for _ in range(count): start = r.pos name_len, stored, original, crc = r.unpack(ENTRY) try: name = r.take(name_len).decode("utf-8") except UnicodeDecodeError as e: raise FormatError(f"entry at {start}: name is not UTF-8") from e check_name(name) if name in seen: raise FormatError(f"duplicate name {name!r}") seen.add(name) if original > MAX_ORIGINAL: raise FormatError(f"{name!r}: declared size {original} over limit") data = r.take(stored) if flags & FLAG_ZLIB: data = inflate(data, original) elif stored != original: raise FormatError(f"{name!r}: stored size {stored} != original size {original}") if zlib.crc32(data) != crc: raise FormatError(f"{name!r}: checksum mismatch") out.append((name, data)) if r.remaining(): raise FormatError(f"{r.remaining()} trailing bytes at offset {r.pos}") return out def write_v2(entries, compress=False): body = bytearray(write_tbx(entries, compress)) body[4] = 2 # version byte return bytes(body) + struct.pack(" MAX_FILE: raise FormatError(f"file over {MAX_FILE} bytes") if len(buf) >= 5 and buf[:4] == MAGIC and buf[4] == 2: if len(buf) < HEADER.size + 4: raise FormatError("version 2 file too short for its checksum") body, (crc,) = bytearray(buf[:-4]), struct.unpack("", e) renamed = bytearray(v1) renamed[21] ^= 1 print("renamed v1 ->", [n for n, _ in read_any(bytes(renamed))]) # 384 388 True True # v1 (1212, 23, 1189, 0) # v2 (1228, 0, 1228, 0) # renamed v2 -> whole-file checksum mismatch # renamed v1 -> ['iello.txt', 'zeros.bin', 'empty'] (Run as written; the output matched the comments.) Explanation: version 1 has no checksum over the names or headers, so changing a name byte gives another valid file. Version 2 adds a CRC of the whole file; read_any checks it first, then hands the body (version patched back to 1) to read_tbx. Every damaged copy is now rejected, at the cost of 4 bytes. A CRC is not protection against deliberate tampering, only against accidents.