1%% -*- mode: erlang; tab-width: 4; indent-tabs-mode: 1; st-rulers: [70] -*-
2%% vim: ts=4 sw=4 ft=erlang noet
3%%%-------------------------------------------------------------------
4%%% @author Andrew Bennett <potatosaladx@gmail.com>
5%%% @copyright 2017-2019, Andrew Bennett
6%%% @doc RFC 4648, Section 4: https://tools.ietf.org/html/rfc4648#section-4
7%%%
8%%% @end
9%%% Created :  11 May 2017 by Andrew Bennett <potatosaladx@gmail.com>
10%%%-------------------------------------------------------------------
11-module(jose_base64).
12-compile({parse_transform, jose_base}).
13
14-include("jose_base.hrl").
15
16%% API
17-export([decode/1]).
18-export([decode/2]).
19-export(['decode!'/1]).
20-export(['decode!'/2]).
21-export([encode/1]).
22-export([encode/2]).
23-export([random/1]).
24-export([random/2]).
25
26% Macros
27-define(B64_TO_INT(C),
28	case C of
29		$A -> 16#00;
30		$B -> 16#01;
31		$C -> 16#02;
32		$D -> 16#03;
33		$E -> 16#04;
34		$F -> 16#05;
35		$G -> 16#06;
36		$H -> 16#07;
37		$I -> 16#08;
38		$J -> 16#09;
39		$K -> 16#0A;
40		$L -> 16#0B;
41		$M -> 16#0C;
42		$N -> 16#0D;
43		$O -> 16#0E;
44		$P -> 16#0F;
45		$Q -> 16#10;
46		$R -> 16#11;
47		$S -> 16#12;
48		$T -> 16#13;
49		$U -> 16#14;
50		$V -> 16#15;
51		$W -> 16#16;
52		$X -> 16#17;
53		$Y -> 16#18;
54		$Z -> 16#19;
55		$a -> 16#1A;
56		$b -> 16#1B;
57		$c -> 16#1C;
58		$d -> 16#1D;
59		$e -> 16#1E;
60		$f -> 16#1F;
61		$g -> 16#20;
62		$h -> 16#21;
63		$i -> 16#22;
64		$j -> 16#23;
65		$k -> 16#24;
66		$l -> 16#25;
67		$m -> 16#26;
68		$n -> 16#27;
69		$o -> 16#28;
70		$p -> 16#29;
71		$q -> 16#2A;
72		$r -> 16#2B;
73		$s -> 16#2C;
74		$t -> 16#2D;
75		$u -> 16#2E;
76		$v -> 16#2F;
77		$w -> 16#30;
78		$x -> 16#31;
79		$y -> 16#32;
80		$z -> 16#33;
81		$0 -> 16#34;
82		$1 -> 16#35;
83		$2 -> 16#36;
84		$3 -> 16#37;
85		$4 -> 16#38;
86		$5 -> 16#39;
87		$6 -> 16#3A;
88		$7 -> 16#3B;
89		$8 -> 16#3C;
90		$9 -> 16#3D;
91		$+ -> 16#3E;
92		$/ -> 16#3F
93	end).
94
95-define(INT_TO_B64(C),
96	case C of
97		16#00 -> $A;
98		16#01 -> $B;
99		16#02 -> $C;
100		16#03 -> $D;
101		16#04 -> $E;
102		16#05 -> $F;
103		16#06 -> $G;
104		16#07 -> $H;
105		16#08 -> $I;
106		16#09 -> $J;
107		16#0A -> $K;
108		16#0B -> $L;
109		16#0C -> $M;
110		16#0D -> $N;
111		16#0E -> $O;
112		16#0F -> $P;
113		16#10 -> $Q;
114		16#11 -> $R;
115		16#12 -> $S;
116		16#13 -> $T;
117		16#14 -> $U;
118		16#15 -> $V;
119		16#16 -> $W;
120		16#17 -> $X;
121		16#18 -> $Y;
122		16#19 -> $Z;
123		16#1A -> $a;
124		16#1B -> $b;
125		16#1C -> $c;
126		16#1D -> $d;
127		16#1E -> $e;
128		16#1F -> $f;
129		16#20 -> $g;
130		16#21 -> $h;
131		16#22 -> $i;
132		16#23 -> $j;
133		16#24 -> $k;
134		16#25 -> $l;
135		16#26 -> $m;
136		16#27 -> $n;
137		16#28 -> $o;
138		16#29 -> $p;
139		16#2A -> $q;
140		16#2B -> $r;
141		16#2C -> $s;
142		16#2D -> $t;
143		16#2E -> $u;
144		16#2F -> $v;
145		16#30 -> $w;
146		16#31 -> $x;
147		16#32 -> $y;
148		16#33 -> $z;
149		16#34 -> $0;
150		16#35 -> $1;
151		16#36 -> $2;
152		16#37 -> $3;
153		16#38 -> $4;
154		16#39 -> $5;
155		16#3A -> $6;
156		16#3B -> $7;
157		16#3C -> $8;
158		16#3D -> $9;
159		16#3E -> $+;
160		16#3F -> $/
161	end).
162
163%%%===================================================================
164%%% API functions
165%%%===================================================================
166
167decode(Input) when ?is_iodata(Input) ->
168	decode(Input, #{}).
169
170decode(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) ->
171	try 'decode!'(Input, Opts) of
172		Output when is_binary(Output) ->
173			{ok, Output}
174	catch
175		_:_ ->
176			error
177	end;
178decode(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) ->
179	decode(Input, maps:from_list(Opts)).
180
181'decode!'(Input) when ?is_iodata(Input) ->
182	'decode!'(Input, #{}).
183
184'decode!'([], #{}) ->
185	<<>>;
186'decode!'(<<>>, #{}) ->
187	<<>>;
188'decode!'(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) ->
189	Padding = maps:get('padding', Opts, nil),
190	Size = erlang:iolist_size(Input),
191	Offset =
192		case Padding of
193			_ when (Padding == false orelse Padding == nil) andalso Size =< 4 ->
194				0;
195			_ when (Padding == false orelse Padding == nil) andalso (Size rem 4) =/= 0 ->
196				Size - (Size rem 4);
197			_ when (Padding == false orelse Padding == nil) ->
198				Size - 4;
199			_ when (Padding == true orelse Padding == nil) andalso Size >= 4 ->
200				Size - 4;
201			_ ->
202				erlang:error({badarg, [Input, Opts]})
203		end,
204	<< Head0:Offset/binary, Tail0/binary >> = ?to_binary(Input),
205	Head = << << (?B64_TO_INT(V)):6 >> || << V >> <= Head0 >>,
206	Tail =
207		case Padding of
208			false ->
209				case Tail0 of
210					<< T0:8, T1:8 >> ->
211						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>;
212					<< T0:8, T1:8, T2:8 >> ->
213						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>;
214					<< T:4/binary >> ->
215						<< << (?B64_TO_INT(V)):6 >> || << V >> <= T >>;
216					<<>> ->
217						<<>>;
218					_ ->
219						erlang:error({badarg, [Input, Opts]})
220				end;
221			nil ->
222				case Tail0 of
223					<< T0:8, T1:8, $=, $= >> ->
224						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>;
225					<< T0:8, T1:8, T2:8, $= >> ->
226						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>;
227					<< T0:8, T1:8 >> ->
228						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>;
229					<< T0:8, T1:8, T2:8 >> ->
230						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>;
231					<< T:4/binary >> ->
232						<< << (?B64_TO_INT(V)):6 >> || << V >> <= T >>;
233					<<>> ->
234						<<>>
235				end;
236			true ->
237				case Tail0 of
238					<< T0:8, T1:8, $=, $= >> ->
239						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1) bsr 4):2 >>;
240					<< T0:8, T1:8, T2:8, $= >> ->
241						<< (?B64_TO_INT(T0)):6, (?B64_TO_INT(T1)):6, (?B64_TO_INT(T2) bsr 2):4 >>;
242					<< T:4/binary >> ->
243						<< << (?B64_TO_INT(V)):6 >> || << V >> <= T >>;
244					<<>> ->
245						<<>>;
246					_ ->
247						erlang:error({badarg, [Input, Opts]})
248				end
249		end,
250	<< Head/binary, Tail/binary >>;
251'decode!'(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) ->
252	'decode!'(Input, maps:from_list(Opts)).
253
254encode(Input) when ?is_iodata(Input) ->
255	encode(Input, #{}).
256
257encode(Input, Opts) when ?is_iodata(Input) andalso is_map(Opts) ->
258	Padding = maps:get('padding', Opts, true),
259	Offset = 6 * (erlang:iolist_size(Input) div 6),
260	<< Head:Offset/binary, Tail/binary >> = ?to_binary(Input),
261	H = << << (encode_pair(V0)):16, (encode_pair(V1)):16, (encode_pair(V2)):16, (encode_pair(V3)):16 >> || << V0:12, V1:12, V2:12, V3:12 >> <= Head >>,
262	{T, Pad} =
263		case Tail of
264			<< T0:12, T1:12, T2:12, T3:4 >> ->
265				{<< (encode_pair(T0)):16, (encode_pair(T1)):16, (encode_pair(T2)):16, (encode_char(T3 bsl 2)):8 >>, << $= >>};
266			<< T0:12, T1:12, T2:8 >> ->
267				{<< (encode_pair(T0)):16, (encode_pair(T1)):16, (encode_pair(T2 bsl 4)):16 >>, << $=, $= >>};
268			<< T0:12, T1:12 >> ->
269				{<< (encode_pair(T0)):16, (encode_pair(T1)):16 >>, <<>>};
270			<< T0:12, T1:4 >> ->
271				{<< (encode_pair(T0)):16, (encode_char(T1 bsl 2)):8 >>, <<>>};
272			<< T0:8 >> ->
273				{<< (encode_pair(T0 bsl 4)):16 >>, << $=, $= >>};
274			<<>> ->
275				{<<>>, <<>>}
276		end,
277	case Padding of
278		true ->
279			<< H/binary, T/binary, Pad/binary >>;
280		false ->
281			<< H/binary, T/binary >>;
282		_ ->
283			erlang:error({badarg, [Input, Opts]})
284	end;
285encode(Input, Opts) when ?is_iodata(Input) andalso is_list(Opts) ->
286	encode(Input, maps:from_list(Opts)).
287
288random(Bytes) when is_integer(Bytes) andalso Bytes >= 0 ->
289	random(Bytes, #{}).
290
291random(0, Opts) when is_map(Opts) ->
292	<<>>;
293random(Bytes, Opts) when (Bytes =:= 1) andalso is_map(Opts) ->
294	erlang:error({badarg, [Bytes, Opts]});
295random(Bytes, Opts) when is_integer(Bytes) andalso Bytes > 0 andalso is_map(Opts) ->
296	Padding = maps:get('padding', Opts, true),
297	R = (Bytes rem 4),
298	Size =
299		case Padding of
300			true when R =:= 0 ->
301				(Bytes * 3) div 4;
302			false when R =:= 0 orelse R =:= 2 orelse R =:= 3 ->
303				(Bytes * 3) div 4;
304			_ ->
305				erlang:error({badarg, [Bytes, Opts]})
306		end,
307	Binary = crypto:strong_rand_bytes(Size),
308	encode(Binary, Opts);
309random(Bytes, Opts) when is_integer(Bytes) andalso Bytes >= 0 andalso is_list(Opts) ->
310	random(Bytes, maps:from_list(Opts)).
311
312%%%-------------------------------------------------------------------
313%%% Internal functions
314%%%-------------------------------------------------------------------
315
316%% @private
317encode_char(V) ->
318	jose_base:encode_char(?INT_TO_B64(V)).
319
320%% @private
321encode_pair(V) ->
322	jose_base:encode_pair(?INT_TO_B64(V), sensitive).
323