%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2022-2026. 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%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: TLS-1.3 FSM (client side)
%%----------------------------------------------------------------------
%%                                 INITIAL_HELLO
%%                                  Client send
%%                                  first ClientHello
%%                                  |                     ---> CONFIG_ERROR
%%                                  |                     Send error to user
%%                                  |                     and shutdown
%%                                  |
%%                                  V
%% RFC 8446
%% A.1.  Client
%%
%%                               START <----+
%%                Send ClientHello |        | Recv HelloRetryRequest
%%           [K_send = early data] |        |
%%                                 v        |
%%            /                 WAIT_SH ----+
%%            |                    | Recv ServerHello
%%            |                    | K_recv = handshake
%%        Can |                    V
%%       send |                 WAIT_EE
%%      early |                    | Recv EncryptedExtensions
%%       data |           +--------+--------+
%%            |     Using |                 | Using certificate
%%            |       PSK |                 v
%%            |           |            WAIT_CERT_CR
%%            |           |        Recv |       | Recv CertificateRequest
%%            |           | Certificate |       v
%%            |           |             |    WAIT_CERT
%%            |           |             |       | Recv Certificate
%%            |           |             v       v
%%            |           |              WAIT_CV
%%            |           |                 | Recv CertificateVerify
%%            |           +> WAIT_FINISHED <+
%%            |                  | Recv Finished
%%            \                  | [Send EndOfEarlyData]
%%                               | K_send = handshake
%%                               | [Send Certificate [+ CertificateVerify]]
%%     Can send                  | Send Finished
%%     app data   -->            | K_send = K_recv = application
%%     after here                v
%%                           CONNECTED
%%

-module(tls_client_connection_1_3).
-moduledoc false.

-include_lib("public_key/include/public_key.hrl").

-include("ssl_alert.hrl").
-include("ssl_connection.hrl").
-include("tls_connection.hrl").
-include("tls_handshake.hrl").
-include("tls_handshake_1_3.hrl").

-behaviour(gen_statem).

%% gen_statem callbacks
-export([init/1,
         callback_mode/0,
         terminate/3,
         code_change/4,
         format_status/1]).

%% gen_statem state functions
-export([config_error/3,
         initial_hello/3,
         user_hello/3,
         start/3,
         wait_sh/3,
         hello_middlebox_assert/3,
         hello_retry_middlebox_assert/3,
         wait_ee/3,
         wait_cert_cr/3,
         wait_cert/3,
         wait_cv/3,
         wait_finished/3,
         connection/3,
         downgrade/3
        ]).

%% Internal API
-export([maybe_send_early_data/1,
         maybe_automatic_session_resumption/1,
         maybe_generate_client_shares/1
        ]).

%%--------------------------------------------------------------------
%% gen_statem callbacks
%%--------------------------------------------------------------------
callback_mode() ->
    [state_functions, state_enter].

init([?CLIENT_ROLE, Sender, Tab, Host, Port, Socket, Options,  User, CbInfo]) ->
    State0 = #state{protocol_specific = Map} =
        tls_gen_connection_1_3:initial_state(?CLIENT_ROLE, Sender, Tab,
                                             Host, Port, Socket,
                                             Options, User, CbInfo),
    #state{static_env = #static_env{user_socket = UserSocket}} = State0,
    User ! {self(), user_socket, UserSocket},
    put(tls_role, client),
    try
	State = ssl_gen_statem:init_ssl_config(State0#state.ssl_options,
                                          ?CLIENT_ROLE, State0),
        tls_gen_connection:initialize_tls_sender(State),
        gen_statem:enter_loop(?MODULE, [], initial_hello, State)
    catch throw:Error ->
            EState = State0#state{protocol_specific = Map#{error => Error}},
            gen_statem:enter_loop(?MODULE, [], config_error, EState)
    end.
terminate(Reason, StateName, State) ->
    ssl_gen_statem:terminate(Reason, StateName, State).

format_status(Data) ->
    ssl_gen_statem:format_status(Data).

code_change(_OldVsn, StateName, State, _) ->
    {ok, StateName, State}.

%--------------------------------------------------------------------
%% state functions
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
-spec initial_hello(gen_statem:event_type(),
	   {start, timeout()} | term(), #state{}) ->
		   gen_statem:state_function_result().
%%--------------------------------------------------------------------
initial_hello(enter, _, State) ->
    {keep_state, State};
initial_hello(Type, Event, State) ->
    tls_client_connection:initial_hello(Type, Event, State).

%%--------------------------------------------------------------------
-spec config_error(gen_statem:event_type(),
	   {start, timeout()} | term(), #state{}) ->
		   gen_statem:state_function_result().
%%--------------------------------------------------------------------
config_error(enter, _, State) ->
    {keep_state, State};
config_error(Type, Event, State) ->
    ssl_gen_statem:config_error(Type, Event, State).

%%--------------------------------------------------------------------
-spec user_hello(gen_statem:event_type(),
                 term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
user_hello(enter, _, State) ->
    {keep_state, State};
user_hello({call, From}, cancel, State) ->
    gen_statem:reply(From, ok),
    ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED,
                                               user_canceled),
                                    user_hello, State);
user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
           #state{handshake_env =  #handshake_env{continue_status = pause} = HSEnv,
                  ssl_options = Options0} = State0) ->
    try ssl_config:update_options(NewOptions, ?CLIENT_ROLE, Options0) of
        Options ->
            State = ssl_gen_statem:ssl_config(Options, ?CLIENT_ROLE, State0),
            {next_state, wait_sh, State#state{recv = State#state.recv#recv{from = From},
                                              handshake_env =
                                                  HSEnv#handshake_env{continue_status
                                                                      = continue}
                                             },
             [{{timeout, handshake}, Timeout, close}]}
    catch
        throw:{error, Reason} ->
            gen_statem:reply(From, {error, Reason}),
            ssl_gen_statem:handle_own_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR, Reason),
                                            user_hello, State0)
    end;
user_hello(Type, Msg, State) ->
    tls_gen_connection_1_3:user_hello(Type, Msg, State).

%%--------------------------------------------------------------------
-spec start(gen_statem:event_type(),
                #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
start(enter, _, State0) ->
    State = tls_gen_connection_1_3:handle_middlebox(State0),
    {next_state, ?STATE(start), State,[]};
start(internal = Type, #change_cipher_spec{} = Msg, State) ->
   tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
                                                    ?STATE(start), State);
start(internal,
      #server_hello{extensions =
                        #{server_hello_selected_version :=
                              #server_hello_selected_version{selected_version
                                                             = Version}}}
      = ServerHello,
      #state{ssl_options = #{handshake := full,
                             versions := SupportedVersions}} = State) ->
    case tls_record:is_acceptable_version(Version, SupportedVersions) of
        true ->
            handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
        false ->
            ssl_gen_statem:handle_own_alert(
              ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?STATE(start), State)
    end;
start(internal, #server_hello{extensions =
                                  #{server_hello_selected_version :=
                                        #server_hello_selected_version{
                                           selected_version = Version}}
                              = Extensions},
      #state{ssl_options = #{versions := SupportedVersions},
             recv = #recv{from = From} = Recv,
             handshake_env = #handshake_env{continue_status = pause}}
      = State) ->
    case tls_record:is_acceptable_version(Version, SupportedVersions) of
        true ->
            {next_state, user_hello,
             State#state{recv=Recv#recv{from = undefined}},
             [{postpone, true},
              {reply, From, {ok, Extensions}}]};
        false ->
            ssl_gen_statem:handle_own_alert(
              ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?STATE(start), State)
    end;
start(internal, #server_hello{} = ServerHello,
      #state{handshake_env =
                 #handshake_env{continue_status = continue}} = State) ->
    handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State);
start(internal, #server_hello{}, State0) ->
    %% Missing mandantory TLS-1.3 extensions,
    %%so it is a previous version hello.
    ssl_gen_statem:handle_own_alert(
      ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION), ?STATE(start), State0);
start(internal, {protocol_record, #ssl_tls{type = ?APPLICATION_DATA}}, State) ->
    Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_initial_handshake),
    ssl_gen_statem:handle_own_alert(Alert, ?STATE(start), State);
start(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(start), State);
start(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(start), State).

%%--------------------------------------------------------------------
-spec wait_sh(gen_statem:event_type(),
                 #server_hello{} | #change_cipher_spec{} | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_sh(enter, _, State0) ->
    State = tls_gen_connection_1_3:handle_middlebox(State0),
    {next_state, wait_sh, State,[]};
wait_sh(internal = Type, #change_cipher_spec{} = Msg, State)->
   tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
                                                    ?STATE(wait_sh), State);
wait_sh(internal, #server_hello{extensions = Extensions},
        #state{handshake_env = #handshake_env{continue_status = pause},
               recv = #recv{from = From} = Recv
              } = State) ->
    {next_state, user_hello,
     State#state{recv = Recv#recv{from = undefined}},
     [{postpone, true},{reply, From, {ok, Extensions}}]};
wait_sh(internal, #server_hello{session_id = ?EMPTY_ID} = Hello,
        #state{session = #session{session_id = ?EMPTY_ID},
               ssl_options = #{middlebox_comp_mode := false}} = State0) ->
    case handle_server_hello(Hello, State0) of
         #alert{} = Alert ->
            ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_sh), State0);
        {State1, start, ServerHello} ->
            %% hello_retry_request: go to start
             {next_state, start, State1, [{next_event, internal, ServerHello}]};
        {State1, wait_ee} ->
             tls_gen_connection:next_event(wait_ee, no_record, State1)
    end;
wait_sh(internal, #server_hello{session_id = SessionId} = Hello,
        #state{session = #session{session_id = SessionId},
               protocol_specific = PS,
               ssl_options = SSLOpts} = State0)
  when not is_map_key(middlebox_comp_mode, SSLOpts) ->
    IsRetry = maps:get(hello_retry, PS, false),
    case handle_server_hello(Hello, State0) of
        #alert{} = Alert ->
            ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_sh), State0);
        {State1 = #state{}, start, ServerHello} ->
            %% hello_retry_request
            {next_state, start, State1, [{next_event, internal, ServerHello}]};
        {State1, wait_ee} when IsRetry == true ->
            tls_gen_connection:next_event(wait_ee, no_record, State1);
        {State1, wait_ee} when IsRetry == false ->
            tls_gen_connection:next_event(hello_middlebox_assert,
                                          no_record, State1)
    end;
wait_sh(internal, #server_hello{},
        #state{ssl_options = SSLOpts} = State0)
  when not is_map_key(middlebox_comp_mode, SSLOpts) ->
    %% RFC 8446 §4.1.3: legacy_session_id_echo does not match — abort.
    Alert = ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, session_id_echo_mismatch),
    ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_sh), State0);
wait_sh(internal, {protocol_record, #ssl_tls{type = ?APPLICATION_DATA}}, State) ->
    Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_initial_handshake),
    ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_sh), State);
wait_sh(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(wait_sh), State);
wait_sh(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(wait_sh), State).

%%--------------------------------------------------------------------
-spec hello_middlebox_assert(gen_statem:event_type(),
                             #change_cipher_spec{} | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
hello_middlebox_assert(enter, _, State) ->
    {keep_state, State};
hello_middlebox_assert(internal, #change_cipher_spec{}, State) ->
    tls_gen_connection:next_event(wait_ee, no_record, State);
hello_middlebox_assert(internal = Type, #encrypted_extensions{} = Msg,
                       #state{ssl_options = #{log_level := Level}} = State) ->
    ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message",
                                     reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION),
    ssl_gen_statem:handle_common_event(Type, Msg, hello_middlebox_assert, State);
hello_middlebox_assert(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(hello_middlebox_assert), State);
hello_middlebox_assert(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(hello_middlebox_assert), State).

%%--------------------------------------------------------------------
-spec hello_retry_middlebox_assert(gen_statem:event_type(),
                                   #server_hello{} | #change_cipher_spec{}
                                  | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
hello_retry_middlebox_assert(enter, _, State) ->
    {keep_state, State};
hello_retry_middlebox_assert(internal, #change_cipher_spec{}, State) ->
    tls_gen_connection:next_event(wait_sh, no_record, State);
hello_retry_middlebox_assert(internal = Type, #server_hello{} = Msg,
                             #state{ssl_options = #{log_level := Level}} = State) ->
    ssl_logger:log(warning, Level, #{description => "Failed to assert middlebox server message",
                                     reason => [{missing, #change_cipher_spec{}}]}, ?LOCATION),
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(hello_retry_middlebox_assert), State);
hello_retry_middlebox_assert(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(hello_retry_middlebox_assert), State);
hello_retry_middlebox_assert(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(hello_retry_middlebox_assert), State).

%%--------------------------------------------------------------------
-spec wait_ee(gen_statem:event_type(),
              #encrypted_extensions{} | #change_cipher_spec{}
             | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_ee(enter, _, State0) ->
    State = tls_gen_connection_1_3:handle_middlebox(State0),
    {next_state, ?STATE(wait_ee), State,[]};
wait_ee(internal = Type, #change_cipher_spec{} = Msg, State) ->
   tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
                                                    wait_ee, State);
wait_ee(internal, #encrypted_extensions{extensions = Extensions}, State0) ->
    case handle_encrypted_extensions(Extensions, State0) of
        #alert{} = Alert ->
            ssl_gen_statem:handle_own_alert(Alert, wait_ee, State0);
        {State, NextState} ->
            tls_gen_connection:next_event(NextState, no_record, State)
    end;
wait_ee(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(wait_ee), State);
wait_ee(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(wait_ee), State).

%%--------------------------------------------------------------------
-spec wait_cert_cr(gen_statem:event_type(),
                   term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_cert_cr(enter, _, State0) ->
    State = tls_gen_connection_1_3:handle_middlebox(State0),
    {next_state, ?STATE(wait_cert_cr), State,[]};
wait_cert_cr(internal = Type, #change_cipher_spec{} = Msg, State) ->
    tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
                                                     wait_cert_cr, State);
wait_cert_cr(internal, #certificate_1_3{} = Certificate, State0) ->
    case handle_certificate(Certificate, State0) of
        {#alert{} = Alert, State} ->
            ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_cert_cr), State);
        {State1, NextState} ->
            tls_gen_connection:next_event(NextState, no_record, State1)
    end;
wait_cert_cr(internal, #certificate_request_1_3{} = CertificateRequest,
             State0) ->
    case handle_certificate_request(CertificateRequest, State0) of
        #alert{} = Alert ->
            ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_cert_cr), State0);
        {State1, NextState} ->
            tls_gen_connection:next_event(NextState, no_record, State1)
   end;
wait_cert_cr(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(wait_cert_cr), State);
wait_cert_cr(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(wait_cert_cr), State).

%%--------------------------------------------------------------------
-spec wait_cert(gen_statem:event_type(),
                term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_cert(Type, Msg, State) ->
    tls_gen_connection_1_3:wait_cert(Type, Msg, State).

%%--------------------------------------------------------------------
-spec wait_cv(gen_statem:event_type(),
              term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_cv(internal,
          #certificate_verify_1_3{} = CertificateVerify, State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        State1 = Maybe(tls_handshake_1_3:verify_signature_algorithm(State0,
                                                                    CertificateVerify)),
        {State, NextState}
            = Maybe(tls_handshake_1_3:verify_certificate_verify(State1,
                                                                CertificateVerify)),
        tls_gen_connection:next_event(NextState, no_record, State)
    catch
        {Ref, {#alert{} = Alert, AState}} ->
            ssl_gen_statem:handle_own_alert(Alert, ?STATE(wait_cv), AState)
    end;
wait_cv(Type, Msg, State) ->
    tls_gen_connection_1_3:wait_cv(Type, Msg, State).

%%--------------------------------------------------------------------
-spec wait_finished(gen_statem:event_type(),
                  #finished{} | #change_cipher_spec{} | term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
wait_finished(enter, _, State0) ->
    State = tls_gen_connection_1_3:handle_middlebox(State0),
    {next_state, ?STATE(wait_finished), State,[]};
wait_finished(internal = Type, #change_cipher_spec{} = Msg, State) ->
    tls_gen_connection_1_3:handle_change_cipher_spec(Type, Msg,
                                                     ?STATE(wait_finished), State);
wait_finished(internal,
              #finished{verify_data = VerifyData},
              #state{static_env = #static_env{protocol_cb = Connection,
                                              role = Role},
                     ssl_options = SSLOpts}
              = State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        Maybe(tls_handshake_1_3:validate_finished(State0, VerifyData)),
        %% D.4.  Middlebox Compatibility Mode
        State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0,
                                                                       first),
        %% Signal change of cipher
        State2 = maybe_send_end_of_early_data(State1),
        %% Maybe send Certificate + CertificateVerify
        State3 = Maybe(maybe_queue_cert_cert_cv(State2)),
        Finished = tls_handshake_1_3:finished(State3),
        %% Encode Finished
        State4 = Connection:queue_handshake(Finished, State3),
        %% Send first flight
        {State5, _} = Connection:send_handshake_flight(State4),
        State6 = tls_handshake_1_3:calculate_write_traffic_secrets(State5),
        State7 = tls_handshake_1_3:calculate_read_traffic_secrets(State6),
        State8 = tls_handshake_1_3:maybe_calculate_resumption_master_secret(State7),
        State9 = ssl_record:step_encryption_state(State8),
        State10 = tls_handshake_1_3:prepare_connection(State9),
        {Record, State} =
            ssl_gen_statem:prepare_connection(State10, tls_gen_connection),
        KeepSecrets = maps:get(keep_secrets, SSLOpts, false),
        tls_gen_connection_1_3:maybe_traffic_keylog_1_3(KeepSecrets, Role,
                                                        State#state.connection_states, 0),
        tls_gen_connection:next_event(connection, Record, State,
                                      [{{timeout, handshake}, cancel}])
    catch
        {Ref, #alert{} = Alert} ->
            ssl_gen_statem:handle_own_alert(Alert, wait_finished, State0)
    end;
wait_finished(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, ?STATE(wait_finished), State);
wait_finished(Type, Msg, State) ->
    ssl_gen_statem:handle_common_event(Type, Msg, ?STATE(wait_finished), State).
%%--------------------------------------------------------------------
-spec connection(gen_statem:event_type(),
                 term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection(enter, _, #state{ssl_options = Opts} = State) ->
    case maps:get(keep_secrets, Opts, undefined) of
        {keylog_hs, _} ->
            %% Mitigate consequences of keylog_hs being activated, as
            %% this forces the client to remember secrets longer, that
            %% is the client certification can fail after the client
            %% reached connection state.
            {next_state, ?STATE(connection), State, [{timeout, 1000, forget}]};
        _ ->
            {keep_state, State}
    end;
connection(timeout, forget, State) ->
    {next_state, ?STATE(connection), tls_handshake_1_3:forget_master_secret(State)};
connection(info, Msg, State) ->
    tls_gen_connection:gen_info(Msg, connection, State);
connection(Type, Msg, State) ->
    tls_gen_connection_1_3:connection(Type, Msg, State).

%%--------------------------------------------------------------------
-spec downgrade(gen_statem:event_type(),
                term(), #state{}) ->
          gen_statem:state_function_result().
%%--------------------------------------------------------------------
downgrade(Type, Msg, State) ->
    tls_gen_connection_1_3:downgrade(Type, Msg, State).

%%--------------------------------------------------------------------
%% Internal API
%%--------------------------------------------------------------------
maybe_send_early_data(#state{
                         handshake_env =
                             #handshake_env{tls_handshake_history = {Hist, _}},
                         protocol_specific = #{sender := _Sender},
                         ssl_options = #{versions := [?TLS_1_3|_],
                                         use_ticket := UseTicket,
                                         session_tickets := SessionTickets,
                                         early_data := EarlyData} = _SslOpts0
                        } = State0) when UseTicket =/= [undefined] andalso
                                         EarlyData =/= undefined ->
    %% D.4.  Middlebox Compatibility Mode
    State1 = tls_gen_connection_1_3:maybe_queue_change_cipher_spec(State0, last),
    %% Early traffic secret
    EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
    case tls_handshake_1_3:get_pre_shared_key_early_data(SessionTickets,
                                                         UseTicket) of
        {ok, {PSK, Cipher, HKDF, MaxSize}} when EarlyDataSize =< MaxSize ->
            State2 =
                tls_handshake_1_3:calculate_client_early_traffic_secret(Hist,
                                                                        PSK,
                                                                        Cipher,
                                                                        HKDF,
                                                                        State1),
            %% Set 0-RTT traffic keys for sending early_data and EndOfEarlyData
            State3 = ssl_record:step_encryption_state_write(State2),
            {ok, tls_handshake_1_3:encode_early_data(Cipher, State3)};
        {ok, {_, _, _, MaxSize}} ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                               {too_much_early_data, {max, MaxSize}})};
        {error, Alert} ->
            {error, Alert}
    end;
maybe_send_early_data(State) ->
    {ok, State}.

maybe_send_end_of_early_data(
  #state{
     handshake_env = #handshake_env{early_data_accepted = true},
     protocol_specific = #{sender := _Sender},
     ssl_options = #{versions := [?TLS_1_3|_],
                     use_ticket := UseTicket,
                     early_data := EarlyData},
     static_env = #static_env{protocol_cb = Connection}
    } = State0) when UseTicket =/= [undefined] andalso
                     EarlyData =/= undefined ->
    %% EndOfEarlydata is encrypted with the 0-RTT traffic keys
    State1 = Connection:queue_handshake(#end_of_early_data{}, State0),
    %% Use handshake keys after EndOfEarlyData is sent
    ssl_record:step_encryption_state_write(State1);
maybe_send_end_of_early_data(State) ->
    State.

%% Configure a suitable session ticket
maybe_automatic_session_resumption(#state{ssl_options =
                                              #{versions := [Version|_],
                                                ciphers := UserSuites,
                                                early_data := EarlyData,
                                                session_tickets :=
                                                    SessionTickets,
                                                server_name_indication := SNI}
                                          = SslOpts0
                                         } = State0)
  when ?TLS_GTE(Version, ?TLS_1_3) andalso
       SessionTickets =:= auto ->
    AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version),
    HashAlgos = cipher_hash_algos(AvailableCipherSuites),
    Ciphers = tls_handshake_1_3:ciphers_for_early_data(AvailableCipherSuites),
    %% Find a pair of tickets KeyPair = {Ticket0, Ticket2} where
    %% Ticket0 satisfies requirements for early_data and session
    %% resumption while Ticket2 can only be used for session
    %% resumption.
    EarlyDataSize = tls_handshake_1_3:early_data_size(EarlyData),
    KeyPair =
        tls_client_ticket_store:find_ticket(self(), Ciphers, HashAlgos,
                                            SNI, EarlyDataSize),
    UseTicket = tls_handshake_1_3:choose_ticket(KeyPair, EarlyData),
    tls_client_ticket_store:lock_tickets(self(), [UseTicket]),
    State = State0#state{ssl_options = SslOpts0#{use_ticket => [UseTicket]}},
    {[UseTicket], State};
maybe_automatic_session_resumption(#state{
                                      ssl_options = #{use_ticket := UseTicket}
                                     } = State) ->
    {UseTicket, State}.

maybe_resumption(#state{handshake_env =
                            #handshake_env{resumption = true}} = State) ->
    {error, {State, wait_finished}};
maybe_resumption(_) ->
    ok.

maybe_generate_client_shares(#{versions := [?TLS_1_3|_],
                               psk_groups := Groups}) ->
    %% Default will be the list of only the most preferred supported group
    generate_client_shares(Groups);
maybe_generate_client_shares(_) ->
    undefined.

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
generate_client_shares(Groups) ->
    KeyShareEntry =
        fun (Group) ->
                #key_share_entry{group = Group,
                                 key_exchange = tls_handshake_1_3:generate_kex_keys(Group)}
        end,
    ClientShares = lists:map(KeyShareEntry, Groups),
    #key_share_client_hello{client_shares = ClientShares}.


handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello, State0) ->
    case do_handle_exlusive_1_3_hello_or_hello_retry_request(ServerHello,
                                                             State0) of
        #alert{} = Alert ->
            ssl_gen_statem:handle_own_alert(Alert, start, State0);
        {State, NextState} ->
            {next_state, NextState, State, []}
    end.

do_handle_exlusive_1_3_hello_or_hello_retry_request(
  #server_hello{cipher_suite = SelectedCipherSuite,
                random = Random,
                session_id = SessionId,
                extensions = Extensions},
  #state{static_env = #static_env{host = Host,
                                  port = Port,
                                  cert_db = CertDbHandle,
                                  cert_db_ref = CertDbRef,
                                  protocol_cb = Connection,
                                  transport_cb = Transport,
                                  socket = Socket},
         handshake_env = #handshake_env{renegotiation = {Renegotiation, _},
                                        stapling_state = StaplingState},
         connection_env = #connection_env{negotiated_version =
                                              NegotiatedVersion},
         protocol_specific = PS,
         ssl_options = #{ciphers := ClientCiphers,
                         supported_groups := ClientGroups0,
                         use_ticket := UseTicket,
                         session_tickets := SessionTickets,
                         log_level := LogLevel} = SslOpts,
                              session = Session0,
         connection_states = ConnectionStates0
        } = State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        ClientGroups =
            Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
        Cookie = maps:get(cookie, Extensions, undefined),

        KeyShare = maps:get(key_share, Extensions, undefined),
        SelectedGroup = server_group(KeyShare),

        %% Upon receipt of this extension in a HelloRetryRequest, the client
        %% MUST verify that (1) the selected_group field corresponds to a group
        %% which was provided in the "supported_groups" extension in the
        %% original ClientHello and (2) the selected_group field does not
        %% correspond to a group which was provided in the "key_share" extension
        %% in the original ClientHello.  If either of these checks fails, then
        %% the client MUST abort the handshake with an "illegal_parameter"
        %% alert.
        case KeyShare of
            #key_share_hello_retry_request{} ->
                OfferedKeyShareGroups = maps:get(psk_groups, SslOpts, [hd(ClientGroups)]),
                Maybe(validate_selected_group(SelectedGroup, ClientGroups,
                                              OfferedKeyShareGroups));
            _ ->
                ok
        end,
        Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),

        %% Otherwise, when sending the new ClientHello, the client MUST
        %% replace the original "key_share" extension with one containing only a
        %% new KeyShareEntry for the group indicated in the selected_group field
        %% of the triggering HelloRetryRequest.
        ClientKeyShare = generate_client_shares([SelectedGroup]),
        TicketData =
            tls_handshake_1_3:get_ticket_data(self(), SessionTickets, UseTicket),
        OcspNonce = maps:get(ocsp_nonce, StaplingState, undefined),
        Hello0 = tls_handshake:client_hello(Host, Port,
                                            ConnectionStates0, SslOpts,
                                            SessionId, Renegotiation,
                                            ClientKeyShare,
                                            TicketData, OcspNonce,
                                            CertDbHandle, CertDbRef),
        %% Echo cookie received in HelloRetryrequest
        Hello1 = tls_handshake_1_3:maybe_add_cookie_extension(Cookie, Hello0),

        %% Update state
        State1 =
            tls_handshake_1_3:update_start_state(State0,
                                                 #{cipher => SelectedCipherSuite,
                                                   key_share => ClientKeyShare,
                                                   session_id => SessionId,
                                                   group => SelectedGroup,
                                                   random => Random}),

        %% Replace ClientHello1 with a special synthetic handshake message
        State2 = tls_handshake_1_3:replace_ch1_with_message_hash(State1),
        #state{handshake_env =
                   #handshake_env{tls_handshake_history = HHistory0}} = State2,

        %% Update pre_shared_key extension with binders (TLS 1.3)
        Hello =
            tls_handshake_1_3:maybe_add_binders(Hello1, HHistory0,
                                                TicketData, NegotiatedVersion),

        {BinMsg0, ConnectionStates, HHistory} =
            Connection:encode_handshake(Hello,
                                        NegotiatedVersion,
                                        ConnectionStates0,
                                        HHistory0),

        %% D.4.  Middlebox Compatibility Mode
        {#state{handshake_env = HsEnv} = State3, BinMsg} =
            tls_gen_connection_1_3:maybe_prepend_change_cipher_spec(State2,
                                                                    BinMsg0),

        tls_socket:send(Transport, Socket, BinMsg),
        ssl_logger:debug(LogLevel, outbound, 'handshake', Hello),
        ssl_logger:debug(LogLevel, outbound, 'record', BinMsg),

        State = State3#state{
                  connection_states = ConnectionStates,
                  session = Session0#session{session_id = Hello#client_hello.session_id},
                  handshake_env = HsEnv#handshake_env{tls_handshake_history = HHistory,
                                                      key_share = ClientKeyShare}
                  },

        %% If it is a hello_retry and middlebox mode is
        %% used assert the change_cipher_spec  message
        %% that the server should send next
        case (maps:get(hello_retry, PS, false)) andalso
            (maps:get(middlebox_comp_mode, SslOpts, true))
        of
            true ->
                {State, hello_retry_middlebox_assert};
            false ->
                {State, wait_sh}
        end
    catch
        {Ref, #alert{} = Alert} ->
            Alert
    end.

handle_server_hello(#server_hello{cipher_suite = SelectedCipherSuite,
                                  random = Random,
                                  session_id = SessionId,
                                  extensions = Extensions} = ServerHello,
                    #state{handshake_env = #handshake_env{key_share = ClientKeyShare},
                           ssl_options = #{ciphers := ClientCiphers,
                                           supported_groups := ClientGroups0,
                                           session_tickets := SessionTickets,
                                           use_ticket := UseTicket}} = State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        ClientGroups =
            Maybe(tls_handshake_1_3:get_supported_groups(ClientGroups0)),
        ServerKeyShare = server_share(maps:get(key_share, Extensions)),
        ServerPreSharedKey = maps:get(pre_shared_key, Extensions, undefined),

        %% RFC 8446 §4.1.4: If the version in the second ServerHello
        %% differs from the HRR, abort with illegal_parameter.
        Maybe(validate_server_version(Extensions)),

        %% Go to state 'start' if server replies with 'HelloRetryRequest'.
        Maybe(tls_handshake_1_3:maybe_hello_retry_request(ServerHello, State0)),

        %% Resumption and PSK
        State1 = tls_gen_connection_1_3:handle_resumption(State0,
                                                          ServerPreSharedKey),

        Maybe(validate_cipher_suite(SelectedCipherSuite, ClientCiphers)),
        Maybe(validate_server_key_share(ClientGroups, ServerKeyShare)),

        %% Get server public key
        #key_share_entry{group = SelectedGroup,
                         key_exchange = ServerPublicKey} = ServerKeyShare,

        ClientPrivateKey =
            client_private_key(SelectedGroup,
                               ClientKeyShare#key_share_client_hello.client_shares),
        %% Update state
        State2 = tls_handshake_1_3:update_start_state(State1,
                                    #{cipher => SelectedCipherSuite,
                                     key_share => ClientKeyShare,
                                     session_id => SessionId,
                                     group => SelectedGroup,
                                     peer_public_key => ServerPublicKey,
                                     random => Random}),

        #state{connection_states = ConnectionStates} = State2,
        #{security_parameters := SecParamsR} =
        ssl_record:pending_connection_state(ConnectionStates, read),
        #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR,

        PSK = Maybe(tls_handshake_1_3:get_pre_shared_key(SessionTickets,
                                                         UseTicket,
                                                         HKDFAlgo,
                                                         ServerPreSharedKey)),
        State3 =
            tls_handshake_1_3:calculate_handshake_secrets(ServerPublicKey,
                                                          ClientPrivateKey,
                                                          SelectedGroup,
                                                          PSK, State2),
        State4 = ssl_record:step_encryption_state_read(State3),
        {State4, wait_ee}
    catch
        {Ref, {State, StateName, ServerHello}} ->
            {State, StateName, ServerHello};
        {Ref, #alert{} = Alert} ->
            Alert
    end.

handle_encrypted_extensions(Extensions, State0) ->
    {Ref, Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        ALPNProtocol0 = maps:get(alpn, Extensions, undefined),
        ALPNProtocol = Maybe(decode_alpn(ALPNProtocol0)),
        EarlyDataIndication = maps:get(early_data, Extensions, undefined),

        %% RFC 6066: handle received/expected maximum fragment length
        Maybe(maybe_max_fragment_length(Extensions, State0)),

        %% Check if early_data is accepted/rejected
        State1 = maybe_check_early_data_indication(EarlyDataIndication, State0),

        %% Go to state 'wait_finished' if using PSK.
        Maybe(maybe_resumption(State1)),

        %% Update state
        #state{handshake_env = HsEnv} = State1,
        State2 = State1#state{handshake_env =
                                  HsEnv#handshake_env{alpn = ALPNProtocol}},
        {State2, wait_cert_cr}
    catch
        {Ref, #alert{} = Alert} ->
            Alert;
        {Ref, {_, _} = Next} ->
            Next
    end.

handle_certificate(#certificate_1_3{} = Certificate, State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        Maybe(tls_handshake_1_3:process_certificate(Certificate, State0))
    catch
        {Ref, #alert{} = Alert} ->
            {Alert, State0};
        {Ref, {#alert{} = Alert, State}} ->
            {Alert, State}
    end.
handle_certificate_request(#certificate_request_1_3{} =
                               CertificateRequest, State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        Maybe(tls_handshake_1_3:process_certificate_request(
                CertificateRequest, State0))
    catch
        {Ref, #alert{} = Alert} ->
            {Alert, State0}
    end.

server_group(undefined) ->
    undefined;
server_group(#key_share_server_hello{server_share = #key_share_entry{group = Group}}) ->
    Group;
server_group(#key_share_hello_retry_request{selected_group = Group}) ->
    Group.

server_share(#key_share_server_hello{server_share = Share}) ->
    Share;
server_share(#key_share_hello_retry_request{selected_group = Share}) ->
    Share.

client_private_key(Group, ClientShares) ->
    case lists:keysearch(Group, 2, ClientShares) of
        {value, #key_share_entry{key_exchange =
                                     PrivateKey = #'ECPrivateKey'{}}} ->
            PrivateKey;
        {value, #key_share_entry{key_exchange = {#'ECPrivateKey'{} = PrivateKey1, {_, PrivateKey2}}}} ->
            {PrivateKey1, PrivateKey2};
        {value, #key_share_entry{key_exchange = {{_, PrivateKey1}, {_, PrivateKey2}}}} ->
            {PrivateKey1, PrivateKey2};
        {value, #key_share_entry{key_exchange = {_, PrivateKey}}} ->
            PrivateKey;
        false ->
            no_suitable_key
    end.

maybe_check_early_data_indication(EarlyDataIndication,
                                  #state{
                                     handshake_env = HsEnv,
                                     ssl_options = #{versions := [?TLS_1_3|_],
                                                     use_ticket := UseTicket,
                                                     early_data := EarlyData}
                                    } = State)
  when UseTicket =/= [undefined] andalso
       EarlyData =/= undefined andalso
       EarlyDataIndication =/= undefined ->
    signal_user_early_data(State, accepted),
    State#state{handshake_env = HsEnv#handshake_env{early_data_accepted = true}};
maybe_check_early_data_indication(EarlyDataIndication,
                                  #state{
                                     protocol_specific = #{sender := _Sender},
                                     ssl_options = #{versions := [?TLS_1_3|_],
                                                     use_ticket := UseTicket,
                                                     early_data := EarlyData}
                                     = _SslOpts0
                                    } = State)
  when UseTicket =/= [undefined] andalso
       EarlyData =/= undefined andalso
       EarlyDataIndication =:= undefined ->
    signal_user_early_data(State, rejected),
    %% Use handshake keys if early_data is rejected.
    ssl_record:step_encryption_state_write(State);
maybe_check_early_data_indication(_, State) ->
    %% Use handshake keys if there is no early_data.
    ssl_record:step_encryption_state_write(State).

signal_user_early_data(#state{
                          connection_env = #connection_env{user_application = {_, User}},
                          static_env = #static_env{user_socket = UserSocket}},
                       Result) ->
    User ! {ssl, UserSocket, {early_data, Result}}.

maybe_max_fragment_length(Extensions, State) ->
    ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined),
    ClientMaxFragEnum = ssl_handshake:max_frag_enum(
                          maps:get(max_fragment_length,
                                   State#state.ssl_options, undefined)),
    if ServerMaxFragEnum == ClientMaxFragEnum ->
            ok;
       true ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
    end.

cipher_hash_algos(Ciphers) ->
    Fun = fun(Cipher) ->
                  #{prf := Hash} = ssl_cipher_format:suite_bin_to_map(Cipher),
                  Hash
          end,
    lists:map(Fun, Ciphers).

maybe_queue_cert_cert_cv(#state{handshake_env = #handshake_env{client_certificate_status = not_requested}}
                         = State) ->
    {ok, State};
maybe_queue_cert_cert_cv(#state{connection_states = _ConnectionStates0,
                                session = #session{session_id = _SessionId,
                                                   own_certificates = OwnCerts},
                                ssl_options = #{} = _SslOpts,
                                handshake_env =
                                    #handshake_env{tls_handshake_history =
                                                       _HHistory0},
                                static_env = #static_env{
                                                protocol_cb = Connection,
                                                cert_db = CertDbHandle,
                                                cert_db_ref = CertDbRef,
                                                socket = _Socket,
                                                transport_cb = _Transport}
                               } = State0) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        %% Create Certificate
        Certificate = Maybe(tls_handshake_1_3:certificate(OwnCerts,
                                                          CertDbHandle,
                                                          CertDbRef, <<>>,
                                                          client)),

        %% Encode Certificate
        State1 = Connection:queue_handshake(Certificate, State0),
        %% Maybe create and queue CertificateVerify
        State = Maybe(maybe_queue_cert_verify(Certificate, State1)),
        {ok, State}
    catch
        {Ref, #alert{} = Alert} ->
            {error, Alert}
    end.

%% Clients MUST send this message whenever authenticating via a certificate
%% (i.e., when the Certificate message is non-empty).
maybe_queue_cert_verify(#certificate_1_3{certificate_list = []}, State) ->
    {ok, State};
maybe_queue_cert_verify(_Certificate,
                        #state{connection_states = _ConnectionStates0,
                               session = #session{sign_alg = SignatureScheme,
                                                  private_key = CertPrivateKey},
                               static_env = #static_env{protocol_cb = Connection}
                              } = State) ->
    {Ref,Maybe} = tls_gen_connection_1_3:do_maybe(),
    try
        CertificateVerify =
            Maybe(tls_handshake_1_3:certificate_verify(CertPrivateKey,
                                                       SignatureScheme,
                                                       State, client)),
        {ok, Connection:queue_handshake(CertificateVerify, State)}
    catch
        {Ref, #alert{} = Alert} ->
            {error, Alert}
    end.

decode_alpn(undefined) ->
    {ok, undefined};
decode_alpn(Encoded) ->
    case ssl_handshake:decode_alpn(Encoded) of
        [Decoded] ->
            {ok, Decoded};
        _  ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, invalid_alpn)}
    end.

%% Verify that selected group is offered by the client.
validate_server_key_share([], _) ->
    {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)};
validate_server_key_share([Group |_ClientGroups], #key_share_entry{group = Group}) ->
    ok;
validate_server_key_share([_|ClientGroups], #key_share_entry{} = ServerKeyShare) ->
    validate_server_key_share(ClientGroups, ServerKeyShare).


%% RFC 8446 §4.1.4: supported_versions in the ServerHello must be TLS 1.3.
validate_server_version(Extensions) ->
    case maps:get(server_hello_selected_version, Extensions, undefined) of
        #server_hello_selected_version{selected_version = ?TLS_1_3} ->
            ok;
        undefined ->
            %% Missing supported_versions — not a valid TLS 1.3 ServerHello
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                               "ServerHello missing supported_versions extension")};
        _ ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                               "ServerHello supported_versions changed after HelloRetryRequest")}
    end.

validate_selected_group(undefined, _, _) ->
    %% HRR with no key_share and no cookie — RFC 8446 §4.1.4 violation
    {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                       "HelloRetryRequest contains neither key_share nor cookie")};
validate_selected_group(SelectedGroup, SupportedGroups, OfferedKeyShareGroups) ->
    %% RFC 8446 §4.2.8:
    %% (1) selected_group MUST be in supported_groups
    %% (2) selected_group MUST NOT already be in the offered key_share
    case lists:member(SelectedGroup, SupportedGroups) of
        true ->
            case lists:member(SelectedGroup, OfferedKeyShareGroups) of
                true ->
                    {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                                       "Selected group sent by the server shall not correspond to a group"
                                       " which was provided in the key_share extension")};
                false ->
                    ok
            end;
        false ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER,
                               "Selected group sent by the server shall correspond to a group"
                               " which was provided in the supported_groups extension")}
    end.

%% RFC 8446 4.1.3 ServerHello
%% A client which receives a cipher suite that was not offered MUST abort the
%% handshake with an "illegal_parameter" alert.
validate_cipher_suite(Cipher, ClientCiphers) ->
    case lists:member(Cipher, ClientCiphers) of
        true ->
            ok;
        false ->
            {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)}
    end.
