Exercise 1 solution def enc_varint(n): if n < 0: raise ValueError("negative") out = bytearray() while True: low = n & 0x7F n >>= 7 if n: out.append(low | 0x80) else: out.append(low) return bytes(out) def dec_varint(buf, pos): result = shift = 0 for i in range(5): if pos >= len(buf): raise ValueError("truncated varint") b = buf[pos] pos += 1 result |= (b & 0x7F) << shift if not b & 0x80: return result, pos shift += 7 raise ValueError("varint longer than 5 bytes") def pack_strings(items): out = bytearray(enc_varint(len(items))) for s in items: data = s.encode("utf-8") out += enc_varint(len(data)) + data return bytes(out) def unpack_strings(buf): count, pos = dec_varint(buf, 0) items = [] for _ in range(count): n, pos = dec_varint(buf, pos) if pos + n > len(buf): raise ValueError(f"string wants {n} bytes, {len(buf) - pos} left") items.append(buf[pos:pos + n].decode("utf-8")) pos += n if pos != len(buf): raise ValueError(f"{len(buf) - pos} trailing byte(s)") return items items = ["", "hi", "café", "x" * 128] blob = pack_strings(items) print(len(blob), blob[:12].hex(" ")) print(unpack_strings(blob) == items) print(enc_varint(128).hex(" "), enc_varint(127).hex(" ")) for label, bad in [ ("cut short", blob[:20]), ("trailing", blob + b"\x00"), ("bad varint", b"\x80\x80\x80\x80\x80\x80"), ("empty", b""), ]: try: unpack_strings(bad) except ValueError as e: print(label, "->", e) try: pack_strings(["ok", chr(0xD800)]) except UnicodeEncodeError: print("unencodable string refused") # 141 04 00 02 68 69 05 63 61 66 c3 a9 80 # True # 80 01 7f # cut short -> string wants 128 bytes, 7 left # trailing -> 1 trailing byte(s) # bad varint -> varint longer than 5 bytes # empty -> truncated varint # unencodable string refused (Run as written; the output matched the comments.) Explanation: A varint stores seven bits of the number per byte, low bits first, and sets the top bit of a byte to say "more follows". Values up to 127 take one byte, 128 takes two, which is why the 128-character string gets a two-byte prefix. The reader bounds the varint at five bytes so a run of continuation bytes cannot loop for ever, checks every string against the bytes that remain before slicing, and rejects trailing bytes so that one input has exactly one meaning. Lengths count bytes, not characters ("café" is 5 bytes).