%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1999-2016. All Rights Reserved.
%% 
%% 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.
%% 
%% %CopyrightEnd%
%%

-module(crypto_SUITE).

-include_lib("common_test/include/ct.hrl").

-export([all/0, suite/0,
         t_md5/1,t_md5_update/1,error/1,unaligned_context/1,random_lists/1,
         misc_errors/1]).

suite() ->
    [{ct_hooks,[ts_install_cth]}].

all() -> 
    [t_md5, t_md5_update, error, unaligned_context,
     random_lists, misc_errors].

%% Test crc32, adler32 and md5 error cases not covered by other tests"
misc_errors(Config) when is_list(Config) ->
    ct:timetrap({minutes, 2}),
    1 = erlang:adler32([]),
    L = lists:duplicate(600,3),
    1135871753 = erlang:adler32(L),
    L2 = lists:duplicate(22000,3),
    1100939744 = erlang:adler32(L2),
    {'EXIT', {badarg,_}} = (catch erlang:adler32(L++[a])),
    {'EXIT', {badarg,_}} = (catch erlang:crc32(L++[a])),
    {'EXIT', {badarg,_}} = (catch erlang:crc32([1,2,3|<<25:7>>])),
    {'EXIT', {badarg,_}} = (catch erlang:crc32([1,2,3|4])),
    Big = 111111111111111111111111111111,
    {'EXIT', {badarg,_}} = (catch erlang:crc32(Big,<<"hej">>)),
    {'EXIT', {badarg,_}} = (catch erlang:crc32(25,[1,2,3|4])),
    {'EXIT', {badarg,_}} = (catch erlang:crc32_combine(Big,3,3)),
    {'EXIT', {badarg,_}} = (catch erlang:crc32_combine(3,Big,3)),
    {'EXIT', {badarg,_}} = (catch erlang:crc32_combine(3,3,Big)),
    {'EXIT', {badarg,_}} = (catch erlang:adler32(Big,<<"hej">>)),
    {'EXIT', {badarg,_}} = (catch erlang:adler32(25,[1,2,3|4])),
    {'EXIT', {badarg,_}} = (catch erlang:adler32_combine(Big,3,3)),
    {'EXIT', {badarg,_}} = (catch erlang:adler32_combine(3,Big,3)),
    {'EXIT', {badarg,_}} = (catch erlang:adler32_combine(3,3,Big)),
    {'EXIT', {badarg,_}} = (catch erlang:md5_update(<<"hej">>,<<"hej">>)),
    {'EXIT', {badarg,_}} = (catch erlang:md5_final(<<"hej">>)),
    ok.


%%
%% Most of the real code for these test cases are in 
%% the modules crypto_reference and random_iolist.
%%
-define(REF,crypto_reference).

nicesplit(N,L) ->
    nicesplit(N,L,[]).
nicesplit(0,Tail,Acc) ->
    {lists:reverse(Acc),Tail};
nicesplit(_,[],Acc) ->
    {lists:reverse(Acc),[]};
nicesplit(N,[H|Tail],Acc) ->
    nicesplit(N-1,Tail,[H|Acc]).

run_in_para([],_) ->
    true;
run_in_para(FunList,Schedulers) ->
    {ThisTime,NextTime} = nicesplit(Schedulers,FunList),
    case length(ThisTime) of 
        1 ->
            [{L,Fun}] = ThisTime,
            try
                Fun()
            catch
                _:Reason ->
                    exit({error_at_line,L,Reason})
            end;
        _ ->
            These = [ {L,erlang:spawn_monitor(F)} || {L,F} <- ThisTime ],
            collect_workers(These)
    end,
    run_in_para(NextTime,Schedulers).

collect_workers([]) ->
    ok;
collect_workers([{L,{Pid,Ref}}|T]) ->
    receive
        {'DOWN',Ref,process,Pid,normal} ->
            collect_workers(T);
        {'DOWN',Ref,process,Pid,Other} ->
            exit({error_at_line,L,Other})
    end.

%% Test crc32, adler32 and md5 on a number of pseudo-randomly generated lists.
random_lists(Config) when is_list(Config) ->
    ct:timetrap({minutes, 5}),
    Num = erlang:system_info(schedulers_online),
    B = list_to_binary(
          lists:duplicate(
            (erlang:system_info(context_reductions)*10) - 50,$!)),
    CRC32_1 = fun(L) -> erlang:crc32(L) end,
    CRC32_2 = fun(L) -> ?REF:crc32(L) end,
    ADLER32_1 = fun(L) -> erlang:adler32(L) end,
    ADLER32_2 = fun(L) -> ?REF:adler32(L) end,
    MD5_1 = fun(L) -> erlang:md5(L) end,
    MD5_2 = fun(L) -> ?REF:md5_final(
                         ?REF:md5_update(?REF:md5_init(),L)) end,
    MD5_3 =  fun(L) -> erlang:md5_final(
                         erlang:md5_update(erlang:md5_init(),L)) end,
    CRC32_1_L = fun(L) -> erlang:crc32([B|L]) end,
    CRC32_2_L = fun(L) -> ?REF:crc32([B|L]) end,
    ADLER32_1_L = fun(L) -> erlang:adler32([B|L]) end,
    ADLER32_2_L = fun(L) -> ?REF:adler32([B|L]) end,
    MD5_1_L = fun(L) -> erlang:md5([B|L]) end,
    MD5_2_L = fun(L) -> ?REF:md5_final(
                           ?REF:md5_update(?REF:md5_init(),[B|L])) end,
    MD5_3_L =  fun(L) -> erlang:md5_final(
                           erlang:md5_update(
                             erlang:md5_init(),[B|L])) end,
    Wlist0 = 
    [{?LINE, fun() -> random_iolist:run(150, CRC32_1, CRC32_2) end},
     {?LINE, fun() -> random_iolist:run(150, ADLER32_1, ADLER32_2) end},
     {?LINE, fun() -> random_iolist:run(150,MD5_1,MD5_2) end},
     {?LINE, fun() -> random_iolist:run(150,MD5_1,MD5_3) end},
     {?LINE, fun() -> random_iolist:run(150, CRC32_1_L, CRC32_2_L) end},
     {?LINE, 
      fun() -> random_iolist:run(150, ADLER32_1_L, ADLER32_2_L) end},
     {?LINE, fun() -> random_iolist:run(150,MD5_1_L,MD5_2_L) end},
     {?LINE, fun() -> random_iolist:run(150,MD5_1_L,MD5_3_L) end}],
    run_in_para(Wlist0,Num),
    CRC32_1_2 = fun(L1,L2) -> erlang:crc32([L1,L2]) end,
    CRC32_2_2 = fun(L1,L2) -> erlang:crc32(erlang:crc32(L1),L2) end,
    CRC32_3_2 = fun(L1,L2) -> erlang:crc32_combine(
                                erlang:crc32(L1),
                                erlang:crc32(L2),
                                erlang:iolist_size(L2)) 
                end,
    ADLER32_1_2 = fun(L1,L2) -> erlang:adler32([L1,L2]) end,
    ADLER32_2_2 = fun(L1,L2) -> erlang:adler32(
                                  erlang:adler32(L1),L2) end,
    ADLER32_3_2 = fun(L1,L2) -> erlang:adler32_combine(
                                  erlang:adler32(L1),
                                  erlang:adler32(L2),
                                  erlang:iolist_size(L2)) 
                  end,
    MD5_1_2 = fun(L1,L2) -> erlang:md5([L1,L2]) end,
    MD5_2_2 = fun(L1,L2) -> 
                      erlang:md5_final(
                        erlang:md5_update(
                          erlang:md5_update(
                            erlang:md5_init(),
                            L1),
                          L2)) 
              end,
    CRC32_1_L_2 = fun(L1,L2) -> erlang:crc32([[B|L1],[B|L2]]) end,
    CRC32_2_L_2 = fun(L1,L2) -> erlang:crc32(
                                  erlang:crc32([B|L1]),[B|L2]) end,
    CRC32_3_L_2 = fun(L1,L2) -> erlang:crc32_combine(
                                  erlang:crc32([B|L1]),
                                  erlang:crc32([B|L2]),
                                  erlang:iolist_size([B|L2])) 
                  end,
    ADLER32_1_L_2 = fun(L1,L2) -> erlang:adler32([[B|L1],[B|L2]]) end,
    ADLER32_2_L_2 = fun(L1,L2) -> erlang:adler32(
                                    erlang:adler32([B|L1]),
                                    [B|L2]) 
                    end,
    ADLER32_3_L_2 = fun(L1,L2) -> erlang:adler32_combine(
                                    erlang:adler32([B|L1]),
                                    erlang:adler32([B|L2]),
                                    erlang:iolist_size([B|L2])) 
                    end,
    MD5_1_L_2 = fun(L1,L2) -> erlang:md5([[B|L1],[B|L2]]) end,
    MD5_2_L_2 = fun(L1,L2) -> 
                        erlang:md5_final(
                          erlang:md5_update(
                            erlang:md5_update(
                              erlang:md5_init(),
                              [B|L1]),
                            [B|L2])) 
                end,
    Wlist1 = 
    [{?LINE, fun() -> random_iolist:run2(150,CRC32_1_2,CRC32_2_2) end},
     {?LINE, fun() -> random_iolist:run2(150,CRC32_1_2,CRC32_3_2) end},
     {?LINE, fun() -> random_iolist:run2(150,ADLER32_1_2,ADLER32_2_2) end},
     {?LINE, fun() -> random_iolist:run2(150,ADLER32_1_2,ADLER32_3_2) end},
     {?LINE, fun() -> random_iolist:run2(150,MD5_1_2,MD5_2_2) end},
     {?LINE, fun() -> random_iolist:run2(150,CRC32_1_L_2,CRC32_2_L_2) end},
     {?LINE, fun() -> random_iolist:run2(150,CRC32_1_L_2,CRC32_3_L_2) end},
     {?LINE, 
      fun() -> random_iolist:run2(150,ADLER32_1_L_2,ADLER32_2_L_2) end},
     {?LINE, 
      fun() -> random_iolist:run2(150,ADLER32_1_L_2,ADLER32_3_L_2) end},
     {?LINE, fun() -> random_iolist:run2(150,MD5_1_L_2,MD5_2_L_2) end}],
    run_in_para(Wlist1,Num),
    ok.

%% Generate MD5 message digests and check the result. Examples are from RFC-1321.
t_md5(Config) when is_list(Config) ->
    t_md5_test("", "d41d8cd98f00b204e9800998ecf8427e"),
    t_md5_test("a", "0cc175b9c0f1b6a831c399e269772661"),
    t_md5_test("abc", "900150983cd24fb0d6963f7d28e17f72"),
    t_md5_test(["message ","digest"], "f96b697d7cb7938d525a2f31aaf161d0"),
    t_md5_test(["message ",unaligned_sub_bin(<<"digest">>)],
               "f96b697d7cb7938d525a2f31aaf161d0"),
    t_md5_test("abcdefghijklmnopqrstuvwxyz",
               "c3fcd3d76192e4007dfb496cca67e13b"),
    t_md5_test("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
               "0123456789",
               "d174ab98d277d9f5a5611c2c9f419d9f"),
    t_md5_test("12345678901234567890123456789012345678901234567890"
               "123456789012345678901234567890",
               "57edf4a22be3c955ac49da2e2107b67a"),
    ok.

%% Generate MD5 message using md5_init, md5_update, and md5_final, and
%% check the result. Examples are from RFC-1321.
t_md5_update(Config) when is_list(Config) ->
    t_md5_update_1(fun(Str) -> Str end),
    t_md5_update_1(fun(Str) -> list_to_binary(Str) end),
    t_md5_update_1(fun(Str) -> unaligned_sub_bin(list_to_binary(Str)) end),
    ok.

t_md5_update_1(Tr) when is_function(Tr, 1) ->
    Ctx = erlang:md5_init(),
    Ctx1 = erlang:md5_update(Ctx, Tr("ABCDEFGHIJKLMNOPQRSTUVWXYZ")),
    Ctx2 = erlang:md5_update(Ctx1, Tr("abcdefghijklmnopqrstuvwxyz"
                                      "0123456789")),
    m(erlang:md5_final(Ctx2),
      hexstr2bin("d174ab98d277d9f5a5611c2c9f419d9f")),
    ok.

%%
%%
error(Config) when is_list(Config) ->
    {'EXIT',{badarg,_}} = (catch erlang:md5(bit_sized_binary(<<"abc">>))),
    Ctx0 = erlang:md5_init(),
    {'EXIT',{badarg,_}} =
    (catch erlang:md5_update(Ctx0, bit_sized_binary(<<"abcfjldjd">>))),
    {'EXIT',{badarg,_}} =
    (catch erlang:md5_update(Ctx0, ["something",bit_sized_binary(<<"abcfjldjd">>)])),
    {'EXIT',{badarg,_}} =
    (catch erlang:md5_update(bit_sized_binary(Ctx0), "something")),
    {'EXIT',{badarg,_}} = (catch erlang:md5_final(bit_sized_binary(Ctx0))),
    m(erlang:md5_final(Ctx0), hexstr2bin("d41d8cd98f00b204e9800998ecf8427e")),
    ok.


%%
%%
unaligned_context(Config) when is_list(Config) ->
    Ctx0 = erlang:md5_init(),
    Ctx1 = erlang:md5_update(unaligned_sub_bin(Ctx0), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
    Ctx = erlang:md5_update(unaligned_sub_bin(Ctx1),
                            "abcdefghijklmnopqrstuvwxyz0123456789"),
    m(erlang:md5_final(unaligned_sub_bin(Ctx)),
      hexstr2bin("d174ab98d277d9f5a5611c2c9f419d9f")),
    ok.

%%
%% Help functions
%%

t_md5_test(Str, ResultStr) ->
    ResultBin = hexstr2bin(ResultStr),
    m(erlang:md5(Str), ResultBin),
    Bin = list_to_binary(Str),
    m(erlang:md5(Bin), ResultBin),
    UnalignedSubBin = unaligned_sub_bin(Bin),
    m(erlang:md5(UnalignedSubBin), ResultBin).

m(X, X) -> true.

hexstr2bin(S) ->
    list_to_binary(hexstr2list(S)).

hexstr2list([X,Y|T]) ->
    [mkint(X)*16 + mkint(Y) | hexstr2list(T)];
hexstr2list([]) ->
    [].

mkint(C) when $0 =< C, C =< $9 ->
    C - $0;
mkint(C) when $A =< C, C =< $F ->
    C - $A + 10;
mkint(C) when $a =< C, C =< $f ->
    C - $a + 10.

unaligned_sub_bin(Bin0) ->
    Bin1 = <<0:3,Bin0/binary,31:5>>,
    Sz = size(Bin0),
    <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
    Bin.

%% Add 1 bit to the size of the binary.
bit_sized_binary(Bin0) ->
    Bin = <<Bin0/binary,1:1>>,
    BitSize = bit_size(Bin),
    BitSize = 8*size(Bin) + 1,
    Bin.

id(I) -> I.