Packages

macula

6.0.0
7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src codec macula_z32.erl
Raw

src/codec/macula_z32.erl

%%% @doc z-base-32 codec (Phil Zimmermann's "Human-Oriented Base-32
%%% Encoding"). Alphabet: `ybndrfg8ejkmcpqxot1uwisza345h769'.
%%%
%%% Used by Macula to encode 32-byte Ed25519 pubkeys as DNS-label-
%%% friendly strings: 32 bytes → 52 ASCII characters, comfortably
%%% within DNS's 63-char per-label cap. PKARR (the
%%% public-key-addressable resource record convention) and projects
%%% like Pubky use this same encoding for the same reason.
%%%
%%% Encoding direction: bit stream is taken MSB-first from the
%%% input bytes, grouped into 5-bit chunks (the final chunk
%%% zero-padded on the right if the input length is not a multiple
%%% of 5 bits). Each chunk indexes into the alphabet.
%%%
%%% Decoding is the inverse: characters → 5-bit values → bit
%%% stream → 8-bit byte stream, dropping any trailing bits left
%%% over from the encoder's right-padding.
%%%
%%% Length convention (the cases that matter for Macula):
%%% - 32 bytes (Ed25519 pubkey) → 52 chars
%%% - 16 bytes (UUID-like) → 26 chars
%%% - empty → empty
%%%
%%% Reference: <a href="https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt">Phil Zimmermann's z-base-32 spec</a>.
%%% @end
-module(macula_z32).
-export([encode/1, decode/1, is_valid_label/1]).
%% z-base-32 alphabet, indexed 0..31.
-define(ALPHABET, <<"ybndrfg8ejkmcpqxot1uwisza345h769">>).
%%====================================================================
%% Public API
%%====================================================================
%% @doc Encode a binary into its z-base-32 representation.
-spec encode(binary()) -> binary().
encode(<<>>) ->
<<>>;
encode(Bytes) when is_binary(Bytes) ->
BitCount = byte_size(Bytes) * 8,
PaddedLen = ((BitCount + 4) div 5) * 5,
PadBits = PaddedLen - BitCount,
Padded = <<Bytes/bitstring, 0:PadBits>>,
encode_chunks(Padded, []).
encode_chunks(<<>>, Acc) ->
list_to_binary(lists:reverse(Acc));
encode_chunks(<<Chunk:5, Rest/bitstring>>, Acc) ->
Char = binary:at(?ALPHABET, Chunk),
encode_chunks(Rest, [Char | Acc]).
%% @doc Decode a z-base-32 string back into its original bytes.
%% Returns `{error, invalid_z32}' on any character outside the
%% alphabet, or when the input length is not a valid encoding of
%% a whole-byte payload (i.e., the bit length isn't a multiple of 8
%% within the rounding-up tolerance).
-spec decode(binary()) -> {ok, binary()} | {error, invalid_z32}.
decode(<<>>) ->
{ok, <<>>};
decode(Z32) when is_binary(Z32) ->
decode_chars(Z32, <<>>).
decode_chars(<<>>, BitAcc) ->
%% BitAcc may be N bits long where N is a multiple of 5 but
%% may not be a multiple of 8. We accept up to 4 trailing
%% padding bits (those produced by encode/1 when the original
%% input wasn't a multiple of 5 bits long); they MUST be zero.
BitLen = bit_size(BitAcc),
ByteLen = BitLen div 8,
PadBits = BitLen rem 8,
decode_padded(PadBits =< 4, BitAcc, ByteLen, PadBits);
decode_chars(<<Char, Rest/binary>>, BitAcc) ->
decode_char(char_to_value(Char), Rest, BitAcc).
decode_padded(false, _BitAcc, _ByteLen, _PadBits) ->
{error, invalid_z32};
decode_padded(true, BitAcc, ByteLen, PadBits) ->
<<Bytes:ByteLen/binary, Pad:PadBits>> = BitAcc,
decode_pad_value(Pad, Bytes).
decode_pad_value(0, Bytes) -> {ok, Bytes};
decode_pad_value(_, _Bytes) -> {error, invalid_z32}.
decode_char({ok, Value}, Rest, BitAcc) ->
decode_chars(Rest, <<BitAcc/bitstring, Value:5>>);
decode_char(error, _Rest, _BitAcc) ->
{error, invalid_z32}.
%% @doc Check whether a binary is a syntactically valid DNS label
%% under the z-base-32 alphabet (every character is in the
%% alphabet, length is between 1 and 63 octets, and it decodes
%% cleanly to a whole-byte payload).
-spec is_valid_label(binary()) -> boolean().
is_valid_label(Bin) when is_binary(Bin) ->
Size = byte_size(Bin),
Size >= 1 andalso Size =< 63 andalso
case decode(Bin) of
{ok, _} -> true;
{error, _} -> false
end;
is_valid_label(_) ->
false.
%%====================================================================
%% Alphabet lookup. Inline guard clauses for speed; the alphabet is
%% small enough that the compiler's case-tree is faster than a map.
%%====================================================================
char_to_value($y) -> {ok, 0};
char_to_value($b) -> {ok, 1};
char_to_value($n) -> {ok, 2};
char_to_value($d) -> {ok, 3};
char_to_value($r) -> {ok, 4};
char_to_value($f) -> {ok, 5};
char_to_value($g) -> {ok, 6};
char_to_value($8) -> {ok, 7};
char_to_value($e) -> {ok, 8};
char_to_value($j) -> {ok, 9};
char_to_value($k) -> {ok, 10};
char_to_value($m) -> {ok, 11};
char_to_value($c) -> {ok, 12};
char_to_value($p) -> {ok, 13};
char_to_value($q) -> {ok, 14};
char_to_value($x) -> {ok, 15};
char_to_value($o) -> {ok, 16};
char_to_value($t) -> {ok, 17};
char_to_value($1) -> {ok, 18};
char_to_value($u) -> {ok, 19};
char_to_value($w) -> {ok, 20};
char_to_value($i) -> {ok, 21};
char_to_value($s) -> {ok, 22};
char_to_value($z) -> {ok, 23};
char_to_value($a) -> {ok, 24};
char_to_value($3) -> {ok, 25};
char_to_value($4) -> {ok, 26};
char_to_value($5) -> {ok, 27};
char_to_value($h) -> {ok, 28};
char_to_value($7) -> {ok, 29};
char_to_value($6) -> {ok, 30};
char_to_value($9) -> {ok, 31};
char_to_value(_) -> error.