-module(nquic_lib_socket).
-moduledoc false.
%% Low-level socket receive and send glue for `nquic_lib'.
%%
%% Internal glue behind the `nquic_lib' facade. Wraps the raw
%% `socket' syscalls used by the owner loop: a single non-blocking
%% `recvmsg' (with GSO-size extraction from control messages), the
%% packet send path (connected vs. unconnected, with GSO run
%% coalescing), and stale `'$socket'' message draining after a socket
%% swap. Runs in the owner process.
-include("nquic_frame.hrl").
-include("nquic_socket.hrl").
-export([
drain_stale_socket_msgs/1,
send_packets/5,
socket_recv_for_ctx/1
]).
-spec drain_stale_socket_msgs(nquic_socket:t()) -> ok.
drain_stale_socket_msgs(Socket) ->
receive
{'$socket', S, _, _} when S =/= Socket ->
drain_stale_socket_msgs(Socket)
after 0 ->
ok
end.
-spec send_one(nquic_socket:t(), nquic_socket:sockaddr(), boolean(), iodata()) ->
ok | {error, term()}.
send_one(Socket, _Peer, true, Data) ->
nquic_socket:send_connected(Socket, Data);
send_one(Socket, Peer, false, Data) ->
nquic_socket:send(Socket, Peer, Data).
-spec send_packets(
nquic_socket:t(),
nquic_socket:sockaddr(),
boolean(),
undefined | pos_integer(),
[iodata()]
) -> ok.
send_packets(_Socket, _Peer, _Connected, _GsoSize, []) ->
ok;
send_packets(Socket, _Peer, true, undefined, [Pkt | Rest]) ->
_ = nquic_socket:send_connected(Socket, Pkt),
send_packets(Socket, _Peer, true, undefined, Rest);
send_packets(Socket, Peer, false, undefined, [Pkt | Rest]) ->
_ = nquic_socket:send(Socket, Peer, Pkt),
send_packets(Socket, Peer, false, undefined, Rest);
send_packets(Socket, Peer, Connected, GsoSize, [Pkt | Rest]) ->
case iolist_size(Pkt) of
GsoSize ->
{Group, Rest1} = take_gso_run(
Rest, GsoSize, ?GSO_BATCH_BUDGET - GsoSize, [Pkt]
),
_ = send_one(Socket, Peer, Connected, Group),
send_packets(Socket, Peer, Connected, GsoSize, Rest1);
_ ->
_ = send_one(Socket, Peer, Connected, Pkt),
send_packets(Socket, Peer, Connected, GsoSize, Rest)
end.
-spec socket_recv_for_ctx(nquic_socket:t()) ->
{ok, nquic_socket:sockaddr(), binary(), undefined | pos_integer()}
| {select, nquic_socket:select_info()}
| {error, term()}.
socket_recv_for_ctx(Socket) ->
case socket:recvmsg(Socket, ?NQUIC_MAX_DATAGRAM, 256, [], nowait) of
{ok, #{addr := Source, iov := [Bin], ctrl := Ctrl}} when is_binary(Bin) ->
{ok, Source, Bin, nquic_socket:get_gso_size_from_cmsg(Ctrl)};
{ok, #{addr := Source, iov := IOV, ctrl := Ctrl}} ->
{ok, Source, iolist_to_binary(IOV), nquic_socket:get_gso_size_from_cmsg(Ctrl)};
{select, _} = S ->
S;
{error, _} = E ->
E
end.
-spec take_gso_run([iodata()], pos_integer(), integer(), [iodata(), ...]) ->
{[iodata(), ...], [iodata()]}.
take_gso_run([], _GsoSize, _Budget, Acc) ->
{lists:reverse(Acc), []};
take_gso_run([Pkt | Rest], GsoSize, Budget, Acc) ->
case iolist_size(Pkt) of
GsoSize when Budget >= GsoSize ->
take_gso_run(Rest, GsoSize, Budget - GsoSize, [Pkt | Acc]);
Smaller when Smaller < GsoSize, Budget >= Smaller ->
{lists:reverse([Pkt | Acc]), Rest};
_ ->
{lists:reverse(Acc), [Pkt | Rest]}
end.