diff --git a/README.md b/README.md index ca57dce..7eccad4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # nuid -![nuid](https://github.com/nomasystems/nuid/actions/workflows/build.yml/badge.svg) +[![nuid](https://github.com/nomasystems/nuid/actions/workflows/ci.yml/badge.svg)](https://github.com/nomasystems/nuid/actions/workflows/ci.yml) `nuid` is an OTP library to generate unique identifiers. diff --git a/rebar.config b/rebar.config index 0d81dc4..2d23bce 100644 --- a/rebar.config +++ b/rebar.config @@ -49,6 +49,7 @@ ]}. {cover_opts, [verbose]}. +{cover_excl_mods, [nuid_base64]}. {cover_enabled, true}. {xref_ignores, [nuid]}. diff --git a/src/nuid_base64.erl b/src/nuid_base64.erl index 97f1911..8b60d96 100644 --- a/src/nuid_base64.erl +++ b/src/nuid_base64.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2021. All Rights Reserved. +%% Copyright Ericsson AB 2007-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -21,68 +21,109 @@ -module(nuid_base64). --export([encode/1, decode/1]). +-export([ + encode/1, + decode/1 +]). -%% RFC 4648ยง5: base64url - Base 64 Encoding alphabet -%-type base64_alphabet() :: $0..$9 | $- | $A..$Z | $_ | $a..$z. -%% Alphabet reordered to be lexicographically ordered. +%% RFC 4648: Base 64 Encoding alphabet +%% -type base64_alphabet() :: $A..$Z | $a..$z | $0..$9 | $- | $_. +%% Section 4, `urlsafe' for RFC 4648 Section 5. +%% It's been reordered to be lexicographically sortable. %% The following type is a subtype of string() for return values %% of encoding functions. -type base64_binary() :: binary(). +%% Decoded sequence of octets +-type byte_string() :: [byte()]. + -spec encode(Data) -> Base64 when - Data :: binary(), + Data :: byte_string() | binary(), Base64 :: base64_binary(). -encode(Bin) -> +encode(Bin) when is_binary(Bin) -> encode_binary(Bin, <<>>). +encode_binary(<>, A) -> + encode_binary( + Ls, + <> + ); encode_binary(<<>>, A) -> A; -encode_binary(<>, A) -> - <>; -encode_binary(<>, A) -> - <>; -encode_binary(<>, A) -> - BB = (B1 bsl 16) bor (B2 bsl 8) bor B3, +encode_binary(<>, A) -> encode_binary( Ls, - <> - ). + <> + ); +encode_binary(<>, A) -> + E1 = b64e(B1), + E2 = b64e(B2 bsl 4), + <>; +encode_binary(<>, A) -> + E1 = b64e(B1), + E2 = b64e(B2), + E3 = b64e(B3 bsl 2), + <>. %% mime_decode strips away all characters not Base64 before %% converting, whereas decode crashes if an illegal character is found + -spec decode(Base64) -> Data when Base64 :: base64_binary(), Data :: binary(). -decode(Bin) -> - decode_binary(Bin, <<>>). - -decode_binary(<>, A) -> - decode_binary(Cs, A, b64d(C1)); +decode(Base64) when is_binary(Base64) -> + decode_binary(Base64, <<>>). + +decode_binary(<>, A) -> + case {b64d(C1), b64d(C2), b64d(C3), b64d(C4)} of + {B1, B2, B3, B4} when + is_integer(B1), + is_integer(B2), + is_integer(B3), + is_integer(B4) + -> + decode_binary(Cs, <>); + {B1, B2, B3, B4} -> + dec_bin(Cs, B1, B2, B3, B4, A) + end; decode_binary(<<>>, A) -> - A. + A; +decode_binary(<>, A) -> + B1 = b64d(C1), + decode_binary(Cs, A, B1). + +dec_bin(Cs, B1, B2, B3, B4, A) -> + decode_binary(Cs, <>). decode_binary(<>, A, B1) -> - decode_binary(Cs, A, B1, b64d(C2)). + B2 = b64d(C2), + decode_binary(Cs, A, B1, B2). decode_binary(<>, A, B1, B2) -> - decode_binary(Cs, A, B1, B2, b64d(C3)); -decode_binary(<<>>, A, B1, B2) -> - <>. + B3 = b64d(C3), + decode_binary(Cs, A, B1, B2, B3); +decode_binary(<<_Cs/bits>>, _A, _B1, _B2) -> + missing_padding_error(). decode_binary(<>, A, B1, B2, B3) -> B4 = b64d(C4), decode_binary(Cs, <>); -decode_binary(<<>>, A, B1, B2, B3) -> - <>. +decode_binary(<<>>, _A, _B1, _B2, _B3) -> + missing_padding_error(). %%%======================================================================== -%%% Internal functions +%%% Error handling functions +%%%======================================================================== + +% always inlined for useful stacktraces when called in tail position +-compile({inline, missing_padding_error/0}). +missing_padding_error() -> + error(missing_padding, none, [{error_info, #{}}]). + %%%======================================================================== %% accessors @@ -178,36 +219,3 @@ b64e(X) -> $z } ). - -%%----------------------------------------------------------------------- -%% Code to generate decode table -%%----------------------------------------------------------------------- -%% code({value, {Pos, _Value}}) -> -%% Pos; -%% code(_) -> -%% bad. -%% -%% alphabet_pos([], _Pos, Acc) -> -%% lists:reverse(Acc); -%% alphabet_pos([Char | Rest], Pos, Acc) -> -%% alphabet_pos(Rest, Pos + 1, [{Pos, Char} | Acc]). -%% -%% decode_tuple(AlphabetPos) -> -%% Seq = lists:seq(1, 256), -%% decode_tuple(AlphabetPos, Seq, []). -%% -%% -%% decode_tuple(_AlphabetPos, [], Acc) -> -%% list_to_tuple(lists:reverse(Acc)); -%% decode_tuple(AlphabetPos, [Char | Rest], Acc) -> -%% Value = code(lists:keysearch(Char, 2, AlphabetPos)), -%% decode_tuple(AlphabetPos, Rest, [Value | Acc]). -%% -%% decode_table() -> -%% 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], -%% AlphabetPos = alphabet_pos(Alphabet, 0, []), -%% decode_tuple(AlphabetPos).