diff options
Diffstat (limited to 'erts/preloaded/src/prim_inet.erl')
-rw-r--r-- | erts/preloaded/src/prim_inet.erl | 1962 |
1 files changed, 1962 insertions, 0 deletions
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl new file mode 100644 index 0000000000..0feb591efb --- /dev/null +++ b/erts/preloaded/src/prim_inet.erl @@ -0,0 +1,1962 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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/1, open/2, fdopen/2, fdopen/3, close/1]). +-export([bind/3, listen/1, listen/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, + 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) -> +%% {ok, insock()} | +%% {error, Reason} +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +open(Protocol) -> open1(Protocol, ?INET_AF_INET). + +open(Protocol, inet) -> open1(Protocol, ?INET_AF_INET); +open(Protocol, inet6) -> open1(Protocol, ?INET_AF_INET6); +open(_, _) -> {error, einval}. + +fdopen(Protocol, Fd) -> fdopen1(Protocol, ?INET_AF_INET, Fd). + +fdopen(Protocol, Fd, inet) -> fdopen1(Protocol, ?INET_AF_INET, Fd); +fdopen(Protocol, Fd, inet6) -> fdopen1(Protocol, ?INET_AF_INET6, Fd); +fdopen(_, _, _) -> {error, einval}. + +open1(Protocol, Family) -> + case open0(Protocol) of + {ok, S} -> + case ctl_cmd(S, ?INET_REQ_OPEN, [Family]) of + {ok, _} -> + {ok,S}; + Error -> + close(S), Error + end; + Error -> Error + end. + +fdopen1(Protocol, Family, Fd) when is_integer(Fd) -> + case open0(Protocol) of + {ok, S} -> + case ctl_cmd(S,?INET_REQ_FDOPEN,[Family,?int32(Fd)]) of + {ok, _} -> {ok,S}; + Error -> close(S), Error + end; + Error -> Error + end. + +open0(Protocol) -> + try erlang:open_port({spawn_driver,protocol2drv(Protocol)}, [binary]) of + Port -> {ok,Port} + catch + error:Reason -> {error,Reason} + end. + +protocol2drv(tcp) -> "tcp_inet"; +protocol2drv(udp) -> "udp_inet"; +protocol2drv(sctp) -> "sctp_inet"; +protocol2drv(_) -> + erlang:error(eprotonosupport). + +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 + 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 + 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) ++ + lists:concat([?int16(Port)++ip_to_bytes(IP) || + {IP, Port} <- Addrs]), + case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of + {ok,_} -> {ok, S}; + 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 + 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,?TCP_REQ_ACCEPT, [enc_time(Time)]) of + {ok, [R1,R0]} -> {ok, ?u16(R1,R0)}; + 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, BackLog) when is_port(S), is_integer(BackLog) -> + case ctl_cmd(S, ?TCP_REQ_LISTEN, [?int16(BackLog)]) of + {ok, _} -> ok; + Error -> Error + end; +listen(S, Flag) when is_port(S), is_boolean(Flag) -> + case ctl_cmd(S, ?SCTP_REQ_LISTEN, enc_value(set, bool8, Flag)) of + {ok,_} -> ok; + 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:send() -> ~p~n", [Reply]), + Reply + end + catch + error:_ -> + ?DBG_FORMAT("prim_inet:send() -> {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 + 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 % 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 + 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 + end; +setpeername(S, undefined) when is_port(S) -> + case ctl_cmd(S, ?INET_REQ_SETPEER, []) of + {ok,[]} -> ok; + 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 + 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 + end; +setsockname(S, undefined) when is_port(S) -> + case ctl_cmd(S, ?INET_REQ_SETNAME, []) of + {ok,[]} -> ok; + 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 + 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); + {error,sctp_reply} -> + %% SCTP: Need to receive the full value: + receive + {inet_reply,S,Res} -> Res + end; + 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 inet: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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% IFLIST(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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + end + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% UNRECV(insock(), data) -> ok | {error, Reason} +%% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unrecv(S, Data) -> + case ctl_cmd(S, ?TCP_REQ_UNRECV, Data) of + {ok, _} -> ok; + 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({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}) -> + case tuple_size(IP) of + 4 -> + [?INET_AF_INET,?int16(Port)|ip4_to_bytes(IP)]; + 8 -> + [?INET_AF_INET6,?int16(Port)|ip6_to_bytes(IP)] + end; +enc_value_2(ether, [X1,X2,X3,X4,X5,X6]) -> [X1,X2,X3,X4,X5,X6]; +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({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}=lists: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) -> + {lists:reverse(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) -> ip; +type_ifopt(broadaddr) -> ip; +type_ifopt(dstaddr) -> ip; +type_ifopt(mtu) -> int; +type_ifopt(netmask) -> ip; +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) -> ether; +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_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. + +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_SCTP] -> {error,sctp_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. |