diff options
Diffstat (limited to 'lib/megaco/test/megaco_test_mg.erl')
-rw-r--r-- | lib/megaco/test/megaco_test_mg.erl | 1585 |
1 files changed, 1585 insertions, 0 deletions
diff --git a/lib/megaco/test/megaco_test_mg.erl b/lib/megaco/test/megaco_test_mg.erl new file mode 100644 index 0000000000..22b65a1ac6 --- /dev/null +++ b/lib/megaco/test/megaco_test_mg.erl @@ -0,0 +1,1585 @@ +%% +%% %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 "MG" used by the test suite +%%---------------------------------------------------------------------- +-module(megaco_test_mg). + +-export([start/5, start/6, stop/1, + get_stats/1, reset_stats/1, + user_info/1, user_info/2, + update_user_info/3, + conn_info/1, conn_info/2, + update_conn_info/3, + service_change/1, + ack_info/2, rep_info/2, + group_requests/2, + notify_request/1, + await_notify_reply/1, + notify_request_and_reply/1, + cancel_request/2, + apply_load/2, + apply_multi_load/3, + enable_test_code/4, + encode_ar_first/2, + verbosity/2]). + +-export([mg/3, notify_request_handler_main/5]). +-export([loader_main/4]). + +%% Megaco callback api +-export([ + handle_connect/4, + handle_disconnect/5, + handle_syntax_error/5, + handle_message_error/5, + handle_trans_request/5, + handle_trans_long_request/5, + handle_trans_reply/6, + handle_trans_ack/6 + ]). + +-include("megaco_test_lib.hrl"). +-include_lib("megaco/include/megaco.hrl"). +-include_lib("megaco/include/megaco_message_v1.hrl"). + +-define(A4444, tid(255*256*256) ). +-define(A4445, tid(255*256*256 + 255) ). +-define(A5555, tid(255*256*256 + 255*256) ). +-define(A5556, tid(255*256*256 + 255*256 + 255) ). + +-record(mg, {mid = undefined, + state = initiated, + req_handler = undefined, + call_mode = async, + group_size = 1, + encode_ar_first = false, + ack_info = undefined, + rep_info = undefined, + load_counter = 0, + reply_counter = 0, + mload_info = undefined, + parent = undefined, + dsi_timer}). + + +%%% -------------------------------------------------------------------- + +start(Node, Mid, Encoding, Transport, Verbosity) -> + %% Conf = [{megaco_trace, io}], + %% Conf = [{megaco_trace, "megaco-mg.trace"}], + Conf = [{megaco_trace, false}], + start(Node, Mid, Encoding, Transport, Conf, Verbosity). + +start(Node, Mid, Encoding, Transport, Conf, Verbosity) -> + d("start mg[~p]: ~p" + "~n Encoding: ~p" + "~n Transport: ~p" + "~n Conf: ~p", [Node, Mid, Encoding, Transport, Conf]), + RI1 = encoding_config(Encoding), + RI2 = transport_config(Transport), + {RI3, Conf1} = transport_opts(Conf), + RI = {receive_info, RI1 ++ RI2 ++ RI3}, + Config = [{local_mid, Mid}, RI] ++ Conf1, + Self = self(), + Fun = + fun() -> + io:format("LOADER(~p,~p) started~n", [self(),node()]), + case (catch mg(Self, Verbosity, Config)) of + {'EXIT', Reason} -> + io:format("LOADER(~p,~p) terminating with exit" + "~n~p" + "~n", [self(), node(), Reason]), + exit(Reason); + Else -> + io:format("LOADER(~p,~p) terminating with" + "~n~p" + "~n", [self(), node(), Else]), + Else + end + end, + true = erlang:monitor_node(Node, true), + Pid = spawn_link(Node, Fun), + %% Pid = spawn_link(Node, ?MODULE, mg, [self(), Verbosity, Config]), + MonRef = (catch erlang:monitor(process, Pid)), + NodePing = net_adm:ping(Node), + ProcInfo = (catch proc_info(Pid)), + i("start -> " + "~n self(): ~p" + "~n node(): ~p" + "~n net_adm:ping(~p): ~p" + "~n Loader: ~p" + "~n Monitor ref: ~p" + "~n Process info: ~p", + [self(), node(), + Node, NodePing, + Pid, + MonRef, ProcInfo]), + await_started(Node, MonRef, Pid). + +proc_info(Pid) -> + rpc:call(node(Pid), erlang, process_info, [Pid]). + +encoding_config({Encoding, EC}) when is_atom(Encoding) andalso is_list(EC) -> + {Mod, Port} = select_encoding(Encoding), + [{encoding_module, Mod}, + {encoding_config, EC}, + {port, Port}]; +encoding_config(Encoding) when is_atom(Encoding) -> + {Mod, Port} = select_encoding(Encoding), + [{encoding_module, Mod}, + {encoding_config, []}, + {port, Port}]; +encoding_config(Encoding) -> + throw({error, {invalid_encoding, Encoding}}). + +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}}). + +transport_config(tcp) -> + [{transport_module, megaco_tcp}]; +transport_config(udp) -> + [{transport_module, megaco_udp}]; +transport_config(TransportConfig) when is_list(TransportConfig) -> + {value, {transport, Trans}} = + lists:keysearch(transport, 1, TransportConfig), + transport_config(Trans) ++ + case lists:keysearch(host, 1, TransportConfig) of + {value, Value} -> + [Value]; + false -> + [] + end. + +transport_opts(Config) -> + case lists:keysearch(transport_opts, 1, Config) of + {value, TO} -> + Config1 = lists:keydelete(transport_opts, 1, Config), + {[TO], Config1}; + false -> + {[], Config} + end. + + +await_started(Node, MonRef, Pid) -> + i("await_started -> entry with" + "~n MonRef: ~p" + "~n Pid: ~p", [MonRef, Pid]), + receive + {started, Pid} -> + d("await_started ~p - started" + "~n Process info: ~p", [Pid, (catch proc_info(Pid))]), + true = erlang:monitor_node(Node, false), + erlang:demonitor(MonRef), + {ok, Pid}; + + {nodedown, Node} -> + i("await_started ~p - received node down", [Pid]), + exit({node_down, Node}); + + {'DOWN', MonRef, process, Pid, Info} -> + i("await_started ~p - received down signal: ~p", + [Pid, Info]), + true = erlang:monitor_node(Node, false), + exit({failed_starting, Pid, Info}); + + {'EXIT', Pid, Reason} -> + i("await_started ~p - received exit signal: ~p", [Pid, Reason]), + true = erlang:monitor_node(Node, false), + exit({failed_starting, Pid, Reason}) + + %% This timeout was originally 10 secs, but on some debug compiled + %% platforms, it was simply not long enough + after 20000 -> + NodePing = net_adm:ping(Node), + ProcInfo = (catch proc_info(Pid)), + FlushQ = megaco_test_lib:flush(), + i("await_started ~p - timeout: " + "~n net_adm:ping(~p): ~p" + "~n Process info: ~p" + "~n Messages in my queue: ~p", + [Pid, Node, NodePing, ProcInfo, FlushQ]), + true = erlang:monitor_node(Node, false), + exit({error, timeout}) + end. + + +verbosity(Pid, V) -> + Pid ! {verbosity, V, self()}. + + +stop(Pid) -> + server_request(Pid, stop, stopped). + + +get_stats(Pid) -> + server_request(Pid, statistics, statistics_reply). + +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) when is_atom(Tag) -> + server_request(Pid, {user_info, Tag}, user_info_ack). + + +update_user_info(Pid, Tag, Val) -> + server_request(Pid, {update_user_info, Tag, Val}, update_user_info_ack). + + +conn_info(Pid) -> + server_request(Pid, {conn_info, all}, conn_info_ack). + +conn_info(Pid, Tag) when is_atom(Tag) -> + server_request(Pid, {conn_info, Tag}, conn_info_ack). + + +update_conn_info(Pid, Tag, Val) -> + server_request(Pid, {update_conn_info, Tag, Val}, update_conn_info_ack). + + +enable_test_code(Pid, Module, Where, Fun) + when is_atom(Module) andalso is_atom(Where) andalso is_function(Fun) -> + Tag = {Module, Where}, + server_request(Pid, {enable_test_code, Tag, Fun}, enable_test_code_reply). + +encode_ar_first(Pid, New) when is_atom(New) -> + server_request(Pid, {encode_ar_first, New}, encode_ar_first_reply). + +service_change(Pid) -> + server_request(Pid, service_change, service_change_reply). + + +group_requests(Pid, N) -> + server_request(Pid, {group_requests, N}, group_requests_reply). + + +ack_info(Pid, InfoPid) -> + Pid ! {{ack_info, InfoPid}, self()}. + +rep_info(Pid, InfoPid) -> + Pid ! {{rep_info, InfoPid}, self()}. + + +notify_request(Pid) -> + Pid ! {notify_request, self()}. + + +await_notify_reply(Pid) -> + await_reply(Pid, notify_request_reply). + + +notify_request_and_reply(Pid) -> + notify_request(Pid), + await_notify_reply(Pid). + + +cancel_request(Pid, Reason) -> + server_request(Pid, cancel_request, Reason, cancel_request_reply). + + +apply_load(Pid, CounterStart) -> + server_request(Pid, apply_load, CounterStart, apply_load_ack). + + +apply_multi_load(Pid, NumLoaders, NumReq) -> + server_request(Pid, apply_multi_load, {NumLoaders, NumReq}, apply_multi_load_ack). + + +server_request(Pid, Req, ReplyTag) -> + Pid ! {Req, self()}, + await_reply(Pid, ReplyTag). + +server_request(Pid, Req, ReqData, ReplyTag) -> + Pid ! {Req, ReqData, self()}, + await_reply(Pid, ReplyTag). + +await_reply(Pid, ReplyTag) -> + await_reply(Pid, ReplyTag, infinity). + +await_reply(Pid, ReplyTag, Timeout) -> + receive + {ReplyTag, Reply, Pid} -> + Reply; + {'EXIT', Pid, Reason} -> + exit({failed, ReplyTag, Pid, Reason}) + after Timeout -> + exit({timeout, ReplyTag, Pid}) + end. + + +server_reply(Pid, ReplyTag, Reply) -> + Pid ! {ReplyTag, Reply, self()}. + + +%%% -------------------------------------------------------------------- + + +mg(Parent, Verbosity, Config) -> + process_flag(trap_exit, true), + put(sname, "MG"), + %% put(verbosity, Verbosity), + put(verbosity, debug), % Enable debug printouts during init + i("mg -> starting"), + %% megaco:enable_trace(max, io), + case (catch init(Config)) of + {error, _} = Error -> + exit(Error); + {'EXIT', Reason} -> + exit({init_failed, Reason}); + {ok, Mid, DSITimer} -> + notify_started(Parent), + MG = #mg{parent = Parent, mid = Mid, dsi_timer = DSITimer}, + i("mg -> started"), + put(verbosity, Verbosity), + case (catch loop(MG)) of + {'EXIT', normal} -> + exit(normal); + {'EXIT', Reason} -> + i("mg failed with reason:~n ~p", [Reason]), + exit(Reason); + Else -> + i("mg terminated: ~n ~p", [Else]), + exit({unexpected, Else}) + end + end. + +init(Config) -> + d("init -> entry with" + "~n Config: ~p", [Config]), + random_init(), + d("init -> random initiated", []), + Mid = get_conf(local_mid, Config), + d("init -> Mid: ~p", [Mid]), + RI = get_conf(receive_info, Config), + d("init -> RI: ~p", [RI]), + + 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, Conf0) 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(), Mid]), + + d("init -> get user info (receive_handle)"), + RH = megaco:user_info(Mid, receive_handle), + d("init -> parse receive info"), + {MgcPort, MgcHost, RH1, TO} = parse_receive_info(RI, RH), + d("init -> start transport (with ~p)", [TO]), + {ok, _CH} = start_transport(MgcPort, MgcHost, RH1, TO), + {ok, Mid, DSITimer}. + + +loop(#mg{parent = Parent, mid = Mid} = S) -> + d("loop -> await request", []), + receive + {display_system_info, Time} -> + display_system_info(S#mg.mid), + NewTimer = create_timer(Time, display_system_info), + loop(S#mg{dsi_timer = NewTimer}); + + {verbosity, V, Parent} -> + i("loop -> received new verbosity: ~p", [V]), + put(verbosity,V), + loop(S); + + + {stop, Parent} -> + i("loop -> stopping", []), + display_system_info(S#mg.mid, "at finish "), + cancel_timer(S#mg.dsi_timer), + Res = do_stop(Mid), + d("loop -> stop result: ~p", [Res]), + server_reply(Parent, stopped, {ok, Res}), + exit(normal); + + {{enable_test_code, Tag, Fun}, Parent} -> + i("loop -> enable_test_code: ~p, ~p", [Tag, Fun]), + Reply = (catch ets:insert(megaco_test_data, {Tag, Fun})), + d("loop -> enable_test_code -> " + "~n Reply: ~p" + "~n ets:tab2list(megaco_test_data): ~p", + [Reply,ets:tab2list(megaco_test_data)]), + server_reply(Parent, enable_test_code_reply, Reply), + loop(S); + + {{encode_ar_first, EAF}, Parent} -> + i("loop -> encode_ar_first: ~p", [EAF]), + {Reply, S1} = handle_encode_ar_first(S, EAF), + server_reply(Parent, encode_ar_first_reply, Reply), + loop(S1#mg{encode_ar_first = EAF}); + + %% Give me statistics + {statistics, Parent} -> + i("loop -> got request for statistics", []), + Stats = do_get_statistics(Mid), + server_reply(Parent, statistics_reply, {ok, Stats}), + loop(S); + + {reset_stats, Parent} -> + i("loop -> got request to reset stats counters", []), + do_reset_stats(Mid), + server_reply(Parent, reset_stats_ack, ok), + loop(S); + + {{user_info, Tag}, Parent} -> + i("loop -> got user_info request for ~w", [Tag]), + Res = do_get_user_info(Mid, Tag), + d("loop -> Res: ~p", [Res]), + server_reply(Parent, user_info_ack, Res), + loop(S); + + {{update_user_info, Tag, Val}, Parent} -> + i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]), + Res = do_update_user_info(Mid, Tag, Val), + d("loop -> Res: ~p", [Res]), + server_reply(Parent, update_user_info_ack, Res), + loop(S); + + {{conn_info, Tag}, Parent} -> + i("loop -> got conn_info request for ~w", [Tag]), + Res = do_get_conn_info(Mid, Tag), + server_reply(Parent, conn_info_ack, Res), + loop(S); + + {{update_conn_info, Tag, Val}, Parent} -> + i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]), + Res = do_update_conn_info(Mid, Tag, Val), + server_reply(Parent, update_conn_info_ack, Res), + loop(S); + + + %% Do a service change + %% No server-reply here. Since the service change is + %% async, the reply (from the MGC) will come later. + {service_change, Parent} -> + i("loop -> received request to perform service change", []), + S1 = + case (catch do_service_change(S)) of + {ok, MG} -> + d("loop -> service change initiated", []), + MG; + Error -> + d("loop -> service change failed: ~p", [Error]), + server_reply(Parent, service_change_reply, Error), + S + end, + loop(S1); + + {{group_requests, N}, Parent} when N > 0 -> + i("loop -> received group_requests ~p", [N]), + Reply = {ok, S#mg.group_size}, + server_reply(Parent, group_requests_reply, Reply), + loop(S#mg{group_size = N}); + + {{ack_info, To}, Parent} -> + i("loop -> received request to inform about received ack's ", []), + loop(S#mg{ack_info = To}); + + {{rep_info, To}, Parent} -> + i("loop -> received request to inform about received rep's ", []), + loop(S#mg{rep_info = To}); + + %% Make a sync-call + {notify_request, Parent} -> + i("loop -> received request to send notify request ", []), + {Res, S1} = do_handle_notify_request(S), + d("loop -> notify request result: ~p", [Res]), + loop(S1); + + %% sync-call complete + {notify_request_complete, NotifyReply, Pid} -> + i("loop -> received notify request complete from " + "~n ~p with" + "~n NotifyReply: ~p", + [Pid, NotifyReply]), + server_reply(Parent, notify_request_reply, NotifyReply), + loop(S#mg{req_handler = undefined}); + + + %% cancel requests + {cancel_request, Reason, Parent} -> + i("loop -> received request to cancel (all) megaco requests ", []), + Res = do_cancel_requests(Mid, Reason), + server_reply(Parent, cancel_request_reply, Res), + loop(S); + + + %% Apply multi-load + {apply_multi_load, {NL, NR}, Parent} -> + i("loop -> received apply_multi_load request: ~w, ~w", [NL, NR]), + S1 = start_loaders(S, NL, NR), + loop(S1); + + + %% Apply some load + {apply_load, Times, Parent} -> + i("loop -> received apply_load request: ~w", [Times]), + S1 = + case update_load_times(S, Times) of + {ok, MG} -> + apply_load_timer(), + server_reply(Parent, apply_load_ack, ok), + MG; + Error -> + server_reply(Parent, apply_load_ack, Error), + S + end, + loop(S1); + + {apply_load_timeout, _} -> + d("loop -> received apply_load timeout", []), + S1 = do_apply_load(S), + loop(S1); + + + %% Megaco callback messages + {request, Request, Mid, From} -> + d("loop -> received megaco request: ~n ~p" + "~n Mid: ~p" + "~n From: ~p", + [Request, Mid, From]), + {Reply, S1} = handle_megaco_request(S, Request), + d("loop -> send (megaco callback) request reply: ~n~p", [Reply]), + From ! {reply, Reply, self()}, + loop(S1); + + + {'EXIT', Pid, Reason} -> + i("loop -> received exit signal from ~p: ~n~p", [Pid, Reason]), + S1 = handle_exit(S, Pid, Reason), + loop(S1); + + + Invalid -> + error_msg("received invalid request: ~n~p", [Invalid]), + loop(S) + + end. + + +handle_encode_ar_first(#mg{encode_ar_first = Old} = MG, New) + when (New =:= true) orelse (New =:= false) -> + {{ok, Old}, MG#mg{encode_ar_first = New}}; +handle_encode_ar_first(MG, New) -> + {{error, {invalid_value, New}}, MG}. + + +%% +%% Stop user +%% +do_stop(Mid) -> + d("do_stop -> stopping user ~p", [Mid]), + Disco = fun close_conn/1, + lists:map(Disco, megaco:user_info(Mid, connections)), + megaco:stop_user(Mid). + +close_conn(CH) -> + d("do_stop -> closing connection ~p", [CH]), + Reason = {stopped_by_user,self()}, + 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), + megaco:cancel(CH, Reason), + case SendMod of + megaco_tcp -> megaco_tcp:close(SendHandle); + megaco_udp -> megaco_udp:close(SendHandle); + SendMod -> exit(Pid, Reason) + end. + + + +%% +%% Get statistics +%% +do_get_statistics(Mid) -> + case megaco:user_info(Mid, connections) of + [CH] -> + do_get_conn_statistics(CH); + [] -> + [] + end. + +do_get_conn_statistics(CH) -> + {ok, Gen} = megaco:get_stats(), + %% Pid = megaco:conn_info(CH, control_pid), + SendMod = megaco:conn_info(CH, send_mod), + SendHandle = megaco:conn_info(CH, send_handle), + {ok, Trans} = + case SendMod of + megaco_tcp -> megaco_tcp:get_stats(SendHandle); + megaco_udp -> megaco_udp:get_stats(SendHandle) + end, + [{gen, Gen}, {trans, Trans}]. + + +%% +%% reset user stats +%% +do_reset_stats(Mid) -> + %% We only have one connection + [CH] = megaco:user_info(Mid, connections), + do_reset_stats1(CH). + +do_reset_stats1(CH) -> + megaco:reset_stats(), + case (catch megaco:conn_info(CH, send_mod)) of + {error, Reason} -> + error_msg("unexpected result when retrieving send module for " + "own connection ~p: ~p. " + "~nexiting...", [CH, Reason]), + exit({invalid_connection, CH, Reason}); + {'EXIT', Reason} -> + error_msg("exit signal when retrieving send module for " + "own connection ~p: ~p. " + "~nexiting...", [CH, Reason]), + exit({invalid_connection, CH, Reason}); + SendMod when is_atom(SendMod) -> + SendMod:reset_stats() + end. + + +%% +%% Get user info for user +%% +do_get_user_info(Mid, all = Tag) -> + case (catch megaco:user_info(Mid, Tag)) of + L when is_list(L) -> + lists:sort(L); + Else -> + Else + end; +do_get_user_info(Mid, Tag) -> + (catch megaco:user_info(Mid, Tag)). + + +%% +%% Update user info for user +%% +do_update_user_info(Mid, Tag, Val) -> + (catch megaco:update_user_info(Mid, Tag, Val)). + + +%% +%% Get conn info +%% +do_get_conn_info(CH, all = Tag) when is_record(CH, megaco_conn_handle) -> + case (catch megaco:conn_info(CH, Tag)) of + L when is_list(L) -> + lists:sort(L); + Else -> + Else + end; +do_get_conn_info(CH, Tag) when is_record(CH, megaco_conn_handle) -> + (catch megaco:conn_info(CH, Tag)); +do_get_conn_info(Mid, Tag) -> + case megaco:user_info(Mid, connections) of + [CH|_] -> + do_get_conn_info(CH, Tag); + [] -> + [] + end. + +%% +%% Update conn info for user +%% +do_update_conn_info(Mid, Tag, Val) -> + %% We only have one connection + [CH] = megaco:user_info(Mid, connections), + (catch megaco:update_conn_info(CH, Tag, Val)). + + + + +%% +%% Perform service change +%% +do_service_change(#mg{mid = Mid, + state = initiated, + encode_ar_first = EAF} = MG) -> + %% We only have one connection + d("do service change for ~p", [Mid]), + [CH] = megaco:user_info(Mid, connections), + Method = restart, + Reason = ?megaco_cold_boot, + case do_service_change(CH, Method, EAF, Reason) of + ok -> + {ok, MG#mg{state = connecting}}; + Error -> + d("service change for ~p failed: ~n~p", [Mid, Error]), + Error + end; +do_service_change(#mg{state = State} = MG) -> + {{error, {invalid_state, State}}, MG}. + +do_service_change(ConnHandle, Method, EAF, Reason) -> + d("sending service change using:" + "~n ConnHandle: ~p" + "~n Method: ~p" + "~n EAF: ~p" + "~n Reason: ~p", [ConnHandle, Method, EAF, Reason]), + SCP = cre_serviceChangeParm(Method, [Reason]), + TermId = [?megaco_root_termination_id], + SCR = cre_serviceChangeReq(TermId, SCP), + CR = cre_commandReq({serviceChangeReq, SCR}), + AR = cre_actionReq(?megaco_null_context_id,[CR]), + send_async(EAF, ConnHandle, [AR], []). + + +%% Make a sync call +do_handle_notify_request(#mg{mid = Mid, + group_size = N, + encode_ar_first = EAF, + state = connected} = MG) -> + d("do_handle_notify_request -> entry"), + [CH] = megaco:user_info(Mid, connections), + Pid = start_notify_request_handler(EAF, CH, N), + {ok, MG#mg{req_handler = Pid}}; +do_handle_notify_request(#mg{state = State} = MG) -> + d("do_handle_notify_request -> entry with" + "~n State: ~p", [State]), + {{error, {invalid_state, State}}, MG}. + + + +%% +%% Cancel requests +%% +do_cancel_requests(Mid, Reason) -> + [CH] = megaco:user_info(Mid, connections), + megaco:cancel(CH, Reason). + + +%% +%% Apply multi load +%% +start_loaders(#mg{mid = Mid, encode_ar_first = EAF} = MG, NumLoaders, Times) -> + [CH] = megaco:user_info(Mid, connections), + Env = get(), + Loaders = start_loaders1(NumLoaders, [], [Env, EAF, Times, CH]), + d("start_loaders -> Loaders: ~n~w", [Loaders]), + MG#mg{mload_info = {Loaders, 0, 0}}. + +start_loaders1(0, Acc, _) -> + Acc; +start_loaders1(N, Acc, Args) -> + Pid = spawn_link(?MODULE, loader_main, Args), + start_loaders1(N-1, [Pid|Acc], Args). + +loader_main(Env, EAF, N, CH) -> + lists:foreach(fun({Tag,Val}) -> put(Tag,Val) end, Env), + loader_main(EAF, N, CH). + +loader_main(_EAF, 0, _) -> + d("loader_main -> done"), + exit(loader_done); +loader_main(EAF, N, CH) -> + d("loader_main -> entry with: ~w", [N]), + {Act, _} = make_notify_request(), + _Res = send_sync(EAF, CH, Act, []), + loader_main(EAF, N-1, CH). + + + +handle_exit(#mg{parent = Pid}, Pid, Reason) -> + error_msg("received exit from the parent:" + "~n ~p", [Reason]), + exit({parent_terminated, Reason}); + +handle_exit(#mg{parent = Parent, req_handler = Pid} = MG, Pid, Reason) -> + error_msg("received unexpected exit from the request handler:" + "~n ~p", [Reason]), + server_reply(Parent, notify_request_reply, + {error, {request_handler_exit, Reason}}), + MG#mg{req_handler = undefined}; + +handle_exit(#mg{parent = Parent, mload_info = {Loaders0, Ok, Err}} = MG, + Pid, loader_done) -> + d("handle_exit(loader_done) -> entry when" + "~n Loaders0: ~p" + "~n Ok: ~p" + "~n Err: ~p", [Loaders0, Ok, Err]), + Loaders = lists:delete(Pid, Loaders0), + LoadInfo = + case Loaders of + [] -> + d("handle_exit -> multi load done", []), + server_reply(Parent, apply_multi_load_ack, {ok, Ok+1, Err}), + undefined; + _ -> + {Loaders, Ok+1, Err} + end, + MG#mg{mload_info = LoadInfo}; + + +handle_exit(#mg{parent = Parent, mload_info = {Loaders, Ok, Err}} = MG, + Pid, Reason) + when length(Loaders) > 0 -> + d("handle_exit -> entry when" + "~n Reason: ~p" + "~n Loaders: ~p" + "~n Ok: ~p" + "~n Err: ~p", [Reason, Loaders, Ok, Err]), + case lists:delete(Pid, Loaders) of + [] -> + %% since we cannot be empty prior the delete, + %% the last one exited... + server_reply(Parent, apply_multi_load, {ok, Ok, Err+1}), + MG#mg{mload_info = undefined}; + Loaders -> + %% Could not be this MG, so go on to the next + error_msg("received unexpected exit signal from ~p:~n~p", + [Pid, Reason]); + Loaders1 -> + %% Not empty, but we removed one + MG#mg{mload_info = {Loaders1,Ok,Err+1}} + end; +handle_exit(_MG, Pid, Reason) -> + error_msg("received unexpected exit signal from ~p:~n~p", + [Pid, Reason]). + + +parse_receive_info(RI, RH) -> + d("parse_receive_info -> get encoding module"), + EM = get_encoding_module(RI), + d("parse_receive_info -> get encoding config"), + EC = get_encoding_config(RI, EM), + d("parse_receive_info -> get transport module"), + TM = get_transport_module(RI), + d("parse_receive_info -> get transport port"), + TP = get_transport_port(RI), + d("parse_receive_info -> get transport host"), + TH = get_transport_host(RI), + d("parse_receive_info -> get transport opts"), + TO = get_transport_opts(RI), + RH1 = RH#megaco_receive_handle{send_mod = TM, + encoding_mod = EM, + encoding_config = EC}, + {TP, TH, RH1, TO}. + + +start_transport(MgcPort, MgcHost, + #megaco_receive_handle{send_mod = megaco_tcp} = RH, TO) -> + start_tcp(MgcPort, MgcHost, RH, TO); +start_transport(MgcPort, MgcHost, + #megaco_receive_handle{send_mod = megaco_udp} = RH, TO) -> + start_udp(MgcPort, MgcHost, RH, TO); +start_transport(_, _, #megaco_receive_handle{send_mod = Mod}, _TO) -> + throw({error, {bad_send_mod, Mod}}). + + +start_tcp(MgcPort, MgcHost, RH, TO) -> + d("start tcp transport: " + "~n MGC Port: ~p" + "~n MGC Host: ~p" + "~n Receive handle: ~p" + "~n Transport options: ~p", [MgcPort, MgcHost, RH, TO]), + case megaco_tcp:start_transport() of + {ok, Sup} -> + d("tcp transport started: ~p", [Sup]), + start_tcp_connect(TO, RH, MgcPort, MgcHost, Sup); + {error, Reason} -> + throw({error, {megaco_tcp_start_transport, Reason}}) + end. + +start_tcp_connect(TO, RH, Port, Host, Sup) -> + d("try tcp connecting to: ~p:~p", [Host, Port]), + Opts = [{host, Host}, + {port, Port}, + {receive_handle, RH}, + {tcp_options, [{nodelay, true}]}] ++ TO, + try_start_tcp_connect(RH, Sup, Opts, 250, noError). + +try_start_tcp_connect(RH, Sup, Opts, Timeout, Error0) when (Timeout < 5000) -> + Sleep = random(Timeout) + 100, + d("try tcp connect (~p,~p)", [Timeout, Sleep]), + case megaco_tcp:connect(Sup, Opts) of + {ok, SendHandle, ControlPid} -> + d("tcp connected: ~p, ~p", [SendHandle, ControlPid]), + megaco_tcp_connect(RH, SendHandle, ControlPid); + Error1 when Error0 =:= noError -> % Keep the first error + d("failed connecting [1]: ~p", [Error1]), + sleep(Sleep), + try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error1); + Error2 -> + d("failed connecting [2]: ~p", [Error2]), + sleep(Sleep), + try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error0) + end; +try_start_tcp_connect(_RH, Sup, _Opts, _Timeout, Error) -> + megaco_tcp:stop_transport(Sup), + throw({error, {megaco_tcp_connect, Error}}). + +megaco_tcp_connect(RH, SendHandle, ControlPid) -> + PrelMgcMid = preliminary_mid, + d("megaco connect", []), + {ok, CH} = megaco:connect(RH, PrelMgcMid, SendHandle, ControlPid), + d("megaco connected: ~p", [CH]), + {ok, CH}. + +start_udp(MgcPort, MgcHost, RH, TO) -> + d("start udp transport (~p)", [MgcPort]), + case megaco_udp:start_transport() of + {ok, Sup} -> + d("udp transport started: ~p", [Sup]), + Opts = [{port, 0}, {receive_handle, RH}] ++ TO, + d("udp open", []), + case megaco_udp:open(Sup, Opts) of + {ok, Handle, ControlPid} -> + d("udp opened: ~p, ~p", [Handle, ControlPid]), + megaco_udp_connect(MgcPort, MgcHost, + RH, Handle, ControlPid); + {error, Reason} -> + throw({error, {megaco_udp_open, Reason}}) + end; + {error, Reason} -> + throw({error, {megaco_udp_start_transport, Reason}}) + end. + +megaco_udp_connect(MgcPort, MgcHost, RH, Handle, ControlPid) -> + MgcMid = preliminary_mid, + SendHandle = megaco_udp:create_send_handle(Handle, MgcHost, MgcPort), + d("megaco connect", []), + {ok, CH} = megaco:connect(RH, MgcMid, SendHandle, ControlPid), + d("megaco connected: ~p", [CH]), + {ok, CH}. + + +update_load_times(#mg{load_counter = 0} = MG, Times) -> + d("update_load_times(0) -> entry with" + "~n Times: ~p", [Times]), + {ok, MG#mg{load_counter = Times}}; +update_load_times(#mg{load_counter = N}, Times) -> + d("update_load_times(~p) -> entry with" + "~n Times: ~p", [N, Times]), + {error, {already_counting, N}}. + + +do_apply_load(#mg{mid = Mid} = MG) -> + d("do_apply_load -> entry"), + case megaco:user_info(Mid, connections) of + [CH] -> + do_apply_load(MG, CH); + [] -> + i("failed to apply load: no connections for ~p", [Mid]), + MG + end. + +do_apply_load(#mg{parent = Parent, + encode_ar_first = EAF, + call_mode = Mode, + group_size = Sz, + load_counter = N0} = MG, CH) -> + d("do_apply_load -> entry with" + "~n Mode: ~p" + "~n Sz: ~p" + "~n N0: ~p", [Mode, Sz, N0]), + {NofSent, Actions, ReplyData} = make_notify_request(N0, Sz), + d("do_apply_load -> notifications constructed:" + "~n NofSent: ~p" + "~n Actions: ~p" + "~n ReplyData: ~p", [NofSent, Actions, ReplyData]), + N = N0 - NofSent, + case Mode of + sync -> + Result = send_sync(EAF, CH, Actions, []), + d("do_apply_load -> call result when N = ~p: ~n~p", [N,Result]), + case N of + 0 -> + d("do_apply_load -> load complete"), + Parent ! {load_complete, self()}, + MG#mg{call_mode = async, load_counter = 0}; + _ -> + d("do_apply_load -> make another round"), + apply_load_timer(), + MG#mg{call_mode = async, load_counter = N} + end; + async -> + Result = send_async(EAF, CH, Actions, [{reply_data, ReplyData}]), + d("do_apply_load -> cast result:~n ~p", [Result]), + MG#mg{call_mode = sync, + load_counter = N, + reply_counter = NofSent} % Outstanding replies + end. + + +start_notify_request_handler(EAF, CH, N) -> + d("start_notify_request_handler -> entry with" + "~n EAF: ~p" + "~n CH: ~p" + "~n N: ~p", [EAF, CH, N]), + Env = get(), + spawn_link(?MODULE, notify_request_handler_main, [self(), Env, EAF, CH, N]). + +notify_request_handler_main(Parent, Env, EAF, CH, N) -> + F = fun({Tag, Val}) -> put(Tag, Val) end, + lists:foreach(F, Env), + d("notify_request_handler_main -> entry with" + "~n Parent: ~p" + "~n EAF: ~p" + "~n CH: ~p" + "~n N: ~p", [Parent, EAF, CH, N]), + Res = do_notify_request(EAF, CH, N), + d("notify_request_handler_main -> notify complete:" + "~n Res: ~p", [Res]), + Parent ! {notify_request_complete, {ok, Res}, self()}, + unlink(Parent), + exit(normal). + +do_notify_request(_EAF, _CH, N) when N =< 0 -> + d("do_notify_request(~p) -> ignoring", [N]), + ignore; +do_notify_request(EAF, CH, 1) -> + d("do_notify_request(1) -> entry with"), + {Action, _} = make_notify_request(), + send_sync(EAF, CH, Action, []); +do_notify_request(EAF, CH, N) -> + d("do_notify_request(~p) -> entry with", [N]), + {N, Actions, _} = make_notify_request(N,N), + send_sync(EAF, CH, Actions, []). + +make_notify_request(N, Sz) when (N >= Sz) andalso (Sz > 0) -> + {Req, ReplyData} = make_notify_request(N, Sz, [], []), + {Sz, Req, ReplyData}; +make_notify_request(N, _Sz) when N > 0 -> + {Req, ReplyData} = make_notify_request(N, N, [], []), + {N, Req, ReplyData}. + + + +make_notify_request(_Offset, 0, Actions, ReplyDatas) -> + {lists:reverse(Actions), lists:reverse(ReplyDatas)}; +make_notify_request(Offset, N, Actions, ReplyDatas) when N > 0 -> + TimeStamp = cre_timeNotation(), + Event = cre_observedEvent("al/of", TimeStamp), + Desc = cre_observedEventsDesc(2000 + N, [Event]), + TidNum = 2#10000000 + Offset - N, + NotifyReq = cre_notifyReq([#megaco_term_id{id = tid(TidNum)}],Desc), + CmdReq = cre_commandReq({notifyReq, NotifyReq}), + ActReq = cre_actionReq(?megaco_null_context_id, [CmdReq]), + make_notify_request(Offset, N-1, [[ActReq]|Actions], [Desc|ReplyDatas]). + +make_notify_request() -> + TimeStamp = cre_timeNotation("19990729", "22000000"), + Event = cre_observedEvent("al/of", TimeStamp), + Desc1 = cre_observedEventsDesc(2221, [Event]), + Desc2 = cre_observedEventsDesc(2222, [Event]), + Desc3 = cre_observedEventsDesc(2223, [Event]), + Desc4 = cre_observedEventsDesc(2224, [Event]), + NotifyReq1 = cre_notifyReq([#megaco_term_id{id = ?A4444}], Desc1), + NotifyReq2 = cre_notifyReq([#megaco_term_id{id = ?A4445}], Desc2), + CmdReq1 = cre_commandReq({notifyReq, NotifyReq1}), + CmdReq2 = cre_commandReq({notifyReq, NotifyReq2}), + ActReq = cre_actionReq(?megaco_null_context_id, [CmdReq1,CmdReq2]), + {[ActReq], [Desc3,Desc4]}. + + +cre_actionReq(Cid, Cmds) -> + #'ActionRequest'{contextId = Cid, + commandRequests = Cmds}. + +cre_commandReq(Cmd) -> + #'CommandRequest'{command = Cmd}. + +cre_serviceChangeReq(TermId, Parms) -> + #'ServiceChangeRequest'{terminationID = TermId, + serviceChangeParms = Parms}. + +cre_serviceChangeParm(Method, Reason) -> + #'ServiceChangeParm'{serviceChangeMethod = Method, + serviceChangeReason = Reason}. + +cre_notifyReq(Tid, EvsDesc) -> + #'NotifyRequest'{terminationID = Tid, observedEventsDescriptor = EvsDesc}. + +% cre_notifyRep(Tid) -> +% #'NotifyReply'{terminationID = [Tid]}. + +% cre_notifyRep(Tid,Err) -> +% #'NotifyReply'{terminationID = [Tid], errorDescriptor = Err}. + +cre_observedEventsDesc(Id, EvList) -> + #'ObservedEventsDescriptor'{requestId = Id, observedEventLst = EvList}. + +cre_observedEvent(Name, Not) -> + #'ObservedEvent'{eventName = Name, timeNotation = Not}. + +cre_timeNotation() -> + {{Year,Month,Day},{Hour,Min,Sec}} = calendar:universal_time(), + D = lists:flatten(io_lib:format("~4..0w~2..0w~2..0w", [Year,Month,Day])), + T = lists:flatten(io_lib:format("~2..0w~2..0w~4..0w", [Hour,Min,Sec])), + cre_timeNotation(D, T). + +cre_timeNotation(D,T) -> + #'TimeNotation'{date = D, time = T}. + +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). + + +%% ----------------------- +%% Handle megaco callbacks +%% + +handle_megaco_request(#mg{state = connecting} = MG, + {handle_connect, _CH, _PV}) -> + d("handle_megaco_request(handle_connect,connecting) -> entry"), + {ok, MG}; +handle_megaco_request(#mg{state = S} = MG, + {handle_connect, _CH, _PV}) -> + d("handle_megaco_request(handle_connect) -> entry"), + Desc = + lists:flatten(io_lib:format("not ready for connect in state ~p", [S])), + ED = cre_error_descr(?megaco_internal_gateway_error, Desc), + {{discard_ack, ED}, MG}; + +handle_megaco_request(#mg{req_handler = Pid} = MG, + {handle_disconnect, _CH, _PV, R}) + when is_pid(Pid) -> + d("handle_megaco_request(handle_disconnect) -> entry with" + "~n Pid: ~p", [Pid]), + Error = {error, {disconnected, R}}, + self() ! {notify_request_complete, Error, Pid}, + unlink(Pid), + exit(Pid, kill), + {ok, MG#mg{req_handler = undefined, state = initiated}}; +handle_megaco_request(MG, {handle_disconnect, _CH, _PV, _R}) -> + d("handle_megaco_request(handle_disconnect) -> entry"), + {ok, MG#mg{state = initiated}}; + +handle_megaco_request(MG, + {handle_syntax_error, _RH, _PV, _ED}) -> + {reply, MG}; + +handle_megaco_request(#mg{req_handler = Pid} = MG, + {handle_message_error, CH, PV, ED}) + when is_pid(Pid) -> + d("handle_megaco_request(handle_message_error) -> entry with" + "~n Pid: ~p" + "~n CH: ~p" + "~n PV: ~p" + "~n ED: ~p", [Pid, CH, PV, ED]), + self() ! {notify_request_complete, ED, Pid}, + unlink(Pid), + exit(Pid, kill), + {no_reply, MG#mg{req_handler = undefined}}; +handle_megaco_request(MG, {handle_message_error, CH, PV, ED}) -> + d("handle_megaco_request(handle_message_error) -> entry with" + "~n CH: ~p" + "~n PV: ~p" + "~n ED: ~p", [CH, PV, ED]), + {no_reply, MG}; + +handle_megaco_request(MG, {handle_trans_request, _CH, _PV, _AR}) -> + ED = cre_error_descr(?megaco_not_implemented, + "Transaction requests not handled"), + {{discard_ack, ED}, MG}; + +handle_megaco_request(MG, + {handle_trans_long_request, _CH, _PV, _RD}) -> + ED = cre_error_descr(?megaco_not_implemented, + "Long transaction requests not handled"), + {{discard_ack, ED}, MG}; + +handle_megaco_request(#mg{rep_info = P} = MG, + {handle_trans_reply, CH, PV, AR, RD}) when is_pid(P) -> + P ! {rep_received, self(), AR}, + do_handle_trans_reply(MG, CH, PV, AR, RD); +handle_megaco_request(MG, {handle_trans_reply, CH, PV, AR, RD}) -> + do_handle_trans_reply(MG, CH, PV, AR, RD); + +handle_megaco_request(#mg{ack_info = P} = MG, + {handle_trans_ack, _CH, _PV, AS, _AD}) when is_pid(P) -> + d("handle_megaco_request(handle_trans_ack,~p) -> entry",[P]), + P ! {ack_received, self(), AS}, + {ok, MG}; +handle_megaco_request(MG, {handle_trans_ack, _CH, _PV, _AS, _AD}) -> + d("handle_megaco_request(handle_trans_ack) -> entry"), + {ok, MG}. + +do_handle_trans_reply(#mg{parent = Parent, state = connecting} = MG, + CH, _PV, {ok, Rep}, _RD) -> + d("do_handle_trans_reply(connecting) -> entry with" + "~n CH: ~p" + "~n Rep: ~p", [CH, Rep]), + server_reply(Parent, service_change_reply, ok), + {ok, MG#mg{state = connected}}; +do_handle_trans_reply(#mg{parent = Parent, load_counter = 0} = MG, + CH, _PV, {ok, Rep}, _RD) -> + d("do_handle_trans_reply(load_counter = 0) -> entry with" + "~n CH: ~p" + "~n Rep: ~p", [CH, Rep, Parent]), + handle_trans_reply_verify_act(Rep), + server_reply(Parent, load_complete, ok), + {ok, MG#mg{reply_counter = 0}}; +do_handle_trans_reply(#mg{reply_counter = 0} = MG, + CH, _PV, {ok, Rep}, _RD) -> + d("do_handle_trans_reply(reply_counter = 0) -> entry with" + "~n CH: ~p" + "~n Rep: ~p", [CH, Rep]), + handle_trans_reply_verify_act(Rep), + apply_load_timer(), + {ok, MG}; +do_handle_trans_reply(#mg{reply_counter = N} = MG, + CH, _PV, {ok, Rep}, _RD) -> + d("do_handle_trans_reply(reply_counter = ~p) -> entry with" + "~n CH: ~p" + "~n Rep: ~p", [N, CH, Rep]), + handle_trans_reply_verify_act(Rep), + apply_load_timer(), + {ok, MG#mg{reply_counter = N-1}}; +do_handle_trans_reply(MG, _CH, _PV, {error, ED}, _RD) -> + i("unexpected error transaction: ~p", [ED]), + {ok, MG}. + + +handle_trans_reply_verify_act([]) -> + ok; +handle_trans_reply_verify_act([#'ActionReply'{commandReply = Rep}|Reps]) -> + handle_trans_reply_verify_cmd(Rep), + handle_trans_reply_verify_act(Reps); +handle_trans_reply_verify_act([Rep|Reps]) -> + i("received 'propably' unexpected reply: ~n~p", [Rep]), + handle_trans_reply_verify_act(Reps). + +handle_trans_reply_verify_cmd([]) -> + ok; +handle_trans_reply_verify_cmd([Cmd|Cmds]) -> + case Cmd of + {notifyReply, #'NotifyReply'{terminationID = [Tid]}} -> + d("received expected notification reply from ~n ~p", [Tid]); + Else -> + i("received unexpected notification reply ~n~p", [Else]) + end, + handle_trans_reply_verify_cmd(Cmds). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +notify_started(Parent) -> + Parent ! {started, self()}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% The megaco user callback interface + +handle_connect(CH, PV, Pid, Mid) -> + case CH#megaco_conn_handle.remote_mid of + preliminary_mid -> + %% Avoids deadlock + ok; + _ -> + Reply = request(Pid, {handle_connect, CH, PV}, Mid), + Reply + end. + +handle_disconnect(_CH, _PV, + {user_disconnect, {stopped_by_user, Pid}}, + Pid, _Mid) -> + ok; +handle_disconnect(CH, PV, R, Pid, Mid) -> + request(Pid, {handle_disconnect, CH, PV, R}, Mid). + +handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) -> + Req = {handle_syntax_error, ReceiveHandle, ProtocolVersion, + ErrorDescriptor}, + request(Pid, Req, Mid). + +handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) -> + Req = {handle_message_error, ConnHandle, ProtocolVersion, ErrorDescriptor}, + request(Pid, Req, Mid). + +handle_trans_request(CH, PV, AR, Pid, Mid) -> + Reply = request(Pid, {handle_trans_request, CH, PV, AR}, Mid), + Reply. + +handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Pid, Mid) -> + Req = {handle_trans_long_request, ConnHandle, ProtocolVersion, ReqData}, + request(Pid, Req, Mid). + +handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData, Pid, Mid) -> + Req = {handle_trans_reply, ConnHandle, ProtocolVersion, + ActualReply, ReplyData}, + request(Pid, Req, Mid). + +handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Pid, Mid) -> + Req = {handle_trans_ack, ConnHandle, ProtocolVersion, AckStatus, AckData}, + request(Pid, Req, Mid). + + +request(Pid, Request, Mid) -> + Pid ! {request, Request, Mid, self()}, + receive + {reply, {delay, To, ED}, Pid} -> + sleep(To), + {discard_ack, ED}; + {reply, Reply, Pid} -> + Reply + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +send_async(true, CH, Actions, Opts) -> + d("send_async(true) -> encode actions first"), + case megaco:encode_actions(CH, Actions, Opts) of + {ok, BinOrBins} -> + d("send_async(true) -> send message"), + megaco:cast(CH, BinOrBins, Opts); + Error -> + d("send_async(true) -> encode failed: ~n~p", [Error]), + Error + end; +send_async(_, CH, Actions, Opts) -> + d("send_async(true) -> send message"), + megaco:cast(CH, Actions, Opts). + +send_sync(true, CH, Actions, Opts) -> + d("send_sync(true) -> encode actions first"), + case megaco:encode_actions(CH, Actions, Opts) of + {ok, BinOrBins} -> + d("send_sync(true) -> send message"), + megaco:call(CH, BinOrBins, Opts); + Error -> + d("send_sync(true) -> encode failed: ~n~p", [Error]), + Error + end; +send_sync(_, CH, Actions, Opts) -> + d("send_sync(false) -> send message"), + megaco:call(CH, Actions, Opts). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sleep(X) -> + receive after X -> ok end. + + +error_msg(F,A) -> error_logger:error_msg("MG: " ++ 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_host(RI) -> + {ok, LocalHost} = inet:gethostname(), + get_conf(host, RI, LocalHost). + +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. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +tid(N) when N >= 0 -> + {Rem1, Val1} = num2str(N), + {Rem2, Val2} = num2str(Rem1), + {0, Val3} = num2str(Rem2), + [Val3, Val2, Val1]. + +num2str(N) when N >= 0 -> + num2str(N, []). + +num2str(Rem, Val) when length(Val) == 8 -> + {Rem, Val}; +num2str(N, Val) -> + D = N div 2, + case N rem 2 of + 1 -> + num2str(D, [$1|Val]); + 0 -> + num2str(D, [$0|Val]) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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) -> + io:format("*** [~s] ~s ~p ~s ***" + "~n " ++ F ++ "~n~n", + [format_timestamp(now()), P, self(), get(sname) | A]); +print(_, _, _, _) -> + ok. + +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). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +random_init() -> + {A,B,C} = now(), + random:seed(A,B,C). + +random() -> + random(50). +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). + + +apply_load_timer() -> + create_timer(random(), apply_load_timeout). + + |