62 lines
1.7 KiB
Python
62 lines
1.7 KiB
Python
"""
|
|
BSD 2-Clause License
|
|
|
|
Copyright (c) 2021, Kirei AB
|
|
All rights reserved.
|
|
"""
|
|
|
|
|
|
from typing import Union
|
|
|
|
|
|
BASE45_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
|
|
BASE45_DICT = {v: i for i, v in enumerate(BASE45_CHARSET)}
|
|
|
|
|
|
def b45encode(buf: bytes) -> bytes:
|
|
"""Convert bytes to base45-encoded string"""
|
|
res = ""
|
|
buflen = len(buf)
|
|
for i in range(0, buflen & ~1, 2):
|
|
x = (buf[i] << 8) + buf[i + 1]
|
|
e, x = divmod(x, 45 * 45)
|
|
d, c = divmod(x, 45)
|
|
res += BASE45_CHARSET[c] + BASE45_CHARSET[d] + BASE45_CHARSET[e]
|
|
if buflen & 1:
|
|
d, c = divmod(buf[-1], 45)
|
|
res += BASE45_CHARSET[c] + BASE45_CHARSET[d]
|
|
return res.encode()
|
|
|
|
|
|
def b45decode(s: Union[bytes, str]) -> bytes:
|
|
"""Decode base45-encoded string to bytes"""
|
|
try:
|
|
if isinstance(s, str):
|
|
buf = [BASE45_DICT[c] for c in s.strip()]
|
|
elif isinstance(s, bytes):
|
|
buf = [BASE45_DICT[c] for c in s.decode()]
|
|
else:
|
|
raise TypeError("Type must be 'str' or 'bytes'")
|
|
|
|
buflen = len(buf)
|
|
if buflen % 3 == 1:
|
|
raise ValueError("Invalid base45 string")
|
|
|
|
res = []
|
|
for i in range(0, buflen, 3):
|
|
if buflen - i >= 3:
|
|
x = buf[i] + buf[i + 1] * 45 + buf[i + 2] * 45 * 45
|
|
if x > 0xFFFF:
|
|
raise ValueError
|
|
res.extend(divmod(x, 256))
|
|
else:
|
|
x = buf[i] + buf[i + 1] * 45
|
|
if x > 0xFF:
|
|
raise ValueError
|
|
res.append(x)
|
|
return bytes(res)
|
|
except (ValueError, KeyError, AttributeError):
|
|
raise ValueError("Invalid base45 string")
|
|
|
|
|