From d714cf095626056c16d385087259e301e1057b78 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Tue, 9 Oct 2018 15:52:15 +0200
Subject: ssl: TLS sender process needs to get updates of the socket option
 packet

If the socket option is set to {packet, 1|2|3|4}  sender process needs to
add a packet length header. If packet is changed with ssl:setopts/2 this needs
to be communicated to tls_sender.
---
 lib/ssl/src/ssl.erl               | 19 ++++++++++++++
 lib/ssl/src/tls_sender.erl        | 24 +++++++++++++++---
 lib/ssl/test/ssl_packet_SUITE.erl | 52 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 3 deletions(-)

(limited to 'lib/ssl')

diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 4cf56035ba..03a1e40bfc 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -604,6 +604,25 @@ getopts(#sslsocket{}, OptionTags) ->
 %%
 %% Description: Sets options
 %%--------------------------------------------------------------------
+setopts(#sslsocket{pid = [Pid, Sender]}, Options0) when is_pid(Pid), is_list(Options0)  ->
+    try proplists:expand([{binary, [{mode, binary}]},
+			  {list, [{mode, list}]}], Options0) of
+        Options ->
+            case proplists:get_value(packet, Options, undefined) of
+                undefined ->
+                    ssl_connection:set_opts(Pid, Options);
+                PacketOpt ->
+                    case tls_sender:setopts(Sender, [{packet, PacketOpt}]) of
+                        ok ->
+                            ssl_connection:set_opts(Pid, Options);
+                        Error ->
+                            Error
+                    end
+            end
+    catch
+        _:_ ->
+            {error, {options, {not_a_proplist, Options0}}}
+    end;
 setopts(#sslsocket{pid = [Pid|_]}, Options0) when is_pid(Pid), is_list(Options0)  ->
     try proplists:expand([{binary, [{mode, binary}]},
 			  {list, [{mode, list}]}], Options0) of
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index ec03000b33..84b861a32c 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -29,7 +29,7 @@
 
 %% API
 -export([start/0, start/1, initialize/2, send_data/2, send_alert/2,
-         send_and_ack_alert/2, renegotiate/1,
+         send_and_ack_alert/2, setopts/2, renegotiate/1,
          update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]).
 
 %% gen_statem callbacks
@@ -81,7 +81,7 @@ initialize(Pid, InitMsg) ->
     gen_statem:call(Pid, {self(), InitMsg}).
 
 %%--------------------------------------------------------------------
--spec send_data(pid(), iodata()) -> ok. 
+-spec send_data(pid(), iodata()) -> ok | {error, term()}.
 %%  Description: Send application data
 %%--------------------------------------------------------------------
 send_data(Pid, AppData) ->
@@ -97,13 +97,20 @@ send_alert(Pid, Alert) ->
     gen_statem:cast(Pid, Alert).
 
 %%--------------------------------------------------------------------
--spec send_and_ack_alert(pid(), #alert{}) -> ok.
+-spec send_and_ack_alert(pid(), #alert{}) -> _.
 %% Description: TLS connection process wants to send an Alert
 %% in the connection state and recive an ack.
 %%--------------------------------------------------------------------
 send_and_ack_alert(Pid, Alert) ->
     gen_statem:cast(Pid, {ack_alert, Alert}).
 
+%%--------------------------------------------------------------------
+-spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}.
+%%  Description: Send application data
+%%--------------------------------------------------------------------
+setopts(Pid, Opts) ->
+    call(Pid, {set_opts, Opts}).
+
 %%--------------------------------------------------------------------
 -spec renegotiate(pid()) -> {ok, WriteState::map()} | {error, closed}.
 %% Description: So TLS connection process can synchronize the 
@@ -201,6 +208,8 @@ connection({call, From}, {application_data, AppData},
         Data ->
             send_application_data(Data, From, ?FUNCTION_NAME, StateData)
     end;
+connection({call, From}, {set_opts, _} = Call, StateData) ->
+    handle_call(From, Call, ?FUNCTION_NAME, StateData);
 connection({call, From}, dist_get_tls_socket, 
            #data{protocol_cb = Connection, 
                  transport_cb = Transport,
@@ -254,6 +263,8 @@ connection(info, Msg, StateData) ->
                   StateData :: term()) ->
                          gen_statem:event_handler_result(atom()).
 %%--------------------------------------------------------------------
+handshake({call, From}, {set_opts, _} = Call, StateData) ->
+    handle_call(From, Call, ?FUNCTION_NAME, StateData);
 handshake({call, _}, _, _) ->
     {keep_state_and_data, [postpone]};
 handshake(cast, {new_write, WritesState, Version}, 
@@ -298,6 +309,9 @@ code_change(_OldVsn, State, Data, _Extra) ->
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
+handle_call(From, {set_opts, Opts}, StateName, #data{socket_options = SockOpts} = StateData) ->
+    {next_state, StateName, StateData#data{socket_options = set_opts(SockOpts, Opts)}, [{reply, From, ok}]}.
+        
 handle_info({'DOWN', Monitor, _, _, Reason}, _, 
             #data{connection_monitor = Monitor,
                   dist_handle = Handle} = StateData) when Handle =/= undefined->
@@ -364,6 +378,10 @@ encode_size_packet(Bin, Size, Max) ->
         false -> 
             <<Len:Size, Bin/binary>>
     end.
+
+set_opts(SocketOptions, [{packet, N}]) ->
+    SocketOptions#socket_options{packet = N}.
+
 time_to_renegotiate(_Data, 
 		    #{current_write := #{sequence_number := Num}}, 
 		    RenegotiateAt) ->
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index 3261244ace..ebf8ddbfac 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -141,6 +141,7 @@ socket_active_packet_tests() ->
      packet_4_active_some_big,
      packet_wait_active,
      packet_size_active,
+     packet_switch,
      %% inet header option should be deprecated!
      header_decode_one_byte_active,
      header_decode_two_bytes_active,
@@ -702,6 +703,34 @@ packet_size_passive(Config) when is_list(Config) ->
     ssl_test_lib:close(Server),
     ssl_test_lib:close(Client).
 
+
+%%--------------------------------------------------------------------
+packet_switch() ->
+    [{doc,"Test packet option {packet, 2} followd by {packet, 4}"}].
+
+packet_switch(Config) when is_list(Config) ->
+    ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+    ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+    {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+    Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+					{from, self()},
+					{mfa, {?MODULE, send_switch_packet ,["Hello World", 4]}},
+					{options, [{nodelay, true},{packet, 2} | ServerOpts]}]),
+    Port = ssl_test_lib:inet_port(Server),
+    Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+					{host, Hostname},
+					{from, self()},
+					{mfa, {?MODULE, recv_switch_packet, ["Hello World", 4]}},
+					{options, [{nodelay, true}, {packet, 2} |
+						   ClientOpts]}]),
+
+    ssl_test_lib:check_result(Client, ok),
+
+    ssl_test_lib:close(Server),
+    ssl_test_lib:close(Client).
+
+
 %%--------------------------------------------------------------------
 packet_cdr_decode() ->
     [{doc,"Test setting the packet option {packet, cdr}, {mode, binary}"}].
@@ -2286,3 +2315,26 @@ client_reject_packet_opt(Config, PacketOpt) ->
                                                          ClientOpts]}]),
     
     ssl_test_lib:check_result(Client, {error, {options, {not_supported, PacketOpt}}}).
+
+
+send_switch_packet(SslSocket, Data, NextPacket) ->
+    ssl:send(SslSocket, Data),
+    receive
+        {ssl, SslSocket, "Hello World"} ->
+            ssl:setopts(SslSocket, [{packet, NextPacket}]),
+            ssl:send(SslSocket, Data),
+            receive 
+                {ssl, SslSocket, "Hello World"} ->
+                    ok
+            end
+    end.
+recv_switch_packet(SslSocket, Data, NextPacket) ->
+    receive
+        {ssl, SslSocket, "Hello World"} ->
+            ssl:send(SslSocket, Data),
+            ssl:setopts(SslSocket, [{packet, NextPacket}]),
+            receive 
+                {ssl, SslSocket, "Hello World"} ->
+                    ssl:send(SslSocket, Data)
+            end
+    end.
-- 
cgit v1.2.3