blob: d8d754f5e11874ebbac0f55567d0e051227adcca [file] [log] [blame]
% 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.
-module(couch_base32).
-export([encode/1, decode/1]).
-define(SET, <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567">>).
-spec encode(binary()) -> binary().
encode(Plain) when is_binary(Plain) ->
IoList = encode(Plain, 0, byte_size(Plain) * 8, []),
iolist_to_binary(lists:reverse(IoList)).
encode(_Plain, _ByteOffset, 0, Acc) ->
Acc;
encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 8 ->
<<A:5, B:3>> = binary:part(Plain, ByteOffset, 1),
[<<(binary:at(?SET, A)),
(binary:at(?SET, B bsl 2)),
"======">> | Acc];
encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 16 ->
<<A:5, B:5, C:5, D:1>> = binary:part(Plain, ByteOffset, 2),
[<<(binary:at(?SET, A)),
(binary:at(?SET, B)),
(binary:at(?SET, C)),
(binary:at(?SET, D bsl 4)),
"====">> | Acc];
encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 24 ->
<<A:5, B:5, C:5, D:5, E:4>> = binary:part(Plain, ByteOffset, 3),
[<<(binary:at(?SET, A)),
(binary:at(?SET, B)),
(binary:at(?SET, C)),
(binary:at(?SET, D)),
(binary:at(?SET, E bsl 1)),
"===">> | Acc];
encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 32 ->
<<A:5, B:5, C:5, D:5, E:5, F:5, G:2>> = binary:part(Plain, ByteOffset, 4),
[<<(binary:at(?SET, A)),
(binary:at(?SET, B)),
(binary:at(?SET, C)),
(binary:at(?SET, D)),
(binary:at(?SET, E)),
(binary:at(?SET, F)),
(binary:at(?SET, G bsl 3)),
"=">> | Acc];
encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining >= 40 ->
<<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5>> =
binary:part(Plain, ByteOffset, 5),
Output = <<(binary:at(?SET, A)),
(binary:at(?SET, B)),
(binary:at(?SET, C)),
(binary:at(?SET, D)),
(binary:at(?SET, E)),
(binary:at(?SET, F)),
(binary:at(?SET, G)),
(binary:at(?SET, H))>>,
encode(Plain, ByteOffset + 5, BitsRemaining - 40, [Output | Acc]).
-spec decode(binary()) -> binary().
decode(Encoded) when is_binary(Encoded) ->
IoList = decode(Encoded, 0, []),
iolist_to_binary(lists:reverse(IoList)).
decode(Encoded, ByteOffset, Acc) when ByteOffset == byte_size(Encoded) ->
Acc;
decode(Encoded, ByteOffset, Acc) ->
case binary:part(Encoded, ByteOffset, 8) of
<<A:1/binary, B:1/binary, "======">> ->
[<<(find_in_set(A)):5,
(find_in_set(B) bsr 2):3>> | Acc];
<<A:1/binary, B:1/binary, C:1/binary, D:1/binary, "====">> ->
[<<(find_in_set(A)):5,
(find_in_set(B)):5,
(find_in_set(C)):5,
(find_in_set(D) bsr 4):1>> | Acc];
<<A:1/binary, B:1/binary, C:1/binary, D:1/binary, E:1/binary, "===">> ->
[<<(find_in_set(A)):5,
(find_in_set(B)):5,
(find_in_set(C)):5,
(find_in_set(D)):5,
(find_in_set(E) bsr 1):4>> | Acc];
<<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
E:1/binary, F:1/binary, G:1/binary, "=">> ->
[<<(find_in_set(A)):5,
(find_in_set(B)):5,
(find_in_set(C)):5,
(find_in_set(D)):5,
(find_in_set(E)):5,
(find_in_set(F)):5,
(find_in_set(G) bsr 3):2>> | Acc];
<<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
E:1/binary, F:1/binary, G:1/binary, H:1/binary>> ->
decode(Encoded, ByteOffset + 8,
[<<(find_in_set(A)):5,
(find_in_set(B)):5,
(find_in_set(C)):5,
(find_in_set(D)):5,
(find_in_set(E)):5,
(find_in_set(F)):5,
(find_in_set(G)):5,
(find_in_set(H)):5>> | Acc])
end.
find_in_set(Char) ->
case binary:match(?SET, Char) of
nomatch ->
erlang:error(not_base32);
{Offset, _} ->
Offset
end.