%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(socket_test_lib).
-export([
pi/1, pi/2, pi/3,
%% Time stuff
timestamp/0,
tdiff/2,
formated_timestamp/0,
format_timestamp/1,
%% String and format
f/2,
%% Generic 'has support' test function(s)
has_support_ipv6/0,
which_local_host_info/1,
which_local_addr/1,
%% Skipping
not_yet_implemented/0,
skip/1
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(FAIL(R), exit(R)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
pi(Item) when is_atom(Item) ->
pi(self(), Item).
pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
{Item, Info} = process_info(Pid, Item),
Info;
pi(Node, Pid) when is_pid(Pid) ->
rpc:call(Node, erlang, process_info, [Pid]).
pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) ->
rpc:call(Node, erlang, process_info, [Pid, Item]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
timestamp() ->
os:timestamp().
tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) ->
T1 = A1*1000000000+B1*1000+(C1 div 1000),
T2 = A2*1000000000+B2*1000+(C2 div 1000),
T2 - T1.
formated_timestamp() ->
format_timestamp(os:timestamp()).
format_timestamp({_N1, _N2, _N3} = TS) ->
{_Date, Time} = calendar:now_to_local_time(TS),
%% {YYYY,MM,DD} = Date,
{Hour,Min,Sec} = Time,
%% FormatTS =
%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w",
%% [YYYY, MM, DD, Hour, Min, Sec, N3]),
FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]),
lists:flatten(FormatTS).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f(F, A) ->
lists:flatten(io_lib:format(F, A)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
has_support_ipv6() ->
case socket:supports(ipv6) of
true ->
ok;
false ->
skip("IPv6 Not Supported")
end,
Domain = inet6,
LocalAddr =
case which_local_addr(Domain) of
{ok, Addr} ->
Addr;
{error, R1} ->
skip(f("Local Address eval failed: ~p", [R1]))
end,
ServerSock =
case socket:open(Domain, dgram, udp) of
{ok, SS} ->
SS;
{error, R2} ->
skip(f("(server) socket open failed: ~p", [R2]))
end,
LocalSA = #{family => Domain, addr => LocalAddr},
ServerPort =
case socket:bind(ServerSock, LocalSA) of
{ok, P1} ->
P1;
{error, R3} ->
socket:close(ServerSock),
skip(f("(server) socket bind failed: ~p", [R3]))
end,
ServerSA = LocalSA#{port => ServerPort},
ClientSock =
case socket:open(Domain, dgram, udp) of
{ok, CS} ->
CS;
{error, R4} ->
skip(f("(client) socket open failed: ~p", [R4]))
end,
case socket:bind(ClientSock, LocalSA) of
{ok, _} ->
ok;
{error, R5} ->
socket:close(ServerSock),
socket:close(ClientSock),
skip(f("(client) socket bind failed: ~p", [R5]))
end,
case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of
ok ->
ok;
{error, R6} ->
socket:close(ServerSock),
socket:close(ClientSock),
skip(f("failed socket sendto test: ~p", [R6]))
end,
case socket:recvfrom(ServerSock) of
{ok, {_, <<"hejsan">>}} ->
socket:close(ServerSock),
socket:close(ClientSock),
ok;
{error, R7} ->
socket:close(ServerSock),
socket:close(ClientSock),
skip(f("failed socket recvfrom test: ~p", [R7]))
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% This gets the local address (not {127, _} or {0, ...} or {16#fe80, ...})
%% We should really implement this using the (new) net module,
%% but until that gets the necessary functionality...
which_local_addr(Domain) ->
case which_local_host_info(Domain) of
{ok, #{addr := Addr}} ->
{ok, Addr};
{error, _Reason} = ERROR ->
ERROR
end.
%% Returns the interface (name), flags and address (not 127...)
%% of the local host.
which_local_host_info(Domain) ->
case inet:getifaddrs() of
{ok, IFL} ->
which_local_host_info(Domain, IFL);
{error, _} = ERROR ->
ERROR
end.
which_local_host_info(_Domain, []) ->
{error, no_address};
which_local_host_info(Domain, [{"lo" ++ _, _}|IFL]) ->
which_local_host_info(Domain, IFL);
which_local_host_info(Domain, [{"docker" ++ _, _}|IFL]) ->
which_local_host_info(Domain, IFL);
which_local_host_info(Domain, [{"br-" ++ _, _}|IFL]) ->
which_local_host_info(Domain, IFL);
which_local_host_info(Domain, [{Name, IFO}|IFL]) ->
try which_local_host_info2(Domain, IFO) of
Info ->
{ok, Info#{name => Name}}
catch
throw:_:_ ->
which_local_host_info(Domain, IFL)
end;
which_local_host_info(Domain, [_|IFL]) ->
which_local_host_info(Domain, IFL).
%% which_local_host_info2(Domain, IFO) ->
%% case lists:keysearch(flags, 1, IFO) of
%% {value, {flags, Flags}} ->
%% which_local_host_info2(Domain, IFO, Flags);
%% false ->
%% {error, no_flags}
%% end.
%% which_local_host_info2(_Domain, [], _Flags) ->
%% {error, no_address};
%% which_local_host_info2(inet = _Domain, [{addr, Addr}|_IFO], Flags)
%% when (size(Addr) =:= 4) andalso (element(1, Addr) =/= 127) ->
%% {ok, {Flags, Addr}};
%% which_local_host_info2(inet6 = _Domain, [{addr, Addr}|_IFO], Flags)
%% when (size(Addr) =:= 8) andalso
%% (element(1, Addr) =/= 0) andalso
%% (element(1, Addr) =/= 16#fe80) ->
%% {ok, {Flags, Addr}};
%% which_local_host_info2(Domain, [_|IFO], Flags) ->
%% which_local_host_info2(Domain, IFO, Flags).
%% foo(Info, inet = Domain, IFO) ->
%% foo(Info, Domain, IFO, [flags, addr, netmask, broadaddr, hwaddr]);
%% foo(Info, inet6 = Domain, IFO) ->
%% foo(Info, Domain, IFO, [flags, addr, netmask, hwaddr]).
which_local_host_info2(inet = _Domain, IFO) ->
Addr = which_local_host_info3(addr, IFO,
fun({A, _, _, _}) when (A =/= 127) -> true;
(_) -> false
end),
NetMask = which_local_host_info3(netmask, IFO,
fun({_, _, _, _}) -> true;
(_) -> false
end),
BroadAddr = which_local_host_info3(broadaddr, IFO,
fun({_, _, _, _}) -> true;
(_) -> false
end),
Flags = which_local_host_info3(flags, IFO, fun(_) -> true end),
#{flags => Flags,
addr => Addr,
broadaddr => BroadAddr,
netmask => NetMask};
which_local_host_info2(inet6 = _Domain, IFO) ->
Addr = which_local_host_info3(addr, IFO,
fun({A, _, _, _, _, _, _, _})
when (A =/= 0) andalso
(A =/= 16#fe80) -> true;
(_) -> false
end),
NetMask = which_local_host_info3(netmask, IFO,
fun({_, _, _, _, _, _, _, _}) -> true;
(_) -> false
end),
Flags = which_local_host_info3(flags, IFO, fun(_) -> true end),
#{flags => Flags,
addr => Addr,
netmask => NetMask}.
which_local_host_info3(_Key, [], _) ->
throw({error, no_address});
which_local_host_info3(Key, [{Key, Val}|IFO], Check) ->
case Check(Val) of
true ->
Val;
false ->
which_local_host_info3(Key, IFO, Check)
end;
which_local_host_info3(Key, [_|IFO], Check) ->
which_local_host_info3(Key, IFO, Check).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
not_yet_implemented() ->
skip("not yet implemented").
skip(Reason) ->
throw({skip, Reason}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%