9191 low = undefined
9292 }).
9393
94+ % % Buffer for unsent encrypted data returned by gen_tcp:send
95+ % % as {error, {timeout, RestData}} when using {inet_backend, socket}
96+ -record (rest ,
97+ {
98+ q_rev = [] % % Remaining encrypted data (iodata)
99+ }).
100+
94101-define (IS_ASYNC (Tag ), Tag =:= select ; Tag =:= completion ).
95102
96103% %%===================================================================
@@ -285,24 +292,52 @@ connection({call, From}, {post_handshake_data, HSData}, #data{buff = Buff} = Sta
285292 case Buff of
286293 undefined ->
287294 send_post_handshake_data (HSData , From , connection , StateData , [{reply , From , ok }]);
288- Async ->
289- {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]}
295+ # async {} = Async ->
296+ {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]};
297+ # rest {} ->
298+ case flush_rest_buffer (StateData ) of
299+ {ok , # data {buff = undefined } = StateData1 } ->
300+ send_post_handshake_data (HSData , From , connection , StateData1 , [{reply , From , ok }]);
301+ {ok , StateData1 } ->
302+ {keep_state , StateData1 , [postpone ]};
303+ {error , Reason , StateData1 } ->
304+ death_row_shutdown ({error , Reason }, StateData1 )
305+ end
290306 end ;
291307connection ({call , From }, {ack_alert , # alert {} = Alert }, # data {buff = Buff } = StateData0 ) ->
292308 case Buff of
293309 undefined ->
294310 StateData = send_tls_alert (Alert , StateData0 ),
295311 {next_state , connection , StateData , [{reply ,From ,ok }]};
296- Async ->
297- {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]}
312+ # async {} = Async ->
313+ {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]};
314+ # rest {} ->
315+ case flush_rest_buffer (StateData0 ) of
316+ {ok , # data {buff = undefined } = StateData1 } ->
317+ StateData = send_tls_alert (Alert , StateData1 ),
318+ {next_state , connection , StateData , [{reply ,From ,ok }]};
319+ {ok , StateData1 } ->
320+ {keep_state , StateData1 , [postpone ]};
321+ {error , Reason , StateData1 } ->
322+ death_row_shutdown ({error , Reason }, StateData1 )
323+ end
298324 end ;
299325connection ({call , From }, renegotiate ,
300326 # data {connection_states = #{current_write := Write }, buff = Buff } = StateData ) ->
301327 case Buff of
302328 undefined ->
303329 {next_state , handshake , StateData , [{reply , From , {ok , Write }}]};
304- Async ->
305- {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]}
330+ # async {} = Async ->
331+ {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]};
332+ # rest {} ->
333+ case flush_rest_buffer (StateData ) of
334+ {ok , # data {buff = undefined } = StateData1 } ->
335+ {next_state , handshake , StateData1 , [{reply , From , {ok , Write }}]};
336+ {ok , StateData1 } ->
337+ {keep_state , StateData1 , [postpone ]};
338+ {error , Reason , StateData1 } ->
339+ death_row_shutdown ({error , Reason }, StateData1 )
340+ end
306341 end ;
307342connection ({call , From }, downgrade , # data {connection_states =
308343 #{current_write := Write }} = StateData ) ->
@@ -329,17 +364,36 @@ connection(internal, {post_handshake_data, From, HSData}, #data{buff = Buff} = S
329364 case Buff of
330365 undefined ->
331366 send_post_handshake_data (HSData , From , connection , StateData , []);
332- Async ->
333- {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]}
367+ # async {} = Async ->
368+ {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]};
369+ # rest {} ->
370+ case flush_rest_buffer (StateData ) of
371+ {ok , # data {buff = undefined } = StateData1 } ->
372+ send_post_handshake_data (HSData , From , connection , StateData1 , []);
373+ {ok , StateData1 } ->
374+ {keep_state , StateData1 , [postpone ]};
375+ {error , Reason , StateData1 } ->
376+ death_row_shutdown ({error , Reason }, StateData1 )
377+ end
334378 end ;
335379
336380connection (cast , # alert {} = Alert , # data {buff = Buff } = StateData0 ) ->
337381 case Buff of
338382 undefined ->
339383 StateData = send_tls_alert (Alert , StateData0 ),
340384 {next_state , connection , StateData };
341- Async ->
342- {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]}
385+ # async {} = Async ->
386+ {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]};
387+ # rest {} ->
388+ case flush_rest_buffer (StateData0 ) of
389+ {ok , # data {buff = undefined } = StateData1 } ->
390+ StateData = send_tls_alert (Alert , StateData1 ),
391+ {next_state , connection , StateData };
392+ {ok , StateData1 } ->
393+ {keep_state , StateData1 , [postpone ]};
394+ {error , Reason , StateData1 } ->
395+ death_row_shutdown ({error , Reason }, StateData1 )
396+ end
343397 end ;
344398connection (cast , {new_write , WritesState , Version , MaxFragLen },
345399 # data {connection_states = ConnectionStates0 , env = Env } = StateData ) ->
@@ -589,6 +643,12 @@ send_or_buffer(Transport, Socket, Msgs, From, #data{buff = undefined} = StateDat
589643 ok ->
590644 send_reply (From , ok ),
591645 {ok , StateData0 };
646+ {error , {timeout , RestData }} ->
647+ % % gen_tcp:send with {inet_backend, socket} returns unsent
648+ % % encrypted data on timeout. Buffer it for retry on next send.
649+ % % Reply {error, timeout} to simulate {inet_backend, inet} behavior.
650+ send_reply (From , {error , timeout }),
651+ {ok , StateData0 # data {buff = # rest {q_rev = RestData }}};
592652 {error , timeout } = Error ->
593653 % % This clause is to retain some backwards compatibility with
594654 % % inet-driver behavior for gen_tcp:send timeout. That
@@ -626,7 +686,25 @@ send_or_buffer(Transport, Socket, Msgs, From, #data{buff = undefined} = StateDat
626686 {block , StateData0 # data {buff = Async # async {reply_to = From }}}
627687 end
628688 end ;
629- % % Buffer exists, push more data to buffer
689+ % % Rest buffer exists, flush buffered data together with new data.
690+ % % Transport is gen_tcp, no async select/completion results.
691+ send_or_buffer (Transport , Socket , Msgs , From ,
692+ # data {buff = # rest {q_rev = BuffData }} = StateData0 ) ->
693+ case tls_socket :send (Transport , Socket , [BuffData | Msgs ]) of
694+ ok ->
695+ send_reply (From , ok ),
696+ {ok , StateData0 # data {buff = undefined }};
697+ {error , {timeout , RestData }} ->
698+ send_reply (From , {error , timeout }),
699+ {ok , StateData0 # data {buff = # rest {q_rev = RestData }}};
700+ {error , timeout } = Error ->
701+ send_reply (From , Error ),
702+ {ok , StateData0 # data {buff = undefined }};
703+ {error , _Err } = Error ->
704+ send_reply (From , Error ),
705+ Error
706+ end ;
707+ % % Async buffer exists, push more data to buffer
630708send_or_buffer (_Transport , _Socket , Msgs , From , # data {buff = Async0 } = StateData ) ->
631709 # async {high = High , size = Sz0 , q_rev = Q } = Async0 ,
632710 Sz = Sz0 + iolist_size (Msgs ),
@@ -639,6 +717,22 @@ send_or_buffer(_Transport, _Socket, Msgs, From, #data{buff = Async0} = StateData
639717 {block , StateData # data {buff = Async # async {reply_to = From }}}
640718 end .
641719
720+ % % Try to flush the #rest{} buffer. Returns {ok, #data{}} on success
721+ % % or timeout, {error, Reason, #data{}} on hard send failure.
722+ flush_rest_buffer (# data {env = # env {socket = Socket ,
723+ transport_cb = Transport },
724+ buff = # rest {q_rev = BuffData }} = StateData ) ->
725+ case tls_socket :send (Transport , Socket , BuffData ) of
726+ ok ->
727+ {ok , StateData # data {buff = undefined }};
728+ {error , {timeout , RestData }} ->
729+ {ok , StateData # data {buff = # rest {q_rev = RestData }}};
730+ {error , timeout } ->
731+ {ok , StateData # data {buff = undefined }};
732+ {error , Reason } ->
733+ {error , Reason , StateData # data {buff = undefined }}
734+ end .
735+
642736do_async_send (_Transport , _Socket , _Handle , _Nextstate , {error , Err } = Error ,
643737 # data {buff = # async {reply_to = From }} = StateData ) ->
644738 send_reply (From , Error ),
0 commit comments