Packages
Jan Henry Nystrom's standard library,CBOR, HPACK, JSON, MessagePack, Syslog, URI, IP address, Timestamp, UUID,TypeID,prefix/range/T-trees, Bloom filters, HTTP client, Magic, Mustache rendering, mocking, cacerts, media type determination, snappy compression, Base32(hex, Geohash, Crockford, Clockwo...
Current section
Files
Jump to
Current section
Files
src/jhn_base.erl
%%==============================================================================
%% Copyright 2026 Jan Henry Nystrom <JanHenryNystrom@gmail.com>
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%==============================================================================
%%%-------------------------------------------------------------------
%%% @doc
%%% A binary data encodinglibrary providing support for a number of
%%% bases, algorithms and alphabets.
%%% If nothing else is specified the "standard" algorithm is used .
%%% The algorithm and alphabet to use in encoding or decoding can be specified
%%% with options provided to encode/3 and decode/3. Further options are the
%%% for the return values of either an iolist or binary, with the exception of
%%% the Z-Base-32 algorithm that returns a bitstring.
%%%
%%% The following documents are the base for the library
%%%
%%% rfc4648: The Base16, Base32, and Base64 Data Encodings
%%% * Base32 standard algorithm (standard)
%%% * Base32 standard and hex alphabets (standard, hex)
%%%
%%% The Base32 wikipage: https://en.wikipedia.org/wiki/Base32
%%% * The Geohash alphabet (geohash)
%%%
%%% Crockford's Base32:
%%% https://datatracker.ietf.org/doc/draft-crockford-davis-base32-for-humans/
%%% * Crockford base32 algorithm (crockford)
%%%
%%% Clockwork: https://github.com/szktty/go-clockwork-base32
%%% * The clockwork algorithm (clockwork)
%%%
%%% Z-Base-32:
%%% https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
%%% * The Z-Base-32 algorithm (zbase)
%%% Does not support binary/iolist deoding since it returns a bitstring only
%%%
%%% rfc9285: The Base45 Data Encoding
%%% * Base 45 standard algorithm (standard)
%%%
%%% The Base58 Encoding Scheme (Bitcoin):
%%% https://datatracker.ietf.org/doc/html/draft-msporny-base58-03
%%% * Base58 standard algorithm (standard)
%%% * Base58 standard alphabet (standard)
%%%
%%% The Flickr Base58 alphabet
%%% https://www.flickr.com/groups/51035612836@N01/discuss/72157616713786392/
%%% * Base58 flickr alphabet (flickr)
%%%
%%% The Ripple Base58 alphabet
%%% https://xrpl.org/docs/references/protocol/data-types/base58-encodings
%%% * Base58 Ripple alphabet (ripple)
%%%
%%% Ascii85: https://en.wikipedia.org/wiki/Ascii85
%%% * The Base85 algorithm
%%%
%%% ZeroMQ spec:32/Z85: https://rfc.zeromq.org/spec/32/
%%% * The ZeroMQ base85 algorithm (z85)
%%%
%%% rfc1924: A Compact Representation of IPv6 Addresses
%%% * The rfc1924 algorithm (ipv6) uses jhn_ip_addr to decode the address
%%%
%%% @end
%% @author Jan Henry Nystrom <JanHenryNystrom@gmail.com>
%% @copyright (C) 2026, Jan Henry Nystrom <JanHenryNystrom@gmail.com>
%%%-------------------------------------------------------------------
-module(jhn_base).
-copyright('Jan Henry Nystrom <JanHenryNystrom@gmail.com>').
%% Library functions
-export([encode/2, encode/3,
decode/2, decode/3]).
%% Types
-type algo() ::
%% base32, base45, base58, base85
standard |
%% base32
crockford | clockwork | zbase |
%% base85
z85 | ipv6.
-type base() :: 32 | 45 | 58 | 85.
-type alphabet() :: standard | hex | geohash | flickr | ripple.
-type opt() :: return_type() | {return_type, return_type()} |
{alpfabet, alphabet()} | {algo, algo()}.
-type return_type() :: iolist | binary.
%% Records
-record(opts, {algo = standard :: algo(),
alphabet = standard :: alphabet() ,
return_type = iolist :: return_type()
}).
%% Defines
%% B32
-define(B32_ALPHABET,
{$A, $B, $C, $D, $E, $F, $G, $H, $I, $J,
$K, $L, $M, $N, $O, $P, $Q, $R, $S, $T,
$U, $V, $W, $X, $Y, $Z, $2, $3, $4, $5,
$6, $7}).
-define(B32_DECODE,
{u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u,
26, 27, 28, 29, 30, 31,
u, u, u, u, u, u, u, u, u,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23,
24, 25}).
%% B32hex
-define(B32HEX_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$A, $B, $C, $D, $E, $F, $G, $H, $I, $J,
$K, $L, $M, $N, $O, $P, $Q, $R, $S, $T,
$U, $V}).
-define(B32HEX_DECODE,
{u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
u, u, u, u, u, u, u,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31}).
%% B32geo
-define(B32GEO_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$b, $c, $d, $e, $f, $g, $h, $j, $k, $m,
$n, $p, $q, $r, $s, $t, $u, $v, $w, $x,
$y, $z}).
-define(B32GEO_DECODE,
{u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,
0,1,2,3,4,5,6,7,8,9,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
10,11,12,13,14,15,16,
u,
17,18,
u,
19,20,
u,
21,22,23,24,25,26,27,28,29,30,31}).
%% B32Crockford
-define(B32CROCKFORD_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$A, $B, $C, $D, $E, $F, $G, $H, $J, $K,
$M, $N, $P, $Q, $R, $S, $T, $V, $W, $X,
$Y, $Z}).
-define(B32CROCKFORD_DECODE,
{u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,
0,1,2,3,4,5,6,7,8,9,
u,u, u,u,u,u,u,
10,11,12,13,14,15,16,17,
u,
18,19,
u,
20,21,
u,
22,23,24,25,26,
u,
27,28,29,30,31}).
%% B32Z
-define(B32Z_ALPHABET,
{$y, $b, $n, $d, $r, $f, $g, $8, $e,
$j, $k, $m, $c, $p, $q, $x, $o, $t,
$1, $u, $w, $i, $s, $z, $a, $3, $4,
$5, $h, $7, $6, $9}).
-define(B32Z_DECODE,
{u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,18,u,25,26,27,30,29,7,
31,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,u,24,1,12,3,8,5,6,28,21,9,10,u,11,2,16,
13,14,4,22,17,19,u,20,15,0,23}).
%% B45
-define(B45_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$A, $B, $C, $D, $E, $F, $G, $H, $I, $J,
$K, $L, $M, $N, $O, $P, $Q, $R, $S, $T,
$U, $V, $W, $X, $Y, $Z, $\s, $$, $%, $*,
$+, $-, $\., $\/, $:
}).
-define(B45_DECODE,
{36,
u, u, u,
37, 38,
u, u, u, u,
39, 40,
u,
41, 42, 43, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44,
u, u, u, u, u, u,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}).
%% B58/Bitcoin
-define(B58_ALPHABET,
{$1, $2, $3, $4, $5, $6, $7, $8, $9,
$A, $B, $C, $D, $E, $F, $G, $H, $J,
$K, $L, $M, $N, $P, $Q, $R, $S, $T,
$U, $V, $W, $X, $Y, $Z, $a, $b, $c,
$d, $e, $f, $g, $h, $i, $j, $k, $m,
$n, $o, $p, $q, $r, $s, $t, $u, $v,
$w, $x, $y, $z}).
-define(B58_DECODE,
{u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u,
0, 1, 2, 3, 4, 5, 6, 7, 8,
u, u, u, u, u, u, u,
9, 10, 11, 12, 13, 14, 15, 16,
u,
17, 18, 19, 20, 21,
u,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
u, u, u, u, u, u,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
u,
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57}).
%% B58/Ripple
-define(RIPPLE_ALPHABET,
{$r, $p, $s, $h, $n, $a, $f, $3, $9, $w, $B,
$U, $D, $N, $E, $G, $H, $J, $K, $L, $M, $4,
$P, $Q, $R, $S, $T, $7, $V, $W, $X, $Y, $Z,
$2, $b, $c, $d, $e, $C, $g, $6, $5, $j, $k,
$m, $8, $o, $F, $q, $i, $1, $t, $u, $v, $A,
$x, $y, $z}).
-define(RIPPLE_DECODE,
{u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u,
u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u,
50, 33, 7, 21, 41, 40, 27, 45, 8,
u, u, u, u, u, u, u,
54, 10, 38, 12, 14, 47, 15, 16,
u,
17, 18, 19, 20, 13,
u,
22, 23, 24, 25, 26, 11, 28, 29, 30, 31, 32,
u, u, u, u, u, u,
5, 34, 35, 36, 37, 6, 39, 3, 49, 42, 43,
u,
44, 4, 46, 1, 48, 0, 2, 51, 52, 53, 9, 55, 56, 57}).
%% B58/Flickr
-define(FLICKR_ALPHABET,
{$1, $2, $3, $4, $5, $6, $7, $8, $9,
$a, $b, $c, $d, $e, $f, $g, $h, $i,
$j, $k, $m, $n, $o, $p, $q, $r, $s,
$t, $u, $v, $w, $x, $y, $z, $A, $B,
$C, $D, $E, $F, $G, $H, $J, $K, $L,
$M, $N, $P, $Q, $R, $S, $T, $U, $V,
$W, $X, $Y, $Z}).
-define(FLICKR_DECODE,
{u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
0,1,2,3,4,5,6,7,8,
u,u,u,u,u,u,u,
34,35,36,37,38,39,40,41,
u,
42,43,44,45,46,
u,
47,48,49,50,51,52,53,54,55,56,57,
u,u,u,u,u,u,
9,10,11,12,13,14,15,16,17,18,19,
u,
20,21,22,23,24,25,26,27,28,29,30,31,32,33}).
%% Z85
-define(Z85_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$a, $b, $c, $d, $e, $f, $g, $h, $i, $j,
$k, $l, $m, $n, $o, $p, $q, $r, $s, $e,
$u, $v, $w, $x, $y, $z, $A, $B, $C, $D,
$E, $F, $G, $H, $I, $J, $K, $L, $M, $N,
$O, $P, $Q, $R, $S, $T, $U, $V, $W, $X,
$Y, $Z, $\., $-, $:, $+, $=, $^, $\!, $\/,
$*, $?, $&, $<, $>, $(, $), $[, $], ${,
$}, $@, $%, $$, $#}).
-define(Z85_DECODE,
{16#00, 16#44, 16#00, 16#54, 16#53, 16#52, 16#48, 16#00,
16#4B, 16#4C, 16#46, 16#41, 16#00, 16#3F, 16#3E, 16#45,
16#00, 16#01, 16#02, 16#03, 16#04, 16#05, 16#06, 16#07,
16#08, 16#09, 16#40, 16#00, 16#49, 16#42, 16#4A, 16#47,
16#51, 16#24, 16#25, 16#26, 16#27, 16#28, 16#29, 16#2A,
16#2B, 16#2C, 16#2D, 16#2E, 16#2F, 16#30, 16#31, 16#32,
16#33, 16#34, 16#35, 16#36, 16#37, 16#38, 16#39, 16#3A,
16#3B, 16#3C, 16#3D, 16#4D, 16#00, 16#4E, 16#43, 16#00,
16#00, 16#0A, 16#0B, 16#0C, 16#0D, 16#0E, 16#0F, 16#10,
16#11, 16#12, 16#13, 16#14, 16#15, 16#16, 16#17, 16#18,
16#19, 16#1A, 16#1B, 16#1C, 16#1D, 16#1E, 16#1F, 16#20,
16#21, 16#22, 16#23, 16#4F, 16#00, 16#50, 16#00, 16#00}).
-define(Z85_85, [52200625, 614125, 7225, 85, 1]).
-define(Z85_256, [16777216, 65536, 256, 1]).
-define(IPV6_ALPHABET,
{$0, $1, $2, $3, $4, $5, $6, $7, $8, $9,
$A, $B, $C, $D, $E, $F, $G, $H, $I, $J,
$K, $L, $M, $N, $O, $P, $Q, $R, $S, $T,
$U, $V, $W, $X, $Y, $Z, $a, $b, $c, $d,
$e, $f, $g, $h, $i, $j, $k, $l, $m, $n,
$o, $p, $q, $r, $s, $t, $u, $v, $w, $x,
$y, $z, $!, $#, $$, $%, $&, $(, $), $*,
$+, $-, $;, $<, $=, $>, $?, $@, $^, $_,
$`, ${, $|, $}, $~}).
-define(IPV6_DECODE,
{u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,u,
u,u,u,62,u,63,64,65,66,u,67,68,69,70,u,71,u,u,0,1,2,3,4,5,6,
7,8,9,u,72,73,74,75,76,77,10,11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,u,u,u,78,79,80,
36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,
56,57,58,59,60,61,81,82,83,84}).
%% ===================================================================
%% Library functions.
%% ===================================================================
%%--------------------------------------------------------------------
%% Function: encode(Base, Data) -> Encoded
%% @doc
%% Encodes the data as an iolist using Base default algorithm and alphabet,
%% equivalent to encode(Base, Data, []).
%% @end
%%--------------------------------------------------------------------
-spec encode(base(), iodata()) -> iolist().
%%--------------------------------------------------------------------
encode(Base, Data) -> encode(Base, Data, []).
%%--------------------------------------------------------------------
%% Function: encode(Base, Data, Opts) -> Encoded
%% @doc
%% Encodes the data as an iolist or binary according to the Base.
%% Options
%% binary : returns a binary,
%% iolist : returns an iolist (default)
%% {return_type, binary | iolist}: returns what is specified
%% {algo, Algo}: uses the algorithm Algo for encoding
%% {alphabet, A}: uses the alphabet for the encoding, must be
%% compatible with the algorithm (no checks are done).
%% @end
%%--------------------------------------------------------------------
-spec encode(base(), iodata()| bitstring(), [opt()]) -> iolist().
%%--------------------------------------------------------------------
encode(Base, Data = [_ | _], Opts) ->
encode(Base, iolist_to_binary(Data), Opts);
encode(Base, Data, Opts) ->
Opts1 = #opts{return_type = Return} = lists:foldr(fun opt/2, #opts{}, Opts),
Result = do_encode(Base, Data, Opts1),
case Return of
iolist -> Result;
binary -> iolist_to_binary(Result)
end.
%%--------------------------------------------------------------------
%% Function: decode(Base, Encoded) -> Data
%% @doc
%% Decodes the data as an iolist using Base default algorithm and alphabet,
%% equivalent to decode(Base, Encoded, []).
%% @end
%%--------------------------------------------------------------------
-spec decode(base(), iodata()) -> iolist() | bitstring().
%%--------------------------------------------------------------------
decode(Base, Data) -> decode(Base, Data, []).
%%--------------------------------------------------------------------
%% Function: decode(Base, Encoded, Opts) -> Data
%% @doc
%% Decodes the data as an iolist, bitstring or binary according to the Base.
%% Options
%% binary : returns a binary, not viable for Z-Base-32
%% iolist : returns an iolist (default)
%% {return_type, binary | iolist}: returns what is specified
%% {algo, Algo}: uses the algorithm Algo for decoding
%% {alphabet, A}: uses the alphabet for the decoding, must be
%% compatible with the algorithm (no checks are done).
%% @end
%%--------------------------------------------------------------------
-spec decode(base(), iodata(), [opt()]) -> iolist() | bitstring().
%%--------------------------------------------------------------------
decode(Base, Data = [_ | _], Opts) ->
decode(Base, iolist_to_binary(Data), Opts);
decode(Base, Data = <<_/binary>>, Opts) ->
Opts1 = #opts{return_type = Return} = lists:foldr(fun opt/2, #opts{}, Opts),
Result = do_decode(Base, Data, Opts1),
case Return of
iolist -> Result;
binary -> iolist_to_binary(Result)
end.
%% ===================================================================
%% Internal functions.
%% ===================================================================
opt(iolist, Opts) -> Opts#opts{return_type = iolist};
opt(binary, Opts) -> Opts#opts{return_type = binary};
opt({return_type, iolist}, Opts) -> Opts#opts{return_type = iolist};
opt({return_type, binary}, Opts) -> Opts#opts{return_type = binary};
opt({alphabet, standard}, Opts) -> Opts#opts{alphabet = standard};
opt({alphabet, hex}, Opts) -> Opts#opts{alphabet = hex};
opt({alphabet, geohash}, Opts) -> Opts#opts{alphabet = geohash};
opt({alphabet, flickr}, Opts) -> Opts#opts{alphabet = flickr};
opt({alphabet, ripple}, Opts) -> Opts#opts{alphabet = ripple};
opt({algo, standard}, Opts) -> Opts#opts{algo = standard};
opt({algo, crockford}, Opts) -> Opts#opts{algo = crockford};
opt({algo, clockwork}, Opts) -> Opts#opts{algo = clockwork};
opt({algo, zbase}, Opts) -> Opts#opts{algo = zbase};
opt({algo, ipv6}, Opts) -> Opts#opts{algo = ipv6};
opt({algo, z85}, Opts) -> Opts#opts{algo = z85}.
%% --------------------------------------------------------------------
%% Encode
%% --------------------------------------------------------------------
do_encode(32, B, #opts{algo = crockford}) ->
encode_b32(B, ?B32CROCKFORD_ALPHABET, false, false, []);
do_encode(32, B, #opts{algo = clockwork}) ->
encode_b32(B, ?B32CROCKFORD_ALPHABET, false, false, []);
do_encode(32, B, #opts{algo = zbase}) ->
encode_zbase(B, []);
do_encode(32, B, #opts{alphabet = standard}) ->
encode_b32(B, ?B32_ALPHABET, false, true, []);
do_encode(32, B, #opts{alphabet = hex}) ->
encode_b32(B, ?B32HEX_ALPHABET, false, true, []);
do_encode(32, B, #opts{alphabet = geohash}) ->
encode_b32(B, ?B32GEO_ALPHABET, false, true, []);
do_encode(45, B, _) ->
encode_b45(B, []);
do_encode(58, B, #opts{alphabet = flickr}) ->
encode_b58(B, ?FLICKR_ALPHABET);
do_encode(58, B, #opts{alphabet = ripple}) ->
encode_b58(B, ?RIPPLE_ALPHABET);
do_encode(58, B, _) ->
encode_b58(B, ?B58_ALPHABET);
do_encode(85, B, #opts{algo = z85}) when (byte_size(B) rem 4) == 0 ->
encode_z85(B, []);
do_encode(85, B, #opts{algo = ipv6}) ->
encode_ipv6(B);
do_encode(85, B, _) ->
Pad = (4 - (byte_size(B) rem 4)) rem 4,
encode_b85(<<B/binary, 0:(Pad * 8)>>, Pad, []).
%% --------------------------------------------------------------------
%% b32
encode_b32(<<>>, _, _, _, Acc) -> lists:reverse(Acc);
encode_b32(<<A:5, B:3>>, Alphabet, S, P, Acc) ->
<<B1:5>> = <<B:3, 0:2>>,
encode_b32(<<>>, Alphabet, S, P, [e_b32(Alphabet, [A, B1], P, 6) | Acc]);
encode_b32(<<A:5, B:5, C:5, D:1>>, Alphabet, S, P, Acc) ->
<<D1:5>> = <<D:1, 0:4>>,
encode_b32(<<>>, Alphabet, S, P, [e_b32(Alphabet, [A, B, C, D1], P,4)|Acc]);
encode_b32(<<A:5, B:5, C:5, D:5, E:4>>, Alphabet, S, P, Acc) ->
<<E1:5>> = <<E:4, 0:1>>,
encode_b32(<<>>, Alphabet, S, P, [e_b32(Alphabet, [A, B,C, D,E1],P,3)|Acc]);
encode_b32(<<A:5, B:5, C:5, D:5, E:5, F:5, G:2>>, Alphabet, S, P, Acc) ->
<<G1:5>> = <<G:2, 0:3>>,
Elt = e_b32(Alphabet, [A, B, C, D, E, F, G1], P, 1),
encode_b32(<<>>, Alphabet, S, P, [Elt | Acc]);
encode_b32(<<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5,T/binary>>,Alpha,S,P,Acc) ->
Elt = e_b32(Alpha, [A, B, C, D, E, F, G, H], P, 0),
encode_b32(T, Alpha, S, P, [Elt | Acc]).
e_b32(Alphabet, Parts, false, _) -> [element(X + 1, Alphabet) || X <- Parts];
e_b32(Alphabet, Parts, true, Pad) ->
[[element(X + 1, Alphabet) || X <- Parts], lists:duplicate(Pad, $=)].
%% --------------------------------------------------------------------
%% zbase
encode_zbase(<<>>, Acc) -> lists:reverse(Acc);
encode_zbase(<<A:1>>, Acc) ->
<<A1:5>> = <<A:1, 0:4>>,
encode_zbase(<<>>, [element(A1 + 1, ?B32Z_ALPHABET) | Acc]);
encode_zbase(<<A:2>>, Acc) ->
<<A1:5>> = <<A:2, 0:3>>,
encode_zbase(<<>>, [element(A1 + 1, ?B32Z_ALPHABET) | Acc]);
encode_zbase(<<A:3>>, Acc) ->
<<A1:5>> = <<A:3, 0:2>>,
encode_zbase(<<>>, [element(A1 + 1, ?B32Z_ALPHABET) | Acc]);
encode_zbase(<<A:4>>, Acc) ->
<<A1:5>> = <<A:4, 0:1>>,
encode_zbase(<<>>, [element(A1 + 1, ?B32Z_ALPHABET) | Acc]);
encode_zbase(<<A:5, T/bits>>, Acc) ->
encode_zbase(T, [element(A + 1, ?B32Z_ALPHABET) | Acc]).
%% --------------------------------------------------------------------
%% b45
encode_b45(<<>>, Acc) -> lists:reverse(Acc);
encode_b45(<<A>>, Acc) ->
C = e_b45(A rem 45 + 1),
D = e_b45((A div 45) rem 45 + 1),
encode_b45(<<>>, [[C, D] | Acc]);
encode_b45(<<A:16, T/binary>>, Acc) ->
C = e_b45(A rem 45 + 1),
D = e_b45((A div 45) rem 45 + 1),
E = e_b45(A div 45 div 45 + 1),
encode_b45(T, [[C, D, E] | Acc]).
e_b45(X) -> element(X, ?B45_ALPHABET).
%% --------------------------------------------------------------------
%% b58
encode_b58(B, Alphabet) ->
e_b58_pad(B, e_b58_c(binary:decode_unsigned(B), Alphabet, [])).
e_b58_c(0, _, Acc) -> Acc;
e_b58_c(I, Alphabet, Acc) ->
e_b58_c(I div 58, Alphabet, [element(I rem 58 + 1, Alphabet) | Acc]).
e_b58_pad(<<0, T/binary>>, Acc) -> e_b58_pad(T, [$1 | Acc]);
e_b58_pad(_, Acc) -> Acc.
%% --------------------------------------------------------------------
%% b85
encode_b85(<<>>, 0, Acc) -> lists:reverse(Acc);
encode_b85(<<>>, 1, [[A, B, C, D | _] | Acc])->lists:reverse([[A, B, C,D]|Acc]);
encode_b85(<<>>, 2, [[A, B, C | _] | Acc]) -> lists:reverse([[A, B, C] | Acc]);
encode_b85(<<>>, 3, [[A, B | _] | Acc]) -> lists:reverse([[A, B] | Acc]);
encode_b85(<<0:32, T/binary>>, Pad, Acc) ->
encode_b85(T, Pad, [$z | Acc]);
encode_b85(<<A, B, C, D, T/binary>>, Pad, Acc) ->
V = (((((A * 256) + B) * 256) + C) * 256) + D,
Es = [(V div Div) rem 85 + 33 || Div <- ?Z85_85],
encode_b85(T, Pad, [Es | Acc]).
%% --------------------------------------------------------------------
%% Z85
encode_z85(<<>>, Acc) -> lists:reverse(Acc);
encode_z85(<<A, B, C, D, T/binary>>, Acc) ->
V = (((((A * 256) + B) * 256) + C) * 256) + D,
Es = [element((V div Div) rem 85 + 1, ?Z85_ALPHABET) || Div <- ?Z85_85],
encode_z85(T, [Es | Acc]).
%% --------------------------------------------------------------------
%% IPV6
encode_ipv6(IPv6) -> encode_ipv6(jhn_ip_addr:decode(IPv6), []).
encode_ipv6(0, Acc) -> Acc;
encode_ipv6(I, Acc) ->
encode_ipv6(I div 85, [element(I rem 85 + 1, ?IPV6_ALPHABET) | Acc]).
%% --------------------------------------------------------------------
%% Decode
%% --------------------------------------------------------------------
do_decode(32, B, #opts{algo = crockford}) ->
decode_b32(crockford_filtermap(B, <<>>), ?B32CROCKFORD_DECODE, []);
do_decode(32, B, #opts{algo = clockwork}) ->
decode_b32(clockwork_filtermap(B, <<>>), ?B32CROCKFORD_DECODE, []);
do_decode(32, B, #opts{algo = zbase}) ->
decode_zbase(B, <<>>);
do_decode(32, B, #opts{alphabet = standard}) ->
decode_b32(B, ?B32_DECODE, []);
do_decode(32, B, #opts{alphabet = hex}) ->
decode_b32(B, ?B32HEX_DECODE, []);
do_decode(32, B, #opts{alphabet = geohash}) ->
decode_b32(B, ?B32GEO_DECODE, []);
do_decode(45, B45, _) ->
decode_b45(B45, []);
do_decode(58, B, #opts{alphabet = flickr}) ->
decode_b58(B, ?FLICKR_DECODE);
do_decode(58, B, #opts{alphabet = ripple}) ->
decode_b58(B, ?RIPPLE_DECODE);
do_decode(58, B, _) ->
decode_b58(B, ?B58_DECODE);
do_decode(85, Z85, #opts{algo = z85}) when (byte_size(Z85) rem 5) == 0 ->
decode_z85(Z85, []);
do_decode(85, Ipv6, #opts{algo = ipv6}) ->
decode_ipv6(Ipv6);
do_decode(85, B85, _) ->
decode_b85(B85, []).
%% --------------------------------------------------------------------
%% b32
decode_b32(<<>>, _, Acc) -> lists:reverse(Acc);
%% With padding
decode_b32(<<A, B, $=, $=, $=,$=, $=, $=>>, Alphabet, Acc) ->
<<B1:3, 0:2>> = <<(element(B, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A], <<B1:3>>) | Acc]);
decode_b32(<<A, B, C, D, $=, $=, $=, $=>>, Alphabet, Acc) ->
<<D1:1, 0:4>> = <<(element(D, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A, B, C], <<D1:1>>) | Acc]);
decode_b32(<<A, B, C, D, E, $=, $=, $=>>, Alphabet, Acc) ->
<<E1:4, 0:1>> = <<(element(E, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A, B, C, D], <<E1:4>>) | Acc]);
decode_b32(<<A, B, C, D, E, F, G, $=>>, Alphabet, Acc) ->
<<G1:2, 0:3>> = <<(element(G, Alphabet)):5>>,
Elt = d_b32(Alphabet, [A, B, C, D, E, F], <<G1:2>>),
decode_b32(<<>>, Alphabet, [Elt | Acc]);
%% Without padding
decode_b32(<<A, B>>, Alphabet, Acc) ->
<<B1:3, 0:2>> = <<(element(B, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A], <<B1:3>>) | Acc]);
decode_b32(<<A, B, C, D>>, Alphabet, Acc) ->
<<D1:1, 0:4>> = <<(element(D, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A, B, C], <<D1:1>>) | Acc]);
decode_b32(<<A, B, C, D, E>>, Alphabet, Acc) ->
<<E1:4, 0:1>> = <<(element(E, Alphabet)):5>>,
decode_b32(<<>>, Alphabet, [d_b32(Alphabet, [A, B, C, D], <<E1:4>>) | Acc]);
decode_b32(<<A, B, C, D, E, F, G>>, Alphabet, Acc) ->
<<G1:2, 0:3>> = <<(element(G, Alphabet)):5>>,
Elt = d_b32(Alphabet, [A, B, C, D, E, F], <<G1:2>>),
decode_b32(<<>>, Alphabet, [Elt | Acc]);
%% Recursive
decode_b32(<<A, B, C, D, E, F, G, H, T/binary>>, Alphabet, Acc) ->
Elt = d_b32(Alphabet, [A, B, C, D, E, F, G, H], <<>>),
decode_b32(T, Alphabet, [Elt | Acc]).
d_b32(Alphabet, Parts, End) ->
<< (<< <<(element(X, Alphabet)):5>> || X <- Parts>>)/bits, End/bits>>.
%% --------------------------------------------------------------------
%% crockford
crockford_filtermap(<<>>, Acc) -> Acc;
%% Filter
crockford_filtermap(<<$-, T/binary>>, Acc) -> crockford_filtermap(T, Acc);
%% Map
%% -> 0
crockford_filtermap(<<$0, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $0>>);
crockford_filtermap(<<$O, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $0>>);
crockford_filtermap(<<$o, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $0>>);
%% -> 1
crockford_filtermap(<<$1, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $1>>);
crockford_filtermap(<<$I, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $1>>);
crockford_filtermap(<<$L, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $1>>);
crockford_filtermap(<<$i, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $1>>);
crockford_filtermap(<<$l, T/binary>>, Acc) ->
crockford_filtermap(T, <<Acc/binary, $1>>);
%% -> Uppercase
crockford_filtermap(<<H, T/binary>>, Acc)
when H >= 50, H =< 57; H >= 65, H =< 90 ->
crockford_filtermap(T, <<Acc/binary, H>>);
crockford_filtermap(<<H, T/binary>>, Acc) when H >= 97, H =< 122 ->
crockford_filtermap(T, <<Acc/binary, (H - 32)>>).
%% --------------------------------------------------------------------
%% clockwork
clockwork_filtermap(<<>>, Acc) -> Acc;
clockwork_filtermap(<<$0>>, Acc) -> Acc;
%% Map
%% -> 0
clockwork_filtermap(<<$0, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $0>>);
clockwork_filtermap(<<$O, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $0>>);
clockwork_filtermap(<<$o, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $0>>);
%% -> 1
clockwork_filtermap(<<$1, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $1>>);
clockwork_filtermap(<<$I, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $1>>);
clockwork_filtermap(<<$L, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $1>>);
clockwork_filtermap(<<$i, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $1>>);
clockwork_filtermap(<<$l, T/binary>>, Acc) ->
clockwork_filtermap(T, <<Acc/binary, $1>>);
%% -> Uppercase
clockwork_filtermap(<<H, T/binary>>, Acc)
when H >= 50, H =< 57; H >= 65, H =< 90 ->
clockwork_filtermap(T, <<Acc/binary, H>>);
clockwork_filtermap(<<H, T/binary>>, Acc) when H >= 97, H =< 122 ->
clockwork_filtermap(T, <<Acc/binary, (H - 32)>>).
%% --------------------------------------------------------------------
%% zbase
decode_zbase(<<>>, Acc) -> Acc;
decode_zbase(<<A, T/bits>>, Acc) ->
<<_:3, A1:5>> = <<(element(A, ?B32Z_DECODE)):8>>,
decode_zbase(T, <<Acc/bits, A1:5>>).
%% --------------------------------------------------------------------
%% B45
decode_b45(<<>>, Acc) -> lists:reverse(Acc);
decode_b45(<<C, D>>, Acc) ->
decode_b45(<<>>, [d_b45(C) + 45 * d_b45(D) | Acc]);
decode_b45(<<C, D, E, T/binary>>, Acc) ->
N = d_b45(C) + 45 * (d_b45(D) + 45 * d_b45(E)),
<<A, B>> = <<N:16>>,
decode_b45(T, [[A, B] | Acc]).
d_b45(C) -> element(C - 31, ?B45_DECODE).
%% --------------------------------------------------------------------
%% B58
decode_b58(B, Alphabet) ->
case d_b58(B, Alphabet) of
0 -> d_b58_pad(B, <<>>);
N -> d_b58_pad(B, binary:encode_unsigned(N))
end.
d_b58(<<C>>, Alphabet) -> element(C, Alphabet);
d_b58(<<C, T/binary>>, Alphabet) -> d_b58(element(C, Alphabet), T, Alphabet).
d_b58(C, <<>>, _) -> C;
d_b58(C, <<H, T/binary>>, Alphabet) ->
d_b58(C * 58 + element(H, Alphabet), T, Alphabet).
d_b58_pad(<<$1, T/binary>>, Acc) -> d_b58_pad(T, [0 | Acc]);
d_b58_pad(_, Acc) -> Acc.
%% --------------------------------------------------------------------
%% B85
decode_b85(<<>>, Acc) -> lists:reverse(Acc);
%% White space
decode_b85(<<$\t, T/binary>>, Acc) -> decode_b85(T, Acc);
decode_b85(<<$\n, T/binary>>, Acc) -> decode_b85(T, Acc);
decode_b85(<<$\v, T/binary>>, Acc) -> decode_b85(T, Acc);
decode_b85(<<$\f, T/binary>>, Acc) -> decode_b85(T, Acc);
decode_b85(<<$\r, T/binary>>, Acc) -> decode_b85(T, Acc);
decode_b85(<<$\s, T/binary>>, Acc) -> decode_b85(T, Acc);
%% Special all zero code
decode_b85(<<$z, T/binary>>, Acc) -> decode_b85(T, [<<0:32>> | Acc]);
%% Padding
decode_b85(<<A, B, C, D, E, T/binary>>, Acc) ->
V = lists:foldl(fun d_b85/2, 0, [A, B, C, D, E]),
decode_b85(T, [[(V div Div) rem 256 || Div <- ?Z85_256] | Acc]);
decode_b85(<<A, B, C, D>>, Acc) ->
V = lists:foldl(fun d_b85/2, 0, [A, B, C, D, $u]),
[A1, B1, C1 | _] = [(V div Div) rem 256 || Div <- ?Z85_256],
lists:reverse([[A1, B1, C1] | Acc]);
decode_b85(<<A, B, C>>, Acc) ->
V = lists:foldl(fun d_b85/2, 0, [A, B, C, $u, $u]),
[A1, B1 | _] = [(V div Div) rem 256 || Div <- ?Z85_256],
lists:reverse([[A1, B1] | Acc]);
decode_b85(<<A, B>>, Acc) ->
V = lists:foldl(fun d_b85/2, 0, [A, B, $u, $u, $u]),
[A1 | _] = [(V div Div) rem 256 || Div <- ?Z85_256],
lists:reverse([[A1] | Acc]).
d_b85(V, Pre) -> (Pre * 85) + V - 33.
%% --------------------------------------------------------------------
%% Z85
decode_z85(<<>>, Acc) -> lists:reverse(Acc);
decode_z85(<<A, B, C, D, E, T/binary>>, Acc) ->
V = lists:foldl(fun d_z85/2, 0, [A, B, C, D, E]),
decode_z85(T, [[(V div Div) rem 256 || Div <- ?Z85_256] | Acc]).
d_z85(V, Pre) -> (Pre * 85) + element(V - 31, ?Z85_DECODE).
%% --------------------------------------------------------------------
%% Ipv6
decode_ipv6(<<C, T/binary>>) ->
jhn_ip_addr:encode(decode_ipv6(T, element(C, ?IPV6_DECODE))).
decode_ipv6(<<>>, C) -> C;
decode_ipv6(<<H, T/binary>>, C) ->
decode_ipv6(T, C * 85 + element(H, ?IPV6_DECODE)).
%% --------------------------------------------------------------------