1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(couch_base32).
14
15-export([encode/1, decode/1]).
16
17-define(SET, <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567">>).
18
19
20-spec encode(binary()) -> binary().
21encode(Plain) when is_binary(Plain) ->
22    IoList = encode(Plain, 0, byte_size(Plain) * 8, []),
23    iolist_to_binary(lists:reverse(IoList)).
24
25encode(_Plain, _ByteOffset, 0, Acc) ->
26    Acc;
27
28encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 8 ->
29    <<A:5, B:3>> = binary:part(Plain, ByteOffset, 1),
30    [<<(binary:at(?SET, A)),
31       (binary:at(?SET, B bsl 2)),
32       "======">> | Acc];
33
34encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 16 ->
35    <<A:5, B:5, C:5, D:1>> = binary:part(Plain, ByteOffset, 2),
36    [<<(binary:at(?SET, A)),
37       (binary:at(?SET, B)),
38       (binary:at(?SET, C)),
39       (binary:at(?SET, D bsl 4)),
40       "====">> | Acc];
41
42encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 24 ->
43    <<A:5, B:5, C:5, D:5, E:4>> = binary:part(Plain, ByteOffset, 3),
44    [<<(binary:at(?SET, A)),
45       (binary:at(?SET, B)),
46       (binary:at(?SET, C)),
47       (binary:at(?SET, D)),
48       (binary:at(?SET, E bsl 1)),
49       "===">> | Acc];
50
51encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining == 32 ->
52    <<A:5, B:5, C:5, D:5, E:5, F:5, G:2>> = binary:part(Plain, ByteOffset, 4),
53    [<<(binary:at(?SET, A)),
54       (binary:at(?SET, B)),
55       (binary:at(?SET, C)),
56       (binary:at(?SET, D)),
57       (binary:at(?SET, E)),
58       (binary:at(?SET, F)),
59       (binary:at(?SET, G bsl 3)),
60       "=">> | Acc];
61
62encode(Plain, ByteOffset, BitsRemaining, Acc) when BitsRemaining >= 40 ->
63    <<A:5, B:5, C:5, D:5, E:5, F:5, G:5, H:5>> =
64        binary:part(Plain, ByteOffset, 5),
65    Output = <<(binary:at(?SET, A)),
66               (binary:at(?SET, B)),
67               (binary:at(?SET, C)),
68               (binary:at(?SET, D)),
69               (binary:at(?SET, E)),
70               (binary:at(?SET, F)),
71               (binary:at(?SET, G)),
72               (binary:at(?SET, H))>>,
73    encode(Plain, ByteOffset + 5, BitsRemaining  - 40, [Output | Acc]).
74
75
76-spec decode(binary()) -> binary().
77decode(Encoded) when is_binary(Encoded) ->
78    IoList = decode(Encoded, 0, []),
79    iolist_to_binary(lists:reverse(IoList)).
80
81decode(Encoded, ByteOffset, Acc) when ByteOffset == byte_size(Encoded) ->
82    Acc;
83decode(Encoded, ByteOffset, Acc) ->
84    case binary:part(Encoded, ByteOffset, 8) of
85        <<A:1/binary, B:1/binary, "======">> ->
86            [<<(find_in_set(A)):5,
87               (find_in_set(B) bsr 2):3>> | Acc];
88        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, "====">> ->
89            [<<(find_in_set(A)):5,
90               (find_in_set(B)):5,
91               (find_in_set(C)):5,
92               (find_in_set(D) bsr 4):1>> | Acc];
93        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary, E:1/binary, "===">> ->
94            [<<(find_in_set(A)):5,
95               (find_in_set(B)):5,
96               (find_in_set(C)):5,
97               (find_in_set(D)):5,
98               (find_in_set(E) bsr 1):4>> | Acc];
99        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
100          E:1/binary, F:1/binary, G:1/binary, "=">> ->
101            [<<(find_in_set(A)):5,
102               (find_in_set(B)):5,
103               (find_in_set(C)):5,
104               (find_in_set(D)):5,
105               (find_in_set(E)):5,
106               (find_in_set(F)):5,
107               (find_in_set(G) bsr 3):2>> | Acc];
108        <<A:1/binary, B:1/binary, C:1/binary, D:1/binary,
109          E:1/binary, F:1/binary, G:1/binary, H:1/binary>> ->
110            decode(Encoded, ByteOffset + 8,
111                   [<<(find_in_set(A)):5,
112                      (find_in_set(B)):5,
113                      (find_in_set(C)):5,
114                      (find_in_set(D)):5,
115                      (find_in_set(E)):5,
116                      (find_in_set(F)):5,
117                      (find_in_set(G)):5,
118                      (find_in_set(H)):5>> | Acc])
119    end.
120
121find_in_set(Char) ->
122    case binary:match(?SET, Char) of
123        nomatch ->
124            erlang:error(not_base32);
125        {Offset, _} ->
126            Offset
127    end.
128