aboutsummaryrefslogtreecommitdiffstats
path: root/lib/megaco/test/megaco_test_mgc.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/megaco/test/megaco_test_mgc.erl')
-rw-r--r--lib/megaco/test/megaco_test_mgc.erl1221
1 files changed, 1221 insertions, 0 deletions
diff --git a/lib/megaco/test/megaco_test_mgc.erl b/lib/megaco/test/megaco_test_mgc.erl
new file mode 100644
index 0000000000..05c482f1af
--- /dev/null
+++ b/lib/megaco/test/megaco_test_mgc.erl
@@ -0,0 +1,1221 @@
+%%
+%% %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).
+