%%%------------------------------------------------------------------- %%% @author Ralf Thomas Pietsch %%% @copyright (C) 2021, Ralf Thomas Pietsch %%% @doc A Base45 encoder/decoder for binary data. %%% %%% Implements the Base45 encoding scheme as defined in %%% RFC 9285. %%% %%% == Examples == %%% %%% Encoding: %%% ``` %%% > base45:encode(<<"Hello!!">>). %%% <<"%69 VD92EX0">> %%% %%% > base45:encode(<<"AB">>). %%% <<"BB8">> %%% %%% > base45:encode(<<100>>). %%% <<"A2">> %%% ''' %%% %%% Decoding: %%% ``` %%% > base45:decode(<<"%69 VD92EX0">>). %%% <<"Hello!!">> %%% %%% > base45:decode(<<"BB8">>). %%% <<"AB">> %%% %%% > base45:decode(<<"A2">>). %%% <<100>> %%% ''' %%% %%% Invalid input raises an error: %%% ``` %%% > base45:decode(<<"GGW">>). %%% ** exception error: {illegal_encoding,<<"GGW">>} %%% %%% > base45:decode(<<"::">>). %%% ** exception error: {illegal_encoding,<<"::">>} %%% %%% > base45:decode(<<"A">>). %%% ** exception error: {illegal_encoding,<<"A">>} %%% %%% > base45:decode(<<"===">>). %%% ** exception error: {illegal_character,<<"=">>} %%% ''' %%% %%% @end %%% Created : 20. Jul 2021 22:11 %%%------------------------------------------------------------------- -module(base45). -author("Ralf Thomas Pietsch "). %% API -export([encode/1, decode/1]). %%%=================================================================== %%% API %%%=================================================================== %%% @doc Encode a binary value into Base45. %%% %%% Two input bytes are encoded into 3 characters, a single remaining byte %%% into 2 characters. An empty binary returns an empty binary. -spec encode(binary()) -> binary(). encode(<<>>) -> <<>>; encode(<>) -> C = encode_part(N rem 45), D = encode_part((N div 45) rem 45), <>; encode(<>) -> C = encode_part(N rem 45), D = encode_part((N div 45) rem 45), E = encode_part((N div 45 div 45)), X = encode(Rest), <>. %%% @doc Decode a Base45 encoded binary value. %%% %%% The input length must be divisible by 3, or have a remainder of 2 %%% (for a trailing single-byte encoding). %%% %%% Raises an `error' exception if the input is not valid Base45: %%%
    %%%
  • `{illegal_encoding, <<_>>}' if a single character remains (invalid length)
  • %%%
  • `{illegal_encoding, <<_, _>>}' if a pair of characters results in a value above 255
  • %%%
  • `{illegal_encoding, <<_, _, _>>}' if a triplet of characters results in a value above 65535
  • %%%
  • `{illegal_character, <>}' if the input contains a character outside the Base45 alphabet
  • %%%
%%% @end -spec decode(binary()) -> binary(). decode(<<>>) -> <<>>; decode(<>) -> erlang:error({illegal_encoding, <>}); decode(<>) -> N = decode_part(C) + 45 * decode_part(D), case N > 255 of true -> erlang:error({illegal_encoding, <>}); false -> <> end; decode(<>) -> N = decode_part(C) + 45 * (decode_part(D) + 45 * decode_part(E)), case N > 65535 of true -> erlang:error({illegal_encoding, <>}); false -> X = decode(Rest), <> end. %%%=================================================================== %%% Internal functions %%%=================================================================== special_chars() -> " $%*+-./:". decode_alphabet() -> [ {36, $ }, {37, $$}, {38, $%}, {39, $*}, {40, $+}, {41, $-}, {42, $.}, {43, $/}, {44, $:} ]. encode_part(N) when N >= 0, N =< 9 -> N + $0; encode_part(N) when N >= 10, N =< 35 -> N - 10 + $A; encode_part(N) when N >= 36, N =< 45 -> lists:nth(N - 35, special_chars()). decode_part(Char) when Char >= $0, Char =< $9 -> Char - $0; decode_part(Char) when Char >= $A, Char =< $Z -> Char - $A + 10; decode_part(Char) -> case lists:filtermap( fun({N, C}) -> case C == Char of true -> {true, N}; false -> false end end, decode_alphabet() ) of [N] -> N; _ -> erlang:error({illegal_character, <>}) end.