# Digits are chosen to be the least ambiguous # They all have unique seven-segment glyphs, and cannot be easily confused by humans digits = '123456789abcdehjnrtyACEFGHJLOPUY' radix = len(digits) def encode(v): if not len(v): return '' n = 0 bits = 0 o = [] pad = (len(v) * 8) % 5 if pad: v = b'\0' + v v = bytearray(v) # For Python 2.7 compatibility for i in range(len(v) - 1, -1, -1): n |= v[i] << bits bits += 8 while bits >= 5: o.insert(0, digits[n & 0x1f]) n >>= 5 bits -= 5 if i == 0 and pad: break return ''.join(o) def decode(s): n = 0 bits = 0 o = bytearray() for i in range(len(s) - 1, -1, -1): n |= digits.index(s[i]) << bits bits += 5 while bits >= 8: o.insert(0, n & 0xff) n >>= 8 bits -= 8 return bytes(o) def test(): from math import ceil assert '' == encode(b'') for (i, oc) in ( (1, '8'), (2, '2'), (3, 'j'), (4, '4'), (5, 'Y'), (6, '8'), (7, '2'), (8, 'j'), (9, '4'), ): ol = int(ceil(i * 8 / 5.)) try: inzero = b'\0' * i inone = b'\xff' * i ezero = encode(inzero) eone = encode(inone) dzero = decode(ezero) done = decode(eone) assert ezero == '1' * ol assert eone == oc + ('Y' * (ol - 1)) assert dzero == inzero assert done == inone except AssertionError: raise AssertionError('Input of length %s failed test' % (i,)) try: for c in range(1024): decode('111' + chr(c)) except ValueError: pass else: raise AssertionError('Invalid decode input (%02x) did not throw a ValueError' % (c,)) if __name__ == '__main__': test() print("Tests passed")