%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%% The SCTP protocol was added 2006
%% by Leonid Timochouk <[email protected]>
%% and Serge Aleynikov <[email protected]>
%% at IDT Corp. Adapted by the OTP team at Ericsson AB.
%%
-module(prim_inet).
%% Primitive inet_drv interface
-export([open/3, fdopen/4, close/1]).
-export([bind/3, listen/1, listen/2, peeloff/2]).
-export([connect/3, connect/4, async_connect/4]).
-export([accept/1, accept/2, async_accept/2]).
-export([shutdown/2]).
-export([send/2, send/3, sendto/4, sendmsg/3]).
-export([recv/2, recv/3, async_recv/3]).
-export([unrecv/2]).
-export([recvfrom/2, recvfrom/3]).
-export([setopt/3, setopts/2, getopt/2, getopts/2, is_sockopt_val/2]).
-export([chgopt/3, chgopts/2]).
-export([getstat/2, getfd/1, getindex/1, getstatus/1, gettype/1,
getifaddrs/1, getiflist/1, ifget/3, ifset/3,
gethostname/1]).
-export([getservbyname/3, getservbyport/3]).
-export([peername/1, setpeername/2]).
-export([sockname/1, setsockname/2]).
-export([attach/1, detach/1]).
-include("inet_sctp.hrl").
-include("inet_int.hrl").
%-define(DEBUG, 1).
-ifdef(DEBUG).
-define(DBG_FORMAT(Format, Args), (io:format((Format), (Args)))).
-else.
-define(DBG_FORMAT(Format, Args), ok).
-endif.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% OPEN(tcp | udp | sctp, inet | inet6, stream | dgram | seqpacket) ->
%% {ok, insock()} |
%% {error, Reason}
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
open(Protocol, Family, Type) ->
open(Protocol, Family, Type, ?INET_REQ_OPEN, []).
fdopen(Protocol, Family, Type, Fd) when is_integer(Fd) ->
open(Protocol, Family, Type, ?INET_REQ_FDOPEN, ?int32(Fd)).
open(Protocol, Family, Type, Req, Data) ->
Drv = protocol2drv(Protocol),
AF = enc_family(Family),
T = enc_type(Type),
try erlang:open_port({spawn_driver,Drv}, [binary]) of
S ->
case ctl_cmd(S, Req, [AF,T,Data]) of
{ok,_} -> {ok,S};
{error,_}=Error ->
close(S),
Error
end
catch
%% The only (?) way to get here is to try to open
%% the sctp driver when it does not exist
error:badarg -> {error,eprotonosupport}
end.
enc_family(inet) -> ?INET_AF_INET;
enc_family(inet6) -> ?INET_AF_INET6.
enc_type(stream) -> ?INET_TYPE_STREAM;
enc_type(dgram) -> ?INET_TYPE_DGRAM;
enc_type(seqpacket) -> ?INET_TYPE_SEQPACKET.
protocol2drv(tcp) -> "tcp_inet";
protocol2drv(udp) -> "udp_inet";
protocol2drv(sctp) -> "sctp_inet".
drv2protocol("tcp_inet") -> tcp;
drv2protocol("udp_inet") -> udp;
drv2protocol("sctp_inet") -> sctp;
drv2protocol(_) -> undefined.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Shutdown(insock(), atom()) -> ok
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% TODO: shutdown equivalent for SCTP
%%
shutdown(S, read) when is_port(S) ->
shutdown_2(S, 0);
shutdown(S, write) when is_port(S) ->
shutdown_1(S, 1);
shutdown(S, read_write) when is_port(S) ->
shutdown_1(S, 2).
shutdown_1(S, How) ->
case subscribe(S, [subs_empty_out_q]) of
{ok,[{subs_empty_out_q,N}]} when N > 0 ->
shutdown_pend_loop(S, N); %% wait for pending output to be sent
_Other -> ok
end,
shutdown_2(S, How).
shutdown_2(S, How) ->
case ctl_cmd(S, ?TCP_REQ_SHUTDOWN, [How]) of
{ok, []} -> ok;
{error,_}=Error -> Error
end.
shutdown_pend_loop(S, N0) ->
receive
{empty_out_q,S} -> ok
after ?INET_CLOSE_TIMEOUT ->
case getstat(S, [send_pend]) of
{ok,[{send_pend,N0}]} -> ok;
{ok,[{send_pend,N}]} -> shutdown_pend_loop(S, N);
_ -> ok
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% CLOSE(insock()) -> ok
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
close(S) when is_port(S) ->
unlink(S), %% avoid getting {'EXIT', S, Reason}
case subscribe(S, [subs_empty_out_q]) of
{ok, [{subs_empty_out_q,N}]} when N > 0 ->
close_pend_loop(S, N); %% wait for pending output to be sent
_ ->
catch erlang:port_close(S),
ok
end.
close_pend_loop(S, N) ->
receive
{empty_out_q,S} ->
catch erlang:port_close(S), ok
after ?INET_CLOSE_TIMEOUT ->
case getstat(S, [send_pend]) of
{ok, [{send_pend,N1}]} ->
if N1 =:= N -> catch erlang:port_close(S), ok;
true -> close_pend_loop(S, N1)
end;
_ ->
catch erlang:port_close(S), ok
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% BIND(insock(), IP, Port) -> {ok, integer()} | {error, Reason}
%%
%% bind the insock() to the interface address given by IP and Port
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
bind(S,IP,Port) when is_port(S), is_integer(Port), Port >= 0, Port =< 65535 ->
case ctl_cmd(S,?INET_REQ_BIND,[?int16(Port),ip_to_bytes(IP)]) of
{ok, [P1,P0]} -> {ok, ?u16(P1, P0)};
{error,_}=Error -> Error
end;
%% Multi-homed "bind": sctp_bindx(). The Op is 'add' or 'remove'.
%% If no addrs are specified, it just does nothing.
%% Function returns {ok, S} on success, unlike TCP/UDP "bind":
bind(S, Op, Addrs) when is_port(S), is_list(Addrs) ->
case Op of
add ->
bindx(S, 1, Addrs);
remove ->
bindx(S, 0, Addrs);
_ -> {error, einval}
end;
bind(_, _, _) -> {error, einval}.
bindx(S, AddFlag, Addrs) ->
case getprotocol(S) of
sctp ->
%% Really multi-homed "bindx". Stringified args:
%% [AddFlag, (Port, IP)+]:
Args =
[?int8(AddFlag)|
[[?int16(Port)|ip_to_bytes(IP)] ||
{IP, Port} <- Addrs]],
case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of
{ok,_} -> {ok, S};
{error,_}=Error -> Error
end;
_ -> {error, einval}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% CONNECT(insock(), IP, Port [,Timeout]) -> ok | {error, Reason}
%%
%% connect the insock() to the address given by IP and Port
%% if timeout is given:
%% timeout < 0 -> infinity
%% 0 -> immediate connect (mostly works for loopback)
%% > 0 -> wait for timout ms if not connected then
%% return {error, timeout}
%%
%% ASYNC_CONNECT(insock(), IP, Port, Timeout) -> {ok, S, Ref} | {error, Reason}
%%
%% a {inet_async,S,Ref,Status} will be sent on socket condition
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% For TCP, UDP or SCTP sockets.
%%
connect(S, IP, Port) -> connect0(S, IP, Port, -1).
connect(S, IP, Port, infinity) -> connect0(S, IP, Port, -1);
connect(S, IP, Port, Time) -> connect0(S, IP, Port, Time).
connect0(S, IP, Port, Time) when is_port(S), Port > 0, Port =< 65535,
is_integer(Time) ->
case async_connect(S, IP, Port, Time) of
{ok, S, Ref} ->
receive
{inet_async, S, Ref, Status} ->
Status
end;
Error -> Error
end.
async_connect(S, IP, Port, Time) ->
case ctl_cmd(S, ?INET_REQ_CONNECT,
[enc_time(Time),?int16(Port),ip_to_bytes(IP)]) of
{ok, [R1,R0]} -> {ok, S, ?u16(R1,R0)};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% ACCEPT(insock() [,Timeout] ) -> {ok,insock()} | {error, Reason}
%%
%% accept incoming connection on listen socket
%% if timeout is given:
%% timeout < 0 -> infinity
%% 0 -> immediate accept (poll)
%% > 0 -> wait for timout ms for accept if no accept then
%% return {error, timeout}
%%
%% ASYNC_ACCEPT(insock(), Timeout)
%%
%% async accept. return {ok,S,Ref} or {error, Reason}
%% the owner of socket S will receive an {inet_async,S,Ref,Status} on
%% socket condition
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% For TCP sockets only.
%%
accept(L) -> accept0(L, -1).
accept(L, infinity) -> accept0(L, -1);
accept(L, Time) -> accept0(L, Time).
accept0(L, Time) when is_port(L), is_integer(Time) ->
case async_accept(L, Time) of
{ok, Ref} ->
receive
{inet_async, L, Ref, {ok,S}} ->
accept_opts(L, S);
{inet_async, L, Ref, Error} ->
Error
end;
Error -> Error
end.
%% setup options from listen socket on the connected socket
accept_opts(L, S) ->
case getopts(L, [active, nodelay, keepalive, delay_send, priority, tos]) of
{ok, Opts} ->
case setopts(S, Opts) of
ok -> {ok, S};
Error -> close(S), Error
end;
Error ->
close(S), Error
end.
async_accept(L, Time) ->
case ctl_cmd(L,?INET_REQ_ACCEPT, [enc_time(Time)]) of
{ok, [R1,R0]} -> {ok, ?u16(R1,R0)};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% LISTEN(insock() [,Backlog]) -> ok | {error, Reason}
%%
%% set listen mode on socket
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% For TCP or SCTP sockets. For SCTP, Boolean backlog value (enable/disable
%% listening) is also accepted:
listen(S) -> listen(S, ?LISTEN_BACKLOG).
listen(S, true) -> listen(S, ?LISTEN_BACKLOG);
listen(S, false) -> listen(S, 0);
listen(S, BackLog) when is_port(S), is_integer(BackLog) ->
case ctl_cmd(S, ?INET_REQ_LISTEN, [?int16(BackLog)]) of
{ok, _} -> ok;
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% PEELOFF(insock(), AssocId) -> {ok,outsock()} | {error, Reason}
%%
%% SCTP: Peel off one association into a type stream socket
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
peeloff(S, AssocId) ->
case ctl_cmd(S, ?SCTP_REQ_PEELOFF, [?int32(AssocId)]) of
inet_reply ->
receive
{inet_reply,S,Res} -> Res
end;
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% SEND(insock(), Data) -> ok | {error, Reason}
%%
%% send Data on the socket (io-list)
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This is a generic "port_command" interface used by TCP, UDP, SCTP, depending
%% on the driver it is mapped to, and the "Data". It actually sends out data,--
%% NOT delegating this task to any back-end. For SCTP, this function MUST NOT
%% be called directly -- use "sendmsg" instead:
%%
send(S, Data, OptList) when is_port(S), is_list(OptList) ->
?DBG_FORMAT("prim_inet:send(~p, ~p)~n", [S,Data]),
try erlang:port_command(S, Data, OptList) of
false -> % Port busy and nosuspend option passed
?DBG_FORMAT("prim_inet:send() -> {error,busy}~n", []),
{error,busy};
true ->
receive
{inet_reply,S,Status} ->
?DBG_FORMAT("prim_inet:send() -> ~p~n", [Status]),
Status
end
catch
error:_Error ->
?DBG_FORMAT("prim_inet:send() -> {error,einval}~n", []),
{error,einval}
end.
send(S, Data) ->
send(S, Data, []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% SENDTO(insock(), IP, Port, Data) -> ok | {error, Reason}
%%
%% send Datagram to the IP at port (Should add sync send!)
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% "sendto" is for UDP. IP and Port are set by the caller to 0 if the socket
%% is known to be connected.
sendto(S, IP, Port, Data) when is_port(S), Port >= 0, Port =< 65535 ->
?DBG_FORMAT("prim_inet:sendto(~p, ~p, ~p, ~p)~n", [S,IP,Port,Data]),
try erlang:port_command(S, [?int16(Port),ip_to_bytes(IP),Data]) of
true ->
receive
{inet_reply,S,Reply} ->
?DBG_FORMAT("prim_inet:sendto() -> ~p~n", [Reply]),
Reply
end
catch
error:_ ->
?DBG_FORMAT("prim_inet:sendto() -> {error,einval}~n", []),
{error,einval}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% SENDMSG(insock(), IP, Port, InitMsg, Data) or
%% SENDMSG(insock(), SndRcvInfo, Data) -> ok | {error, Reason}
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% SCTP: Sending data over an existing association: no need for a destination
%% addr; uses SndRcvInfo:
%%
sendmsg(S, #sctp_sndrcvinfo{}=SRI, Data) when is_port(S) ->
Type = type_opt(set, sctp_default_send_param),
try type_value(set, Type, SRI) of
true ->
send(S, [enc_value(set, Type, SRI)|Data]);
false -> {error,einval}
catch
Reason -> {error,Reason}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% RECV(insock(), Length, [Timeout]) -> {ok,Data} | {error, Reason}
%%
%% receive Length data bytes from a socket
%% if 0 is given then a Data packet is requested (see setopt (packet))
%% N read N bytes
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% "recv" is for TCP:
recv(S, Length) -> recv0(S, Length, -1).
recv(S, Length, infinity) -> recv0(S, Length,-1);
recv(S, Length, Time) when is_integer(Time) -> recv0(S, Length, Time).
recv0(S, Length, Time) when is_port(S), is_integer(Length), Length >= 0 ->
case async_recv(S, Length, Time) of
{ok, Ref} ->
receive
{inet_async, S, Ref, Status} -> Status;
{'EXIT', S, _Reason} ->
{error, closed}
end;
Error -> Error
end.
async_recv(S, Length, Time) ->
case ctl_cmd(S, ?TCP_REQ_RECV, [enc_time(Time), ?int32(Length)]) of
{ok,[R1,R0]} -> {ok, ?u16(R1,R0)};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% RECVFROM(insock(), Lenth [Timeout]) -> {ok,{IP,Port,Data}} | {error, Reason}
%% For SCTP: -> {ok,{IP,Port,[AncData],Data}}
%% | {error, Reason}
%% receive Length data bytes from a datagram socket sent from IP at Port
%% if 0 is given then a Data packet is requested (see setopt (packet))
%% N read N bytes
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% "recvfrom" is for both UDP and SCTP.
%% NB: "Length" is actually ignored for these protocols, since they are msg-
%% oriented: preserved here only for API compatibility.
%%
recvfrom(S, Length) ->
recvfrom0(S, Length, -1).
recvfrom(S, Length, infinity) ->
recvfrom0(S, Length, -1);
recvfrom(S, Length, Time) when is_integer(Time), Time < 16#ffffffff ->
recvfrom0(S, Length, Time);
recvfrom(_, _, _) -> {error,einval}.
recvfrom0(S, Length, Time)
when is_port(S), is_integer(Length), Length >= 0, Length =< 16#ffffffff ->
case ctl_cmd(S, ?PACKET_REQ_RECV,[enc_time(Time),?int32(Length)]) of
{ok,[R1,R0]} ->
Ref = ?u16(R1,R0),
receive
% Success, UDP:
{inet_async, S, Ref, {ok, [F,P1,P0 | AddrData]}} ->
{IP,Data} = get_ip(F, AddrData),
{ok, {IP, ?u16(P1,P0), Data}};
% Success, SCTP:
{inet_async, S, Ref, {ok, {[F,P1,P0 | Addr], AncData, DE}}} ->
{IP, _} = get_ip(F, Addr),
{ok, {IP, ?u16(P1,P0), AncData, DE}};
% Back-end error:
{inet_async, S, Ref, Error={error, _}} ->
Error
end;
{error,_}=Error ->
Error % Front-end error
end;
recvfrom0(_, _, _) -> {error,einval}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% PEERNAME(insock()) -> {ok, {IP, Port}} | {error, Reason}
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
peername(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_PEER, []) of
{ok, [F, P1,P0 | Addr]} ->
{IP, _} = get_ip(F, Addr),
{ok, { IP, ?u16(P1, P0) }};
{error,_}=Error -> Error
end.
setpeername(S, {IP,Port}) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_SETPEER, [?int16(Port),ip_to_bytes(IP)]) of
{ok,[]} -> ok;
{error,_}=Error -> Error
end;
setpeername(S, undefined) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_SETPEER, []) of
{ok,[]} -> ok;
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% SOCKNAME(insock()) -> {ok, {IP, Port}} | {error, Reason}
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sockname(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_NAME, []) of
{ok, [F, P1, P0 | Addr]} ->
{IP, _} = get_ip(F, Addr),
{ok, { IP, ?u16(P1, P0) }};
{error,_}=Error -> Error
end.
setsockname(S, {IP,Port}) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_SETNAME, [?int16(Port),ip_to_bytes(IP)]) of
{ok,[]} -> ok;
{error,_}=Error -> Error
end;
setsockname(S, undefined) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_SETNAME, []) of
{ok,[]} -> ok;
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% SETOPT(insock(), Opt, Value) -> ok | {error, Reason}
%% SETOPTS(insock(), [{Opt,Value}]) -> ok | {error, Reason}
%%
%% set socket, ip and driver option
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
setopt(S, Opt, Value) when is_port(S) ->
setopts(S, [{Opt,Value}]).
setopts(S, Opts) when is_port(S) ->
case encode_opt_val(Opts) of
{ok, Buf} ->
case ctl_cmd(S, ?INET_REQ_SETOPTS, Buf) of
{ok, _} -> ok;
{error,_}=Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETOPT(insock(), Opt) -> {ok,Value} | {error, Reason}
%% GETOPTS(insock(), [Opt]) -> {ok, [{Opt,Value}]} | {error, Reason}
%% get socket, ip and driver option
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getopt(S, Opt) when is_port(S), is_atom(Opt) ->
case getopts(S, [Opt]) of
{ok,[{_,Value}]} -> {ok, Value};
Error -> Error
end.
getopts(S, Opts) when is_port(S), is_list(Opts) ->
case encode_opts(Opts) of
{ok,Buf} ->
case ctl_cmd(S, ?INET_REQ_GETOPTS, Buf) of
{ok,Rep} ->
%% Non-SCTP: "Rep" contains the encoded option vals:
decode_opt_val(Rep);
inet_reply ->
%% SCTP: Need to receive the full value:
receive
{inet_reply,S,Res} -> Res
end;
{error,_}=Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% CHGOPT(insock(), Opt) -> {ok,Value} | {error, Reason}
%% CHGOPTS(insock(), [Opt]) -> {ok, [{Opt,Value}]} | {error, Reason}
%% change socket, ip and driver option
%%
%% Same as setopts except for record value options where undefined
%% fields are read with getopts before setting.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
chgopt(S, Opt, Value) when is_port(S) ->
chgopts(S, [{Opt,Value}]).
chgopts(S, Opts) when is_port(S), is_list(Opts) ->
case getopts(S, need_template(Opts)) of
{ok,Templates} ->
try merge_options(Opts, Templates) of
NewOpts ->
setopts(S, NewOpts)
catch
Reason -> {error,Reason}
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% getifaddrs(insock()) -> {ok,IfAddrsList} | {error, Reason}
%%
%% IfAddrsList = [{Name,[Opts]}]
%% Name = string()
%% Opts = {flags,[Flag]} | {addr,Addr} | {netmask,Addr} | {broadaddr,Addr}
%% | {dstaddr,Addr} | {hwaddr,HwAddr} | {mtu,integer()}
%% Flag = up | broadcast | loopback | running | multicast
%% Addr = ipv4addr() | ipv6addr()
%% HwAddr = ethernet_addr()
%%
%% get interface name and addresses list
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getifaddrs(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_GETIFADDRS, []) of
{ok, Data} ->
{ok, comp_ifaddrs(build_ifaddrs(Data), ktree_empty())};
{error,enotsup} ->
case getiflist(S) of
{ok, IFs} ->
{ok, getifaddrs_ifget(S, IFs)};
Err1 -> Err1
end;
Err2 -> Err2
end.
%% Restructure interface properties per interface and remove duplicates
comp_ifaddrs([{If,Opts}|IfOpts], T) ->
case ktree_is_defined(If, T) of
true ->
OptSet = comp_ifaddrs_add(ktree_get(If, T), Opts),
comp_ifaddrs(IfOpts, ktree_update(If, OptSet, T));
false ->
OptSet = comp_ifaddrs_add(ktree_empty(), Opts),
comp_ifaddrs(IfOpts, ktree_insert(If, OptSet, T))
end;
comp_ifaddrs([], T) ->
[{If,ktree_keys(ktree_get(If, T))} || If <- ktree_keys(T)].
comp_ifaddrs_add(OptSet, [Opt|Opts]) ->
case ktree_is_defined(Opt, OptSet) of
true
when element(1, Opt) =:= flags;
element(1, Opt) =:= hwaddr ->
comp_ifaddrs_add(OptSet, Opts);
_ ->
comp_ifaddrs_add(ktree_insert(Opt, undefined, OptSet), Opts)
end;
comp_ifaddrs_add(OptSet, []) -> OptSet.
%% Legacy emulation of getifaddrs
getifaddrs_ifget(_, []) -> [];
getifaddrs_ifget(S, [IF|IFs]) ->
case ifget(S, IF, [flags]) of
{ok,[{flags,Flags}]=FlagsVals} ->
BroadOpts =
case member(broadcast, Flags) of
true ->
[broadaddr,hwaddr];
false ->
[hwaddr]
end,
P2POpts =
case member(pointtopoint, Flags) of
true ->
[dstaddr|BroadOpts];
false ->
BroadOpts
end,
getifaddrs_ifget(S, IFs, IF, FlagsVals, [addr,netmask|P2POpts]);
_ ->
getifaddrs_ifget(S, IFs, IF, [], [addr,netmask,hwaddr])
end.
getifaddrs_ifget(S, IFs, IF, FlagsVals, Opts) ->
OptVals =
case ifget(S, IF, Opts) of
{ok,OVs} -> OVs;
_ -> []
end,
[{IF,FlagsVals++OptVals}|getifaddrs_ifget(S, IFs)].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% getiflist(insock()) -> {ok,IfNameList} | {error, Reason}
%%
%% get interface name list
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getiflist(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_GETIFLIST, []) of
{ok, Data} -> {ok, build_iflist(Data)};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% ifget(insock(), IFOpts) -> {ok,IfNameList} | {error, Reason}
%%
%% get interface name list
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ifget(S, Name, Opts) ->
case encode_ifname(Name) of
{ok, Buf1} ->
case encode_ifopts(Opts,[]) of
{ok, Buf2} ->
case ctl_cmd(S, ?INET_REQ_IFGET, [Buf1,Buf2]) of
{ok, Data} -> decode_ifopts(Data,[]);
{error,_}=Error -> Error
end;
Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% ifset(insock(), Name, IFOptVals) -> {ok,IfNameList} | {error, Reason}
%%
%% set interface parameters
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ifset(S, Name, Opts) ->
case encode_ifname(Name) of
{ok, Buf1} ->
case encode_ifopt_val(Opts,[]) of
{ok, Buf2} ->
case ctl_cmd(S, ?INET_REQ_IFSET, [Buf1,Buf2]) of
{ok, _} -> ok;
{error,_}=Error -> Error
end;
Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% subscribe(insock(), SubsList) -> {ok,StatReply} | {error, Reason}
%%
%% Subscribe on socket events (from driver)
%%
%% Available event subscriptions:
%% subs_empty_out_q: StatReply = [{subs_empty_out_q, N}], where N
%% is current queue length. When the queue becomes empty
%% a {empty_out_q, insock()} message will be sent to
%% subscribing process and the subscription will be
%% removed. If N = 0, the queue is empty and no
%% subscription is made.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
subscribe(S, Sub) when is_port(S), is_list(Sub) ->
case encode_subs(Sub) of
{ok, Bytes} ->
case ctl_cmd(S, ?INET_REQ_SUBSCRIBE, Bytes) of
{ok, Data} -> decode_subs(Data);
{error,_}=Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETSTAT(insock(), StatList) -> {ok,StatReply} | {error, Reason}
%%
%% get socket statistics (from driver)
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getstat(S, Stats) when is_port(S), is_list(Stats) ->
case encode_stats(Stats) of
{ok, Bytes} ->
case ctl_cmd(S, ?INET_REQ_GETSTAT, Bytes) of
{ok, Data} -> decode_stats(Data);
{error,_}=Error -> Error
end;
Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETFD(insock()) -> {ok,integer()} | {error, Reason}
%%
%% get internal file descriptor
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getfd(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_GETFD, []) of
{ok, [S3,S2,S1,S0]} -> {ok, ?u32(S3,S2,S1,S0)};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETIX(insock()) -> {ok,integer()} | {error, Reason}
%%
%% get internal socket index
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getindex(S) when is_port(S) ->
%% NOT USED ANY MORE
{error, einval}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETTYPE(insock()) -> {ok,{Family,Type}} | {error, Reason}
%%
%% get family/type of a socket
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
gettype(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_GETTYPE, []) of
{ok, [F3,F2,F1,F0,T3,T2,T1,T0]} ->
Family = case ?u32(F3,F2,F1,F0) of
?INET_AF_INET -> inet;
?INET_AF_INET6 -> inet6;
_ -> undefined
end,
Type = case ?u32(T3,T2,T1,T0) of
?INET_TYPE_STREAM -> stream;
?INET_TYPE_DGRAM -> dgram;
?INET_TYPE_SEQPACKET -> seqpacket;
_ -> undefined
end,
{ok, {Family, Type}};
{error,_}=Error -> Error
end.
getprotocol(S) when is_port(S) ->
{name,Drv} = erlang:port_info(S, name),
drv2protocol(Drv).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% IS_SCTP(insock()) -> true | false
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% is_sctp(S) when is_port(S) ->
%% case gettype(S) of
%% {ok, {_, seqpacket}} -> true;
%% _ -> false
%% end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETSTATUS(insock()) -> {ok,Status} | {error, Reason}
%%
%% get socket status
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getstatus(S) when is_port(S) ->
case ctl_cmd(S, ?INET_REQ_GETSTATUS, []) of
{ok, [S3,S2,S1,S0]} ->
{ok, dec_status(?u32(S3,S2,S1,S0))};
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETHOSTNAME(insock()) -> {ok,HostName} | {error, Reason}
%%
%% get host name
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
gethostname(S) when is_port(S) ->
ctl_cmd(S, ?INET_REQ_GETHOSTNAME, []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETSERVBYNAME(insock(),Name,Proto) -> {ok,Port} | {error, Reason}
%%
%% get service port
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getservbyname(S,Name,Proto) when is_port(S), is_atom(Name), is_atom(Proto) ->
getservbyname1(S, atom_to_list(Name), atom_to_list(Proto));
getservbyname(S,Name,Proto) when is_port(S), is_atom(Name), is_list(Proto) ->
getservbyname1(S, atom_to_list(Name), Proto);
getservbyname(S,Name,Proto) when is_port(S), is_list(Name), is_atom(Proto) ->
getservbyname1(S, Name, atom_to_list(Proto));
getservbyname(S,Name,Proto) when is_port(S), is_list(Name), is_list(Proto) ->
getservbyname1(S, Name, Proto);
getservbyname(_,_, _) ->
{error, einval}.
getservbyname1(S,Name,Proto) ->
L1 = length(Name),
L2 = length(Proto),
if L1 > 255 -> {error, einval};
L2 > 255 -> {error, einval};
true ->
case ctl_cmd(S, ?INET_REQ_GETSERVBYNAME, [L1,Name,L2,Proto]) of
{ok, [P1,P0]} ->
{ok, ?u16(P1,P0)};
{error,_}=Error ->
Error
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% GETSERVBYPORT(insock(),Port,Proto) -> {ok,Port} | {error, Reason}
%%
%% get service port from portnumber and protocol
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
getservbyport(S,Port,Proto) when is_port(S), is_atom(Proto) ->
getservbyport1(S, Port, atom_to_list(Proto));
getservbyport(S,Port,Proto) when is_port(S), is_list(Proto) ->
getservbyport1(S, Port, Proto);
getservbyport(_, _, _) ->
{error, einval}.
getservbyport1(S,Port,Proto) ->
L = length(Proto),
if Port < 0 -> {error, einval};
Port > 16#ffff -> {error, einval};
L > 255 -> {error, einval};
true ->
case ctl_cmd(S, ?INET_REQ_GETSERVBYPORT, [?int16(Port),L,Proto]) of
{ok, Name} -> {ok, Name};
{error,_}=Error -> Error
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% UNRECV(insock(), data) -> ok | {error, Reason}
%%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
unrecv(S, Data) ->
case ctl_cmd(S, ?TCP_REQ_UNRECV, Data) of
{ok, _} -> ok;
{error,_}=Error -> Error
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% DETACH(insock()) -> ok
%%
%% unlink from a socket
%%
%% ATTACH(insock()) -> ok | {error, Reason}
%%
%% link and connect to a socket
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
detach(S) when is_port(S) ->
unlink(S),
ok.
attach(S) when is_port(S) ->
try erlang:port_connect(S, self()) of
true -> link(S), ok
catch
error:Reason -> {error,Reason}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% INTERNAL FUNCTIONS
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
is_sockopt_val(Opt, Val) ->
Type = type_opt(set, Opt),
try type_value(set, Type, Val)
catch
_ -> false
end.
%%
%% Socket options processing: Encoding option NAMES:
%%
enc_opt(reuseaddr) -> ?INET_OPT_REUSEADDR;
enc_opt(keepalive) -> ?INET_OPT_KEEPALIVE;
enc_opt(dontroute) -> ?INET_OPT_DONTROUTE;
enc_opt(linger) -> ?INET_OPT_LINGER;
enc_opt(broadcast) -> ?INET_OPT_BROADCAST;
enc_opt(sndbuf) -> ?INET_OPT_SNDBUF;
enc_opt(recbuf) -> ?INET_OPT_RCVBUF;
enc_opt(priority) -> ?INET_OPT_PRIORITY;
enc_opt(tos) -> ?INET_OPT_TOS;
enc_opt(nodelay) -> ?TCP_OPT_NODELAY;
enc_opt(multicast_if) -> ?UDP_OPT_MULTICAST_IF;
enc_opt(multicast_ttl) -> ?UDP_OPT_MULTICAST_TTL;
enc_opt(multicast_loop) -> ?UDP_OPT_MULTICAST_LOOP;
enc_opt(add_membership) -> ?UDP_OPT_ADD_MEMBERSHIP;
enc_opt(drop_membership) -> ?UDP_OPT_DROP_MEMBERSHIP;
enc_opt(buffer) -> ?INET_LOPT_BUFFER;
enc_opt(header) -> ?INET_LOPT_HEADER;
enc_opt(active) -> ?INET_LOPT_ACTIVE;
enc_opt(packet) -> ?INET_LOPT_PACKET;
enc_opt(mode) -> ?INET_LOPT_MODE;
enc_opt(deliver) -> ?INET_LOPT_DELIVER;
enc_opt(exit_on_close) -> ?INET_LOPT_EXITONCLOSE;
enc_opt(high_watermark) -> ?INET_LOPT_TCP_HIWTRMRK;
enc_opt(low_watermark) -> ?INET_LOPT_TCP_LOWTRMRK;
enc_opt(bit8) -> ?INET_LOPT_BIT8;
enc_opt(send_timeout) -> ?INET_LOPT_TCP_SEND_TIMEOUT;
enc_opt(send_timeout_close) -> ?INET_LOPT_TCP_SEND_TIMEOUT_CLOSE;
enc_opt(delay_send) -> ?INET_LOPT_TCP_DELAY_SEND;
enc_opt(packet_size) -> ?INET_LOPT_PACKET_SIZE;
enc_opt(read_packets) -> ?INET_LOPT_READ_PACKETS;
enc_opt(raw) -> ?INET_OPT_RAW;
% Names of SCTP opts:
enc_opt(sctp_rtoinfo) -> ?SCTP_OPT_RTOINFO;
enc_opt(sctp_associnfo) -> ?SCTP_OPT_ASSOCINFO;
enc_opt(sctp_initmsg) -> ?SCTP_OPT_INITMSG;
enc_opt(sctp_autoclose) -> ?SCTP_OPT_AUTOCLOSE;
enc_opt(sctp_nodelay) -> ?SCTP_OPT_NODELAY;
enc_opt(sctp_disable_fragments) -> ?SCTP_OPT_DISABLE_FRAGMENTS;
enc_opt(sctp_i_want_mapped_v4_addr)-> ?SCTP_OPT_I_WANT_MAPPED_V4_ADDR;
enc_opt(sctp_maxseg) -> ?SCTP_OPT_MAXSEG;
enc_opt(sctp_set_peer_primary_addr)-> ?SCTP_OPT_SET_PEER_PRIMARY_ADDR;
enc_opt(sctp_primary_addr) -> ?SCTP_OPT_PRIMARY_ADDR;
enc_opt(sctp_adaptation_layer) -> ?SCTP_OPT_ADAPTATION_LAYER;
enc_opt(sctp_peer_addr_params) -> ?SCTP_OPT_PEER_ADDR_PARAMS;
enc_opt(sctp_default_send_param) -> ?SCTP_OPT_DEFAULT_SEND_PARAM;
enc_opt(sctp_events) -> ?SCTP_OPT_EVENTS;
enc_opt(sctp_delayed_ack_time) -> ?SCTP_OPT_DELAYED_ACK_TIME;
enc_opt(sctp_status) -> ?SCTP_OPT_STATUS;
enc_opt(sctp_get_peer_addr_info) -> ?SCTP_OPT_GET_PEER_ADDR_INFO.
%%
%%
%% Decoding option NAMES:
%%
dec_opt(?INET_OPT_REUSEADDR) -> reuseaddr;
dec_opt(?INET_OPT_KEEPALIVE) -> keepalive;
dec_opt(?INET_OPT_DONTROUTE) -> dontroute;
dec_opt(?INET_OPT_LINGER) -> linger;
dec_opt(?INET_OPT_BROADCAST) -> broadcast;
dec_opt(?INET_OPT_SNDBUF) -> sndbuf;
dec_opt(?INET_OPT_RCVBUF) -> recbuf;
dec_opt(?INET_OPT_PRIORITY) -> priority;
dec_opt(?INET_OPT_TOS) -> tos;
dec_opt(?TCP_OPT_NODELAY) -> nodelay;
dec_opt(?UDP_OPT_MULTICAST_IF) -> multicast_if;
dec_opt(?UDP_OPT_MULTICAST_TTL) -> multicast_ttl;
dec_opt(?UDP_OPT_MULTICAST_LOOP) -> multicast_loop;
dec_opt(?UDP_OPT_ADD_MEMBERSHIP) -> add_membership;
dec_opt(?UDP_OPT_DROP_MEMBERSHIP) -> drop_membership;
dec_opt(?INET_LOPT_BUFFER) -> buffer;
dec_opt(?INET_LOPT_HEADER) -> header;
dec_opt(?INET_LOPT_ACTIVE) -> active;
dec_opt(?INET_LOPT_PACKET) -> packet;
dec_opt(?INET_LOPT_MODE) -> mode;
dec_opt(?INET_LOPT_DELIVER) -> deliver;
dec_opt(?INET_LOPT_EXITONCLOSE) -> exit_on_close;
dec_opt(?INET_LOPT_TCP_HIWTRMRK) -> high_watermark;
dec_opt(?INET_LOPT_TCP_LOWTRMRK) -> low_watermark;
dec_opt(?INET_LOPT_BIT8) -> bit8;
dec_opt(?INET_LOPT_TCP_SEND_TIMEOUT) -> send_timeout;
dec_opt(?INET_LOPT_TCP_SEND_TIMEOUT_CLOSE) -> send_timeout_close;
dec_opt(?INET_LOPT_TCP_DELAY_SEND) -> delay_send;
dec_opt(?INET_LOPT_PACKET_SIZE) -> packet_size;
dec_opt(?INET_LOPT_READ_PACKETS) -> read_packets;
dec_opt(?INET_OPT_RAW) -> raw;
dec_opt(I) when is_integer(I) -> undefined.
%% Metatypes:
%% [] Value must be 'undefined' or nonexistent
%% for setopts and getopts.
%% [Type] Value required for setopts and getopts,
%% will be encoded for both.
%% [Type,Default] Default used if value is 'undefined'.
%% [[Type,Default]] A combination of the two above.
%% Type Value must be 'undefined' or nonexistent for getops,
%% required for setopts.
%%
%% The use of [] and [[Type,Default]] is commented out in enc_value/2
%% and type_value/2 below since they are only used in record fields.
%% And record fields does not call enc_value/2 nor type_value/2.
%% Anyone introducing these metatypes otherwhere will have to activate
%% those clauses in enc_value/2 and type_value/2. You have been warned!
type_opt(get, raw) -> [{[int],[int],[binary_or_uint]}];
type_opt(_, raw) -> {int,int,binary};
%% NB: "sctp_status" and "sctp_get_peer_addr_info" are read-only options,
%% so they should not be NOT encoded for use with "setopt".
type_opt(get, sctp_status) ->
[{record,#sctp_status{
assoc_id = [sctp_assoc_id],
_ = []}}];
type_opt(get, sctp_get_peer_addr_info) ->
[{record,#sctp_paddrinfo{
assoc_id = [[sctp_assoc_id,0]],
address = [[addr,{any,0}]],
_ = []}}];
type_opt(_, Opt) ->
type_opt_1(Opt).
%% Types of option values, by option name:
%%
type_opt_1(reuseaddr) -> bool;
type_opt_1(keepalive) -> bool;
type_opt_1(dontroute) -> bool;
type_opt_1(linger) -> {bool,int};
type_opt_1(broadcast) -> bool;
type_opt_1(sndbuf) -> int;
type_opt_1(recbuf) -> int;
type_opt_1(priority) -> int;
type_opt_1(tos) -> int;
type_opt_1(nodelay) -> bool;
%% multicast
type_opt_1(multicast_ttl) -> int;
type_opt_1(multicast_loop) -> bool;
type_opt_1(multicast_if) -> ip;
type_opt_1(add_membership) -> {ip,ip};
type_opt_1(drop_membership) -> {ip,ip};
%% driver options
type_opt_1(header) -> uint;
type_opt_1(buffer) -> int;
type_opt_1(active) ->
{enum,[{false, ?INET_PASSIVE},
{true, ?INET_ACTIVE},
{once, ?INET_ONCE}]};
type_opt_1(packet) ->
{enum,[{0, ?TCP_PB_RAW},
{1, ?TCP_PB_1},
{2, ?TCP_PB_2},
{4, ?TCP_PB_4},
{raw,?TCP_PB_RAW},
{sunrm, ?TCP_PB_RM},
{asn1, ?TCP_PB_ASN1},
{cdr, ?TCP_PB_CDR},
{fcgi, ?TCP_PB_FCGI},
{line, ?TCP_PB_LINE_LF},
{tpkt, ?TCP_PB_TPKT},
{http, ?TCP_PB_HTTP},
{httph,?TCP_PB_HTTPH},
{http_bin, ?TCP_PB_HTTP_BIN},
{httph_bin,?TCP_PB_HTTPH_BIN},
{ssl, ?TCP_PB_SSL_TLS}, % obsolete
{ssl_tls, ?TCP_PB_SSL_TLS}]};
type_opt_1(mode) ->
{enum,[{list, ?INET_MODE_LIST},
{binary, ?INET_MODE_BINARY}]};
type_opt_1(deliver) ->
{enum,[{port, ?INET_DELIVER_PORT},
{term, ?INET_DELIVER_TERM}]};
type_opt_1(exit_on_close) -> bool;
type_opt_1(low_watermark) -> int;
type_opt_1(high_watermark) -> int;
type_opt_1(bit8) ->
{enum,[{clear, ?INET_BIT8_CLEAR},
{set, ?INET_BIT8_SET},
{on, ?INET_BIT8_ON},
{off, ?INET_BIT8_OFF}]};
type_opt_1(send_timeout) -> time;
type_opt_1(send_timeout_close) -> bool;
type_opt_1(delay_send) -> bool;
type_opt_1(packet_size) -> uint;
type_opt_1(read_packets) -> uint;
%%
%% SCTP options (to be set). If the type is a record type, the corresponding
%% record signature is returned, otherwise, an "elementary" type tag
%% is returned:
%%
%% for SCTP_OPT_RTOINFO
type_opt_1(sctp_rtoinfo) ->
[{record,#sctp_rtoinfo{
assoc_id = [[sctp_assoc_id,0]],
initial = [uint32,0],
max = [uint32,0],
min = [uint32,0]}}];
%% for SCTP_OPT_ASSOCINFO
type_opt_1(sctp_associnfo) ->
[{record,#sctp_assocparams{
assoc_id = [[sctp_assoc_id,0]],
asocmaxrxt = [uint16,0],
number_peer_destinations = [uint16,0],
peer_rwnd = [uint32,0],
local_rwnd = [uint32,0],
cookie_life = [uint32,0]}}];
%% for SCTP_OPT_INITMSG and SCTP_TAG_SEND_ANC_INITMSG (send*)
type_opt_1(sctp_initmsg) ->
[{record,#sctp_initmsg{
num_ostreams = [uint16,0],
max_instreams = [uint16,0],
max_attempts = [uint16,0],
max_init_timeo = [uint16,0]}}];
%%
type_opt_1(sctp_nodelay) -> bool;
type_opt_1(sctp_autoclose) -> uint;
type_opt_1(sctp_disable_fragments) -> bool;
type_opt_1(sctp_i_want_mapped_v4_addr) -> bool;
type_opt_1(sctp_maxseg) -> uint;
%% for SCTP_OPT_PRIMARY_ADDR
type_opt_1(sctp_primary_addr) ->
[{record,#sctp_prim{
assoc_id = [sctp_assoc_id],
addr = addr}}];
%% for SCTP_OPT_SET_PEER_PRIMARY_ADDR
type_opt_1(sctp_set_peer_primary_addr) ->
[{record,#sctp_setpeerprim{
assoc_id = [sctp_assoc_id],
addr = addr}}];
%% for SCTP_OPT_ADAPTATION_LAYER
type_opt_1(sctp_adaptation_layer) ->
[{record,#sctp_setadaptation{
adaptation_ind = [uint32,0]}}];
%% for SCTP_OPT_PEER_ADDR_PARAMS
type_opt_1(sctp_peer_addr_params) ->
[{record,#sctp_paddrparams{
assoc_id = [[sctp_assoc_id,0]],
address = [[addr,{any,0}]],
hbinterval = [uint32,0],
pathmaxrxt = [uint16,0],
pathmtu = [uint32,0],
sackdelay = [uint32,0],
flags =
[{bitenumlist,
[{hb_enable, ?SCTP_FLAG_HB_ENABLE},
{hb_disable, ?SCTP_FLAG_HB_DISABLE},
{hb_demand, ?SCTP_FLAG_HB_DEMAND},
{pmtud_enable, ?SCTP_FLAG_PMTUD_ENABLE},
{pmtud_disable, ?SCTP_FLAG_PMTUD_DISABLE},
{sackdelay_enable, ?SCTP_FLAG_SACKDELAY_ENABLE},
{sackdelay_disable, ?SCTP_FLAG_SACKDELAY_DISABLE}],
uint32},[]]}}];
%% for SCTP_OPT_DEFAULT_SEND_PARAM and SCTP_TAG_SEND_ANC_PARAMS (on send*)
type_opt_1(sctp_default_send_param) ->
[{record,#sctp_sndrcvinfo{
stream = [uint16,0],
ssn = [],
flags =
[{bitenumlist,
[{unordered, ?SCTP_FLAG_UNORDERED},
{addr_over, ?SCTP_FLAG_ADDR_OVER},
{abort, ?SCTP_FLAG_ABORT},
{eof, ?SCTP_FLAG_EOF}],
uint16},[]],
ppid = [uint32,0],
context = [uint32,0],
timetolive = [uint32,0],
tsn = [],
cumtsn = [],
assoc_id = [[sctp_assoc_id,0]]}}];
%% for SCTP_OPT_EVENTS
type_opt_1(sctp_events) ->
[{record,#sctp_event_subscribe{
data_io_event = [bool8,true],
association_event = [bool8,true],
address_event = [bool8,true],
send_failure_event = [bool8,true],
peer_error_event = [bool8,true],
shutdown_event = [bool8,true],
partial_delivery_event = [bool8,true],
adaptation_layer_event = [bool8,false],
authentication_event = [bool8,false]}}];
%% for SCTP_OPT_DELAYED_ACK_TIME
type_opt_1(sctp_delayed_ack_time) ->
[{record,#sctp_assoc_value{
assoc_id = [[sctp_assoc_id,0]],
assoc_value = [uint32,0]}}];
%%
type_opt_1(undefined) -> undefined;
type_opt_1(O) when is_atom(O) -> undefined.
%% Get. No supplied value.
type_value(get, undefined) -> false; % Undefined type
%% These two clauses can not happen since they are only used
%% in record fields - from record fields they must have a
%% value though it might be 'undefined', so record fields
%% calls type_value/3, not type_value/2.
%% type_value(get, []) -> true; % Ignored
%% type_value(get, [[Type,Default]]) -> % Required field, default value
%% type_value(get, Type, Default);
type_value(get, [{record,Types}]) -> % Implied default value for record
type_value_record(get, Types,
erlang:make_tuple(tuple_size(Types), undefined), 2);
type_value(get, [_]) -> false; % Required value missing
type_value(get, _) -> true. % Field is supposed to be undefined
%% Get and set. Value supplied.
type_value(_, undefined, _) -> false; % Undefined type
type_value(_, [], undefined) -> true; % Ignored
type_value(_, [], _) -> false; % Value should not be supplied
type_value(Q, [Type], Value) -> % Required field, proceed
type_value_default(Q, Type, Value);
type_value(set, Type, Value) -> % Required for setopts
type_value_default(set, Type, Value);
type_value(_, _, undefined) -> true; % Value should be undefined for
type_value(_, _, _) -> false. % other than setopts.
type_value_default(Q, [Type,Default], undefined) ->
type_value_1(Q, Type, Default);
type_value_default(Q, [Type,_], Value) ->
type_value_1(Q, Type, Value);
type_value_default(Q, Type, Value) ->
type_value_1(Q, Type, Value).
type_value_1(Q, {record,Types}, undefined) ->
type_value_record(Q, Types,
erlang:make_tuple(tuple_size(Types), undefined), 2);
type_value_1(Q, {record,Types}, Values)
when tuple_size(Types) =:= tuple_size(Values) ->
type_value_record(Q, Types, Values, 2);
type_value_1(Q, Types, Values)
when tuple_size(Types) =:= tuple_size(Values) ->
type_value_tuple(Q, Types, Values, 1);
type_value_1(_, Type, Value) ->
type_value_2(Type, Value).
type_value_tuple(Q, Types, Values, N)
when is_integer(N), N =< tuple_size(Types) ->
type_value(Q, element(N, Types), element(N, Values))
andalso type_value_tuple(Q, Types, Values, N+1);
type_value_tuple(_, _, _, _) -> true.
type_value_record(Q, Types, Values, N)
when is_integer(N), N =< tuple_size(Types) ->
case type_value(Q, element(N, Types), element(N, Values)) of
true -> type_value_record(Q, Types, Values, N+1);
false ->
erlang:throw({type,{record,Q,Types,Values,N}})
end;
type_value_record(_, _, _, _) -> true.
%% Simple run-time type-checking of (option) values: type -vs- value:
%% NB: the LHS is the TYPE, not the option name!
%%
%% Returns true | false | throw(ErrorReason) only for record types
%%
type_value_2(undefined, _) -> false;
%%
type_value_2(bool, true) -> true;
type_value_2(bool, false) -> true;
type_value_2(bool8, true) -> true;
type_value_2(bool8, false) -> true;
type_value_2(int, X) when is_integer(X) -> true;
type_value_2(uint, X) when is_integer(X), X >= 0 -> true;
type_value_2(uint32, X) when X band 16#ffffffff =:= X -> true;
type_value_2(uint24, X) when X band 16#ffffff =:= X -> true;
type_value_2(uint16, X) when X band 16#ffff =:= X -> true;
type_value_2(uint8, X) when X band 16#ff =:= X -> true;
type_value_2(time, infinity) -> true;
type_value_2(time, X) when is_integer(X), X >= 0 -> true;
type_value_2(ip,{A,B,C,D}) when ?ip(A,B,C,D) -> true;
type_value_2(addr, {any,Port}) ->
type_value_2(uint16, Port);
type_value_2(addr, {loopback,Port}) ->
type_value_2(uint16, Port);
type_value_2(addr, {{A,B,C,D},Port}) when ?ip(A,B,C,D) ->
type_value_2(uint16, Port);
type_value_2(addr, {{A,B,C,D,E,F,G,H},Port}) when ?ip6(A,B,C,D,E,F,G,H) ->
type_value_2(uint16, Port);
type_value_2(ether,[X1,X2,X3,X4,X5,X6])
when ?ether(X1,X2,X3,X4,X5,X6) -> true;
type_value_2({enum,List}, Enum) ->
case enum_val(Enum, List) of
{value,_} -> true;
false -> false
end;
type_value_2(sockaddr, Addr) ->
case Addr of
any -> true;
loopback -> true;
{A,B,C,D} when ?ip(A,B,C,D) -> true;
{A,B,C,D,E,F,G,H} when ?ip6(A,B,C,D,E,F,G,H) -> true;
_ -> false
end;
type_value_2(linkaddr, Addr) when is_list(Addr) ->
case len(Addr, 32768) of
undefined -> false;
_ -> true
end;
type_value_2({bitenumlist,List}, EnumList) ->
case enum_vals(EnumList, List) of
Ls when is_list(Ls) -> true;
false -> false
end;
type_value_2({bitenumlist,List,_}, EnumList) ->
case enum_vals(EnumList, List) of
Ls when is_list(Ls) -> true;
false -> false
end;
type_value_2(binary,Bin) when is_binary(Bin) -> true;
type_value_2(binary_or_uint,Bin) when is_binary(Bin) -> true;
type_value_2(binary_or_uint,Int) when is_integer(Int), Int >= 0 -> true;
%% Type-checking of SCTP options
type_value_2(sctp_assoc_id, X)
when X band 16#ffffffff =:= X -> true;
type_value_2(_, _) -> false.
%% Get. No supplied value.
%%
%% These two clauses can not happen since they are only used
%% in record fields - from record fields they must have a
%% value though it might be 'undefined', so record fields
%% calls enc_value/3, not enc_value/2.
%% enc_value(get, []) -> []; % Ignored
%% enc_value(get, [[Type,Default]]) -> % Required field, default value
%% enc_value(get, Type, Default);
enc_value(get, [{record,Types}]) -> % Implied default value for record
enc_value_tuple(get, Types,
erlang:make_tuple(tuple_size(Types), undefined), 2);
enc_value(get, _) -> [].
%% Get and set
enc_value(_, [], _) -> []; % Ignored
enc_value(Q, [Type], Value) -> % Required field, proceed
enc_value_default(Q, Type, Value);
enc_value(set, Type, Value) -> % Required for setopts
enc_value_default(set, Type, Value);
enc_value(_, _, _) -> []. % Not encoded for other than setopts
enc_value_default(Q, [Type,Default], undefined) ->
enc_value_1(Q, Type, Default);
enc_value_default(Q, [Type,_], Value) ->
enc_value_1(Q, Type, Value);
enc_value_default(Q, Type, Value) ->
enc_value_1(Q, Type, Value).
enc_value_1(Q, {record,Types}, undefined) ->
enc_value_tuple(Q, Types,
erlang:make_tuple(tuple_size(Types), undefined), 2);
enc_value_1(Q, {record,Types}, Values)
when tuple_size(Types) =:= tuple_size(Values) ->
enc_value_tuple(Q, Types, Values, 2);
enc_value_1(Q, Types, Values) when tuple_size(Types) =:= tuple_size(Values) ->
enc_value_tuple(Q, Types, Values, 1);
enc_value_1(_, Type, Value) ->
enc_value_2(Type, Value).
enc_value_tuple(Q, Types, Values, N)
when is_integer(N), N =< tuple_size(Types) ->
[enc_value(Q, element(N, Types), element(N, Values))
|enc_value_tuple(Q, Types, Values, N+1)];
enc_value_tuple(_, _, _, _) -> [].
%%
%% Encoding of option VALUES:
%%
enc_value_2(bool, true) -> [0,0,0,1];
enc_value_2(bool, false) -> [0,0,0,0];
enc_value_2(bool8, true) -> [1];
enc_value_2(bool8, false) -> [0];
enc_value_2(int, Val) -> ?int32(Val);
enc_value_2(uint, Val) -> ?int32(Val);
enc_value_2(uint32, Val) -> ?int32(Val);
enc_value_2(uint24, Val) -> ?int24(Val);
enc_value_2(uint16, Val) -> ?int16(Val);
enc_value_2(uint8, Val) -> ?int8(Val);
enc_value_2(time, infinity) -> ?int32(-1);
enc_value_2(time, Val) -> ?int32(Val);
enc_value_2(ip,{A,B,C,D}) -> [A,B,C,D];
enc_value_2(ip, any) -> [0,0,0,0];
enc_value_2(ip, loopback) -> [127,0,0,1];
enc_value_2(addr, {any,Port}) ->
[?INET_AF_ANY|?int16(Port)];
enc_value_2(addr, {loopback,Port}) ->
[?INET_AF_LOOPBACK|?int16(Port)];
enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 4 ->
[?INET_AF_INET,?int16(Port)|ip4_to_bytes(IP)];
enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 8 ->
[?INET_AF_INET6,?int16(Port)|ip6_to_bytes(IP)];
enc_value_2(ether, [_,_,_,_,_,_]=Xs) -> Xs;
enc_value_2(sockaddr, any) ->
[?INET_AF_ANY];
enc_value_2(sockaddr, loopback) ->
[?INET_AF_LOOPBACK];
enc_value_2(sockaddr, IP) when tuple_size(IP) =:= 4 ->
[?INET_AF_INET|ip4_to_bytes(IP)];
enc_value_2(sockaddr, IP) when tuple_size(IP) =:= 8 ->
[?INET_AF_INET6|ip6_to_bytes(IP)];
enc_value_2(linkaddr, Linkaddr) ->
[?int16(length(Linkaddr)),Linkaddr];
enc_value_2(sctp_assoc_id, Val) -> ?int32(Val);
%% enc_value_2(sctp_assoc_id, Bin) -> [byte_size(Bin),Bin];
enc_value_2({enum,List}, Enum) ->
{value,Val} = enum_val(Enum, List),
?int32(Val);
enc_value_2({bitenumlist,List}, EnumList) ->
Vs = enum_vals(EnumList, List),
Val = borlist(Vs, 0),
?int32(Val);
enc_value_2({bitenumlist,List,Type}, EnumList) ->
Vs = enum_vals(EnumList, List),
Value = borlist(Vs, 0),
enc_value_2(Type, Value);
enc_value_2(binary,Bin) -> [?int32(byte_size(Bin)),Bin];
enc_value_2(binary_or_uint,Datum) when is_binary(Datum) ->
[1,enc_value_2(binary, Datum)];
enc_value_2(binary_or_uint,Datum) when is_integer(Datum) ->
[0,enc_value_2(uint, Datum)].
%%
%% Decoding of option VALUES receved from "getopt":
%% NOT required for SCTP, as it always returns ready terms, not lists:
%%
dec_value(bool, [0,0,0,0|T]) -> {false,T};
dec_value(bool, [_,_,_,_|T]) -> {true,T};
%% Currently not used i.e only used by SCTP that does not dec_value/2
%% dec_value(bool8, [0|T]) -> {false,T};
%% dec_value(bool8, [_|T]) -> {true,T};
dec_value(int, [X3,X2,X1,X0|T]) -> {?i32(X3,X2,X1,X0),T};
dec_value(uint, [X3,X2,X1,X0|T]) -> {?u32(X3,X2,X1,X0),T};
%% Currently not used i.e only used by SCTP that does not dec_value/2
%% dec_value(uint32, [X3,X2,X1,X0|T]) -> {?u32(X3,X2,X1,X0),T};
%% dec_value(uint24, [X2,X1,X0|T]) -> {?u24(X2,X1,X0),T};
%% dec_value(uint16, [X1,X0|T]) -> {?u16(X1,X0),T};
%% dec_value(uint8, [X0|T]) -> {?u8(X0),T};
dec_value(time, [X3,X2,X1,X0|T]) ->
case ?i32(X3,X2,X1,X0) of
-1 -> {infinity, T};
Val -> {Val, T}
end;
dec_value(ip, [A,B,C,D|T]) -> {{A,B,C,D}, T};
%% dec_value(ether, [X1,X2,X3,X4,X5,X6|T]) -> {[X1,X2,X3,X4,X5,X6],T};
dec_value(sockaddr, [X|T]) ->
get_ip(X, T);
dec_value(linkaddr, [X1,X0|T]) ->
split(?i16(X1,X0), T);
dec_value({enum,List}, [X3,X2,X1,X0|T]) ->
Val = ?i32(X3,X2,X1,X0),
case enum_name(Val, List) of
{name, Enum} -> {Enum, T};
_ -> {undefined, T}
end;
dec_value({bitenumlist,List}, [X3,X2,X1,X0|T]) ->
Val = ?i32(X3,X2,X1,X0),
{enum_names(Val, List), T};
%% Currently not used i.e only used by SCTP that does not dec_value/2
%% dec_value({bitenumlist,List,Type}, T0) ->
%% {Val,T} = dec_value(Type, T0),
%% {enum_names(Val, List), T};
dec_value(binary,[L0,L1,L2,L3|List]) ->
Len = ?i32(L0,L1,L2,L3),
{X,T}=split(Len,List),
{list_to_binary(X),T};
dec_value(Types, List) when is_tuple(Types) ->
{L,T} = dec_value_tuple(Types, List, 1, []),
{list_to_tuple(L),T};
dec_value(Type, Val) ->
erlang:error({decode,Type,Val}).
%% dec_value(_, B) ->
%% {undefined, B}.
dec_value_tuple(Types, List, N, Acc)
when is_integer(N), N =< tuple_size(Types) ->
{Term,Tail} = dec_value(element(N, Types), List),
dec_value_tuple(Types, Tail, N+1, [Term|Acc]);
dec_value_tuple(_, List, _, Acc) ->
{rev(Acc),List}.
borlist([V|Vs], Value) ->
borlist(Vs, V bor Value);
borlist([], Value) -> Value.
enum_vals([Enum|Es], List) ->
case enum_val(Enum, List) of
false -> false;
{value,Value} -> [Value | enum_vals(Es, List)]
end;
enum_vals([], _) -> [].
enum_names(Val, [{Enum,BitVal} |List]) ->
if Val band BitVal =:= BitVal ->
[Enum | enum_names(Val, List)];
true ->
enum_names(Val, List)
end;
enum_names(_, []) -> [].
enum_val(Enum, [{Enum,Value}|_]) -> {value,Value};
enum_val(Enum, [_|List]) -> enum_val(Enum, List);
enum_val(_, []) -> false.
enum_name(Val, [{Enum,Val}|_]) -> {name,Enum};
enum_name(Val, [_|List]) -> enum_name(Val, List);
enum_name(_, []) -> false.
%% Encoding for setopts
%%
%% encode opt/val REVERSED since options are stored in reverse order
%% i.e. the recent options first (we must process old -> new)
encode_opt_val(Opts) ->
try
enc_opt_val(Opts, [])
catch
Reason -> {error,Reason}
end.
enc_opt_val([{active,once}|Opts], Acc) ->
%% Specially optimized because {active,once} will be used for
%% every packet, not only once when initializing the socket.
%% Measurements show that this optimization is worthwhile.
enc_opt_val(Opts, [<<?INET_LOPT_ACTIVE:8,?INET_ONCE:32>>|Acc]);
enc_opt_val([{raw,P,O,B}|Opts], Acc) ->
enc_opt_val(Opts, Acc, raw, {P,O,B});
enc_opt_val([{Opt,Val}|Opts], Acc) ->
enc_opt_val(Opts, Acc, Opt, Val);
enc_opt_val([binary|Opts], Acc) ->
enc_opt_val(Opts, Acc, mode, binary);
enc_opt_val([list|Opts], Acc) ->
enc_opt_val(Opts, Acc, mode, list);
enc_opt_val([_|_], _) -> {error,einval};
enc_opt_val([], Acc) -> {ok,Acc}.
enc_opt_val(Opts, Acc, Opt, Val) when is_atom(Opt) ->
Type = type_opt(set, Opt),
case type_value(set, Type, Val) of
true ->
enc_opt_val(Opts, [enc_opt(Opt),enc_value(set, Type, Val)|Acc]);
false -> {error,einval}
end;
enc_opt_val(_, _, _, _) -> {error,einval}.
%% Encoding for getopts
%%
%% "encode_opts" is for "getopt" only, not setopt". But it uses "enc_opt" which
%% is common for "getopt" and "setopt":
encode_opts(Opts) ->
try enc_opts(Opts) of
Buf -> {ok,Buf}
catch
Error -> {error,Error}
end.
% Raw options are a special case, they need to be rewritten to be properly
% handled and the types need checking even when querying.
enc_opts([{raw,P,O,S}|Opts]) ->
enc_opts(Opts, raw, {P,O,S});
enc_opts([{Opt,Val}|Opts]) ->
enc_opts(Opts, Opt, Val);
enc_opts([Opt|Opts]) ->
enc_opts(Opts, Opt);
enc_opts([]) -> [].
enc_opts(Opts, Opt) when is_atom(Opt) ->
Type = type_opt(get, Opt),
case type_value(get, Type) of
true ->
[enc_opt(Opt),enc_value(get, Type)|enc_opts(Opts)];
false ->
throw(einval)
end;
enc_opts(_, _) ->
throw(einval).
enc_opts(Opts, Opt, Val) when is_atom(Opt) ->
Type = type_opt(get, Opt),
case type_value(get, Type, Val) of
true ->
[enc_opt(Opt),enc_value(get, Type, Val)|enc_opts(Opts)];
false ->
throw(einval)
end;
enc_opts(_, _, _) ->
throw(einval).
%% Decoding of raw list data options
%%
decode_opt_val(Buf) ->
try dec_opt_val(Buf) of
Result -> {ok,Result}
catch
Error -> {error,Error}
end.
dec_opt_val([B|Buf]=BBuf) ->
case dec_opt(B) of
undefined ->
erlang:error({decode,BBuf});
Opt ->
Type = type_opt(dec, Opt),
dec_opt_val(Buf, Opt, Type)
end;
dec_opt_val([]) -> [].
dec_opt_val(Buf, raw, Type) ->
{{P,O,B},T} = dec_value(Type, Buf),
[{raw,P,O,B}|dec_opt_val(T)];
dec_opt_val(Buf, Opt, Type) ->
{Val,T} = dec_value(Type, Buf),
[{Opt,Val}|dec_opt_val(T)].
%% Pre-processing of options for chgopts
%%
%% Return list of option requests for getopts
%% for all options that containing 'undefined' record fields.
%%
need_template([{Opt,undefined}=OV|Opts]) when is_atom(Opt) ->
[OV|need_template(Opts)];
need_template([{Opt,Val}|Opts]) when is_atom(Opt) ->
case need_template(Val, 2) of
true ->
[{Opt,undefined}|need_template(Opts)];
false ->
need_template(Opts)
end;
need_template([_|Opts]) ->
need_template(Opts);
need_template([]) -> [].
%%
need_template(T, N) when is_integer(N), N =< tuple_size(T) ->
case element(N, T) of
undefined -> true;
_ ->
need_template(T, N+1)
end;
need_template(_, _) -> false.
%% Replace 'undefined' record fields in option values with values
%% from template records.
%%
merge_options([{Opt,undefined}|Opts], [{Opt,_}=T|Templates]) ->
[T|merge_options(Opts, Templates)];
merge_options([{Opt,Val}|Opts], [{Opt,Template}|Templates])
when is_atom(Opt), tuple_size(Val) >= 2 ->
Key = element(1, Val),
Size = tuple_size(Val),
if Size =:= tuple_size(Template), Key =:= element(1, Template) ->
%% is_record(Template, Key)
[{Opt,list_to_tuple([Key|merge_fields(Val, Template, 2)])}
|merge_options(Opts, Templates)];
true ->
throw({merge,Val,Template})
end;
merge_options([OptVal|Opts], Templates) ->
[OptVal|merge_options(Opts, Templates)];
merge_options([], []) -> [];
merge_options(Opts, Templates) ->
throw({merge,Opts,Templates}).
merge_fields(Opt, Template, N) when is_integer(N), N =< tuple_size(Opt) ->
case element(N, Opt) of
undefined ->
[element(N, Template)|merge_fields(Opt, Template, N+1)];
Val ->
[Val|merge_fields(Opt, Template, N+1)]
end;
merge_fields(_, _, _) -> [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% handle interface options
%%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
type_ifopt(addr) -> sockaddr;
type_ifopt(broadaddr) -> sockaddr;
type_ifopt(dstaddr) -> sockaddr;
type_ifopt(mtu) -> int;
type_ifopt(netmask) -> sockaddr;
type_ifopt(flags) ->
{bitenumlist,
[{up, ?INET_IFF_UP},
{down, ?INET_IFF_DOWN},
{broadcast, ?INET_IFF_BROADCAST},
{no_broadcast, ?INET_IFF_NBROADCAST},
{loopback, ?INET_IFF_LOOPBACK},
{pointtopoint, ?INET_IFF_POINTTOPOINT},
{no_pointtopoint, ?INET_IFF_NPOINTTOPOINT},
{running, ?INET_IFF_RUNNING},
{multicast, ?INET_IFF_MULTICAST}]};
type_ifopt(hwaddr) -> linkaddr;
type_ifopt(Opt) when is_atom(Opt) -> undefined.
enc_ifopt(addr) -> ?INET_IFOPT_ADDR;
enc_ifopt(broadaddr) -> ?INET_IFOPT_BROADADDR;
enc_ifopt(dstaddr) -> ?INET_IFOPT_DSTADDR;
enc_ifopt(mtu) -> ?INET_IFOPT_MTU;
enc_ifopt(netmask) -> ?INET_IFOPT_NETMASK;
enc_ifopt(flags) -> ?INET_IFOPT_FLAGS;
enc_ifopt(hwaddr) -> ?INET_IFOPT_HWADDR;
enc_ifopt(Opt) when is_atom(Opt) -> -1.
dec_ifopt(?INET_IFOPT_ADDR) -> addr;
dec_ifopt(?INET_IFOPT_BROADADDR) -> broadaddr;
dec_ifopt(?INET_IFOPT_DSTADDR) -> dstaddr;
dec_ifopt(?INET_IFOPT_MTU) -> mtu;
dec_ifopt(?INET_IFOPT_NETMASK) -> netmask;
dec_ifopt(?INET_IFOPT_FLAGS) -> flags;
dec_ifopt(?INET_IFOPT_HWADDR) -> hwaddr;
dec_ifopt(I) when is_integer(I) -> undefined.
%% decode if options returns a reversed list
decode_ifopts([B | Buf], Acc) ->
case dec_ifopt(B) of
undefined ->
{error, einval};
Opt ->
{Val,T} = dec_value(type_ifopt(Opt), Buf),
decode_ifopts(T, [{Opt,Val} | Acc])
end;
decode_ifopts(_,Acc) -> {ok,Acc}.
%% encode if options return a reverse list
encode_ifopts([Opt|Opts], Acc) ->
case enc_ifopt(Opt) of
-1 -> {error,einval};
B -> encode_ifopts(Opts,[B|Acc])
end;
encode_ifopts([],Acc) -> {ok,Acc}.
%% encode if options return a reverse list
encode_ifopt_val([{Opt,Val}|Opts], Buf) ->
Type = type_ifopt(Opt),
try type_value(set, Type, Val) of
true ->
encode_ifopt_val(Opts,
[Buf,enc_ifopt(Opt),enc_value(set, Type, Val)]);
false -> {error,einval}
catch
Reason -> {error,Reason}
end;
encode_ifopt_val([], Buf) -> {ok,Buf}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% handle subscribe options
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
encode_subs(L) ->
try enc_subs(L) of
Result -> {ok,Result}
catch
Error -> {error,Error}
end.
enc_subs([H|T]) ->
case H of
subs_empty_out_q -> [?INET_SUBS_EMPTY_OUT_Q|enc_subs(T)]%;
%%Dialyzer _ -> throw(einval)
end;
enc_subs([]) -> [].
decode_subs(Bytes) ->
try dec_subs(Bytes) of
Result -> {ok,Result}
catch
Error -> {error,Error}
end.
dec_subs([X,X3,X2,X1,X0|R]) ->
Val = ?u32(X3,X2,X1,X0),
case X of
?INET_SUBS_EMPTY_OUT_Q -> [{subs_empty_out_q,Val}|dec_subs(R)];
_ -> throw(einval)
end;
dec_subs([]) -> [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% handle statictics options
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
encode_stats(L) ->
try enc_stats(L) of
Result -> {ok,Result}
catch
Error -> {error,Error}
end.
enc_stats([H|T]) ->
case H of
recv_cnt -> [?INET_STAT_RECV_CNT |enc_stats(T)];
recv_max -> [?INET_STAT_RECV_MAX |enc_stats(T)];
recv_avg -> [?INET_STAT_RECV_AVG |enc_stats(T)];
recv_dvi -> [?INET_STAT_RECV_DVI |enc_stats(T)];
send_cnt -> [?INET_STAT_SEND_CNT |enc_stats(T)];
send_max -> [?INET_STAT_SEND_MAX |enc_stats(T)];
send_avg -> [?INET_STAT_SEND_AVG |enc_stats(T)];
send_pend -> [?INET_STAT_SEND_PEND|enc_stats(T)];
send_oct -> [?INET_STAT_SEND_OCT |enc_stats(T)];
recv_oct -> [?INET_STAT_RECV_OCT |enc_stats(T)];
_ -> throw(einval)
end;
enc_stats([]) -> [].
decode_stats(Bytes) ->
try dec_stats(Bytes) of
Result -> {ok,Result}
catch
Error -> {error,Error}
end.
dec_stats([?INET_STAT_SEND_OCT,X7,X6,X5,X4,X3,X2,X1,X0|R]) ->
Val = ?u64(X7,X6,X5,X4,X3,X2,X1,X0),
[{send_oct, Val}|dec_stats(R)];
dec_stats([?INET_STAT_RECV_OCT,X7,X6,X5,X4,X3,X2,X1,X0|R]) ->
Val = ?u64(X7,X6,X5,X4,X3,X2,X1,X0),
[{recv_oct, Val}|dec_stats(R)];
dec_stats([X,X3,X2,X1,X0|R]) ->
Val = ?u32(X3,X2,X1,X0),
case X of
?INET_STAT_RECV_CNT -> [{recv_cnt,Val} |dec_stats(R)];
?INET_STAT_RECV_MAX -> [{recv_max,Val} |dec_stats(R)];
?INET_STAT_RECV_AVG -> [{recv_avg,Val} |dec_stats(R)];
?INET_STAT_RECV_DVI -> [{recv_dvi,Val} |dec_stats(R)];
?INET_STAT_SEND_CNT -> [{send_cnt,Val} |dec_stats(R)];
?INET_STAT_SEND_MAX -> [{send_max,Val} |dec_stats(R)];
?INET_STAT_SEND_AVG -> [{send_avg,Val} |dec_stats(R)];
?INET_STAT_SEND_PEND -> [{send_pend,Val}|dec_stats(R)];
_ -> throw(einval)
end;
dec_stats([]) -> [].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% handle status options
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dec_status(Flags) ->
enum_names(Flags,
[
{busy, ?INET_F_BUSY},
%% {listening, ?INET_F_LST}, NOT USED ANY MORE
{accepting, ?INET_F_ACC},
{connecting, ?INET_F_CON},
{listen, ?INET_F_LISTEN},
{connected, ?INET_F_ACTIVE},
{bound, ?INET_F_BOUND},
{open, ?INET_F_OPEN}
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% UTILS
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
enc_time(Time) when Time < 0 -> [255,255,255,255];
enc_time(Time) -> ?int32(Time).
encode_ifname(Name) when is_atom(Name) -> encode_ifname(atom_to_list(Name));
encode_ifname(Name) ->
N = length(Name),
if N > 255 -> {error, einval};
true -> {ok,[N | Name]}
end.
build_ifaddrs(Cs) ->
build_ifaddrs(Cs, []).
%%
build_ifaddrs([], []) ->
[];
build_ifaddrs([0|Cs], Acc) ->
Name = utf8_to_characters(rev(Acc)),
{Opts,Rest} = build_ifaddrs_opts(Cs, []),
[{Name,Opts}|build_ifaddrs(Rest)];
build_ifaddrs([C|Cs], Acc) ->
build_ifaddrs(Cs, [C|Acc]).
build_ifaddrs_opts([0|Cs], Acc) ->
{rev(Acc),Cs};
build_ifaddrs_opts([C|Cs]=CCs, Acc) ->
case dec_ifopt(C) of
undefined ->
erlang:error(badarg, [CCs,Acc]);
Opt ->
Type = type_ifopt(Opt),
{Val,Rest} = dec_value(Type, Cs),
build_ifaddrs_opts(Rest, [{Opt,Val}|Acc])
end.
build_iflist(Cs) ->
build_iflist(Cs, [], []).
%% Turn a NULL separated list of chars into a list of strings, removing
%% duplicates.
build_iflist([0|L], Acc, [H|T]) ->
case rev(Acc) of
H -> build_iflist(L, [], [H|T]);
N -> build_iflist(L, [], [N,H|T])
end;
build_iflist([0|L], Acc, []) ->
build_iflist(L, [], [rev(Acc)]);
build_iflist([C|L], Acc, List) ->
build_iflist(L, [C|Acc], List);
build_iflist([], [], List) ->
rev(List);
build_iflist([], Acc, List) ->
build_iflist([0], Acc, List).
rev(L) -> rev(L,[]).
rev([C|L],Acc) -> rev(L,[C|Acc]);
rev([],Acc) -> Acc.
split(N, L) -> split(N, L, []).
split(0, L, R) when is_list(L) -> {rev(R),L};
split(N, [H|T], R) when is_integer(N), N > 0 -> split(N-1, T, [H|R]).
len(L, N) -> len(L, N, 0).
len([], N, C) when is_integer(N), N >= 0 -> C;
len(L, 0, _) when is_list(L) -> undefined;
len([_|L], N, C) when is_integer(N), N >= 0 -> len(L, N-1, C+1).
member(X, [X|_]) -> true;
member(X, [_|Xs]) -> member(X, Xs);
member(_, []) -> false.
%% Lookup tree that keeps key insert order
ktree_empty() -> {[],tree()}.
ktree_is_defined(Key, {_,T}) -> tree(T, Key, is_defined).
ktree_get(Key, {_,T}) -> tree(T, Key, get).
ktree_insert(Key, V, {Keys,T}) -> {[Key|Keys],tree(T, Key, {insert,V})}.
ktree_update(Key, V, {Keys,T}) -> {Keys,tree(T, Key, {update,V})}.
ktree_keys({Keys,_}) -> rev(Keys).
%% Simple lookup tree. Hash the key to get statistical balance.
%% Key is matched equal, not compared equal.
tree() -> nil.
tree(T, Key, Op) -> tree(T, Key, Op, erlang:phash2(Key)).
tree(nil, _, is_defined, _) -> false;
tree(nil, K, {insert,V}, _) -> {K,V,nil,nil};
tree({K,_,_,_}, K, is_defined, _) -> true;
tree({K,V,_,_}, K, get, _) -> V;
tree({K,_,L,R}, K, {update,V}, _) -> {K,V,L,R};
tree({K0,V0,L,R}, K, Op, H) ->
H0 = erlang:phash2(K0),
if H0 < H; H0 =:= H, K0 < K ->
if is_tuple(Op) ->
{K0,V0,tree(L, K, Op, H),R};
true ->
tree(L, K, Op, H)
end;
true ->
if is_tuple(Op) ->
{K0,V0,L,tree(R, K, Op, H)};
true ->
tree(R, K, Op, H)
end
end.
utf8_to_characters([]) -> [];
utf8_to_characters([B|Bs]=Arg) when (B band 16#FF) =:= B ->
if 16#F8 =< B ->
erlang:error(badarg, [Arg]);
16#F0 =< B ->
utf8_to_characters(Bs, B band 16#07, 3);
16#E0 =< B ->
utf8_to_characters(Bs, B band 16#0F, 2);
16#C0 =< B ->
utf8_to_characters(Bs, B band 16#1F, 1);
16#80 =< B ->
erlang:error(badarg, [Arg]);
true ->
[B|utf8_to_characters(Bs)]
end.
%%
utf8_to_characters(Bs, U, 0) ->
[U|utf8_to_characters(Bs)];
utf8_to_characters([B|Bs], U, N) when ((B band 16#3F) bor 16#80) =:= B ->
utf8_to_characters(Bs, (U bsl 6) bor (B band 16#3F), N-1).
ip_to_bytes(IP) when tuple_size(IP) =:= 4 -> ip4_to_bytes(IP);
ip_to_bytes(IP) when tuple_size(IP) =:= 8 -> ip6_to_bytes(IP).
ip4_to_bytes({A,B,C,D}) ->
[A band 16#ff, B band 16#ff, C band 16#ff, D band 16#ff].
ip6_to_bytes({A,B,C,D,E,F,G,H}) ->
[?int16(A), ?int16(B), ?int16(C), ?int16(D),
?int16(E), ?int16(F), ?int16(G), ?int16(H)].
get_ip(?INET_AF_INET, Addr) -> get_ip4(Addr);
get_ip(?INET_AF_INET6, Addr) -> get_ip6(Addr).
get_ip4([A,B,C,D | T]) -> {{A,B,C,D},T}.
get_ip6([X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16 | T]) ->
{ { ?u16(X1,X2),?u16(X3,X4),?u16(X5,X6),?u16(X7,X8),
?u16(X9,X10),?u16(X11,X12),?u16(X13,X14),?u16(X15,X16)}, T}.
%% Control command
ctl_cmd(Port, Cmd, Args) ->
?DBG_FORMAT("prim_inet:ctl_cmd(~p, ~p, ~p)~n", [Port,Cmd,Args]),
Result =
try erlang:port_control(Port, Cmd, Args) of
[?INET_REP_OK|Reply] -> {ok,Reply};
[?INET_REP] -> inet_reply;
[?INET_REP_ERROR|Err] -> {error,list_to_atom(Err)}
catch
error:_ -> {error,einval}
end,
?DBG_FORMAT("prim_inet:ctl_cmd() -> ~p~n", [Result]),
Result.