%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-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%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Implements an "MGC" used by the test suite
%%----------------------------------------------------------------------
-module(megaco_test_mgc).
-export([start/4, start/5, stop/1,
get_stats/2, reset_stats/1,
user_info/1, user_info/2, conn_info/1, conn_info/2,
update_user_info/3, update_conn_info/3,
request_ignore/1,
request_discard/1, request_discard/2,
request_pending/1, request_pending/2, request_pending_ignore/1,
request_handle/1, request_handle/2,
request_handle_pending/1, request_handle_pending/2,
request_handle_sloppy/1, request_handle_sloppy/2,
ack_info/2, abort_info/2, req_info/2,
disconnect/2,
verbosity/2]).
-export([mgc/3]).
%% Megaco callback api
-export([
handle_connect/3,
handle_disconnect/4,
handle_syntax_error/4,
handle_message_error/4,
handle_trans_request/4,
handle_trans_long_request/4,
handle_trans_reply/5,
handle_trans_ack/5,
handle_unexpected_trans/4,
handle_trans_request_abort/5
]).
-include("megaco_test_lib.hrl").
-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").
-define(A4444, ["11111111", "00000000", "00000000"]).
-define(A4445, ["11111111", "00000000", "11111111"]).
-define(A5555, ["11111111", "11111111", "00000000"]).
-define(A5556, ["11111111", "11111111", "11111111"]).
-define(valid_actions,
[ignore, pending, pending_ignore, discard_ack, handle_ack, handle_pending_ack, handle_sloppy_ack]).
-record(mgc, {parent = undefined,
tcp_sup = undefined,
udp_sup = undefined,
req_action = discard_ack,
req_timeout = 0,
mid = undefined,
ack_info = undefined,
abort_info = undefined,
req_info = undefined,
mg = [],
dsi_timer}).
%%% ------------------------------------------------------------------
start(Node, Mid, ET, Verbosity) ->
%% Conf = [{megaco_trace, io}],
%% Conf = [{megaco_trace, "megaco-mgc.trace"}],
Conf = [{megaco_trace, false}],
start(Node, Mid, ET, Conf, Verbosity).
start(Node, Mid, ET, Conf, Verbosity) ->
d("start mgc[~p]: ~p", [Node, Mid]),
RI = {receive_info, mk_recv_info(ET)},
Config = [{local_mid, Mid}, RI] ++ Conf,
Pid = spawn_link(Node, ?MODULE, mgc, [self(), Verbosity, Config]),
await_started(Pid).
mk_recv_info(ET) ->
mk_recv_info(ET, []).
mk_recv_info([], Acc) ->
Acc;
mk_recv_info([{Encoding, Transport}|ET], Acc)
when is_atom(Encoding) andalso is_atom(Transport) ->
{EMod, Port} = select_encoding(Encoding),
TMod = select_transport(Transport),
RI = [{encoding_module, EMod},
{encoding_config, []},
{transport_module, TMod},
{port, Port}],
mk_recv_info(ET, [RI|Acc]);
mk_recv_info([{Encoding, Transport, TO}|ET], Acc)
when is_atom(Encoding) andalso is_atom(Transport) andalso is_list(TO) ->
{EMod, Port} = select_encoding(Encoding),
TMod = select_transport(Transport),
RI = [{encoding_module, EMod},
{encoding_config, []},
{transport_module, TMod},
{port, Port},
{transport_opts, TO}],
mk_recv_info(ET, [RI|Acc]);
mk_recv_info([{Encoding, EC, Transport}|ET], Acc)
when is_atom(Encoding) andalso is_list(EC) andalso is_atom(Transport) ->
{EMod, Port} = select_encoding(Encoding),
TMod = select_transport(Transport),
RI = [{encoding_module, EMod},
{encoding_config, EC},
{transport_module, TMod},
{port, Port}],
mk_recv_info(ET, [RI|Acc]);
mk_recv_info([ET|_], _) ->
throw({error, {invalid_encoding_transport, ET}}).
select_encoding(text) ->
{megaco_pretty_text_encoder, 2944};
select_encoding(pretty_text) ->
{megaco_pretty_text_encoder, 2944};
select_encoding(compact_text) ->
{megaco_compact_text_encoder, 2944};
select_encoding(binary) ->
{megaco_ber_bin_encoder, 2945};
select_encoding(erl_dist) ->
{megaco_erl_dist_encoder, 2946};
select_encoding(Encoding) ->
throw({error, {invalid_encoding, Encoding}}).
select_transport(tcp) ->
megaco_tcp;
select_transport(udp) ->
megaco_udp;
select_transport(Transport) ->
throw({error, {invalid_transport, Transport}}).
await_started(Pid) ->
receive
{started, Pid} ->
d("await_started ~p: ok", [Pid]),
{ok, Pid};
{'EXIT', Pid,
{failed_starting_tcp_listen, {could_not_start_listener, {gen_tcp_listen, eaddrinuse}}}} ->
i("await_started ~p: address already in use", [Pid]),
?SKIP(eaddrinuse);
{'EXIT', Pid, Reason} ->
i("await_started ~p: received exit signal: ~p", [Pid, Reason]),
exit({failed_starting, Pid, Reason})
after 10000 ->
i("await_started ~p: timeout", [Pid]),
exit({error, timeout})
end.
stop(Pid) ->
server_request(Pid, stop, stopped).
get_stats(Pid, No) ->
server_request(Pid, {statistics, No}, {statistics_reply, No}).
reset_stats(Pid) ->
server_request(Pid, reset_stats, reset_stats_ack).
user_info(Pid) ->
server_request(Pid, {user_info, all}, user_info_ack).
user_info(Pid, Tag) ->
server_request(Pid, {user_info, Tag}, user_info_ack).
conn_info(Pid) ->
server_request(Pid, {conn_info, all}, conn_info_ack).
conn_info(Pid, Tag) ->
server_request(Pid, {conn_info, Tag}, conn_info_ack).
update_user_info(Pid, Tag, Val) ->
server_request(Pid, {update_user_info, Tag, Val}, update_user_info_ack).
update_conn_info(Pid, Tag, Val) ->
server_request(Pid, {update_conn_info, Tag, Val}, update_conn_info_ack).
disconnect(Pid, Reason) ->
server_request(Pid, {disconnect, Reason}, disconnected).
ack_info(Pid, InfoPid) ->
Pid ! {ack_info, InfoPid, self()}.
abort_info(Pid, InfoPid) ->
Pid ! {abort_info, InfoPid, self()}.
req_info(Pid, InfoPid) ->
Pid ! {req_info, InfoPid, self()}.
verbosity(Pid, V) ->
Pid ! {verbosity, V, self()}.
request_ignore(Pid) ->
request_action(Pid, {ignore, infinity}).
request_pending_ignore(Pid) ->
request_action(Pid, {pending_ignore, infinity}).
request_discard(Pid) ->
request_discard(Pid,0).
request_discard(Pid, To) ->
request_action(Pid, {discard_ack, To}).
request_pending(Pid) ->
request_pending(Pid, 5000).
request_pending(Pid, To) ->
request_action(Pid, {pending, To}).
request_handle(Pid) ->
request_handle(Pid, 0).
request_handle(Pid, To) ->
request_action(Pid, {handle_ack, To}).
request_handle_pending(Pid) ->
request_handle_pending(Pid, 0).
request_handle_pending(Pid, To) ->
request_action(Pid, {handle_pending_ack, To}).
request_handle_sloppy(Pid) ->
request_handle_sloppy(Pid, 0).
request_handle_sloppy(Pid, To) ->
request_action(Pid, {handle_sloppy_ack, To}).
request_action(Pid, Action) ->
server_request(Pid, request_action, Action, request_action_ack).
server_request(Pid, Req, ReplyTag) ->
Pid ! {Req, self()},
receive
{ReplyTag, Reply, Pid} ->
Reply;
{'EXIT', Pid, Reason} ->
exit({failed, Req, Pid, Reason})
after 10000 ->
exit({timeout, Req, Pid})
end.
server_request(Pid, Req, ReqData, ReplyTag) ->
Pid ! {Req, ReqData, self()},
receive
{ReplyTag, Reply, Pid} ->
Reply;
{'EXIT', Pid, Reason} ->
exit({failed, Req, Pid, Reason})
after 10000 ->
exit({timeout, Req, Pid})
end.
server_reply(Pid, ReplyTag, Reply) ->
Pid ! {ReplyTag, Reply, self()}.
%%% ------------------------------------------------------------------
mgc(Parent, Verbosity, Config) ->
process_flag(trap_exit, true),
put(verbosity, Verbosity),
put(sname, "MGC"),
i("mgc -> starting"),
case (catch init(Config)) of
{error, Reason} ->
exit(Reason);
{Mid, TcpSup, UdpSup, DSITimer} ->
notify_started(Parent),
S = #mgc{parent = Parent,
tcp_sup = TcpSup,
udp_sup = UdpSup,
mid = Mid,
dsi_timer = DSITimer},
i("mgc -> started"),
display_system_info("at start "),
loop(S)
end.
init(Config) ->
d("init -> entry"),
random_init(),
Mid = get_conf(local_mid, Config),
RI = get_conf(receive_info, Config),
d("init -> maybe start the display system info timer"),
DSITimer =
case get_conf(display_system_info, Config, undefined) of
Time when is_integer(Time) ->
d("init -> creating display system info timer"),
create_timer(Time, display_system_info);
_ ->
undefined
end,
Conf0 = lists:keydelete(display_system_info, 1, Config),
d("init -> start megaco"),
application:start(megaco),
d("init -> possibly enable megaco trace"),
case lists:keysearch(megaco_trace, 1, Config) of
{value, {megaco_trace, true}} ->
megaco:enable_trace(max, io);
{value, {megaco_trace, io}} ->
megaco:enable_trace(max, io);
{value, {megaco_trace, File}} when is_list(File) ->
megaco:enable_trace(max, File);
_ ->
ok
end,
Conf1 = lists:keydelete(megaco_trace, 1, Conf0),
d("init -> start megaco user"),
Conf2 = lists:keydelete(local_mid, 1, Conf1),
Conf3 = lists:keydelete(receive_info, 1, Conf2),
ok = megaco:start_user(Mid, Conf3),
d("init -> update user info (user_mod)"),
ok = megaco:update_user_info(Mid, user_mod, ?MODULE),
d("init -> update user info (user_args)"),
ok = megaco:update_user_info(Mid, user_args, [self()]),
d("init -> get user info (receive_handle)"),
RH = megaco:user_info(Mid,receive_handle),
d("init -> parse receive info"),
Transports = parse_receive_info(RI, RH),
d("init -> start transports"),
{Tcp, Udp} = start_transports(Transports),
{Mid, Tcp, Udp, DSITimer}.
loop(S) ->
d("loop -> await request"),
receive
{display_system_info, Time} ->
display_system_info(S#mgc.mid),
NewTimer = create_timer(Time, display_system_info),
loop(S#mgc{dsi_timer = NewTimer});
{stop, Parent} when S#mgc.parent =:= Parent ->
i("loop -> stopping", []),
display_system_info(S#mgc.mid, "at finish "),
cancel_timer(S#mgc.dsi_timer),
Mid = S#mgc.mid,
(catch close_conns(Mid)),
megaco:stop_user(Mid),
application:stop(megaco),
i("loop -> stopped", []),
server_reply(Parent, stopped, ok),
exit(normal);
{{disconnect, Reason}, Parent} when S#mgc.parent == Parent ->
i("loop -> disconnecting", []),
Mid = S#mgc.mid,
[Conn|_] = megaco:user_info(Mid, connections),
Res = megaco:disconnect(Conn, {self(), Reason}),
server_reply(Parent, disconnected, Res),
loop(S);
{{update_user_info, Tag, Val}, Parent} when S#mgc.parent == Parent ->
i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]),
Res = (catch megaco:update_user_info(S#mgc.mid, Tag, Val)),
d("loop -> Res: ~p", [Res]),
server_reply(Parent, update_user_info_ack, Res),
loop(S);
{{user_info, Tag}, Parent} when S#mgc.parent == Parent ->
i("loop -> got user_info request for ~w", [Tag]),
Res = (catch megaco:user_info(S#mgc.mid, Tag)),
d("loop -> Res: ~p", [Res]),
server_reply(Parent, user_info_ack, Res),
loop(S);
{{update_conn_info, Tag, Val}, Parent} when S#mgc.parent == Parent ->
i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]),
Conns = megaco:user_info(S#mgc.mid, connections),
Fun = fun(CH) ->
(catch megaco:update_conn_info(CH, Tag, Val))
end,
Res = lists:map(Fun, Conns),
d("loop -> Res: ~p", [Res]),
server_reply(Parent, update_conn_info_ack, Res),
loop(S);
{{conn_info, Tag}, Parent} when S#mgc.parent == Parent ->
i("loop -> got conn_info request for ~w", [Tag]),
Conns = megaco:user_info(S#mgc.mid, connections),
Fun = fun(CH) ->
{CH, (catch megaco:conn_info(CH, Tag))}
end,
Res = lists:map(Fun, Conns),
d("loop -> Res: ~p", [Res]),
server_reply(Parent, conn_info_ack, Res),
loop(S);
%%
{request_action, {Action, To}, Parent} when S#mgc.parent == Parent ->
i("loop -> got new request_action: ~p:~w", [Action,To]),
{Reply, S1} =
case lists:member(Action, ?valid_actions) of
true when To >= 0; To == infinity ->
{{ok, S#mgc.req_action},
S#mgc{req_action = Action, req_timeout = To}};
true ->
{{error, {invalid_action_timeout, To}}, S};
false ->
{{error, {invalid_action, Action}}, S}
end,
server_reply(Parent, request_action_ack, Reply),
loop(S1);
%% Reset stats
{reset_stats, Parent} when S#mgc.parent == Parent ->
i("loop -> got request to reset stats counters"),
do_reset_stats(S#mgc.mid),
server_reply(Parent, reset_stats_ack, ok),
loop(S);
%% Give me statistics
{{statistics, 1}, Parent} when S#mgc.parent == Parent ->
i("loop -> got request for statistics 1"),
{ok, Gen} = megaco:get_stats(),
GetTrans =
fun(CH) ->
Reason = {statistics, CH},
Pid = megaco:conn_info(CH, control_pid),
SendMod = megaco:conn_info(CH, send_mod),
SendHandle = megaco:conn_info(CH, send_handle),
{ok, Stats} =
case SendMod of
megaco_tcp -> megaco_tcp:get_stats(SendHandle);
megaco_udp -> megaco_udp:get_stats(SendHandle);
SendMod -> exit(Pid, Reason)
end,
{SendHandle, Stats}
end,
Mid = S#mgc.mid,
Trans =
lists:map(GetTrans, megaco:user_info(Mid, connections)),
Reply = {ok, [{gen, Gen}, {trans, Trans}]},
server_reply(Parent, {statistics_reply, 1}, Reply),
loop(S);
{{statistics, 2}, Parent} when S#mgc.parent == Parent ->
i("loop -> got request for statistics 2"),
{ok, Gen} = megaco:get_stats(),
#mgc{tcp_sup = TcpSup, udp_sup = UdpSup} = S,
TcpStats = get_trans_stats(TcpSup, megaco_tcp),
UdpStats = get_trans_stats(UdpSup, megaco_udp),
Reply = {ok, [{gen, Gen}, {trans, [TcpStats, UdpStats]}]},
server_reply(Parent, {statistics_reply, 2}, Reply),
loop(S);
%% Megaco callback messages
{request, Request, From} ->
d("loop -> received megaco request from ~p:~n~p",
[From, Request]),
{Reply, S1} = handle_megaco_request(Request, S),
d("loop -> send request reply: ~n~p", [Reply]),
reply(From, Reply),
loop(S1);
{ack_info, To, Parent} when S#mgc.parent == Parent ->
i("loop -> received request to inform about received ack's ", []),
loop(S#mgc{ack_info = To});
{abort_info, To, Parent} when S#mgc.parent == Parent ->
i("loop -> received request to inform about received aborts ", []),
loop(S#mgc{abort_info = To});
{req_info, To, Parent} when S#mgc.parent == Parent ->
i("loop -> received request to inform about received req's ", []),
loop(S#mgc{req_info = To});
{verbosity, V, Parent} when S#mgc.parent == Parent ->
i("loop -> received new verbosity: ~p", [V]),
put(verbosity,V),
loop(S);
{'EXIT', Pid, Reason} when S#mgc.tcp_sup =:= Pid ->
error_msg("MGC received unexpected exit "
"from TCP transport supervisor (~p):~n~p",
[Pid, Reason]),
i("loop -> [tcp] exiting", []),
display_system_info(S#mgc.mid, "at bad finish (tcp) "),
cancel_timer(S#mgc.dsi_timer),
Mid = S#mgc.mid,
(catch close_conns(Mid)),
megaco:stop_user(Mid),
application:stop(megaco),
i("loop -> stopped", []),
StopReason = {error, {tcp_terminated, Pid, Reason}},
server_reply(S#mgc.parent, stopped, StopReason),
exit(StopReason);
{'EXIT', Pid, Reason} when S#mgc.udp_sup =:= Pid ->
error_msg("MGC received unexpected exit "
"from UDP transport supervisor (~p):~n~p",
[Pid, Reason]),
i("loop -> [udp] exiting", []),
display_system_info(S#mgc.mid, "at bad finish (udp) "),
cancel_timer(S#mgc.dsi_timer),
Mid = S#mgc.mid,
(catch close_conns(Mid)),
megaco:stop_user(Mid),
application:stop(megaco),
i("loop -> stopped", []),
StopReason = {error, {udp_terminated, Pid, Reason}},
server_reply(S#mgc.parent, stopped, StopReason),
exit(StopReason);
Invalid ->
i("loop -> received invalid request: ~p", [Invalid]),
loop(S)
end.
do_reset_stats(Mid) ->
megaco:reset_stats(),
do_reset_trans_stats(megaco:user_info(Mid, connections), []).
do_reset_trans_stats([], _Reset) ->
ok;
do_reset_trans_stats([CH|CHs], Reset) ->
SendMod = megaco:conn_info(CH, send_mod),
case lists:member(SendMod, Reset) of
true ->
do_reset_trans_stats(CHs, Reset);
false ->
SendMod:reset_stats(),
do_reset_trans_stats(CHs, [SendMod|Reset])
end.
close_conns(Mid) ->
Reason = {self(), ignore},
Disco = fun(CH) ->
(catch do_close_conn(CH, Reason))
end,
lists:map(Disco, megaco:user_info(Mid, connections)).
do_close_conn(CH, Reason) ->
d("close connection to ~p", [CH#megaco_conn_handle.remote_mid]),
Pid = megaco:conn_info(CH, control_pid),
SendMod = megaco:conn_info(CH, send_mod),
SendHandle = megaco:conn_info(CH, send_handle),
megaco:disconnect(CH, Reason),
case SendMod of
megaco_tcp -> megaco_tcp:close(SendHandle);
megaco_udp -> megaco_udp:close(SendHandle);
SendMod -> exit(Pid, Reason)
end.
get_trans_stats(P, SendMod) when is_pid(P) ->
case (catch SendMod:get_stats()) of
{ok, Stats} ->
{SendMod, Stats};
Else ->
{SendMod, Else}
end;
get_trans_stats(_P, SendMod) ->
{SendMod, undefined}.
parse_receive_info([], _RH) ->
throw({error, no_receive_info});
parse_receive_info(RI, RH) ->
parse_receive_info(RI, RH, []).
parse_receive_info([], _RH, Transports) ->
d("parse_receive_info -> done when"
"~n Transports: ~p", [Transports]),
Transports;
parse_receive_info([RI|RIs], RH, Transports) ->
d("parse_receive_info -> parse receive info"),
case (catch parse_receive_info1(RI, RH)) of
{error, Reason} ->
i("failed parsing receive info: ~p~n~p", [RI, Reason]),
exit({failed_parsing_recv_info, RI, Reason});
RH1 ->
parse_receive_info(RIs, RH, [RH1|Transports])
end.
parse_receive_info1(RI, RH) ->
d("parse_receive_info1 -> get encoding module"),
EM = get_encoding_module(RI),
d("parse_receive_info1 -> get encoding config"),
EC = get_encoding_config(RI, EM),
d("parse_receive_info1 -> get transport module"),
TM = get_transport_module(RI),
d("parse_receive_info1 -> get transport port"),
TP = get_transport_port(RI),
d("parse_receive_info1 -> get transport opts"),
TO = get_transport_opts(RI),
RH1 = RH#megaco_receive_handle{send_mod = TM,
encoding_mod = EM,
encoding_config = EC},
d("parse_receive_info1 -> "
"~n Transport Opts: ~p"
"~n Port: ~p"
"~n Receive handle: ~p", [TO, TP, RH1]),
{TO, TP, RH1}.
%% --------------------------------------------------------
%% On some platforms there seem to take some time before
%% a port is released by the OS (after having been used,
%% as is often the case in the test suites).
%% So, starting the transports is done in two steps.
%% First) Start the actual transport(s)
%% Second) Create the listener (tcp) or open the
%% send/receive port (udp).
%% The second step *may* need to be repeated!
%% --------------------------------------------------------
start_transports([]) ->
throw({error, no_transport});
start_transports(Transports) when is_list(Transports) ->
{Tcp, Udp} = start_transports1(Transports, undefined, undefined),
ok = start_transports2(Transports, Tcp, Udp),
{Tcp, Udp}.
start_transports1([], Tcp, Udp) ->
{Tcp, Udp};
start_transports1([{_TO, _Port, RH}|Transports], Tcp, Udp)
when ((RH#megaco_receive_handle.send_mod =:= megaco_tcp) andalso
(not is_pid(Tcp))) ->
case megaco_tcp:start_transport() of
{ok, Sup} ->
start_transports1(Transports, Sup, Udp);
Else ->
throw({error, {failed_starting_tcp_transport, Else}})
end;
start_transports1([{_TO, _Port, RH}|Transports], Tcp, Udp)
when ((RH#megaco_receive_handle.send_mod =:= megaco_udp) andalso
(not is_pid(Udp))) ->
case megaco_udp:start_transport() of
{ok, Sup} ->
start_transports1(Transports, Tcp, Sup);
Else ->
throw({error, {failed_starting_udp_transport, Else}})
end;
start_transports1([_|Transports], Tcp, Udp) ->
start_transports1(Transports, Tcp, Udp).
start_transports2([], _, _) ->
ok;
start_transports2([{TO, Port, RH}|Transports], Tcp, Udp)
when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
start_tcp(TO, RH, Port, Tcp),
start_transports2(Transports, Tcp, Udp);
start_transports2([{TO, Port, RH}|Transports], Tcp, Udp)
when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
start_udp(TO, RH, Port, Udp),
start_transports2(Transports, Tcp, Udp).
start_tcp(TO, RH, Port, Sup) ->
d("start tcp transport"),
start_tcp(TO, RH, Port, Sup, 250).
start_tcp(TO, RH, Port, Sup, Timeout)
when is_pid(Sup) andalso is_integer(Timeout) andalso (Timeout > 0) ->
d("tcp listen on ~p", [Port]),
Opts = [{port, Port},
{receive_handle, RH},
{tcp_options, [{nodelay, true}]}] ++ TO,
try_start_tcp(Sup, Opts, Timeout, noError).
try_start_tcp(Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
Sleep = random(Timeout) + 100,
d("try create tcp listen socket (~p,~p)", [Timeout, Sleep]),
case megaco_tcp:listen(Sup, Opts) of
ok ->
d("listen socket created", []),
Sup;
Error1 when Error0 =:= noError -> % Keep the first error
d("failed creating listen socket [1]: ~p", [Error1]),
sleep(Sleep),
try_start_tcp(Sup, Opts, Timeout*2, Error1);
Error2 ->
d("failed creating listen socket [2]: ~p", [Error2]),
sleep(Sleep),
try_start_tcp(Sup, Opts, Timeout*2, Error0)
end;
try_start_tcp(Sup, _Opts, _Timeout, Error) ->
megaco_tcp:stop_transport(Sup),
case Error of
{error, Reason} ->
throw({error, {failed_starting_tcp_listen, Reason}});
_ ->
throw({error, {failed_starting_tcp_listen, Error}})
end.
start_udp(TO, RH, Port, Sup) ->
d("start udp transport"),
start_udp(TO, RH, Port, Sup, 250).
start_udp(TO, RH, Port, Sup, Timeout) ->
d("udp open ~p", [Port]),
Opts = [{port, Port}, {receive_handle, RH}] ++ TO,
try_start_udp(Sup, Opts, Timeout, noError).
try_start_udp(Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
d("try open udp socket (~p)", [Timeout]),
case megaco_udp:open(Sup, Opts) of
{ok, _SendHandle, _ControlPid} ->
d("port opened", []),
Sup;
Error1 when Error0 =:= noError -> % Keep the first error
d("failed open port [1]: ~p", [Error1]),
sleep(Timeout),
try_start_udp(Sup, Opts, Timeout*2, Error1);
Error2 ->
d("failed open port [2]: ~p", [Error2]),
sleep(Timeout),
try_start_udp(Sup, Opts, Timeout*2, Error0)
end;
try_start_udp(Sup, _Opts, _Timeout, Error) ->
megaco_udp:stop_transport(Sup),
throw({error, {failed_starting_udp_open, Error}}).
%% -----------------------
%% Handle megaco callbacks
%%
handle_megaco_request({handle_connect, CH, _PV}, #mgc{mg = MGs} = S) ->
case lists:member(CH, MGs) of
true ->
i("MG already connected: ~n ~p", [CH]),
{error, S};
false ->
{ok, S#mgc{mg = [CH|MGs]}}
end;
handle_megaco_request({handle_disconnect, CH, _PV, R}, S) ->
d("handle_megaco_request(handle_disconnect) -> entry with"
"~n CH: ~p"
"~n R: ~p", [CH, R]),
CancelRes = (catch megaco:cancel(CH, R)), % Cancel the outstanding messages
d("handle_megaco_request(handle_disconnect) -> megaco cancel result: ~p", [CancelRes]),
MGs = lists:delete(CH, S#mgc.mg),
d("handle_megaco_request(handle_disconnect) -> MGs: ~p", [MGs]),
{ok, S#mgc{mg = MGs}};
handle_megaco_request({handle_syntax_error, _RH, _PV, _ED}, S) ->
{reply, S};
handle_megaco_request({handle_message_error, _CH, _PV, _ED}, S) ->
{no_reply, S};
handle_megaco_request({handle_trans_request, CH, PV, ARs},
#mgc{req_info = P} = S) when is_pid(P) ->
d("handle_megaco_request(handle_trans_request,~p) -> entry", [P]),
P ! {req_received, self(), ARs},
do_handle_trans_request(CH, PV, ARs, S);
handle_megaco_request({handle_trans_request, CH, PV, ARs}, S) ->
d("handle_megaco_request(handle_trans_request) -> entry"),
do_handle_trans_request(CH, PV, ARs, S);
handle_megaco_request({handle_trans_long_request, CH, PV, RD}, S) ->
d("handle_megaco_request(handle_long_trans_request) -> entry"),
Reply0 = handle_act_requests(CH, PV, RD, discard_ack),
Reply =
case S of
#mgc{req_action = ignore, req_timeout = To} ->
d("handle_megaco_request(handle_long_trans_request) -> "
"~n To: ~p", [To]),
{delay_reply, To, Reply0};
_ ->
d("handle_megaco_request(handle_long_trans_request) -> "
"~n S: ~p", [S]),
Reply0
end,
{Reply, S};
handle_megaco_request({handle_trans_reply, _CH, _PV, _AR, _RD}, S) ->
{ok, S};
handle_megaco_request({handle_trans_ack, CH, PV, AS, AD},
#mgc{ack_info = P} = S) when is_pid(P) ->
d("handle_megaco_request(handle_trans_ack,~p) -> entry when"
"~n CH: ~p"
"~n PV: ~p"
"~n AS: ~p"
"~n AD: ~p", [P, CH, PV, AS, AD]),
P ! {ack_received, self(), AS},
{ok, S};
handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, S) ->
d("handle_megaco_request(handle_trans_ack) -> entry with"
"~n CH: ~p"
"~n PV: ~p"
"~n AS: ~p"
"~n AD: ~p", [CH, PV, AS, AD]),
{ok, S};
handle_megaco_request({handle_unexpected_trans, CH, PV, TR}, S) ->
d("handle_megaco_request(handle_unexpected_trans) -> entry with"
"~n CH: ~p"
"~n PV: ~p"
"~n TR: ~p", [CH, PV, TR]),
{ok, S};
handle_megaco_request({handle_trans_request_abort, CH, PV, TI, Handler}, S) ->
d("handle_megaco_request(handle_trans_request_abort) -> entry with"
"~n CH: ~p"
"~n PV: ~p"
"~n TI: ~p"
"~n Handler: ~p", [CH, PV, TI, Handler]),
Reply =
case S#mgc.abort_info of
P when is_pid(P) ->
P ! {abort_received, self(), TI},
ok;
_ ->
ok
end,
{Reply, S}.
do_handle_trans_request(CH, PV, ARs,
#mgc{req_action = Action, req_timeout = To} = S) ->
d("do_handle_megaco_request(handle_trans_request) -> entry with"
"~n Action: ~p"
"~n To: ~p", [Action, To]),
case handle_act_requests(CH, PV, ARs, Action) of
{pending_ignore, ActReqs} ->
{{pending, ActReqs}, S#mgc{req_action = ignore}};
Reply ->
{{delay_reply, To, Reply}, S}
end.
handle_act_requests(_CH, _PV, _ActReqs, ignore) ->
ignore;
handle_act_requests(_CH, _PV, ActReqs, pending) ->
{pending, ActReqs};
handle_act_requests(_CH, _PV, ActReqs, pending_ignore) ->
{pending_ignore, ActReqs};
handle_act_requests(CH, PV, ActReqs, handle_ack) ->
Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
{{handle_ack, ActReqs}, Reply};
handle_act_requests(CH, PV, ActReqs, handle_sloppy_ack) ->
Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
{{handle_sloppy_ack, ActReqs}, Reply};
handle_act_requests(CH, PV, ActReqs, _) ->
Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
{discard_ack, Reply}.
do_handle_act_requests(_CH, _PV, [], ActReplies) ->
lists:reverse(ActReplies);
do_handle_act_requests(CH, PV, [ActReq|ActReqs], ActReplies) ->
ActReply = handle_act_request(CH, PV, ActReq),
do_handle_act_requests(CH, PV, ActReqs, [ActReply|ActReplies]).
handle_act_request(CH, PV, ActReq) ->
#'ActionRequest'{contextId = CtxId, commandRequests = Cmds} = ActReq,
CmdReplies = handle_cmd_requests(CH, PV, CtxId, Cmds),
#'ActionReply'{contextId = CtxId,
commandReply = CmdReplies}.
handle_cmd_requests(CH, PV, ?megaco_null_context_id,
[#'CommandRequest'{command={serviceChangeReq,Req}}]) ->
Rep = service_change(CH, PV, Req),
[{serviceChangeReply, Rep}];
handle_cmd_requests(CH, PV, CtxId, Cmds) ->
do_handle_cmd_requests(CH, PV, CtxId, Cmds, []).
do_handle_cmd_requests(_CH, _PV, _CtxId, [], CmdReplies) ->
lists:reverse(CmdReplies);
do_handle_cmd_requests(CH, PV, CtxId, [Cmd|Cmds], CmdReplies) ->
CmdReply = handle_cmd_request(CH, PV, CtxId, Cmd),
do_handle_cmd_requests(CH, PV, CtxId, Cmds, [CmdReply|CmdReplies]).
handle_cmd_request(CH, PV, CtxId,
#'CommandRequest'{command = {Tag,Req}}) ->
case Tag of
notifyReq ->
(catch handle_notify_req(CH,PV,CtxId,Req));
serviceChangeReq ->
ED = cre_error_descr(?megaco_not_implemented,
"Service change only allowed "
"on null context handled"),
throw(ED);
_ ->
Code = ?megaco_not_implemented,
ED = cre_error_descr(Code,"Unknown command requst received:"
"~n Tag: ~p~n Req: ~p",[Tag,Req]),
throw(ED)
end.
handle_notify_req(CH, PV, CtxId,
#'NotifyRequest'{terminationID = [Tid],
observedEventsDescriptor = EvDesc}) ->
handle_event(CH, PV, CtxId, Tid, EvDesc).
handle_event(_CH, _PV, _Cid, Tid, EvDesc) ->
d("handle_event -> received"
"~n EvDesc: ~p"
"~n Tid: ~p", [EvDesc, Tid]),
{notifyReply, cre_notifyRep(Tid)}.
service_change(CH, _PV, SCR) ->
SCP = SCR#'ServiceChangeRequest'.serviceChangeParms,
#'ServiceChangeParm'{serviceChangeAddress = Address,
serviceChangeProfile = Profile,
serviceChangeReason = [_Reason]} = SCP,
TermId = SCR#'ServiceChangeRequest'.terminationID,
if
TermId == [?megaco_root_termination_id] ->
MyMid = CH#megaco_conn_handle.local_mid,
Res = {serviceChangeResParms,
cre_serviceChangeResParms(MyMid, Address, Profile)},
cre_serviceChangeReply(TermId, Res);
true ->
Res = {errorDescriptor,
cre_error_descr(?megaco_not_implemented,
"Only handled for root")},
cre_serviceChangeReply(TermId, Res)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
cre_serviceChangeReply(TermId, Result) ->
#'ServiceChangeReply'{terminationID = TermId,
serviceChangeResult = Result}.
cre_serviceChangeResParms(Mid, Addr, Prof) ->
#'ServiceChangeResParm'{serviceChangeMgcId = Mid,
serviceChangeAddress = Addr,
serviceChangeProfile = Prof}.
cre_notifyRep(Tid) ->
#'NotifyReply'{terminationID = [Tid]}.
% cre_notifyRep(Tid,Err) ->
% #'NotifyReply'{terminationID = [Tid], errorDescriptor = Err}.
cre_error_descr(Code,Text) ->
#'ErrorDescriptor'{errorCode = Code, errorText = Text}.
cre_error_descr(Code,FormatString,Args) ->
Text = lists:flatten(io_lib:format(FormatString,Args)),
cre_error_descr(Code,Text).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
notify_started(Parent) ->
Parent ! {started, self()}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The megaco user callback interface
handle_connect(CH, PV, Pid) ->
case CH#megaco_conn_handle.remote_mid of
preliminary_mid ->
%% Avoids deadlock
ok;
_ ->
Reply = request(Pid, {handle_connect, CH, PV}),
Reply
end.
handle_disconnect(_CH, _PV,
{user_disconnect, {Pid, ignore}},
Pid) ->
ok;
handle_disconnect(CH, _PV,
{user_disconnect, {Pid, cancel}},
Pid) ->
megaco:cancel(CH, disconnected),
ok;
handle_disconnect(CH, PV, R, Pid) ->
request(Pid, {handle_disconnect, CH, PV, R}).
handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor, Pid) ->
Req = {handle_syntax_error, ReceiveHandle, ProtocolVersion,
ErrorDescriptor},
request(Pid, Req).
handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor, Pid) ->
Req = {handle_message_error, ConnHandle, ProtocolVersion, ErrorDescriptor},
request(Pid, Req).
handle_trans_request(CH, PV, AR, Pid) ->
Reply = request(Pid, {handle_trans_request, CH, PV, AR}),
Reply.
handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Pid) ->
Req = {handle_trans_long_request, ConnHandle, ProtocolVersion, ReqData},
request(Pid, Req).
handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData, Pid) ->
Req = {handle_trans_reply, ConnHandle, ProtocolVersion,
ActualReply, ReplyData},
request(Pid, Req).
handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Pid) ->
Req = {handle_trans_ack, ConnHandle, ProtocolVersion, AckStatus, AckData},
request(Pid, Req).
handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Pid) ->
Req = {handle_unexpected_trans, ConnHandle, ProtocolVersion, Trans},
request(Pid, Req).
handle_trans_request_abort(ConnHandle, ProtocolVersion, TransId,
Handler, Pid) ->
Req = {handle_trans_request_abort,
ConnHandle, ProtocolVersion, TransId, Handler},
request(Pid, Req).
request(Pid, Request) ->
Pid ! {request, Request, self()},
receive
{reply, {delay_reply, To, Reply}, Pid} ->
megaco:report_event(ignore, self(), Pid,
"reply: delay_reply", [To, Reply]),
sleep(To),
megaco:report_event(ignore, self(), Pid,
"reply: delay done now return", []),
Reply;
{reply, {exit, To, Reason}, Pid} ->
megaco:report_event(ignore, self(), Pid,
"reply: exit", [To, Reason]),
sleep(To),
megaco:report_event(ignore, self(), Pid,
"reply: sleep done now exit", []),
exit(Reason);
{reply, Reply, Pid} ->
megaco:report_event(ignore, self(), Pid, "reply", [Reply]),
Reply
end.
reply(To, Reply) ->
To ! {reply, Reply, self()}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
sleep(X) ->
d("sleep -> ~w", [X]),
receive after X -> ok end.
error_msg(F,A) -> error_logger:error_msg("MGC: " ++ F ++ "~n",A).
get_encoding_module(RI) ->
case (catch get_conf(encoding_module, RI)) of
{error, _} ->
undefined;
Val ->
Val
end.
get_encoding_config(RI, EM) ->
case text_codec(EM) of
true ->
case megaco:system_info(text_config) of
[Conf] when is_list(Conf) ->
Conf;
_ ->
[]
end;
false ->
get_conf(encoding_config, RI)
end.
text_codec(megaco_compact_text_encoder) ->
true;
text_codec(megaco_pretty_text_encoder) ->
true;
text_codec(_) ->
false.
get_transport_module(RI) ->
get_conf(transport_module, RI).
get_transport_port(RI) ->
get_conf(port, RI).
get_transport_opts(RI) ->
get_conf(transport_opts, RI, []).
get_conf(Key, Config) ->
case lists:keysearch(Key, 1, Config) of
{value, {Key, Val}} ->
Val;
_ ->
exit({error, {not_found, Key, Config}})
end.
get_conf(Key, Config, Default) ->
case lists:keysearch(Key, 1, Config) of
{value, {Key, Val}} ->
Val;
_ ->
Default
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
random_init() ->
{A,B,C} = now(),
random:seed(A,B,C).
random(N) ->
random:uniform(N).
display_system_info(Mid) ->
display_system_info(Mid, "").
display_system_info(Mid, Pre) ->
TimeStr = format_timestamp(now()),
MibStr = lists:flatten(io_lib:format("~p ", [Mid])),
megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr).
create_timer(Time, Event) ->
erlang:send_after(Time, self(), {Event, Time}).
cancel_timer(undefined) ->
ok;
cancel_timer(Ref) ->
erlang:cancel_timer(Ref).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
i(F) ->
i(F, []).
i(F, A) ->
print(info, get(verbosity), "", F, A).
d(F) ->
d(F, []).
d(F, A) ->
print(debug, get(verbosity), "DBG: ", F, A).
printable(_, debug) -> true;
printable(info, info) -> true;
printable(_,_) -> false.
print(Severity, Verbosity, P, F, A) ->
print(printable(Severity,Verbosity), P, F, A).
print(true, P, F, A) ->
print(P, F, A);
print(_, _, _, _) ->
ok.
print(P, F, A) ->
io:format("*** [~s] ~s ~p ~s ***"
"~n " ++ F ++ "~n~n",
[format_timestamp(now()), P, self(), get(sname) | A]).
format_timestamp({_N1, _N2, N3} = Now) ->
{Date, Time} = calendar:now_to_datetime(Now),
{YYYY,MM,DD} = Date,
{Hour,Min,Sec} = Time,
FormatDate =
io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
[YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
lists:flatten(FormatDate).