%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2020. 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(openssl_session_SUITE).

-include_lib("common_test/include/ct.hrl").
%% Callback functions
-export([all/0,
         groups/0,
         init_per_suite/1,
         end_per_suite/1,
         init_per_group/2,
         end_per_group/2,
         init_per_testcase/2,
         end_per_testcase/2]).

%% Testcases
-export([reuse_session_erlang_server/0,
         reuse_session_erlang_server/1,
         reuse_session_erlang_client/0,
         reuse_session_erlang_client/1
         ]).

-define(SLEEP, 1000).
-define(EXPIRE, 10).
-define(TIMEOUT, {seconds, 120}).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

all() -> 
    case ssl_test_lib:openssl_sane_dtls() of 
        true ->
            [{group, 'tlsv1.2'},
             {group, 'tlsv1.1'},
             {group, 'tlsv1'},
             {group, 'dtlsv1.2'},
             {group, 'dtlsv1'}];
        false ->
            [{group, 'tlsv1.2'},
             {group, 'tlsv1.1'},
             {group, 'tlsv1'}
             ]
    end.

groups() ->
     case ssl_test_lib:openssl_sane_dtls() of 
         true ->
             [{'tlsv1.2', [], tests()},
              {'tlsv1.1', [], tests()},
              {'tlsv1', [], tests()},
              {'dtlsv1.2', [], tests()},
              {'dtlsv1', [], tests()}
             ];
        false ->
             [{'tlsv1.2', [], tests()},
              {'tlsv1.1', [], tests()},
              {'tlsv1', [], tests()}
           ]
     end.
 
tests() ->
    [    
         reuse_session_erlang_server,
         reuse_session_erlang_client
    ].


init_per_suite(Config0) ->
    case os:find_executable("openssl") of
        false ->
            {skip, "Openssl not found"};
        _ ->
            ct:pal("Version: ~p", [os:cmd("openssl version")]),
            catch crypto:stop(),
            try crypto:start() of
                ok ->
                    ssl_test_lib:clean_start(),
                    {ClientOpts, ServerOpts} = 
                        ssl_test_lib:make_rsa_cert_chains([{server_chain, ssl_test_lib:default_cert_chain_conf()},
                                                           {client_chain, ssl_test_lib:default_cert_chain_conf()}], 
                                                          Config0, "openssl_session_SUITE"),
                    [{client_opts, ClientOpts}, {server_opts, ServerOpts} | Config0]
            catch _:_  ->
                    {skip, "Crypto did not start"}
            end
    end.

end_per_suite(_Config) ->
    ssl:stop(),
    application:stop(crypto),
    ssl_test_lib:kill_openssl().

init_per_group(GroupName, Config) ->
    ssl_test_lib:init_per_group_openssl(GroupName, Config).

end_per_group(GroupName, Config) ->
    ssl_test_lib:end_per_group(GroupName, Config).

init_per_testcase(reuse_session_erlang_client, Config) ->
    ct:timetrap(?EXPIRE * 1000 * 5),
    ssl:stop(),
    application:load(ssl),
    application:set_env(ssl, session_lifetime, ?EXPIRE),
    ssl:start(),
    Config;
init_per_testcase(reuse_session_erlang_server, Config) ->
    case ssl_test_lib:working_openssl_client() of
        true ->
            Version = ssl_test_lib:protocol_version(Config),
            case ssl_test_lib:is_dtls_version(Version) of
                true ->
                    case ssl_test_lib:openssl_sane_dtls_session_reuse() of
                        true ->
                            ct:timetrap(?TIMEOUT),
                            Config;
                        false ->
                            {skip, "Broken OpenSSL DTLS session reuse"}
                    end;
                false ->
                    ct:timetrap(?TIMEOUT),
                    Config
            end;
        false  ->
            {skip, "Broken OpenSSL s_client"}
    end;
init_per_testcase(_TestCase, Config) ->
    ct:timetrap(?TIMEOUT),
    Config.

end_per_testcase(reuse_session_erlang_client, Config) ->
    application:unset_env(ssl, session_lifetime),
    Config;
end_per_testcase(_, Config) ->
    Config.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
reuse_session_erlang_server() ->
    [{doc, "Test erlang server with openssl client that reconnects with the"
      "same session id, to test reusing of sessions."}].
reuse_session_erlang_server(Config) when is_list(Config) ->
    ClientOpts = proplists:get_value(client_opts, Config),
    ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
    Version = ssl_test_lib:protocol_version(Config),
    
    {_, ServerNode, _} = ssl_test_lib:run_where(Config),
    
    Ciphers = common_ciphers(Version),

    Data = "From openssl to erlang",
    
    Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, 
                                        {from, self()},
                                        {mfa, {ssl_test_lib, active_recv, [length(Data)]}},
                                        {reconnect_times, 5},
                                        {options, [{ciphers, Ciphers},
                                                   {versions, [Version]}| ServerOpts]}]),
    Port = ssl_test_lib:inet_port(Server),

    
    {_Client, OpenSSLPort} = ssl_test_lib:start_client(openssl, [{port, Port}, 
                                                                 {reconnect, true},
                                                                 {options, [{ciphers, Ciphers} | ClientOpts]}, 
                                                                 return_port], Config),
    true = port_command(OpenSSLPort, Data),
    
    ssl_test_lib:check_result(Server, Data),
    ssl_test_lib:close(Server).

%%--------------------------------------------------------------------

reuse_session_erlang_client() ->
    [{doc, "Test erlang ssl client that wants to reuse sessions"}].
reuse_session_erlang_client(Config) when is_list(Config) -> 
    ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
    ServerOpts = proplists:get_value(server_opts, Config),
    Version = ssl_test_lib:protocol_version(Config),

    {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
    Ciphers = common_ciphers(Version),
    Server = ssl_test_lib:start_server(openssl, [], 
                                       [{server_opts, [{ciphers, Ciphers} | ServerOpts]} | Config]),
    Port = ssl_test_lib:inet_port(Server),    
    
    Client0 =
        ssl_test_lib:start_client([{node, ClientNode},
                                   {port, Port}, {host, Hostname},
                                   {mfa, {ssl_test_lib, session_id, []}},
                                   {from, self()}, 
                                   {options, [{reuse_sessions, save}, 
                                              {verify, verify_peer},
                                              {ciphers, Ciphers},
                                              {versions, [Version]} | ClientOpts]}]),
    
    SID = receive
              {Client0, Id0} ->
                  Id0
          end,
    
    ssl_test_lib:close(Client0),
    
    Client1 =
        ssl_test_lib:start_client([{node, ClientNode},
                                   {port, Port}, {host, Hostname},
                                   {mfa, {ssl_test_lib, session_id, []}},
                                   {from, self()},  {options, [ {ciphers, Ciphers},  
                                                                {versions, [Version]},
                                                                {reuse_session, SID} | ClientOpts]}]),
    receive
        {Client1, SID} ->
            ok
    after ?SLEEP ->
            ct:fail(session_not_reused)
    end,
    
    
    ssl_test_lib:close(Client1),
    %% Make sure session is unregistered due to expiration
    ct:sleep(20000),
    
    Client2 =
        ssl_test_lib:start_client([{node, ClientNode},
                                   {port, Port}, {host, Hostname},
                                   {mfa, {ssl_test_lib, session_id, []}},
                                   {from, self()},  {options, [{ciphers, Ciphers},
                                                               {versions, [Version]} | ClientOpts]}]),
    receive
        {Client2, ID} ->
            case ID of
                SID ->    
                    ct:fail(expired_session_reused);
                _  ->
                    ok
            end
    end,
    ssl_test_lib:close(Client2).


%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------

common_ciphers(Version) ->
    OpenSSLCiphers = ssl_test_lib:openssl_ciphers(),
    ErlOpenSSLCiphers = [ssl:str_to_suite(C) || C <- OpenSSLCiphers],
    ErlCiphers = ssl:cipher_suites(all, Version),
    [Suite || Suite <- ErlOpenSSLCiphers, lists:member(Suite, ErlCiphers)].

