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 (sync ,
97+ {
98+ q_rev = [] % % Remaining encrypted data (iodata)
99+ }).
100+
94101-define (IS_ASYNC (Tag ), Tag =:= select ; Tag =:= completion ).
95102
96103% %%===================================================================
@@ -285,24 +292,49 @@ 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+ # sync {} ->
298+ StateData1 = flush_sync_buffer (StateData ),
299+ case StateData1 # data .buff of
300+ undefined ->
301+ send_post_handshake_data (HSData , From , connection , StateData1 , [{reply , From , ok }]);
302+ # sync {} ->
303+ {keep_state , StateData1 , [postpone ]}
304+ end
290305 end ;
291306connection ({call , From }, {ack_alert , # alert {} = Alert }, # data {buff = Buff } = StateData0 ) ->
292307 case Buff of
293308 undefined ->
294309 StateData = send_tls_alert (Alert , StateData0 ),
295310 {next_state , connection , StateData , [{reply ,From ,ok }]};
296- Async ->
297- {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]}
311+ # async {} = Async ->
312+ {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]};
313+ # sync {} ->
314+ StateData1 = flush_sync_buffer (StateData0 ),
315+ case StateData1 # data .buff of
316+ undefined ->
317+ StateData = send_tls_alert (Alert , StateData1 ),
318+ {next_state , connection , StateData , [{reply ,From ,ok }]};
319+ # sync {} ->
320+ {keep_state , StateData1 , [postpone ]}
321+ end
298322 end ;
299323connection ({call , From }, renegotiate ,
300324 # data {connection_states = #{current_write := Write }, buff = Buff } = StateData ) ->
301325 case Buff of
302326 undefined ->
303327 {next_state , handshake , StateData , [{reply , From , {ok , Write }}]};
304- Async ->
305- {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]}
328+ # async {} = Async ->
329+ {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]};
330+ # sync {} ->
331+ StateData1 = flush_sync_buffer (StateData ),
332+ case StateData1 # data .buff of
333+ undefined ->
334+ {next_state , handshake , StateData1 , [{reply , From , {ok , Write }}]};
335+ # sync {} ->
336+ {keep_state , StateData1 , [postpone ]}
337+ end
306338 end ;
307339connection ({call , From }, downgrade , # data {connection_states =
308340 #{current_write := Write }} = StateData ) ->
@@ -329,17 +361,34 @@ connection(internal, {post_handshake_data, From, HSData}, #data{buff = Buff} = S
329361 case Buff of
330362 undefined ->
331363 send_post_handshake_data (HSData , From , connection , StateData , []);
332- Async ->
333- {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]}
364+ # async {} = Async ->
365+ {next_state , async_wait , StateData # data {buff = Async # async {low = 0 }}, [postpone ]};
366+ # sync {} ->
367+ StateData1 = flush_sync_buffer (StateData ),
368+ case StateData1 # data .buff of
369+ undefined ->
370+ send_post_handshake_data (HSData , From , connection , StateData1 , []);
371+ # sync {} ->
372+ {keep_state , StateData1 , [postpone ]}
373+ end
334374 end ;
335375
336376connection (cast , # alert {} = Alert , # data {buff = Buff } = StateData0 ) ->
337377 case Buff of
338378 undefined ->
339379 StateData = send_tls_alert (Alert , StateData0 ),
340380 {next_state , connection , StateData };
341- Async ->
342- {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]}
381+ # async {} = Async ->
382+ {next_state , async_wait , StateData0 # data {buff = Async # async {low = 0 }}, [postpone ]};
383+ # sync {} ->
384+ StateData1 = flush_sync_buffer (StateData0 ),
385+ case StateData1 # data .buff of
386+ undefined ->
387+ StateData = send_tls_alert (Alert , StateData1 ),
388+ {next_state , connection , StateData };
389+ # sync {} ->
390+ {keep_state , StateData1 , [postpone ]}
391+ end
343392 end ;
344393connection (cast , {new_write , WritesState , Version , MaxFragLen },
345394 # data {connection_states = ConnectionStates0 , env = Env } = StateData ) ->
@@ -589,6 +638,12 @@ send_or_buffer(Transport, Socket, Msgs, From, #data{buff = undefined} = StateDat
589638 ok ->
590639 send_reply (From , ok ),
591640 {ok , StateData0 };
641+ {error , {timeout , RestData }} ->
642+ % % gen_tcp:send with {inet_backend, socket} returns unsent
643+ % % encrypted data on timeout. Buffer it for retry on next send.
644+ % % Reply {error, timeout} to simulate {inet_backend, inet} behavior.
645+ send_reply (From , {error , timeout }),
646+ {ok , StateData0 # data {buff = # sync {q_rev = RestData }}};
592647 {error , timeout } = Error ->
593648 % % This clause is to retain some backwards compatibility with
594649 % % inet-driver behavior for gen_tcp:send timeout. That
@@ -626,7 +681,25 @@ send_or_buffer(Transport, Socket, Msgs, From, #data{buff = undefined} = StateDat
626681 {block , StateData0 # data {buff = Async # async {reply_to = From }}}
627682 end
628683 end ;
629- % % Buffer exists, push more data to buffer
684+ % % Timeout buffer exists, flush buffered data together with new data.
685+ % % Transport is gen_tcp (not tls_socket_tcp) so only sync results.
686+ send_or_buffer (Transport , Socket , Msgs , From ,
687+ # data {buff = # sync {q_rev = BuffData }} = StateData0 ) ->
688+ case tls_socket :send (Transport , Socket , [BuffData | Msgs ], nowait ) of
689+ ok ->
690+ send_reply (From , ok ),
691+ {ok , StateData0 # data {buff = undefined }};
692+ {error , {timeout , RestData }} ->
693+ send_reply (From , {error , timeout }),
694+ {ok , StateData0 # data {buff = # sync {q_rev = RestData }}};
695+ {error , timeout } = Error ->
696+ send_reply (From , Error ),
697+ {ok , StateData0 # data {buff = undefined }};
698+ {error , _Err } = Error ->
699+ send_reply (From , Error ),
700+ Error
701+ end ;
702+ % % Async buffer exists, push more data to buffer
630703send_or_buffer (_Transport , _Socket , Msgs , From , # data {buff = Async0 } = StateData ) ->
631704 # async {high = High , size = Sz0 , q_rev = Q } = Async0 ,
632705 Sz = Sz0 + iolist_size (Msgs ),
@@ -639,6 +712,22 @@ send_or_buffer(_Transport, _Socket, Msgs, From, #data{buff = Async0} = StateData
639712 {block , StateData # data {buff = Async # async {reply_to = From }}}
640713 end .
641714
715+ % % Try to flush the #sync{} buffer. Returns the updated #data{}
716+ % % with buff set to undefined on success, or a new #sync{} on timeout.
717+ flush_sync_buffer (# data {env = # env {socket = Socket ,
718+ transport_cb = Transport },
719+ buff = # sync {q_rev = BuffData }} = StateData ) ->
720+ case tls_socket :send (Transport , Socket , BuffData , nowait ) of
721+ ok ->
722+ StateData # data {buff = undefined };
723+ {error , {timeout , RestData }} ->
724+ StateData # data {buff = # sync {q_rev = RestData }};
725+ {error , timeout } ->
726+ StateData # data {buff = undefined };
727+ {error , _ } ->
728+ StateData # data {buff = undefined }
729+ end .
730+
642731do_async_send (_Transport , _Socket , _Handle , _Nextstate , {error , Err } = Error ,
643732 # data {buff = # async {reply_to = From }} = StateData ) ->
644733 send_reply (From , Error ),
0 commit comments