%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2020. 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%
%%
-module(socket).
-compile({no_auto_import,[error/1]}).
%% Administrative and "global" utility functions
-export([
number_of/0,
which_sockets/0, which_sockets/1,
debug/1, socket_debug/1, use_registry/1,
info/0, info/1,
supports/0, supports/1, supports/2,
is_supported/1, is_supported/2, is_supported/3
]).
-export([
open/1, open/2, open/3, open/4,
bind/2, bind/3,
connect/1, connect/2, connect/3,
listen/1, listen/2,
accept/1, accept/2,
send/2, send/3, send/4,
sendto/3, sendto/4, sendto/5,
sendmsg/2, sendmsg/3, sendmsg/4,
recv/1, recv/2, recv/3, recv/4,
recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4,
recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5,
close/1,
shutdown/2,
setopt/3, setopt_native/3, setopt/4,
getopt/2, getopt_native/3, getopt/3,
sockname/1,
peername/1,
cancel/2
]).
-export_type([
socket/0,
select_tag/0,
select_handle/0,
select_info/0,
socket_counters/0,
socket_info/0,
domain/0,
type/0,
protocol/0,
port_number/0,
in_addr/0,
in6_addr/0,
sockaddr/0,
sockaddr_in/0,
sockaddr_in6/0,
sockaddr_un/0,
sockaddr_ll/0,
send_flag/0,
recv_flag/0,
sockopt_level/0,
otp_socket_option/0,
socket_option/0,
timeval/0,
ip_tos/0,
ip_pktinfo/0,
ipv6_pktinfo/0,
msghdr_flag/0,
msghdr/0,
cmsghdr_level/0,
cmsghdr_type/0,
cmsghdr_recv/0, cmsghdr_send/0,
ee_origin/0,
icmp_dest_unreach/0,
icmpv6_dest_unreach/0,
extended_err/0
]).
%% Also in prim_socket
-define(REGISTRY, socket_registry).
-type socket_counters() :: #{read_byte := non_neg_integer(),
read_fails := non_neg_integer(),
read_pkg := non_neg_integer(),
read_pkg_max := non_neg_integer(),
read_tries := non_neg_integer(),
read_waits := non_neg_integer(),
write_byte := non_neg_integer(),
write_fails := non_neg_integer(),
write_pkg := non_neg_integer(),
write_pkg_max := non_neg_integer(),
write_tries := non_neg_integer(),
write_waits := non_neg_integer(),
acc_success := non_neg_integer(),
acc_fails := non_neg_integer(),
acc_tries := non_neg_integer(),
acc_waits := non_neg_integer()}.
-type socket_info() :: #{domain := domain(),
type := type(),
protocol := protocol(),
ctrl := pid(),
ctype := normal | fromfd | {fromfd, integer()},
counters := socket_counters(),
num_readers := non_neg_integer(),
num_writers := non_neg_integer(),
num_acceptors := non_neg_integer(),
writable := boolean(),
readable := boolean()}.
%% We support only a subset of all domains.
-type domain() :: local | inet | inet6.
%% We support only a subset of all types.
%% RDM - Reliably Delivered Messages
-type type() :: stream | dgram | raw | rdm | seqpacket.
%% We support all protocols enumerated by getprotoent(),
%% and all of ip | ipv6 | tcp | udp | sctp that are supported
%% by the platform, even if not enumerated by getprotoent(),
%% plus native protocol numbers.
-type protocol() :: atom() | integer().
-type port_number() :: 0..65535.
-type in_addr() :: {0..255, 0..255, 0..255, 0..255}.
-type in6_flow_info() :: 0..16#FFFFF.
-type in6_scope_id() :: 0..16#FFFFFFFF.
-type in6_addr() ::
{0..65535,
0..65535,
0..65535,
0..65535,
0..65535,
0..65535,
0..65535,
0..65535}.
-type timeval() :: #{sec := integer(),
usec := integer()}.
%% If the integer value is used, its up to the caller to ensure its valid!
-type ip_tos() :: lowdelay |
throughput |
reliability |
mincost |
integer().
-type ip_pktinfo() :: #{
ifindex := non_neg_integer(), % Interface Index
spec_dst := in_addr(), % Local Address
addr := in_addr() % Header Destination address
}.
-type ipv6_pktinfo() :: #{
addr := in6_addr(),
ifindex := integer()
}.
-type sockaddr_un() :: #{family := local,
path := binary() | string()}.
-type sockaddr_in() :: #{family := inet,
port := port_number(),
%% The 'broadcast' here is the "limited broadcast"
addr := any | broadcast | loopback | in_addr()}.
-type sockaddr_in6() :: #{family := inet6,
port := port_number(),
addr := any | loopback | in6_addr(),
flowinfo := in6_flow_info(),
scope_id := in6_scope_id()}.
-type sockaddr_ll() :: #{family := packet,
protocol := non_neg_integer(),
ifindex := integer(),
pkttype := packet_type(),
hatype := non_neg_integer(),
addr := binary()}.
-type packet_type() :: host | broadcast | multicast | otherhost |
outgoing | loopback | user | kernel | fastroute |
non_neg_integer().
-type sockaddr() :: sockaddr_in() |
sockaddr_in6() |
sockaddr_un() |
sockaddr_ll().
%% otp - This option is internal to our (OTP) implementation.
%% socket - The socket layer (SOL_SOCKET).
%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?).
%% ipv6 - The IPv6 layer (SOL_IPV6).
%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP).
%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP).
%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP).
%% Int - Raw level, sent down and used "as is".
%% Its up to the caller to make sure this is correct!
-type sockopt_level() :: %otp |
socket |
ip | ipv6 | tcp | udp | sctp.% |
%non_neg_integer().
%% There are some options that are 'read-only'.
%% Should those be included here or in a special list?
%% Should we just document it and leave it to the user?
%% Or catch it in the encode functions?
%% A setopt for a readonly option leads to {error, invalid}?
%% Do we really need a sndbuf?
-type otp_socket_option() ::
debug |
iow |
controlling_process |
rcvbuf | % sndbuf |
rcvctrlbuf |
sndctrlbuf |
meta |
use_registry |
fd.
-type socket_option() ::
{Level :: socket,
Opt ::
acceptconn |
acceptfilter |
bindtodevice |
broadcast |
busy_poll |
debug |
domain |
dontroute |
error |
keepalive |
linger |
mark |
oobinline |
passcred |
peek_off |
peercred |
priority |
protocol |
rcvbuf |
rcvbufforce |
rcvlowat |
rcvtimeo |
reuseaddr |
reuseport |
rxq_ovfl |
setfib |
sndbuf |
sndbufforce |
sndlowat |
sndtimeo |
timestamp |
type} |
{Level :: ip,
Opt ::
add_membership |
add_source_membership |
block_source |
dontfrag |
drop_membership |
drop_source_membership |
freebind |
hdrincl |
minttl |
msfilter |
mtu |
mtu_discover |
multicast_all |
multicast_if |
multicast_loop |
multicast_ttl |
nodefrag |
options |
pktinfo |
recverr |
recvif |
recvdstaddr |
recvopts |
recvorigdstaddr |
recvtos |
recvttl |
retopts |
router_alert |
sndsrcaddr |
tos |
transparent |
ttl |
unblock_source} |
{Level :: ipv6,
Opt ::
addrform |
add_membership |
authhdr |
auth_level |
checksum |
drop_membership |
dstopts |
esp_trans_level |
esp_network_level |
faith |
flowinfo |
hopopts |
ipcomp_level |
join_group |
leave_group |
mtu |
mtu_discover |
multicast_hops |
multicast_if |
multicast_loop |
portrange |
pktoptions |
recverr |
recvhoplimit | hoplimit |
recvpktinfo | pktinfo |
recvtclass |
router_alert |
rthdr |
tclass |
unicast_hops |
use_min_mtu |
v6only} |
{Level :: tcp,
Opt ::
congestion |
cork |
info |
keepcnt |
keepidle |
keepintvl |
maxseg |
md5sig |
nodelay |
noopt |
nopush |
syncnt |
user_timeout} |
{Level :: udp, Opt :: cork} |
{Level :: sctp,
Opt ::
adaption_layer |
associnfo |
auth_active_key |
auth_asconf |
auth_chunk |
auth_key |
auth_delete_key |
autoclose |
context |
default_send_params |
delayed_ack_time |
disable_fragments |
hmac_ident |
events |
explicit_eor |
fragment_interleave |
get_peer_addr_info |
initmsg |
i_want_mapped_v4_addr |
local_auth_chunks |
maxseg |
maxburst |
nodelay |
partial_delivery_point |
peer_addr_params |
peer_auth_chunks |
primary_addr |
reset_streams |
rtoinfo |
set_peer_primary_addr |
status |
use_ext_recvinfo}.
%% The names of these macros match the names of corresponding
%%C functions in the NIF code, so a search will match both
%%
-define(socket_tag, '$socket').
%%
%% Our socket abstract data type
-define(socket(Ref), {?socket_tag, (Ref)}).
%%
%% Messages sent from the nif-code to erlang processes:
-define(socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}).
-opaque socket() :: ?socket(reference()).
-type send_flag() :: confirm |
dontroute |
eor |
more |
nosignal |
oob.
%% Note that not all of these flags are useful for every recv function!
%%
-type recv_flag() :: cmsg_cloexec |
errqueue |
oob |
peek |
trunc.
-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc.
-type msghdr() :: #{
%% *Optional* target address
%% Used on an unconnected socket to specify the
%% target address for a datagram.
addr := sockaddr(),
iov := [binary()],
%% The maximum size of the control buffer is platform
%% specific. It is the users responsibility to ensure
%% that its not exceeded.
ctrl := [cmsghdr_recv()] | [cmsghdr_send()],
%% Only valid with recvmsg
flags := [msghdr_flag()]
}.
%% We are able to (completely) decode *some* control message headers.
%% Even if we are able to decode both level and type, we may not be
%% able to decode the data, in which case it will be a binary.
-type cmsghdr_level() :: socket | ip | ipv6 | integer().
-type cmsghdr_type() :: credentials |
hoplevel |
origdstaddr |
pktinfo |
recvtos |
rights |
timestamp |
tos |
ttl |
integer().
-type cmsghdr_recv() ::
#{level := socket, type := timestamp, data := timeval()} |
#{level := socket, type := rights, data := binary()} |
#{level := socket, type := credentials, data := binary()} |
#{level := socket, type := integer(), data := binary()} |
#{level := ip, type := tos, data := ip_tos()} |
#{level := ip, type := recvtos, data := ip_tos()} |
#{level := ip, type := ttl, data := integer()} |
#{level := ip, type := recvttl, data := integer()} |
#{level := ip, type := pktinfo, data := ip_pktinfo()} |
#{level := ip, type := origdstaddr, data := sockaddr_in()} |
#{level := ip, type := recverr,
data := extended_err() | binary()} |
#{level := ip, type := integer(), data := binary()} |
#{level := ipv6, type := hoplevel, data := integer()} |
#{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} |
#{level := ipv6, type := recverr,
data := extended_err() | binary()} |
#{level := ipv6, type := tclass, data := integer()} |
#{level := ipv6, type := integer(), data := binary()} |
#{level := integer(), type := integer(), data := binary()}.
-type cmsghdr_send() ::
#{level := socket, type := timestamp, data := binary()} |
#{level := socket, type := rights, data := binary()} |
#{level := socket, type := credentials, data := binary()} |
#{level := socket, type := integer(), data := binary()} |
#{level := ip, type := tos,
data := ip_tos() | binary()} |
#{level := ip, type := ttl,
data := integer() | binary()} |
#{level := ip, type := integer(), data := binary()} |
#{level := ipv6, type := tclass, data := integer()} |
#{level := ipv6, type := integer(), data := binary()} |
#{level := udp, type := integer(), data := binary()} |
#{level := integer(), type := integer(), data := binary()}.
-type ee_origin() :: none | local | icmp | icmp6 | 0..16#FF.
-type icmp_dest_unreach() ::
net_unreach | host_unreach | port_unreach | frag_needed |
net_unknown | host_unknown | 0..16#FF.
-type icmpv6_dest_unreach() ::
noroute | adm_prohibited | not_neighbour | addr_unreach |
port_unreach | policy_fail | reject_route | 0..16#FF.
-type extended_err() ::
#{error := term(),
origin := icmp,
type := dest_unreach,
code := icmp_dest_unreach(),
info := 0..16#FFFFFFFF,
data := 0..16#FFFFFFFF,
offender := undefined | sockaddr()} |
#{error := term(),
origin := icmp,
type := time_exceeded | 0..16#FF,
code := 0..16#FF,
info := 0..16#FFFFFFFF,
data := 0..16#FFFFFFFF,
offender := undefined | sockaddr()} |
#{error := term(),
origin := icmp6,
type := dest_unreach,
code := icmpv6_dest_unreach(),
info := 0..16#FFFFFFFF,
data := 0..16#FFFFFFFF,
offender := undefined | sockaddr()} |
#{error := term(),
origin := icmp6,
type := pkt_toobig | time_exceeded | 0..16#FF,
code := 0..16#FF,
info := 0..16#FFFFFFFF,
data := 0..16#FFFFFFFF,
offender := undefined | sockaddr()} |
#{error := term(),
origin := ee_origin(),
type := 0..16#FF,
code := 0..16#FF,
info := 0..16#FFFFFFFF,
data := 0..16#FFFFFFFF,
offender := undefined | sockaddr()}.
-type posix() :: inet:posix().
%% ===========================================================================
%%
%% Interface term formats
%%
-opaque select_tag() :: atom().
-opaque select_handle() :: reference().
-type select_info() ::
{select_info,
SelectTag :: select_tag(),
SelectHandle :: select_handle()}.
-define(SELECT_INFO(T, R), {select_info, T, R}).
-define(SELECT(T, R), {select, ?SELECT_INFO(T, R)}).
%% ===========================================================================
%%
%% Defaults
%%
-define(ESOCK_LISTEN_BACKLOG_DEFAULT, 5).
-define(ESOCK_ACCEPT_TIMEOUT_DEFAULT, infinity).
-define(ESOCK_SEND_FLAGS_DEFAULT, []).
-define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity).
-define(ESOCK_SENDTO_FLAGS_DEFAULT, []).
-define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
-define(ESOCK_SENDMSG_FLAGS_DEFAULT, []).
-define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
-define(ESOCK_RECV_FLAGS_DEFAULT, []).
-define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity).
%% ===========================================================================
%%
%% Administrative and utility API
%%
%% ===========================================================================
%% *** number_of ***
%%
%% Interface function to the socket registry
%% returns the number of existing (and "alive") sockets.
%%
-spec number_of() -> non_neg_integer().
number_of() ->
?REGISTRY:number_of().
%% *** which_sockets/0,1 ***
%%
%% Interface function to the socket registry
%% Returns a list of all the sockets, accoring to the filter rule.
%%
-spec which_sockets() -> [socket()].
which_sockets() ->
?REGISTRY:which_sockets(fun(_) -> true end).
-spec which_sockets(FilterRule) -> [socket()] when
FilterRule :: inet | inet6 |
stream | dgram | seqpacket |
sctp | tcp | udp |
pid() |
fun((socket_info()) -> boolean()).
which_sockets(Domain)
when ((Domain =:= inet) orelse (Domain =:= inet6)) ->
?REGISTRY:which_sockets(fun(#{domain := D}) when (D =:= Domain) -> true;
(_) -> false end);
which_sockets(Type)
when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) ->
?REGISTRY:which_sockets(fun(#{type := T}) when (T =:= Type) -> true;
(_) -> false end);
which_sockets(Proto)
when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) ->
?REGISTRY:which_sockets(fun(#{protocol := P}) when (P =:= Proto) -> true;
(_) -> false end);
which_sockets(CTRL)
when is_pid(CTRL) ->
?REGISTRY:which_sockets(fun(#{ctrl := C}) when (C =:= CTRL) -> true;
(_) -> false end);
which_sockets(Filter) when is_function(Filter, 1) ->
?REGISTRY:which_sockets(Filter);
which_sockets(Other) ->
erlang:error(badarg, [Other]).
%% ===========================================================================
%%
%% Debug features
%%
%% ===========================================================================
-spec info() -> map().
%%
info() ->
prim_socket:info().
-spec debug(D :: boolean()) -> ok.
%%
debug(D) when is_boolean(D) ->
prim_socket:debug(D);
debug(D) ->
erlang:error(badarg, [D]).
-spec socket_debug(D :: boolean()) -> ok.
%%
socket_debug(D) when is_boolean(D) ->
prim_socket:socket_debug(D);
socket_debug(D) ->
erlang:error(badarg, [D]).
-spec use_registry(D :: boolean()) -> ok.
%%
use_registry(D) when is_boolean(D) ->
prim_socket:use_registry(D).
%% ===========================================================================
%%
%% info - Get miscellaneous information about a socket.
%%
%% Generates a list of various info about the socket, such as counter values.
%%
%% Do *not* call this function often.
%%
%% ===========================================================================
-spec info(Socket) -> socket_info() when
Socket :: socket().
%%
info(?socket(SockRef)) when is_reference(SockRef) ->
prim_socket:info(SockRef);
info(Socket) ->
erlang:error(badarg, [Socket]).
%% ===========================================================================
%%
%% supports - get information about what the platform "supports".
%%
%% Generates a list of various info about what the plaform can support.
%% The most obvious case is 'options'.
%%
%% Each item in a 'supports'-list will appear only *one* time.
%%
%% ===========================================================================
-spec supports() -> [{Key1 :: term(),
boolean() | [{Key2 :: term(),
boolean() | [{Key3 :: term(),
boolean()}]}]}].
supports() ->
[{Key1, supports(Key1)}
|| Key1 <- [options, send_flags, recv_flags, protocols]]
++ prim_socket:supports().
-spec supports(Key1 :: term()) ->
[{Key2 :: term(),
boolean() | [{Key3 :: term(),
boolean()}]}].
%%
supports(options) ->
[{Level, supports(options, Level)}
|| Level <- [socket, ip, ipv6, tcp, udp, sctp]];
supports(Key) ->
prim_socket:supports(Key).
-spec supports(Key1 :: term(), Key2 :: term()) ->
[{Key3 :: term(),
boolean()}].
%%
supports(Key1, Key2) ->
prim_socket:supports(Key1, Key2).
-spec is_supported(Key1 :: term()) ->
boolean().
is_supported(Key1) ->
get_is_supported(Key1, supports()).
%%
-spec is_supported(Key1 :: term(), Key2 :: term()) ->
boolean().
is_supported(Key1, Key2) ->
get_is_supported(Key2, supports(Key1)).
%%
-spec is_supported(Key1 :: term(), Key2 :: term(), Key3 :: term()) ->
boolean().
is_supported(Key1, Key2, Key3) ->
get_is_supported(Key3, supports(Key1, Key2)).
get_is_supported(Key, Supported) ->
case lists:keyfind(Key, 1, Supported) of
false ->
false;
{_, Value} ->
if
is_boolean(Value) ->
Value;
is_list(Value) ->
false
end
end.
%% ===========================================================================
%%
%% The proper socket API
%%
%% ===========================================================================
%% ===========================================================================
%%
%%
%%
%% The nif sets up a monitor to this process, and if it dies the socket
%% is closed. It is also used if someone wants to monitor the socket.
%%
%% We may therefor need monitor function(s):
%%
%% socket:monitor(Socket)
%% socket:demonitor(Socket)
%%
%%
%%
%% ===========================================================================
%%
%% open - create an endpoint for communication
%%
-spec open(FD) -> {ok, Socket} | {error, Reason} when
FD :: integer(),
Socket :: socket(),
Reason ::
posix() | domain | type | protocol.
open(FD) when is_integer(FD) ->
open(FD, #{});
open(FD) ->
erlang:error(badarg, [FD]).
-spec open(FD, Opts) -> {ok, Socket} | {error, Reason} when
FD :: integer(),
Opts ::
#{domain => domain(),
type => type(),
protocol => protocol(),
dup => boolean(),
debug => boolean(),
use_registry => boolean()},
Socket :: socket(),
Reason ::
posix() | domain | type | protocol;
(Domain, Type) -> {ok, Socket} | {error, Reason} when
Domain :: domain(),
Type :: type(),
Socket :: socket(),
Reason :: posix() | protocol.
open(FD, Opts) when is_integer(FD), is_map(Opts) ->
case prim_socket:open(FD, Opts) of
{ok, SockRef} ->
Socket = ?socket(SockRef),
{ok, Socket};
{error, _} = ERROR ->
ERROR
end;
open(Domain, Type) ->
open(Domain, Type, 0).
-spec open(Domain, Type, Opts) -> {ok, Socket} | {error, Reason} when
Domain :: domain(),
Type :: type(),
Opts :: map(),
Socket :: socket(),
Reason :: posix() | protocol;
(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when
Domain :: domain(),
Type :: type(),
Protocol :: protocol(),
Socket :: socket(),
Reason :: posix() | protocol.
open(Domain, Type, Opts) when is_map(Opts) ->
open(Domain, Type, 0, Opts);
open(Domain, Type, Protocol) ->
open(Domain, Type, Protocol, #{}).
-spec open(Domain, Type, Protocol, Opts) -> {ok, Socket} | {error, Reason} when
Domain :: domain(),
Type :: type(),
Protocol :: protocol(),
Opts ::
#{netns => string(),
debug => boolean(),
use_registry => boolean()},
Socket :: socket(),
Reason :: posix() | protocol.
open(Domain, Type, Protocol, Opts) when is_map(Opts) ->
case prim_socket:open(Domain, Type, Protocol, Opts) of
{ok, SockRef} ->
Socket = ?socket(SockRef),
{ok, Socket};
{error, _} = ERROR ->
ERROR
end;
open(Domain, Type, Protocol, Opts) ->
erlang:error(badarg, [Domain, Type, Protocol, Opts]).
%% ===========================================================================
%%
%% bind - bind a name (an address) to a socket
%%
%% Note that the short (atom) addresses only work for some domains,
%% and that the nif will reject 'broadcast' for other domains than 'inet'
%%
-spec bind(Socket, Addr) -> {ok, Port} | {error, Reason} when
Socket :: socket(),
Addr :: sockaddr() | any | broadcast | loopback,
Port :: port_number(),
Reason :: posix() | closed | invalid.
bind(?socket(SockRef) = Socket, Addr) when is_reference(SockRef) ->
if
is_map(Addr) ->
prim_socket:bind(SockRef, Addr);
%%
Addr =:= any;
Addr =:= broadcast;
Addr =:= loopback ->
case prim_socket:getopt(SockRef, otp, domain) of
{ok, Domain}
when Domain =:= inet;
Domain =:= inet6 ->
prim_socket:bind(
SockRef, #{family => Domain, addr => Addr});
{ok, _Domain} ->
{error, eafnosupport};
{error, _} = ERROR ->
ERROR
end;
%%
true ->
erlang:error(badarg, [Socket, Addr])
end;
bind(Socket, Addr) ->
erlang:error(badarg, [Socket, Addr]).
%% ===========================================================================
%%
%% bind - Add or remove a bind addresses on a socket
%%
%% Calling this function is only valid if the socket is:
%% type = seqpacket
%% protocol = sctp
%%
%% If the domain is inet, then all addresses *must* be IPv4.
%% If the domain is inet6, the addresses can be aither IPv4 or IPv6.
%%
-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when
Socket :: socket(),
Addrs :: [sockaddr()],
Action :: add | remove,
Reason :: posix() | closed.
bind(?socket(SockRef), Addrs, Action)
when is_reference(SockRef)
andalso is_list(Addrs)
andalso (Action =:= add
orelse Action =:= remove) ->
prim_socket:bind(SockRef, Addrs, Action);
bind(Socket, Addrs, Action) ->
erlang:error(badarg, [Socket, Addrs, Action]).
%% ===========================================================================
%%
%% connect - initiate a connection on a socket
%%
-spec connect(Socket) -> ok | {error, Reason} when
Socket :: socket(),
Reason :: posix() | closed | invalid | already.
%% Finalize connect after connect(,, nowait) and received
%% select message - see connect_deadline/3
%%
connect(?socket(SockRef))
when is_reference(SockRef) ->
prim_socket:connect(SockRef);
connect(Socket) ->
erlang:error(badarg, [Socket]).
-spec connect(Socket, SockAddr) -> ok | {error, Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
Reason :: posix() | closed | invalid | already.
connect(Socket, SockAddr) ->
connect(Socket, SockAddr, infinity).
-spec connect(Socket, SockAddr, nowait) ->
ok | {select, SelectInfo} | {error, Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid | already;
(Socket, SockAddr, Timeout) -> ok | {error, Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
Timeout :: timeout(),
Reason :: posix() | closed | invalid | already | timeout.
%%
%% Is it possible to connect with family = local for the (dest) sockaddr?
%%
connect(?socket(SockRef) = Socket, SockAddr, Timeout)
when is_reference(SockRef) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, SockAddr, Timeout]);
nowait ->
connect_nowait(SockRef, SockAddr);
Deadline ->
connect_deadline(SockRef, SockAddr, Deadline)
end;
connect(Socket, SockAddr, Timeout) ->
erlang:error(badarg, [Socket, SockAddr, Timeout]).
connect_nowait(SockRef, SockAddr) ->
case prim_socket:connect(SockRef, SockAddr) of
{select, Ref} ->
?SELECT(connect, Ref);
Result ->
Result
end.
connect_deadline(SockRef, SockAddr, Deadline) ->
case prim_socket:connect(SockRef, SockAddr) of
{select, Ref} ->
%% Connecting...
Timeout = timeout(Deadline),
receive
?socket_msg(_Socket, select, Ref) ->
prim_socket:connect(SockRef);
?socket_msg(_Socket, abort, {Ref, Reason}) ->
{error, Reason}
after Timeout ->
cancel(SockRef, connect, Ref),
{error, timeout}
end;
Result ->
Result
end.
%% ===========================================================================
%%
%% listen - listen for connections on a socket
%%
-spec listen(Socket) -> ok | {error, Reason} when
Socket :: socket(),
Reason :: posix() | closed.
listen(Socket) ->
listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT).
-spec listen(Socket, Backlog) -> ok | {error, Reason} when
Socket :: socket(),
Backlog :: integer(),
Reason :: posix() | closed.
listen(?socket(SockRef), Backlog)
when is_reference(SockRef), is_integer(Backlog) ->
prim_socket:listen(SockRef, Backlog);
listen(Socket, Backlog) ->
erlang:error(badarg, [Socket, Backlog]).
%% ===========================================================================
%%
%% accept, accept4 - accept a connection on a socket
%%
-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when
LSocket :: socket(),
Socket :: socket(),
Reason :: posix() | closed | invalid.
accept(Socket) ->
accept(Socket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT).
-spec accept(LSocket, nowait) ->
{ok, Socket} |
{select, SelectInfo} |
{error, Reason} when
LSocket :: socket(),
Socket :: socket(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when
LSocket :: socket(),
Timeout :: timeout(),
Socket :: socket(),
Reason :: posix() | closed | invalid | timeout.
accept(?socket(LSockRef) = Socket, Timeout)
when is_reference(LSockRef) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, Timeout]);
nowait ->
accept_nowait(LSockRef);
Deadline ->
accept_deadline(LSockRef, Deadline)
end;
accept(Socket, Timeout) ->
erlang:error(badarg, [Socket, Timeout]).
accept_nowait(LSockRef) ->
AccRef = make_ref(),
case prim_socket:accept(LSockRef, AccRef) of
select ->
?SELECT(accept, AccRef);
Result ->
accept_result(LSockRef, AccRef, Result)
end.
accept_deadline(LSockRef, Deadline) ->
AccRef = make_ref(),
case prim_socket:accept(LSockRef, AccRef) of
select ->
%% Each call is non-blocking, but even then it takes
%% *some* time, so just to be sure, recalculate before
%% the receive.
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(LSockRef), select, AccRef) ->
accept_deadline(LSockRef, Deadline);
?socket_msg(_Socket, abort, {AccRef, Reason}) ->
{error, Reason}
after Timeout ->
cancel(LSockRef, accept, AccRef),
{error, timeout}
end;
Result ->
accept_result(LSockRef, AccRef, Result)
end.
accept_result(LSockRef, AccRef, Result) ->
case Result of
{ok, SockRef} ->
Socket = ?socket(SockRef),
{ok, Socket};
{error, _} = ERROR ->
cancel(LSockRef, accept, AccRef), % Just to be on the safe side...
ERROR
end.
%% ===========================================================================
%%
%% send, sendto, sendmsg - send a message on a socket
%%
-spec send(Socket, Data) -> ok | {error, Reason} when
Socket :: socket(),
Data :: iodata(),
Reason :: term().
send(Socket, Data) ->
send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT).
-spec send(Socket, Data, Flags) -> ok | {error, Reason} when
Socket :: socket(),
Data :: iodata(),
Flags :: [send_flag()],
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Timeout :: nowait) ->
ok |
{select, SelectInfo} |
{ok, {RestData, SelectInfo}} |
{error, Reason} when
Socket :: socket(),
Data :: iodata(),
RestData :: binary(),
SelectInfo :: select_info(),
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Timeout) -> ok | {error, Reason} when
Socket :: socket(),
Data :: iodata(),
Timeout :: timeout(),
Reason :: {posix() | closed | invalid | timeout,
Remaining :: pos_integer()}.
send(Socket, Data, Flags) when is_list(Flags) ->
send(Socket, Data, Flags, ?ESOCK_SEND_TIMEOUT_DEFAULT);
send(Socket, Data, Timeout) ->
send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout).
-spec send(Socket, Data, Flags, nowait) -> ok |
{select, SelectInfo} |
{ok, {RestData, SelectInfo}} |
{error, Reason} when
Socket :: socket(),
Data :: iodata(),
Flags :: [send_flag()],
RestData :: binary(),
SelectInfo :: select_info(),
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Flags, Timeout) -> ok | {error, Reason} when
Socket :: socket(),
Data :: iodata(),
Flags :: [send_flag()],
Timeout :: timeout(),
Reason :: {posix() | closed | invalid | timeout,
Remaining :: pos_integer()}.
send(Socket, Data, Flags, Timeout) when is_list(Data) ->
Bin = erlang:list_to_binary(Data),
send(Socket, Bin, Flags, Timeout);
send(?socket(SockRef) = Socket, Data, Flags, Timeout)
when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
To = undefined,
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, Data, Flags, Timeout]);
nowait ->
send_common_nowait(SockRef, Data, To, Flags, send);
Deadline ->
send_common_deadline(SockRef, Data, To, Flags, Deadline, send)
end;
send(Socket, Data, Flags, Timeout) ->
erlang:error(badarg, [Socket, Data, Flags, Timeout]).
send_common_nowait(SockRef, Data, To, Flags, SendName) ->
SendRef = make_ref(),
case
case SendName of
send ->
prim_socket:send(SockRef, SendRef, Data, Flags);
sendto ->
prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
end
of
{ok, Written} ->
%% We are partially done, but the user don't want to wait (here)
%% for completion
<<_:Written/binary, Rest/binary>> = Data,
{ok, {Rest, ?SELECT_INFO(SendName, SendRef)}};
select ->
?SELECT(SendName, SendRef);
Result ->
send_common_result(Data, Result)
end.
send_common_deadline(SockRef, Data, To, Flags, Deadline, SendName) ->
SendRef = make_ref(),
case
case SendName of
send ->
prim_socket:send(SockRef, SendRef, Data, Flags);
sendto ->
prim_socket:sendto(SockRef, SendRef, Data, To, Flags)
end
of
{ok, Written} ->
%% We are partially done, wait for continuation
Timeout = timeout(Deadline),
receive
?socket_msg(_Socket, select, SendRef)
when (Written > 0) ->
<<_:Written/binary, Rest/binary>> = Data,
send_common_deadline(
SockRef, Rest, To, Flags, Deadline, SendName);
?socket_msg(_Socket, select, SendRef) ->
send_common_deadline(
SockRef, Data, To, Flags, Deadline, SendName);
?socket_msg(_Socket, abort, {SendRef, Reason}) ->
{error, {Reason, byte_size(Data)}}
after Timeout ->
_ = cancel(SockRef, SendName, SendRef),
{error, {timeout, byte_size(Data)}}
end;
select ->
%% Wait for continuation
Timeout = timeout(Deadline),
receive
?socket_msg(_Socket, select, SendRef) ->
send_common_deadline(
SockRef, Data, To, Flags, Deadline, SendName);
?socket_msg(_Socket, abort, {SendRef, Reason}) ->
{error, {Reason, byte_size(Data)}}
after Timeout ->
_ = cancel(SockRef, SendName, SendRef),
{error, {timeout, byte_size(Data)}}
end;
%%
{error, ealready = Reason} ->
%% Internal error:
%% we called send, got eagain, and called send again
%% - without waiting for select message
erlang:error(Reason);
Result ->
send_common_result(Data, Result)
end.
send_common_result(Data, Result) ->
case Result of
ok ->
ok;
{error, Reason} ->
{error, {Reason, byte_size(Data)}}
end.
%% ---------------------------------------------------------------------------
%%
-spec sendto(Socket, Data, Dest) ->
ok | {error, Reason} when
Socket :: socket(),
Data :: binary(),
Dest :: sockaddr(),
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()}.
sendto(Socket, Data, Dest) ->
sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT).
-spec sendto(Socket, Data, Dest, Flags) ->
ok | {error, Reason} when
Socket :: socket(),
Data :: binary(),
Dest :: sockaddr(),
Flags :: [send_flag()],
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Dest, Timeout :: nowait) ->
ok |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Data :: iodata(),
Dest :: sockaddr(),
SelectInfo :: select_info(),
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Dest, Timeout) ->
ok | {error, Reason} when
Socket :: socket(),
Data :: iodata(),
Dest :: sockaddr(),
Timeout :: timeout(),
Reason :: {posix() | closed | invalid | timeout,
Remaining :: pos_integer()}.
sendto(Socket, Data, Dest, Flags) when is_list(Flags) ->
sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT);
sendto(Socket, Data, Dest, Timeout) ->
sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout).
-spec sendto(Socket, Data, Dest, Flags, nowait) ->
ok |
{ok, {binary(), SelectInfo}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Data :: binary(),
Dest :: sockaddr(),
Flags :: [send_flag()],
SelectInfo :: select_info(),
Reason :: {posix() | closed | invalid,
Remaining :: pos_integer()};
(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when
Socket :: socket(),
Data :: binary(),
Dest :: sockaddr(),
Flags :: [send_flag()],
Timeout :: timeout(),
Reason :: {posix() | closed | invalid | timeout,
Remaining :: pos_integer()}.
sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) ->
Bin = erlang:list_to_binary(Data),
sendto(Socket, Bin, Dest, Flags, Timeout);
sendto(?socket(SockRef) = Socket, Data, Dest, Flags, Timeout)
when is_reference(SockRef), is_binary(Data), is_list(Flags) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, Data, Dest, Flags, Timeout]);
nowait ->
send_common_nowait(SockRef, Data, Dest, Flags, sendto);
Deadline ->
send_common_deadline(
SockRef, Data, Dest, Flags, Deadline, sendto)
end;
sendto(Socket, Data, Dest, Flags, Timeout) ->
erlang:error(badarg, [Socket, Data, Dest, Flags, Timeout]).
%% ---------------------------------------------------------------------------
%%
%% The only part of the msghdr() that *must* exist (a connected
%% socket need not specify the addr field) is the iov.
%% The ctrl field is optional, and the addr and flags are not
%% used when sending.
%%
-spec sendmsg(Socket, MsgHdr) ->
ok |
{ok, Remaining} |
{error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Remaining :: erlang:iovec(),
Reason :: term().
sendmsg(Socket, MsgHdr) ->
sendmsg(Socket, MsgHdr,
?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT).
-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Flags :: [send_flag()],
Reason :: posix() | closed;
(Socket, MsgHdr, Timeout :: nowait) ->
ok |
{ok, Remaining} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Remaining :: erlang:iovec(),
SelectInfo :: select_info(),
Reason :: posix() | closed;
(Socket, MsgHdr, Timeout) -> ok | {error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Timeout :: timeout(),
Reason :: posix() | closed | timeout.
sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) ->
sendmsg(Socket, MsgHdr, Flags, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT);
sendmsg(Socket, MsgHdr, Timeout) ->
sendmsg(Socket, MsgHdr, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout).
-spec sendmsg(Socket, MsgHdr, Flags, nowait) ->
ok |
{ok, Remaining} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Flags :: [send_flag()],
Remaining :: erlang:iovec(),
SelectInfo :: select_info(),
Reason :: posix() | closed;
(Socket, MsgHdr, Flags, Timeout) ->
ok |
{ok, Remaining} |
{error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Flags :: [send_flag()],
Timeout :: timeout(),
Remaining :: erlang:iovec(),
Reason :: posix() | closed | timeout.
sendmsg(?socket(SockRef) = Socket, MsgHdr, Flags, Timeout)
when is_reference(SockRef), is_map(MsgHdr), is_list(Flags) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, MsgHdr, Flags, Timeout]);
Deadline ->
sendmsg_loop(SockRef, MsgHdr, Flags, Deadline)
end;
sendmsg(Socket, MsgHdr, Flags, Timeout) ->
erlang:error(badarg, [Socket, MsgHdr, Flags, Timeout]).
sendmsg_loop(SockRef, MsgHdr, Flags, Deadline) ->
SendRef = make_ref(),
case prim_socket:sendmsg(SockRef, SendRef, MsgHdr, Flags) of
ok ->
%% We are done
ok;
%%
{ok, Written} when is_integer(Written) andalso (Written > 0) ->
%% We should not retry here since the protocol may not
%% be able to handle a message being split. Leave it to
%% the caller to figure out (call again with the rest).
%%
%% We need to cancel this partial write.
%%
_ = cancel(SockRef, sendmsg, SendRef),
{ok, sendmsg_rest(maps:get(iov, MsgHdr), Written)};
%%
select when (Deadline =:= nowait) ->
?SELECT(sendmsg, SendRef);
select ->
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(SockRef), select, SendRef) ->
sendmsg_loop(SockRef, MsgHdr, Flags, Deadline);
?socket_msg(_Socket, abort, {SendRef, Reason}) ->
{error, Reason}
after Timeout ->
_ = cancel(SockRef, sendmsg, SendRef),
{error, timeout}
end;
%%
{error, ealready = Reason} when Deadline =/= nowait ->
%% Internal error:
%% we called send, got eagain, and called send again
%% - without waiting for select message
erlang:error(Reason);
{error, _} = ERROR ->
ERROR
end.
sendmsg_rest([B|IOVec], Written) when Written >= byte_size(B) ->
sendmsg_rest(IOVec, Written - byte_size(B));
sendmsg_rest([B|IOVec], Written) ->
<<_:Written/binary, Rest/binary>> = B,
[Rest|IOVec].
%% ===========================================================================
%%
%% recv, recvfrom, recvmsg - receive a message from a socket
%%
%% Description:
%% There is a special case for the argument Length. If its set to zero (0),
%% it means "give me everything you have".
%%
%% Returns: {ok, Binary} | {error, Reason}
%% Binary - The received data as a binary
%% Reason - The error reason:
%% timeout | {timeout, AccData} |
%% posix() | {posix(), AccData} |
%% atom() | {atom(), AccData}
%% AccData - The data (as a binary) that we did manage to receive
%% before the timeout.
%%
%% Arguments:
%% Socket - The socket to read from.
%% Length - The number of bytes to read.
%% Flags - A list of "options" for the read.
%% Timeout - Time-out in milliseconds.
-spec recv(Socket) ->
{ok, Data} |
{error, Reason} when
Socket :: socket(),
Data :: binary(),
Reason ::
posix() | closed | invalid |
{posix() | closed | invalid, Data :: binary()}.
recv(Socket) ->
recv(Socket, 0).
-spec recv(Socket, Length) ->
{ok, Data} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Data :: binary(),
Reason ::
posix() | closed | invalid |
{posix() | closed | invalid, Data :: binary()}.
recv(Socket, Length) ->
recv(Socket, Length,
?ESOCK_RECV_FLAGS_DEFAULT,
?ESOCK_RECV_TIMEOUT_DEFAULT).
-spec recv(Socket, Length, Flags) ->
{ok, Data} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Flags :: [recv_flag()],
Data :: binary(),
Reason ::
posix() | closed | invalid |
{posix() | closed | invalid, Data :: binary()};
(Socket, Length, Timeout :: nowait) ->
{ok, Data} |
{ok, {Data, SelectInfo}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Data :: binary(),
SelectInfo :: select_info(),
Reason ::
posix() | closed | invalid |
{posix() | closed | invalid, Data :: binary()};
(Socket, Length, Timeout) ->
{ok, Data} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Timeout :: timeout(),
Data :: binary(),
Reason ::
posix() | closed | invalid | timeout |
{posix() | closed | invalid | timeout, Data :: binary()}.
recv(Socket, Length, Flags) when is_list(Flags) ->
recv(Socket, Length, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
recv(Socket, Length, Timeout) ->
recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-spec recv(Socket, Length, Flags, nowait) ->
{ok, Data} |
{ok, {Data, SelectInfo}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Flags :: [recv_flag()],
Data :: binary(),
SelectInfo :: select_info(),
Reason ::
posix() | closed | invalid |
{posix() | closed | invalid, Data :: binary()};
(Socket, Length, Flags, Timeout) ->
{ok, Data} |
{error, Reason} when
Socket :: socket(),
Length :: non_neg_integer(),
Flags :: [recv_flag()],
Timeout :: timeout(),
Data :: binary(),
Reason ::
posix() | closed | invalid | timeout |
{posix() | closed | invalid | timeout, Data :: binary()}.
recv(?socket(SockRef) = Socket, Length, Flags, Timeout)
when is_reference(SockRef),
is_integer(Length), Length >= 0,
is_list(Flags) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, Length, Flags, Timeout]);
nowait ->
recv_nowait(SockRef, Length, Flags, <<>>);
Deadline ->
recv_deadline(SockRef, Length, Flags, Deadline, <<>>)
end;
recv(Socket, Length, Flags, Timeout) ->
erlang:error(badarg, [Socket, Length, Flags, Timeout]).
%% We will only recurse with Length == 0 if Length is 0,
%% so Length == 0 means to return all available data also when recursing
recv_nowait(SockRef, Length, Flags, Acc) ->
RecvRef = make_ref(),
case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
{more, Bin} ->
%% We got what we requested but will not waste more time
%% although there might be more data available
{ok, bincat(Acc, Bin)};
{select, Bin} ->
%% We got less than requested so the caller will
%% get a select message when there might be more to read
{ok, {bincat(Acc, Bin), ?SELECT_INFO(recv, RecvRef)}};
select ->
%% The caller will get a select message when there
%% might me data to read
if
byte_size(Acc) =:= 0 ->
?SELECT(recv, RecvRef);
true ->
{ok, {Acc, ?SELECT_INFO(recv, RecvRef)}}
end;
Result ->
recv_result(Acc, Result)
end.
recv_deadline(SockRef, Length, Flags, Deadline, Acc) ->
RecvRef = make_ref(),
case prim_socket:recv(SockRef, RecvRef, Length, Flags) of
{more, Bin} ->
%% There is more data readily available
%% - repeat unless time's up
Timeout = timeout(Deadline),
if
0 < Timeout ->
%% Recv more
recv_deadline(
SockRef, Length, Flags, Deadline, bincat(Acc, Bin));
true ->
{ok, bincat(Acc, Bin)}
end;
%%
{select, Bin} ->
%% We got less than requested
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(SockRef), select, RecvRef) ->
if
0 < Timeout ->
%% Recv more
recv_deadline(
SockRef, Length - byte_size(Bin), Flags,
Deadline, bincat(Acc, Bin));
true ->
{error, {timeout, bincat(Acc, Bin)}}
end;
?socket_msg(_Socket, abort, {RecvRef, Reason}) ->
{error, {Reason, bincat(Acc, Bin)}}
after Timeout ->
cancel(SockRef, recv, RecvRef),
{error, {timeout, bincat(Acc, Bin)}}
end;
%%
select when Length =:= 0, 0 < byte_size(Acc) ->
%% We first got some data and are then asked to wait,
%% but we only want the first that comes
%% - cancel and return what we have
cancel(SockRef, recv, RecvRef),
{ok, Acc};
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(SockRef), select, RecvRef) ->
if
0 < Timeout ->
%% Retry
recv_deadline(
SockRef, Length, Flags, Deadline, Acc);
true ->
recv_error(Acc, timeout)
end;
?socket_msg(_Socket, abort, {RecvRef, Reason}) ->
recv_error(Acc, Reason)
after Timeout ->
cancel(SockRef, recv, RecvRef),
recv_error(Acc, timeout)
end;
%%
{error, ealready = Reason} ->
%% Internal error:
%% we called recv, got eagain, and called recv again
%% - without waiting for select message
erlang:error(Reason);
Result ->
recv_result(Acc, Result)
end.
recv_result(Acc, Result) ->
case Result of
{ok, Bin} ->
{ok, bincat(Acc, Bin)};
{error, _} = ERROR when byte_size(Acc) =:= 0 ->
ERROR;
{error, Reason} ->
{error, {Reason, Acc}}
end.
recv_error(Acc, Reason) ->
if
byte_size(Acc) =:= 0 ->
{error, Reason};
true ->
{error, {Reason, Acc}}
end.
%% ---------------------------------------------------------------------------
%%
%% With recvfrom we get messages, which means that regardless of how
%% much we want to read, we return when we get a message.
%% The MaxSize argument basically defines the size of our receive
%% buffer. By setting the size to zero (0), we use the configured
%% size (see setopt).
%% It may be impossible to know what (buffer) size is appropriate
%% "in advance", and in those cases it may be convenient to use the
%% (recv) 'peek' flag. When this flag is provided the message is *not*
%% "consumed" from the underlying (OS) buffers, so another recvfrom call
%% is needed, possibly with a then adjusted buffer size.
%%
-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when
Socket :: socket(),
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid.
recvfrom(Socket) ->
recvfrom(Socket, 0).
-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid.
recvfrom(Socket, BufSz) ->
recvfrom(Socket, BufSz,
?ESOCK_RECV_FLAGS_DEFAULT,
?ESOCK_RECV_TIMEOUT_DEFAULT).
-spec recvfrom(Socket, Flags, nowait) ->
{ok, {Source, Data}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Flags :: [recv_flag()],
Source :: sockaddr() | undefined,
Data :: binary(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, Flags, Timeout) ->
{ok, {Source, Data}} |
{error, Reason} when
Socket :: socket(),
Flags :: [recv_flag()],
Timeout :: timeout(),
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid | timeout;
(Socket, BufSz, Flags) ->
{ok, {Source, Data}} | {error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Flags :: [recv_flag()],
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid;
(Socket, BufSz, nowait) ->
{ok, {Source, Data}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Source :: sockaddr() | undefined,
Data :: binary(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, BufSz, Timeout) ->
{ok, {Source, Data}} | {error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Timeout :: timeout(),
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid | timeout.
recvfrom(Socket, Flags, Timeout) when is_list(Flags) ->
recvfrom(Socket, 0, Flags, Timeout);
recvfrom(Socket, BufSz, Flags) when is_list(Flags) ->
recvfrom(Socket, BufSz, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
recvfrom(Socket, BufSz, Timeout) ->
recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-spec recvfrom(Socket, BufSz, Flags, nowait) ->
{ok, {Source, Data}} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Flags :: [recv_flag()],
Source :: sockaddr() | undefined,
Data :: binary(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, BufSz, Flags, Timeout) ->
{ok, {Source, Data}} |
{error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
Flags :: [recv_flag()],
Timeout :: timeout(),
Source :: sockaddr() | undefined,
Data :: binary(),
Reason :: posix() | closed | invalid | timeout.
recvfrom(?socket(SockRef) = Socket, BufSz, Flags, Timeout)
when is_reference(SockRef),
is_integer(BufSz), 0 =< BufSz,
is_list(Flags) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, BufSz, Flags, Timeout]);
nowait ->
recvfrom_nowait(SockRef, BufSz, Flags);
Deadline ->
recvfrom_deadline(SockRef, BufSz, Flags, Deadline)
end;
recvfrom(Socket, BufSz, Flags, Timeout) ->
erlang:error(badarg, [Socket, BufSz, Flags, Timeout]).
recvfrom_nowait(SockRef, BufSz, Flags) ->
RecvRef = make_ref(),
case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
select ->
?SELECT(recvfrom, RecvRef);
Result ->
recvfrom_result(Result)
end.
recvfrom_deadline(SockRef, BufSz, Flags, Deadline) ->
RecvRef = make_ref(),
case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(SockRef), select, RecvRef) ->
recvfrom_deadline(SockRef, BufSz, Flags, Deadline);
?socket_msg(_Socket, abort, {RecvRef, Reason}) ->
{error, Reason}
after Timeout ->
cancel(SockRef, recvfrom, RecvRef),
{error, timeout}
end;
{error, ealready = Reason} ->
%% Internal error:
%% we called recvfrom, got eagain, and called recvfrom again
%% - without waiting for select message
erlang:error(Reason);
Result ->
recvfrom_result(Result)
end.
recvfrom_result(Result) ->
case Result of
{ok, {_Source, _NewData}} = OK ->
OK;
{error, _Reason} = ERROR ->
ERROR
end.
%% ---------------------------------------------------------------------------
%%
-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid.
recvmsg(Socket) ->
recvmsg(Socket, 0, 0,
?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when
Socket :: socket(),
Flags :: [recv_flag()],
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid;
(Socket, Timeout :: nowait) -> {ok, MsgHdr} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
MsgHdr :: msghdr(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when
Socket :: socket(),
Timeout :: timeout(),
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid | timeout.
recvmsg(Socket, Flags) when is_list(Flags) ->
recvmsg(Socket, 0, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT);
recvmsg(Socket, Timeout) ->
recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout).
-spec recvmsg(Socket, Flags, nowait) -> {ok, MsgHdr} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
Flags :: [recv_flag()],
MsgHdr :: msghdr(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when
Socket :: socket(),
Flags :: [recv_flag()],
Timeout :: timeout(),
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid | timeout;
(Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
CtrlSz :: non_neg_integer(),
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid.
recvmsg(Socket, Flags, Timeout) when is_list(Flags) ->
recvmsg(Socket, 0, 0, Flags, Timeout);
recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) ->
recvmsg(Socket, BufSz, CtrlSz,
?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT).
-spec recvmsg(Socket, BufSz, CtrlSz, Flags, nowait) ->
{ok, MsgHdr} |
{select, SelectInfo} |
{error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
CtrlSz :: non_neg_integer(),
Flags :: [recv_flag()],
MsgHdr :: msghdr(),
SelectInfo :: select_info(),
Reason :: posix() | closed | invalid;
(Socket, BufSz, CtrlSz, Flags, Timeout) ->
{ok, MsgHdr} |
{error, Reason} when
Socket :: socket(),
BufSz :: non_neg_integer(),
CtrlSz :: non_neg_integer(),
Flags :: [recv_flag()],
Timeout :: timeout(),
MsgHdr :: msghdr(),
Reason :: posix() | closed | invalid | timeout.
recvmsg(?socket(SockRef) = Socket, BufSz, CtrlSz, Flags, Timeout)
when is_reference(SockRef),
is_integer(BufSz), 0 =< BufSz,
is_integer(CtrlSz), 0 =< CtrlSz,
is_list(Flags) ->
case deadline(Timeout) of
badarg = Reason ->
erlang:error(Reason, [Socket, BufSz, CtrlSz, Flags, Timeout]);
nowait ->
recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags);
Deadline ->
recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline)
end;
recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) ->
erlang:error(badarg, [Socket, BufSz, CtrlSz, Flags, Timeout]).
recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags) ->
RecvRef = make_ref(),
case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
select ->
?SELECT(recvmsg, RecvRef);
Result ->
recvmsg_result(Result)
end.
recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) ->
RecvRef = make_ref(),
case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of
select ->
%% There is nothing just now, but we will be notified when there
%% is something to read (a select message).
Timeout = timeout(Deadline),
receive
?socket_msg(?socket(SockRef), select, RecvRef) ->
recvmsg_deadline(
SockRef, BufSz, CtrlSz, Flags, Deadline);
?socket_msg(_Socket, abort, {RecvRef, Reason}) ->
{error, Reason}
after Timeout ->
cancel(SockRef, recvmsg, RecvRef),
{error, timeout}
end;
%%
{error, ealready = Reason} ->
%% Internal error:
%% we called recvmsg, got eagain, and called recvmsg again
%% - without waiting for select message
erlang:error(Reason);
Result ->
recvmsg_result(Result)
end.
recvmsg_result(Result) ->
case Result of
{ok, _MsgHdr} = OK ->
OK;
{error, _Reason} = ERROR ->
ERROR
end.
%% ===========================================================================
%%
%% close - close a file descriptor
%%
%% Closing a socket is a two stage rocket (because of linger).
%% We need to perform the actual socket close while in BLOCKING mode.
%% But that would hang the entire VM, so what we do is divide the
%% close in two steps:
%% 1) prim_socket:nif_close + the socket_stop (nif) callback function
%% This is for everything that can be done safely NON-BLOCKING.
%% 2) prim_socket:nif_finalize_close which is executed by a *dirty* scheduler
%% Before we call the socket close function, we set the socket
%% BLOCKING. Thereby linger is handled properly.
-spec close(Socket) -> ok | {error, Reason} when
Socket :: socket(),
Reason :: posix() | closed | timeout.
close(?socket(SockRef))
when is_reference(SockRef) ->
case prim_socket:close(SockRef) of
ok ->
prim_socket:finalize_close(SockRef);
{ok, CloseRef} ->
%% We must wait for the socket_stop callback function to
%% complete its work
receive
?socket_msg(?socket(SockRef), close, CloseRef) ->
prim_socket:finalize_close(SockRef)
end;
{error, _} = ERROR ->
ERROR
end;
close(Socket) ->
erlang:error(badarg, [Socket]).
%% ===========================================================================
%%
%% shutdown - shut down part of a full-duplex connection
%%
-spec shutdown(Socket, How) -> ok | {error, Reason} when
Socket :: socket(),
How :: read | write | read_write,
Reason :: posix() | closed.
shutdown(?socket(SockRef), How)
when is_reference(SockRef) ->
prim_socket:shutdown(SockRef, How);
shutdown(Socket, How) ->
erlang:error(badarg, [Socket, How]).
%% ===========================================================================
%%
%% setopt - manipulate individual properties of a socket
%%
%% What properties are valid depend on what kind of socket it is
%% (domain, type and protocol)
%% If its an "invalid" option (or value), we should not crash but return some
%% useful error...
%%
%%
%%
%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING
%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!!
%%
%%
-spec setopt(socket(),
SocketOption ::
{Level :: otp, Opt :: otp_socket_option()},
_) ->
ok | {error, invalid | closed};
(socket(),
SocketOption :: socket_option(),
_) ->
ok | {error, posix() | invalid | closed}.
setopt(?socket(SockRef), {Level, Opt}, Value)
when is_reference(SockRef) ->
prim_socket:setopt(SockRef, Level, Opt, Value);
setopt(Socket, SocketOption, Value) ->
erlang:error(badarg, [Socket, SocketOption, Value]).
%% Backwards compatibility
setopt(Socket, Level, Opt, Value)
when is_integer(Opt), is_binary(Value) ->
setopt_native(Socket, {Level, Opt}, Value);
setopt(Socket, Level, Opt, Value) ->
setopt(Socket, {Level, Opt}, Value).
-spec setopt_native(socket(),
SocketOption ::
socket_option() |
{Level :: sockopt_level()
| (NativeLevel :: non_neg_integer()),
NativeOpt :: non_neg_integer()},
Value :: integer() | boolean() | binary()) ->
%% Possible here to add type tagged values
%% a'la {uint16, 0..16#FFFF}
ok | {error, posix() | invalid | closed}.
setopt_native(?socket(SockRef), {Level, Opt}, Value)
when is_reference(SockRef) ->
prim_socket:setopt_native(SockRef, Level, Opt, Value);
setopt_native(Socket, SocketOption, Value) ->
erlang:error(badarg, [Socket, SocketOption, Value]).
%% ===========================================================================
%%
%% getopt - retrieve individual properties of a socket
%%
%% What properties are valid depend on what kind of socket it is
%% (domain, type and protocol).
%% If its an "invalid" option, we should not crash but return some
%% useful error...
%%
%% When specifying level as an integer, and therefor using "native mode",
%% we should make it possible to specify common types instead of the
%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer()
%%
-spec getopt(socket(),
SocketOption ::
{Level :: otp,
Opt :: otp_socket_option()}) ->
{ok, Value :: term()} |
{error, invalid | closed};
(socket(),
SocketOption :: socket_option()) ->
{ok, Value :: term()} |
{error, posix() | invalid | closed}.
getopt(?socket(SockRef), {Level, Opt})
when is_reference(SockRef) ->
prim_socket:getopt(SockRef, Level, Opt);
getopt(Socket, SocketOption) ->
erlang:error(badarg, [Socket, SocketOption]).
%% Backwards compatibility
getopt(Socket, Level, {NativeOpt, ValueSpec})
when is_integer(NativeOpt) ->
getopt_native(Socket, {Level, NativeOpt}, ValueSpec);
getopt(Socket, Level, Opt) ->
getopt(Socket, {Level, Opt}).
-spec getopt_native(socket(),
SocketOption ::
socket_option() |
{Level :: sockopt_level()
| (NativeLevel :: non_neg_integer()),
NativeOpt :: non_neg_integer()},
ValueType :: integer) ->
{ok, Value :: integer()} |
{error, posix() | invalid | closed};
(socket(),
SocketOption ::
socket_option() |
{Level :: sockopt_level()
| (NativeLevel :: non_neg_integer()),
NativeOpt :: non_neg_integer()},
ValueType :: boolean) ->
{ok, Value :: boolean()} |
{error, posix() | invalid | closed};
(socket(),
SocketOption ::
socket_option() |
{Level :: sockopt_level()
| (NativeLevel :: non_neg_integer()),
NativeOpt :: non_neg_integer()},
ValueSize :: non_neg_integer()) ->
{ok, Value :: binary()} |
{error, posix() | invalid | closed}.
getopt_native(?socket(SockRef), {Level, Opt}, ValueSpec) ->
prim_socket:getopt_native(SockRef, Level, Opt, ValueSpec);
getopt_native(Socket, SocketOption, ValueSpec) ->
erlang:error(badarg, [Socket, SocketOption, ValueSpec]).
%% ===========================================================================
%%
%% sockname - return the current address of the socket.
%%
%%
-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
Reason :: posix() | closed | bad_data.
sockname(?socket(SockRef))
when is_reference(SockRef) ->
prim_socket:sockname(SockRef);
sockname(Socket) ->
erlang:error(badarg, [Socket]).
%% ===========================================================================
%%
%% peername - return the address of the peer *connected* to the socket.
%%
%%
-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when
Socket :: socket(),
SockAddr :: sockaddr(),
Reason :: posix() | closed | bad_data.
peername(?socket(SockRef))
when is_reference(SockRef) ->
prim_socket:peername(SockRef);
peername(Socket) ->
erlang:error(badarg, [Socket]).
%% ===========================================================================
%%
%% cancel - cancel an operation resulting in a select
%%
%% A call to accept, recv/recvfrom/recvmsg and send/sendto/sendmsg
%% can result in a select if they are called with the Timeout argument
%% set to nowait. This is indicated by the return of the select-info.
%% Such a operation can be cancelled by calling this function.
%%
-spec cancel(Socket, SelectInfo) -> ok | {error, Reason} when
Socket :: socket(),
SelectInfo :: select_info(),
Reason :: closed | invalid.
cancel(?socket(SockRef), ?SELECT_INFO(Tag, Ref))
when is_reference(SockRef) ->
cancel(SockRef, Tag, Ref);
cancel(Socket, SelectInfo) ->
erlang:error(badarg, [Socket, SelectInfo]).
cancel(SockRef, Op, OpRef) ->
case prim_socket:cancel(SockRef, Op, OpRef) of
%% The select has already completed
{error, select_sent} ->
flush_select_msg(SockRef, OpRef),
_ = flush_abort_msg(SockRef, OpRef),
ok;
{error, not_found} ->
_ = flush_abort_msg(SockRef, OpRef),
{error, invalid};
Other ->
_ = flush_abort_msg(SockRef, OpRef),
Other
end.
flush_select_msg(SockRef, Ref) ->
receive
?socket_msg(?socket(SockRef), select, Ref) ->
ok
after 0 ->
ok
end.
flush_abort_msg(SockRef, Ref) ->
receive
?socket_msg(?socket(SockRef), abort, {Ref, Reason}) ->
Reason
after 0 ->
ok
end.
%% ===========================================================================
%%
%% Misc utility functions
%%
%% ===========================================================================
%% formated_timestamp() ->
%% format_timestamp(os:timestamp()).
%% format_timestamp(Now) ->
%% N2T = fun(N) -> calendar:now_to_local_time(N) end,
%% format_timestamp(Now, N2T, true).
%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
%% FormatExtra = ".~.2.0w",
%% ArgsExtra = [N3 div 10000],
%% format_timestamp(N, N2T, FormatExtra, ArgsExtra);
%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
%% FormatExtra = "",
%% ArgsExtra = [],
%% format_timestamp(N, N2T, FormatExtra, ArgsExtra).
%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
%% {Date, Time} = N2T(N),
%% {YYYY,MM,DD} = Date,
%% {Hour,Min,Sec} = Time,
%% FormatDate =
%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
%% lists:flatten(FormatDate).
deadline(Timeout) ->
case Timeout of
nowait ->
Timeout;
infinity ->
Timeout;
0 ->
zero;
_ when is_integer(Timeout), 0 < Timeout ->
timestamp() + Timeout;
_ ->
badarg
end.
timeout(Deadline) ->
case Deadline of
infinity ->
Deadline;
zero ->
0;
_ ->
Now = timestamp(),
if
Deadline > Now ->
Deadline - Now;
true ->
0
end
end.
timestamp() ->
erlang:monotonic_time(milli_seconds).
-compile({inline, [bincat/2]}).
bincat(<<>>, <<_/binary>> = B) -> B;
bincat(<<_/binary>> = A, <<>>) -> A;
bincat(<<_/binary>> = A, <<_/binary>> = B) ->
<>.
%% p(F) ->
%% p(F, []).
%% p(F, A) ->
%% p(get(sname), F, A).
%% p(undefined, F, A) ->
%% p("***", F, A);
%% p(SName, F, A) ->
%% TS = formated_timestamp(),
%% io:format(user,"[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]),
%% io:format("[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]).