-module(vb64). -export([enc/1, dec/1]). -export([test/0]). test() -> test(100). test(N) when N >= 0 -> RandBytes = rand:bytes(N), % encode the bytes CorrectEnc = erlang:binary_to_list(base64:encode(RandBytes)), MyEnc = enc(RandBytes), EncodeOk = CorrectEnc =:= MyEnc, % decode the bytes MyDec = dec(MyEnc), DecodeOk = MyDec =:= RandBytes, % print results if error ok = case EncodeOk of true -> ok; false -> ok = io:format("===~n" "Encode failure!~n" "input : ~tw~n" "expected output : ~tw~n" "actual output : ~tw~n" "===~n~n", [RandBytes, CorrectEnc, MyEnc]), ok end, ok = case DecodeOk of true -> ok; false -> ok = io:format("===~n" "Decode failure!~n" "input : ~tw~n" "expected output : ~tw~n" "actual output : ~tw~n" "===~n~n", [MyEnc, RandBytes, MyDec]), ok end, % recurse test(N - 1); test(_) -> ok. -spec enc(Binary) -> Base64 when Binary :: binary(), Base64 :: string(). %% @doc %% "Encode" (from the perspective of the program) binary data into base64 %% @end % general case: at least 3 bytes (24 bits = 6+6+6+6) remaining % % 12345678 abcdefgh 12345678 ... % 123456 78abcd efgh12 345678 ... % A B C D Rest % convert to chars -> % CA CB CC CD enc(<>) -> CA = int2char(A), CB = int2char(B), CC = int2char(C), CD = int2char(D), [CA, CB, CC, CD | enc(Rest)]; % terminal case: 2 bytes (16 bits = 6+6+4) remaining % % 12345678 abcdefgh % 123456 78abcd efgh__ % A B C bsl 2 % convert to chars -> % CA CB CC = enc(<>) -> CA = int2char(A), CB = int2char(B), CC = int2char(C bsl 2), [CA, CB, CC, $=]; % terminal case: 1 byte (8 bits = 6+2) remaining % % 12345678 -> % 123456 78____ % A B bsl 4 % convert to chars -> % CA CB = = enc(<>) -> CA = int2char(A), CB = int2char(B bsl 4), [CA, CB, $=, $=]; % terminal case: 0 bytes remaining enc(<<>>) -> []. -spec dec(Base64) -> Binary when Base64 :: string(), Binary :: binary(). %% @doc %% "Decode" (from the perspective of the program) a Base64 string into binary data dec(Base64_String) -> dec(Base64_String, <<>>). % terminal case: two equal signs at the end = 1 byte (8 bits = 6+2) remaining % input (characters) -> % W X = = % convert to numbers -> % abcdef gh____ = = % NW NX % regroup -> % abcdefgh ____ abcdef gh____ % <> = << NW:6, NX:6 >> dec([W, X, $=, $=], Acc) -> NW = char2int(W), NX = char2int(X), <> = <>, <>; % terminal case: one equal sign at the end = 2 bytes remaining % % input (characters) -> % W X Y = % convert to numbers -> % abcdef gh1234 5678__ = % NW NX NY % regroup -> % abcdefgh 12345678 __ abcdef gh1234 5678__ % << B1:8, B2:8, 0:2 >> = << NW:6, NX:6 NY:6 >> dec([W, X, Y, $=], Acc) -> NW = char2int(W), NX = char2int(X), NY = char2int(Y), <> = <>, <>; % terminal case: 0 bytes remaining % nothing to do dec([], Acc) -> Acc; % general case: no equal signs = 3 or more bytes remaining % % input (characters) -> % W X Y Z % convert to numbers -> % abcdef gh1234 5678ab cdefgh % NW NX NY NZ % decompose -> % abcdefgh 12345678 abcdefgh abcdef gh1234 5678ab cdefgh % << B1:8, B2:8, B3:2 >> = << NW:6, NX:6 NY:6, NZ:6 >> dec([W, X, Y, Z | Rest], Acc) -> NW = char2int(W), NX = char2int(X), NY = char2int(Y), NZ = char2int(Z), NewAcc = <>, dec(Rest, NewAcc). int2char( 0) -> $A; int2char( 1) -> $B; int2char( 2) -> $C; int2char( 3) -> $D; int2char( 4) -> $E; int2char( 5) -> $F; int2char( 6) -> $G; int2char( 7) -> $H; int2char( 8) -> $I; int2char( 9) -> $J; int2char(10) -> $K; int2char(11) -> $L; int2char(12) -> $M; int2char(13) -> $N; int2char(14) -> $O; int2char(15) -> $P; int2char(16) -> $Q; int2char(17) -> $R; int2char(18) -> $S; int2char(19) -> $T; int2char(20) -> $U; int2char(21) -> $V; int2char(22) -> $W; int2char(23) -> $X; int2char(24) -> $Y; int2char(25) -> $Z; int2char(26) -> $a; int2char(27) -> $b; int2char(28) -> $c; int2char(29) -> $d; int2char(30) -> $e; int2char(31) -> $f; int2char(32) -> $g; int2char(33) -> $h; int2char(34) -> $i; int2char(35) -> $j; int2char(36) -> $k; int2char(37) -> $l; int2char(38) -> $m; int2char(39) -> $n; int2char(40) -> $o; int2char(41) -> $p; int2char(42) -> $q; int2char(43) -> $r; int2char(44) -> $s; int2char(45) -> $t; int2char(46) -> $u; int2char(47) -> $v; int2char(48) -> $w; int2char(49) -> $x; int2char(50) -> $y; int2char(51) -> $z; int2char(52) -> $0; int2char(53) -> $1; int2char(54) -> $2; int2char(55) -> $3; int2char(56) -> $4; int2char(57) -> $5; int2char(58) -> $6; int2char(59) -> $7; int2char(60) -> $8; int2char(61) -> $9; int2char(62) -> $+; int2char(63) -> $/. char2int($A) -> 0; char2int($B) -> 1; char2int($C) -> 2; char2int($D) -> 3; char2int($E) -> 4; char2int($F) -> 5; char2int($G) -> 6; char2int($H) -> 7; char2int($I) -> 8; char2int($J) -> 9; char2int($K) -> 10; char2int($L) -> 11; char2int($M) -> 12; char2int($N) -> 13; char2int($O) -> 14; char2int($P) -> 15; char2int($Q) -> 16; char2int($R) -> 17; char2int($S) -> 18; char2int($T) -> 19; char2int($U) -> 20; char2int($V) -> 21; char2int($W) -> 22; char2int($X) -> 23; char2int($Y) -> 24; char2int($Z) -> 25; char2int($a) -> 26; char2int($b) -> 27; char2int($c) -> 28; char2int($d) -> 29; char2int($e) -> 30; char2int($f) -> 31; char2int($g) -> 32; char2int($h) -> 33; char2int($i) -> 34; char2int($j) -> 35; char2int($k) -> 36; char2int($l) -> 37; char2int($m) -> 38; char2int($n) -> 39; char2int($o) -> 40; char2int($p) -> 41; char2int($q) -> 42; char2int($r) -> 43; char2int($s) -> 44; char2int($t) -> 45; char2int($u) -> 46; char2int($v) -> 47; char2int($w) -> 48; char2int($x) -> 49; char2int($y) -> 50; char2int($z) -> 51; char2int($0) -> 52; char2int($1) -> 53; char2int($2) -> 54; char2int($3) -> 55; char2int($4) -> 56; char2int($5) -> 57; char2int($6) -> 58; char2int($7) -> 59; char2int($8) -> 60; char2int($9) -> 61; char2int($+) -> 62; char2int($/) -> 63.