diff --git a/utils/vw/src/vb64.erl b/utils/vw/src/vb64.erl new file mode 100644 index 0000000..b7d1cc6 --- /dev/null +++ b/utils/vw/src/vb64.erl @@ -0,0 +1,298 @@ +-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 2 +% 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 +% decompose -> +% 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 +% decompose -> +% 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.