%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-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%
%%
%%
%% A library module used by the example Diameter nodes. Does little
%% more than provide an alternate/simplified transport configuration.
%%
-module(node).
-export([start/2,
listen/2,
connect/2,
stop/1]).
-export([message/3]).
-type protocol()
:: tcp | sctp.
-type ip_address()
:: default
| inet:ip_address().
-type server_transport()
:: protocol()
| {protocol(), ip_address(), non_neg_integer()}.
-type server_opts()
:: server_transport()
| {server_transport(), [diameter:transport_opt()]}
| [diameter:transport_opt()].
-type client_transport()
:: protocol() | any
| {protocol() | any, ip_address(), non_neg_integer()}
| {protocol() | any, ip_address(), ip_address(), non_neg_integer()}.
-type client_opts()
:: client_transport()
| {client_transport(), [diameter:transport_opt()]}
| [diameter:transport_opt()].
%% The server_transport() and client_transport() config is just
%% convenience: arbitrary options can be specifed as a
%% [diameter:transport_opt()].
-define(DEFAULT_PORT, 3868).
%% ---------------------------------------------------------------------------
%% Interface functions
%% ---------------------------------------------------------------------------
%% start/2
-spec start(diameter:service_name(), [diameter:service_opt()])
-> ok
| {error, term()}.
start(Name, Opts)
when is_atom(Name), is_list(Opts) ->
diameter:start_service(Name, Opts).
%% connect/2
-spec connect(diameter:service_name(), client_opts())
-> {ok, diameter:transport_ref()}
| {error, term()}.
connect(Name, Opts)
when is_list(Opts) ->
diameter:add_transport(Name, {connect, Opts});
connect(Name, {T, Opts}) ->
connect(Name, Opts ++ client_opts(T));
connect(Name, T) ->
connect(Name, [{connect_timer, 5000} | client_opts(T)]).
%% listen/2
-spec listen(diameter:service_name(), server_opts())
-> {ok, diameter:transport_ref()}
| {error, term()}.
listen(Name, Opts)
when is_list(Opts) ->
diameter:add_transport(Name, {listen, Opts});
listen(Name, {T, Opts}) ->
listen(Name, Opts ++ server_opts(T));
listen(Name, T) ->
listen(Name, server_opts(T)).
%% stop/1
-spec stop(diameter:service_name())
-> ok
| {error, term()}.
stop(Name) ->
diameter:stop_service(Name).
%% ---------------------------------------------------------------------------
%% Internal functions
%% ---------------------------------------------------------------------------
%% server_opts/1
%%
%% Return transport options for a listening transport.
server_opts({T, Addr, Port}) ->
[{transport_module, tmod(T)},
{transport_config, [{reuseaddr, true},
{sender, true},
{message_cb, [fun ?MODULE:message/3, 0]},
{ip, addr(Addr)},
{port, Port}]}];
server_opts(T) ->
server_opts({T, loopback, ?DEFAULT_PORT}).
%% client_opts/1
%%
%% Return transport options for a connecting transport.
client_opts({T, LA, RA, RP})
when T == all; %% backwards compatibility
T == any ->
[[S, {C,Os}], T] = [client_opts({P, LA, RA, RP}) || P <- [sctp,tcp]],
[S, {C,Os,2000} | T];
client_opts({T, LA, RA, RP}) ->
[{transport_module, tmod(T)},
{transport_config, [{raddr, addr(RA)},
{rport, RP},
{reuseaddr, true}
| ip(LA)]}];
client_opts({T, RA, RP}) ->
client_opts({T, default, RA, RP});
client_opts(T) ->
client_opts({T, loopback, loopback, ?DEFAULT_PORT}).
%% ---------------------------------------------------------------------------
tmod(tcp) -> diameter_tcp;
tmod(sctp) -> diameter_sctp.
ip(default) ->
[];
ip(loopback) ->
[{ip, {127,0,0,1}}];
ip(Addr) ->
[{ip, Addr}].
addr(loopback) ->
{127,0,0,1};
addr(A) ->
A.
%% ---------------------------------------------------------------------------
%% message/3
%%
%% Simple message callback that limits the number of concurrent
%% requests on the peer connection in question.
%% Incoming request.
message(recv, <<_:32, 1:1, _/bits>> = Bin, N) ->
[Bin, N < 32, fun ?MODULE:message/3, N+1];
%% Outgoing request.
message(ack, <<_:32, 1:1, _/bits>>, _) ->
[];
%% Incoming answer or request discarded.
message(ack, _, N) ->
[N =< 32, fun ?MODULE:message/3, N-1];
%% Outgoing message or incoming answer.
message(_, Bin, _) ->
[Bin].