src/grisp_connect_utils.erl

-module(grisp_connect_utils).


%--- Exports -------------------------------------------------------------------

% API functions
-export([retry_delay/1]).
-export([check_inet_ipv4/0]).


%--- API Functions -------------------------------------------------------------

check_inet_ipv4() ->
    case get_ip_of_valid_interfaces() of
        {ok, {IP1, _, _, _} = IP} when IP1 =/= 127 -> {ok, IP};
        _ -> invalid
    end.


%--- Internal Functions --------------------------------------------------------

retry_delay(0) ->
    0;
retry_delay(RetryCount) ->
    %% Calculate the connection delay in milliseconds with exponential backoff.
    %% The delay is selected randomly between `1000' and
    %% `2 ^ RETRY_COUNT - 1000' with a maximum value of `64000'.
    %% Loosely inspired by https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
    MinDelay = 1000,
    MaxDelay = 64000,
    MaxRandomDelay = min(MaxDelay, (1 bsl RetryCount) * 1000) - MinDelay,
    MinDelay + rand:uniform(MaxRandomDelay).

get_ipv4_from_opts([]) ->
    undefined;
get_ipv4_from_opts([{addr, {_1, _2, _3, _4}} | _]) ->
    {ok, {_1, _2, _3, _4}};
get_ipv4_from_opts([_ | TL]) ->
    case get_ipv4_from_opts(TL) of
        {ok, _IP} = Result -> Result;
        Other -> Other
    end.

has_ipv4(Opts) ->
    get_ipv4_from_opts(Opts) =/= undefined.

flags_are_ok(Flags) ->
    lists:member(up, Flags) and
        lists:member(running, Flags) and
        not lists:member(loopback, Flags).

get_valid_interfaces() ->
    case inet:getifaddrs() of
        {error, _Reason} = Error -> Error;
        {ok, Interfaces} ->
            {ok, [Opts || {_Name, [{flags, Flags} | Opts]} <- Interfaces,
                          flags_are_ok(Flags), has_ipv4(Opts)]}
    end.

get_ip_of_valid_interfaces() ->
    case get_valid_interfaces() of
        {error, _Reason} = Error -> Error;
        {ok, [Opts | _]} -> get_ipv4_from_opts(Opts);
        _ -> undefined
    end.