pass-sanitaire-check/pscheck/base45.py

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")