1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2021. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%%----------------------------------------------------------------------
23%% Purpose: Implements an "MGC" used by the test suite
24%%----------------------------------------------------------------------
25-module(megaco_test_mgc).
26
27-export([start/4, start/5, stop/1,
28	 get_stats/2, reset_stats/1,
29	 user_info/1, user_info/2, conn_info/1, conn_info/2,
30	 update_user_info/3, update_conn_info/3,
31	 request_ignore/1,
32	 request_discard/1, request_discard/2,
33	 request_pending/1, request_pending/2, request_pending_ignore/1,
34	 request_handle/1, request_handle/2,
35	 request_handle_pending/1, request_handle_pending/2,
36	 request_handle_sloppy/1, request_handle_sloppy/2,
37	 ack_info/2, abort_info/2, req_info/2,
38	 disconnect/2,
39	 verbosity/2]).
40-export([mgc/3]).
41
42%% Megaco callback api
43-export([
44	 handle_connect/3,
45	 handle_disconnect/4,
46	 handle_syntax_error/4,
47	 handle_message_error/4,
48	 handle_trans_request/4,
49	 handle_trans_long_request/4,
50	 handle_trans_reply/5,
51	 handle_trans_ack/5,
52         handle_unexpected_trans/4,
53         handle_trans_request_abort/5
54	]).
55
56-include("megaco_test_lib.hrl").
57-include_lib("megaco/include/megaco.hrl").
58-include_lib("megaco/include/megaco_message_v1.hrl").
59
60-define(NO_ERROR, 'no error').
61
62-define(A4444, ["11111111", "00000000", "00000000"]).
63-define(A4445, ["11111111", "00000000", "11111111"]).
64-define(A5555, ["11111111", "11111111", "00000000"]).
65-define(A5556, ["11111111", "11111111", "11111111"]).
66
67-define(valid_actions,
68	[ignore, pending, pending_ignore, discard_ack, handle_ack, handle_pending_ack, handle_sloppy_ack]).
69
70-record(mgc, {parent       = undefined,
71              inet_backend = default,
72	      tcp_sup      = undefined,
73	      udp_sup      = undefined,
74	      req_action   = discard_ack,
75	      req_timeout  = 0,
76	      mid          = undefined,
77	      ack_info     = undefined,
78	      abort_info   = undefined,
79	      req_info     = undefined,
80	      mg           = [],
81	      dsi_timer,
82              evs          = []}).
83
84-define(EVS_MAX, 10).
85
86
87%%% ------------------------------------------------------------------
88
89start(Node, Mid, ET, Verbosity) ->
90    %% Conf = [{megaco_trace, io}],
91    %% Conf = [{megaco_trace, "megaco-mgc.trace"}],
92    Conf = [{megaco_trace, false}],
93    start(Node, Mid, ET, Conf, Verbosity).
94
95start(Node, Mid, ET, Conf, Verbosity) ->
96    d("start mgc[~p]: ~p"
97      "~n      ET:   ~p"
98      "~n      Conf: ~p", [Node, Mid, ET, Conf]),
99    RI          = {receive_info, mk_recv_info(ET)},
100    Config      = [{local_mid, Mid}, RI] ++ Conf,
101    Self        = self(),
102    true        = erlang:monitor_node(Node, true),
103    MGC         = fun() -> mgc(Self, Verbosity, Config) end,
104    {Pid, MRef} = spawn_monitor(Node, MGC),
105    NodePing    = net_adm:ping(Node),
106    ProcInfo    = (catch proc_info(Pid)),
107    i("start mgc[~p] -> ~p"
108      "~n      self():       ~p"
109      "~n      node():       ~p"
110      "~n      Node ping:    ~p"
111      "~n      Loader:       ~p"
112      "~n      Monitor ref:  ~p"
113      "~n      Process info: ~p",
114      [Node, Pid,
115       Self, node(), NodePing, Pid, MRef, ProcInfo]),
116    await_started(Node, Pid, MRef).
117
118proc_info(Pid) ->
119    rpc:call(node(Pid), erlang, process_info, [Pid]).
120
121mk_recv_info(ET) ->
122    mk_recv_info(ET, []).
123
124mk_recv_info([], Acc) ->
125    Acc;
126mk_recv_info([{Encoding, Transport}|ET], Acc)
127  when is_atom(Encoding) andalso is_atom(Transport) ->
128    {EMod, Port} = select_encoding(Encoding),
129    TMod         = select_transport(Transport),
130    RI = [{encoding_module,  EMod},
131	  {encoding_config,  []},
132	  {transport_module, TMod},
133	  {port,             Port}],
134    mk_recv_info(ET, [RI|Acc]);
135mk_recv_info([{Encoding, Transport, TO}|ET], Acc)
136  when is_atom(Encoding) andalso is_atom(Transport) andalso is_list(TO) ->
137    {EMod, Port} = select_encoding(Encoding),
138    TMod         = select_transport(Transport),
139    RI = [{encoding_module,  EMod},
140	  {encoding_config,  []},
141	  {transport_module, TMod},
142	  {port,             Port},
143          {transport_opts,   TO}],
144    mk_recv_info(ET, [RI|Acc]);
145mk_recv_info([{Encoding, EC, Transport}|ET], Acc)
146  when is_atom(Encoding) andalso is_list(EC) andalso is_atom(Transport) ->
147    {EMod, Port} = select_encoding(Encoding),
148    TMod         = select_transport(Transport),
149    RI = [{encoding_module,  EMod},
150	  {encoding_config,  EC},
151	  {transport_module, TMod},
152	  {port,             Port}],
153    mk_recv_info(ET, [RI|Acc]);
154mk_recv_info([ET|_], _) ->
155    throw({error, {invalid_encoding_transport, ET}}).
156
157select_encoding(text) ->
158    {megaco_pretty_text_encoder, 2944};
159select_encoding(pretty_text) ->
160    {megaco_pretty_text_encoder, 2944};
161select_encoding(compact_text) ->
162    {megaco_compact_text_encoder, 2944};
163select_encoding(binary) ->
164    {megaco_ber_encoder, 2945};
165select_encoding(erl_dist) ->
166    {megaco_erl_dist_encoder, 2946};
167select_encoding(Encoding) ->
168    throw({error, {invalid_encoding, Encoding}}).
169
170select_transport(tcp) ->
171    megaco_tcp;
172select_transport(udp) ->
173    megaco_udp;
174select_transport(Transport) ->
175    throw({error, {invalid_transport, Transport}}).
176
177
178await_started(Node, Pid, MRef) ->
179    receive
180	{started, Pid} ->
181	    i("await_started ~p: ok", [Pid]),
182	    true = erlang:monitor_node(Node, false),
183	    erlang:demonitor(MRef),
184	    {ok, Pid};
185
186	{nodedown, Node} ->
187	    i("await_started ~p - received node down", [Pid]),
188	    exit({node_down, Node});
189
190        {'DOWN', MRef, process, Pid,
191         {failed_starting_tcp_listen,
192          {could_not_start_listener, {gen_tcp_listen, eaddrinuse}}}} ->
193	    e("await_started ~p: address already in use", [Pid]),
194	    true = erlang:monitor_node(Node, false),
195	    ?SKIP(eaddrinuse);
196
197        {'DOWN', MRef, process, Pid, Reason} ->
198	    e("await_started ~p: received exit signal: ~p", [Pid, Reason]),
199	    true = erlang:monitor_node(Node, false),
200	    exit({failed_starting, Pid, Reason})
201
202    after 10000 ->
203	    NodePing = net_adm:ping(Node),
204	    ProcInfo = (catch proc_info(Pid)),
205	    FlushQ = megaco_test_lib:flush(),
206	    e("await_started ~p - timeout: "
207	      "~n      net_adm:ping(~p):     ~p"
208	      "~n      Process info:         ~p"
209	      "~n      Messages in my queue: ~p",
210	      [Pid, Node, NodePing, ProcInfo, FlushQ]),
211	    true = erlang:monitor_node(Node, false),
212	    exit({error, timeout})
213    end.
214
215
216stop(Pid) ->
217    server_request(Pid, stop, stopped).
218
219get_stats(Pid, No) ->
220    server_request(Pid, {statistics, No}, {statistics_reply, No}).
221
222reset_stats(Pid) ->
223    server_request(Pid, reset_stats, reset_stats_ack).
224
225user_info(Pid) ->
226    server_request(Pid, {user_info, all}, user_info_ack).
227
228user_info(Pid, Tag) ->
229    server_request(Pid, {user_info, Tag}, user_info_ack).
230
231conn_info(Pid) ->
232    server_request(Pid, {conn_info, all}, conn_info_ack).
233
234conn_info(Pid, Tag) ->
235    server_request(Pid, {conn_info, Tag}, conn_info_ack).
236
237update_user_info(Pid, Tag, Val) ->
238    server_request(Pid, {update_user_info, Tag, Val}, update_user_info_ack).
239
240update_conn_info(Pid, Tag, Val) ->
241    server_request(Pid, {update_conn_info, Tag, Val}, update_conn_info_ack).
242
243disconnect(Pid, Reason) ->
244    server_request(Pid, {disconnect, Reason}, disconnected).
245
246ack_info(Pid, InfoPid) ->
247    Pid ! {ack_info, InfoPid, self()}.
248
249abort_info(Pid, InfoPid) ->
250    Pid ! {abort_info, InfoPid, self()}.
251
252req_info(Pid, InfoPid) ->
253    Pid ! {req_info, InfoPid, self()}.
254
255verbosity(Pid, V) ->
256    Pid ! {verbosity, V, self()}.
257
258request_ignore(Pid) ->
259    request_action(Pid, {ignore, infinity}).
260
261request_pending_ignore(Pid) ->
262    request_action(Pid, {pending_ignore, infinity}).
263
264request_discard(Pid) ->
265    request_discard(Pid,0).
266
267request_discard(Pid, To) ->
268    request_action(Pid, {discard_ack, To}).
269
270request_pending(Pid) ->
271    request_pending(Pid, 5000).
272
273request_pending(Pid, To) ->
274    request_action(Pid, {pending, To}).
275
276request_handle(Pid) ->
277    request_handle(Pid, 0).
278
279request_handle(Pid, To) ->
280    request_action(Pid, {handle_ack, To}).
281
282request_handle_pending(Pid) ->
283    request_handle_pending(Pid, 0).
284
285request_handle_pending(Pid, To) ->
286    request_action(Pid, {handle_pending_ack, To}).
287
288request_handle_sloppy(Pid) ->
289    request_handle_sloppy(Pid, 0).
290
291request_handle_sloppy(Pid, To) ->
292    request_action(Pid, {handle_sloppy_ack, To}).
293
294request_action(Pid, Action) ->
295    server_request(Pid, request_action, Action, request_action_ack).
296
297
298server_request(Pid, Req, ReplyTag) ->
299    Pid ! {Req, self()},
300    receive
301	{ReplyTag, Reply, Pid} ->
302	    Reply;
303	{'EXIT', Pid, Reason} ->
304	    exit({failed, Req, Pid, Reason})
305    after 10000 ->
306	    exit({timeout, Req, Pid})
307    end.
308
309server_request(Pid, Req, ReqData, ReplyTag) ->
310    Pid ! {Req, ReqData, self()},
311    receive
312	{ReplyTag, Reply, Pid} ->
313	    Reply;
314	{'EXIT', Pid, Reason} ->
315	    exit({failed, Req, Pid, Reason})
316    after 10000 ->
317	    exit({timeout, Req, Pid})
318    end.
319
320
321server_reply(Pid, ReplyTag, Reply) ->
322    Pid ! {ReplyTag, Reply, self()}.
323
324
325%%% ------------------------------------------------------------------
326
327
328mgc(Parent, Verbosity, Config) ->
329    process_flag(trap_exit, true),
330    put(verbosity, Verbosity),
331    put(sname,   "MGC"),
332    i("mgc -> starting"),
333    case (catch init(Config)) of
334	{error, Reason} ->
335	    exit(Reason);
336	{IB, Mid, TcpSup, UdpSup, DSITimer} ->
337	    notify_started(Parent),
338	    S = #mgc{parent       = Parent,
339		     tcp_sup      = TcpSup,
340		     udp_sup      = UdpSup,
341		     mid          = Mid,
342		     dsi_timer    = DSITimer,
343                     inet_backend = IB},
344	    i("mgc -> started"),
345	    display_system_info("at start "),
346	    loop(evs(S, started))
347    end.
348
349init(Config) ->
350    d("init -> entry"),
351    random_init(),
352
353    IB  = get_conf(inet_backend, Config, default),
354    Mid = get_conf(local_mid,    Config),
355    RI  = get_conf(receive_info, Config),
356
357    d("init -> "
358      "~n      Inet Backend: ~p"
359      "~n      Mid:          ~p"
360      "~n      RI:           ~p", [IB, Mid, RI]),
361    DSITimer =
362	case get_conf(display_system_info, Config, undefined) of
363	    Time when is_integer(Time) ->
364		d("init -> creating display system info timer"),
365		create_timer(Time, display_system_info);
366	    _ ->
367		undefined
368	end,
369    Conf0 = lists:keydelete(display_system_info, 1, Config),
370    Conf1 = lists:keydelete(inet_backend,        1, Conf0),
371
372    d("init -> start megaco"),
373    application:start(megaco),
374
375    d("init -> possibly enable megaco trace"),
376    case lists:keysearch(megaco_trace, 1, Config) of
377	{value, {megaco_trace, true}} ->
378	    megaco:enable_trace(max, io);
379	{value, {megaco_trace, io}} ->
380	    megaco:enable_trace(max, io);
381	{value, {megaco_trace, File}} when is_list(File) ->
382	    megaco:enable_trace(max, File);
383	_ ->
384	    ok
385    end,
386    Conf2 = lists:keydelete(megaco_trace,    1, Conf1),
387
388    d("init -> start megaco user"),
389    Conf3 = lists:keydelete(local_mid,    1, Conf2),
390    Conf4 = lists:keydelete(receive_info, 1, Conf3),
391    ok = megaco:start_user(Mid, Conf4),
392
393    d("init -> update user info (user_mod)"),
394    ok = megaco:update_user_info(Mid, user_mod,  ?MODULE),
395
396    d("init -> update user info (user_args)"),
397    ok = megaco:update_user_info(Mid, user_args, [self()]),
398
399    d("init -> get user info (receive_handle)"),
400    RH = megaco:user_info(Mid,receive_handle),
401    d("init -> parse receive info"),
402    Transports = parse_receive_info(RI, RH),
403
404    d("init -> start transports"),
405    {Tcp, Udp} = start_transports(IB, Transports),
406    {IB, Mid, Tcp, Udp, DSITimer}.
407
408loop(S) ->
409    d("loop -> await request"),
410    receive
411	{display_system_info, Time} ->
412	    display_system_info(S#mgc.mid),
413	    NewTimer = create_timer(Time, display_system_info),
414	    loop(evs(S#mgc{dsi_timer = NewTimer}, {dsi, Time}));
415
416	{stop, Parent} when S#mgc.parent =:= Parent ->
417	    i("loop -> stopping"),
418	    display_system_info(S#mgc.mid, "at finish "),
419	    cancel_timer(S#mgc.dsi_timer),
420  	    Mid = S#mgc.mid,
421	    (catch close_conns(Mid)),
422	    megaco:stop_user(Mid),
423	    application:stop(megaco),
424	    i("loop -> stopped"),
425	    server_reply(Parent, stopped, ok),
426	    done(evs(S, stop), normal);
427
428	{{disconnect, Reason}, Parent} when S#mgc.parent == Parent ->
429	    i("loop -> disconnecting"),
430  	    Mid = S#mgc.mid,
431	    [Conn|_] = megaco:user_info(Mid, connections),
432	    Res = megaco:disconnect(Conn, {self(), Reason}),
433	    server_reply(Parent, disconnected, Res),
434	    loop(evs(S, {disconnect, Reason}));
435
436	{{update_user_info, Tag, Val}, Parent} when S#mgc.parent == Parent ->
437	    i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]),
438	    Res = (catch megaco:update_user_info(S#mgc.mid, Tag, Val)),
439	    d("loop -> Res: ~p", [Res]),
440	    server_reply(Parent, update_user_info_ack, Res),
441	    loop(evs(S, {uui, {Tag, Val}}));
442
443        {{user_info, Tag}, Parent} when S#mgc.parent == Parent ->
444            i("loop -> got user_info request for ~w", [Tag]),
445            Res = (catch megaco:user_info(S#mgc.mid, Tag)),
446            d("loop -> Res: ~p", [Res]),
447            server_reply(Parent, user_info_ack, Res),
448            loop(evs(S, {ui, Tag}));
449
450	{{update_conn_info, Tag, Val}, Parent} when S#mgc.parent == Parent ->
451	    i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]),
452	    Conns = megaco:user_info(S#mgc.mid, connections),
453	    Fun = fun(CH) ->
454			  (catch megaco:update_conn_info(CH, Tag, Val))
455		  end,
456	    Res = lists:map(Fun, Conns),
457	    d("loop -> Res: ~p", [Res]),
458	    server_reply(Parent, update_conn_info_ack, Res),
459            loop(evs(S, {uci, {Tag, Val}}));
460
461	{{conn_info, Tag}, Parent} when S#mgc.parent =:= Parent ->
462	    i("loop -> got conn_info request for ~w", [Tag]),
463	    Conns = megaco:user_info(S#mgc.mid, connections),
464	    Fun = fun(CH) ->
465			  {CH, (catch megaco:conn_info(CH, Tag))}
466		  end,
467	    Res = lists:map(Fun, Conns),
468	    d("loop -> Res: ~p", [Res]),
469	    server_reply(Parent, conn_info_ack, Res),
470	    loop(evs(S, {ci, Tag}));
471
472
473	%%
474        {request_action, {Action, To}, Parent} when S#mgc.parent == Parent ->
475	    i("loop -> got new request_action: ~p:~w", [Action,To]),
476	    {Reply, S1} =
477		case lists:member(Action, ?valid_actions) of
478		    true when To >= 0; To == infinity ->
479			{{ok, S#mgc.req_action},
480			 S#mgc{req_action = Action, req_timeout = To}};
481		    true ->
482			{{error, {invalid_action_timeout, To}}, S};
483		    false ->
484			{{error, {invalid_action, Action}}, S}
485		end,
486	    server_reply(Parent, request_action_ack, Reply),
487            loop(evs(S1, {req_act, {Action, To}}));
488
489
490	%% Reset stats
491	{reset_stats, Parent} when S#mgc.parent == Parent ->
492	    i("loop -> got request to reset stats counters"),
493	    do_reset_stats(S#mgc.mid),
494	    server_reply(Parent, reset_stats_ack, ok),
495	    loop(evs(S, rst_stats));
496
497
498	%% Give me statistics
499	{{statistics, 1}, Parent} when S#mgc.parent == Parent ->
500	    i("loop(stats1) -> got request for statistics 1"),
501	    {ok, Gen} = megaco:get_stats(),
502	    i("loop(stats1) -> gen stats: "
503              "~n      ~p", [Gen]),
504	    GetTrans =
505		fun(CH) ->
506                        i("loop(stats1):GetTrans -> "
507                          "get stats for connection ~p", [CH]),
508			Reason = {statistics, CH},
509			Pid = megaco:conn_info(CH, control_pid),
510                        i("loop(stats1):GetTrans -> control pid: ~p", [Pid]),
511			SendMod = megaco:conn_info(CH, send_mod),
512                        i("loop(stats1):GetTrans -> "
513                          "send module: ~p", [SendMod]),
514			SendHandle = megaco:conn_info(CH, send_handle),
515                        i("loop(stats1):GetTrans -> "
516                          "send handle: ~p", [SendHandle]),
517			{ok, Stats} =
518			    case SendMod of
519				megaco_tcp -> megaco_tcp:get_stats(SendHandle);
520				megaco_udp -> megaco_udp:get_stats(SendHandle);
521				SendMod    -> exit(Pid, Reason)
522			    end,
523                        i("loop(stats1):GetTrans -> stats: "
524                          "~n      ~p", [Stats]),
525			{SendHandle, Stats}
526		end,
527	    Mid   = S#mgc.mid,
528	    Trans = lists:map(GetTrans, megaco:user_info(Mid, connections)),
529	    Reply = {ok, [{gen, Gen}, {trans, Trans}]},
530	    i("loop(stats1) -> send reply"),
531	    server_reply(Parent, {statistics_reply, 1}, Reply),
532	    i("loop(stats1) -> done"),
533	    loop(evs(S, {stats, 1}));
534
535
536	{{statistics, 2}, Parent} when S#mgc.parent == Parent ->
537	    i("loop(stats2) -> got request for statistics 2"),
538	    {ok, Gen} = megaco:get_stats(),
539	    #mgc{tcp_sup = TcpSup, udp_sup = UdpSup} = S,
540	    TcpStats = get_trans_stats(TcpSup, megaco_tcp),
541	    UdpStats = get_trans_stats(UdpSup, megaco_udp),
542	    Reply = {ok, [{gen, Gen}, {trans, [TcpStats, UdpStats]}]},
543	    i("loop(stats2) -> send reply"),
544	    server_reply(Parent, {statistics_reply, 2}, Reply),
545	    i("loop(stats2) -> done"),
546	    loop(evs(S, {stats, 2}));
547
548
549	%% Megaco callback messages
550	{request, Request, From} ->
551	    d("loop(request) -> received megaco request from ~p:"
552              "~n      ~p", [From, Request]),
553	    {Reply, S1} = handle_megaco_request(Request, S),
554	    d("loop(request) -> send reply: ~n~p", [Reply]),
555	    reply(From, Reply),
556	    d("loop(request) -> done"),
557	    loop(evs(S1, {req, Request}));
558
559
560	{ack_info, To, Parent} when S#mgc.parent == Parent ->
561	    i("loop -> received request to inform about received ack's "),
562	    loop(evs(S#mgc{ack_info = To}, {acki, To}));
563
564
565	{abort_info, To, Parent} when S#mgc.parent == Parent ->
566	    i("loop -> received request to inform about received aborts "),
567	    loop(evs(S#mgc{abort_info = To}, {abi, To}));
568
569
570	{req_info, To, Parent} when S#mgc.parent == Parent ->
571	    i("loop -> received request to inform about received req's "),
572	    loop(evs(S#mgc{req_info = To}, {reqi, To}));
573
574
575	{verbosity, V, Parent} when S#mgc.parent == Parent ->
576	    i("loop -> received new verbosity: ~p", [V]),
577	    put(verbosity,V),
578	    loop(evs(S, {verb, V}));
579
580
581	{'EXIT', Pid, Reason} when S#mgc.tcp_sup =:= Pid ->
582	    error_msg("MGC received unexpected exit "
583		      "from TCP transport supervisor (~p):"
584                      "~n   ~p", [Pid, Reason]),
585	    i("loop -> [tcp] exiting"),
586	    display_system_info(S#mgc.mid, "at bad finish (tcp) "),
587	    cancel_timer(S#mgc.dsi_timer),
588  	    Mid = S#mgc.mid,
589	    (catch close_conns(Mid)),
590	    megaco:stop_user(Mid),
591	    application:stop(megaco),
592	    i("loop -> stopped"),
593	    StopReason = {error, {tcp_terminated, Pid, Reason}},
594	    server_reply(S#mgc.parent, stopped, StopReason),
595	    done(evs(S, {tcp_sup_exit, Reason}), StopReason);
596
597
598	{'EXIT', Pid, Reason} when S#mgc.udp_sup =:= Pid ->
599	    error_msg("MGC received unexpected exit "
600		      "from UDP transport supervisor (~p):"
601                      "~n   ~p", [Pid, Reason]),
602	    i("loop -> [udp] exiting"),
603	    display_system_info(S#mgc.mid, "at bad finish (udp) "),
604	    cancel_timer(S#mgc.dsi_timer),
605  	    Mid = S#mgc.mid,
606	    (catch close_conns(Mid)),
607	    megaco:stop_user(Mid),
608	    application:stop(megaco),
609	    i("loop -> stopped"),
610	    StopReason = {error, {udp_terminated, Pid, Reason}},
611	    server_reply(S#mgc.parent, stopped, StopReason),
612	    done(evs(S, {udp_sup_exit, Reason}), StopReason);
613
614
615	Invalid ->
616	    i("loop -> received invalid request: ~p", [Invalid]),
617	    loop(evs(S, {invalid, Invalid}))
618    end.
619
620
621evs(#mgc{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) ->
622    echo_evs(S#mgc{evs = [{?FTS(), Ev}|EVS]});
623evs(#mgc{evs = EVS} = S, Ev) ->
624    echo_evs(S#mgc{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}).
625
626echo_evs(#mgc{evs = EVS} = S) ->
627    i("Events: "
628      "~n      ~p", [EVS]),
629    S.
630
631done(#mgc{evs = EVS}, Reason) ->
632    info_msg("Exiting with latest event(s): "
633             "~n   ~p"
634             "~n", [EVS]),
635    exit(Reason).
636
637
638do_reset_stats(Mid) ->
639    megaco:reset_stats(),
640    do_reset_trans_stats(megaco:user_info(Mid, connections), []).
641
642do_reset_trans_stats([], _Reset) ->
643    ok;
644do_reset_trans_stats([CH|CHs], Reset) ->
645    SendMod = megaco:conn_info(CH, send_mod),
646    case lists:member(SendMod, Reset) of
647	true ->
648	    do_reset_trans_stats(CHs, Reset);
649	false ->
650	    SendMod:reset_stats(),
651	    do_reset_trans_stats(CHs, [SendMod|Reset])
652    end.
653
654
655close_conns(Mid) ->
656    Reason = {self(), ignore},
657    Disco  = fun(CH) ->
658		     (catch do_close_conn(CH, Reason))
659	     end,
660    lists:map(Disco, megaco:user_info(Mid, connections)).
661
662do_close_conn(CH, Reason) ->
663    d("close connection to ~p", [CH#megaco_conn_handle.remote_mid]),
664    Pid        = megaco:conn_info(CH, control_pid),
665    SendMod    = megaco:conn_info(CH, send_mod),
666    SendHandle = megaco:conn_info(CH, send_handle),
667    megaco:disconnect(CH, Reason),
668    case SendMod of
669	megaco_tcp -> megaco_tcp:close(SendHandle);
670	megaco_udp -> megaco_udp:close(SendHandle);
671	SendMod    -> exit(Pid, Reason)
672    end.
673
674get_trans_stats(P, SendMod) when is_pid(P) ->
675    case (catch SendMod:get_stats()) of
676	{ok, Stats} ->
677	    {SendMod, Stats};
678	Else ->
679	    {SendMod, Else}
680    end;
681get_trans_stats(_P, SendMod) ->
682    {SendMod, undefined}.
683
684parse_receive_info([], _RH) ->
685    throw({error, no_receive_info});
686parse_receive_info(RI, RH) ->
687    parse_receive_info(RI, RH, []).
688
689parse_receive_info([], _RH, Transports) ->
690    d("parse_receive_info -> done when"
691      "~n      Transports: ~p", [Transports]),
692    Transports;
693parse_receive_info([RI|RIs], RH, Transports) ->
694    d("parse_receive_info -> parse receive info"),
695    case (catch parse_receive_info1(RI, RH)) of
696	{error, Reason} ->
697	    e("failed parsing receive info: ~p~n~p", [RI, Reason]),
698	    exit({failed_parsing_recv_info, RI, Reason});
699	RH1 ->
700	    parse_receive_info(RIs, RH, [RH1|Transports])
701    end.
702
703parse_receive_info1(RI, RH) ->
704    d("parse_receive_info1 -> get encoding module"),
705    EM = get_encoding_module(RI),
706    d("parse_receive_info1 -> get encoding config"),
707    EC = get_encoding_config(RI, EM),
708    d("parse_receive_info1 -> get transport module"),
709    TM = get_transport_module(RI),
710    d("parse_receive_info1 -> get transport port"),
711    TP = get_transport_port(RI),
712    d("parse_receive_info1 -> get transport opts"),
713    TO = get_transport_opts(RI),
714    RH1 = RH#megaco_receive_handle{send_mod        = TM,
715				   encoding_mod    = EM,
716				   encoding_config = EC},
717    d("parse_receive_info1 -> "
718      "~n      Transport Opts: ~p"
719      "~n      Port:           ~p"
720      "~n      Receive handle: ~p", [TO, TP, RH1]),
721    {TO, TP, RH1}.
722
723
724
725%% --------------------------------------------------------
726%% On some platforms there seem to take some time before
727%% a port is released by the OS (after having been used,
728%% as is often the case in the test suites).
729%% So, starting the transports is done in two steps.
730%% First)  Start the actual transport(s)
731%% Second) Create the listener (tcp) or open the
732%%         send/receive port (udp).
733%% The second step *may* need to be repeated!
734%% --------------------------------------------------------
735start_transports(_, []) ->
736    throw({error, no_transport});
737start_transports(IB, Transports) when is_list(Transports) ->
738    {Tcp, Udp} = start_transports1(Transports, undefined, undefined),
739    ok = start_transports2(IB, Transports, Tcp, Udp),
740    {Tcp, Udp}.
741
742start_transports1([], Tcp, Udp) ->
743    {Tcp, Udp};
744start_transports1([{_TO, _Port, RH}|Transports], Tcp, Udp)
745  when ((RH#megaco_receive_handle.send_mod =:= megaco_tcp) andalso
746	(not is_pid(Tcp)))  ->
747    d("try start tcp transport service"),
748    case megaco_tcp:start_transport() of
749	{ok, Sup} ->
750            d("tcp transport service started: ~p", [Sup]),
751	    start_transports1(Transports, Sup, Udp);
752	Else ->
753            e("Failed starting TCP transport service:"
754              "~n   ~p", [Else]),
755	    throw({error, {failed_starting_tcp_transport, Else}})
756    end;
757start_transports1([{_TO, _Port, RH}|Transports], Tcp, Udp)
758  when ((RH#megaco_receive_handle.send_mod =:= megaco_udp) andalso
759	(not is_pid(Udp))) ->
760    d("try start udp transport servuice"),
761    case megaco_udp:start_transport() of
762	{ok, Sup} ->
763            d("udp transport started: ~p", [Sup]),
764	    start_transports1(Transports, Tcp, Sup);
765	Else ->
766            e("Failed starting UDP transport service:"
767              "~n   ~p", [Else]),
768	    throw({error, {failed_starting_udp_transport, Else}})
769    end;
770start_transports1([_|Transports], Tcp, Udp) ->
771    start_transports1(Transports, Tcp, Udp).
772
773start_transports2(_, [], _, _) ->
774    ok;
775start_transports2(IB, [{TO, Port, RH}|Transports], Tcp, Udp)
776  when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
777    start_tcp(IB, TO, RH, Port, Tcp),
778    start_transports2(IB, Transports, Tcp, Udp);
779start_transports2(IB, [{TO, Port, RH}|Transports], Tcp, Udp)
780  when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
781    start_udp(IB, TO, RH, Port, Udp),
782    start_transports2(IB, Transports, Tcp, Udp).
783
784start_tcp(IB, TO, RH, Port, Sup) ->
785    d("start tcp transport"),
786    start_tcp(IB, TO, RH, Port, Sup, 250).
787
788start_tcp(IB, TO, RH, Port, Sup, Timeout)
789  when is_pid(Sup) andalso is_integer(Timeout) andalso (Timeout > 0) ->
790    d("tcp listen on ~p", [Port]),
791    Opts = [{inet_backend,   IB},
792            {port,           Port},
793	    {receive_handle, RH},
794	    {tcp_options,    [{nodelay, true}]}] ++ TO,
795    try_start_tcp(Sup, Opts, Timeout, ?NO_ERROR).
796
797try_start_tcp(Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
798    Sleep = random(Timeout) + 100,
799    d("try create tcp listen socket (~p,~p)", [Timeout, Sleep]),
800    case megaco_tcp:listen(Sup, Opts) of
801	ok ->
802	    d("listen socket created", []),
803	    Sup;
804	Error1 when Error0 =:= ?NO_ERROR -> % Keep the first error
805	    d("failed creating listen socket [1]: ~p", [Error1]),
806	    sleep(Sleep),
807	    try_start_tcp(Sup, Opts, Timeout*2, Error1);
808	Error2 ->
809	    d("failed creating listen socket [2]: ~p", [Error2]),
810	    sleep(Sleep),
811	    try_start_tcp(Sup, Opts, Timeout*2, Error0)
812    end;
813try_start_tcp(Sup, _Opts, _Timeout, Error) ->
814    megaco_tcp:stop_transport(Sup),
815    case Error of
816	{error, Reason} ->
817	    throw({error, {failed_starting_tcp_listen, Reason}});
818	_ ->
819	    throw({error, {failed_starting_tcp_listen, Error}})
820    end.
821
822
823start_udp(IB, TO, RH, Port, Sup) ->
824    d("start udp transport"),
825    start_udp(IB, TO, RH, Port, Sup, 250).
826
827start_udp(IB, TO, RH, Port, Sup, Timeout) ->
828    d("udp open ~p", [Port]),
829    Opts = [{inet_backend,   IB},
830            {port,           Port},
831            {receive_handle, RH}] ++ TO,
832    try_start_udp(Sup, Opts, Timeout, ?NO_ERROR).
833
834try_start_udp(Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
835    d("try open udp socket (~p)", [Timeout]),
836    case megaco_udp:open(Sup, Opts) of
837	{ok, _SendHandle, _ControlPid} ->
838	    d("port opened", []),
839	    Sup;
840	Error1 when Error0 =:= ?NO_ERROR -> % Keep the first error
841	    d("failed open port [1]: ~p", [Error1]),
842	    sleep(Timeout),
843	    try_start_udp(Sup, Opts, Timeout*2, Error1);
844	Error2 ->
845	    d("failed open port [2]: ~p", [Error2]),
846	    sleep(Timeout),
847	    try_start_udp(Sup, Opts, Timeout*2, Error0)
848    end;
849try_start_udp(Sup, _Opts, _Timeout, Error) ->
850    megaco_udp:stop_transport(Sup),
851    throw({error, {failed_starting_udp_open, Error}}).
852
853
854%% -----------------------
855%% Handle megaco callbacks
856%%
857
858handle_megaco_request({handle_connect, CH, _PV}, #mgc{mg = MGs} = S) ->
859    case lists:member(CH, MGs) of
860	true ->
861	    i("MG already connected: ~n   ~p", [CH]),
862	    {error, S};
863	false ->
864	    {ok, S#mgc{mg = [CH|MGs]}}
865    end;
866
867handle_megaco_request({handle_disconnect, CH, _PV, R}, S) ->
868    d("handle_megaco_request(handle_disconnect) -> entry with"
869      "~n   CH: ~p"
870      "~n   R:  ~p", [CH, R]),
871    CancelRes = (catch megaco:cancel(CH, R)), % Cancel the outstanding messages
872    d("handle_megaco_request(handle_disconnect) -> megaco cancel result: ~p", [CancelRes]),
873    MGs = lists:delete(CH, S#mgc.mg),
874    d("handle_megaco_request(handle_disconnect) -> MGs: ~p", [MGs]),
875    {ok, S#mgc{mg = MGs}};
876
877handle_megaco_request({handle_syntax_error, _RH, _PV, _ED}, S) ->
878    {reply, S};
879
880handle_megaco_request({handle_message_error, _CH, _PV, _ED}, S) ->
881    {no_reply, S};
882
883handle_megaco_request({handle_trans_request, CH, PV, ARs},
884		      #mgc{req_info = P} = S) when is_pid(P) ->
885    d("handle_megaco_request(handle_trans_request,~p) -> entry", [P]),
886    P ! {req_received, self(), ARs},
887    do_handle_trans_request(CH, PV, ARs, S);
888handle_megaco_request({handle_trans_request, CH, PV, ARs}, S) ->
889    d("handle_megaco_request(handle_trans_request) -> entry"),
890    do_handle_trans_request(CH, PV, ARs, S);
891
892handle_megaco_request({handle_trans_long_request, CH, PV, RD}, S) ->
893    d("handle_megaco_request(handle_long_trans_request) -> entry"),
894    Reply0 = handle_act_requests(CH, PV, RD, discard_ack),
895    Reply  =
896	case S of
897	    #mgc{req_action = ignore, req_timeout = To} ->
898		d("handle_megaco_request(handle_long_trans_request) -> "
899		  "~n   To: ~p", [To]),
900		{delay_reply, To, Reply0};
901	    _ ->
902		d("handle_megaco_request(handle_long_trans_request) -> "
903		  "~n   S: ~p", [S]),
904		Reply0
905	end,
906    {Reply, S};
907
908handle_megaco_request({handle_trans_reply, _CH, _PV, _AR, _RD}, S) ->
909    {ok, S};
910
911handle_megaco_request({handle_trans_ack, CH, PV, AS, AD},
912		      #mgc{ack_info = P} = S) when is_pid(P) ->
913    d("handle_megaco_request(handle_trans_ack,~p) -> entry when"
914      "~n      CH: ~p"
915      "~n      PV: ~p"
916      "~n      AS: ~p"
917      "~n      AD: ~p", [P, CH, PV, AS, AD]),
918    P ! {ack_received, self(), AS},
919    {ok, S};
920
921handle_megaco_request({handle_trans_ack, CH, PV, AS, AD}, S) ->
922    d("handle_megaco_request(handle_trans_ack) -> entry with"
923      "~n      Conn Handle:  ~p"
924      "~n      Prot Version: ~p"
925      "~n      Ack Status:   ~p"
926      "~n      Ack Data:     ~p", [CH, PV, AS, AD]),
927    {ok, S};
928
929handle_megaco_request({handle_unexpected_trans, CH, PV, TR}, S) ->
930    d("handle_megaco_request(handle_unexpected_trans) -> entry with"
931      "~n      CH: ~p"
932      "~n      PV: ~p"
933      "~n      TR: ~p", [CH, PV, TR]),
934    {ok, S};
935
936handle_megaco_request({handle_trans_request_abort, CH, PV, TI, Handler}, S) ->
937    d("handle_megaco_request(handle_trans_request_abort) -> entry with"
938      "~n      CH:      ~p"
939      "~n      PV:      ~p"
940      "~n      TI:      ~p"
941      "~n      Handler: ~p", [CH, PV, TI, Handler]),
942    Reply =
943	case S#mgc.abort_info of
944	    P when is_pid(P) ->
945		P ! {abort_received, self(), TI},
946		ok;
947	    _ ->
948		ok
949	end,
950    {Reply, S}.
951
952
953do_handle_trans_request(CH, PV, ARs,
954			#mgc{req_action = Action, req_timeout = To} = S) ->
955    d("do_handle_megaco_request(handle_trans_request) -> entry with"
956      "~n      Action: ~p"
957      "~n      To:     ~p", [Action, To]),
958    case handle_act_requests(CH, PV, ARs, Action) of
959	{pending_ignore, ActReqs} ->
960	    {{pending, ActReqs}, S#mgc{req_action = ignore}};
961	Reply ->
962	    {{delay_reply, To, Reply}, S}
963    end.
964
965
966handle_act_requests(_CH, _PV, _ActReqs, ignore) ->
967    ignore;
968handle_act_requests(_CH, _PV, ActReqs, pending) ->
969    {pending, ActReqs};
970handle_act_requests(_CH, _PV, ActReqs, pending_ignore) ->
971    {pending_ignore, ActReqs};
972handle_act_requests(CH, PV, ActReqs, handle_ack) ->
973    Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
974    {{handle_ack, ActReqs}, Reply};
975handle_act_requests(CH, PV, ActReqs, handle_sloppy_ack) ->
976    Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
977    {{handle_sloppy_ack, ActReqs}, Reply};
978handle_act_requests(CH, PV, ActReqs, _) ->
979    Reply = (catch do_handle_act_requests(CH, PV, ActReqs, [])),
980    {discard_ack, Reply}.
981
982do_handle_act_requests(_CH, _PV, [], ActReplies) ->
983    lists:reverse(ActReplies);
984do_handle_act_requests(CH, PV, [ActReq|ActReqs], ActReplies) ->
985    ActReply = handle_act_request(CH, PV, ActReq),
986    do_handle_act_requests(CH, PV, ActReqs, [ActReply|ActReplies]).
987
988handle_act_request(CH, PV, ActReq) ->
989    #'ActionRequest'{contextId = CtxId, commandRequests = Cmds} = ActReq,
990    CmdReplies = handle_cmd_requests(CH, PV, CtxId, Cmds),
991    #'ActionReply'{contextId    = CtxId,
992		   commandReply = CmdReplies}.
993
994handle_cmd_requests(CH, PV, ?megaco_null_context_id,
995			[#'CommandRequest'{command={serviceChangeReq,Req}}]) ->
996    Rep = service_change(CH, PV, Req),
997    [{serviceChangeReply, Rep}];
998handle_cmd_requests(CH, PV, CtxId, Cmds) ->
999    do_handle_cmd_requests(CH, PV, CtxId, Cmds, []).
1000
1001do_handle_cmd_requests(_CH, _PV, _CtxId, [], CmdReplies) ->
1002    lists:reverse(CmdReplies);
1003do_handle_cmd_requests(CH, PV, CtxId, [Cmd|Cmds], CmdReplies) ->
1004    CmdReply = handle_cmd_request(CH, PV, CtxId, Cmd),
1005    do_handle_cmd_requests(CH, PV, CtxId, Cmds, [CmdReply|CmdReplies]).
1006
1007handle_cmd_request(CH, PV, CtxId,
1008		       #'CommandRequest'{command = {Tag,Req}}) ->
1009    case Tag of
1010        notifyReq ->
1011            (catch handle_notify_req(CH,PV,CtxId,Req));
1012
1013        serviceChangeReq ->
1014	    ED =  cre_error_descr(?megaco_not_implemented,
1015				  "Service change only allowed "
1016				  "on null context handled"),
1017	    throw(ED);
1018
1019        _ ->
1020            Code = ?megaco_not_implemented,
1021            ED   = cre_error_descr(Code,"Unknown command requst received:"
1022                                   "~n   Tag: ~p~n   Req: ~p",[Tag,Req]),
1023            throw(ED)
1024    end.
1025
1026handle_notify_req(CH, PV, CtxId,
1027		      #'NotifyRequest'{terminationID            = [Tid],
1028				       observedEventsDescriptor = EvDesc}) ->
1029    handle_event(CH, PV, CtxId, Tid, EvDesc).
1030
1031handle_event(_CH, _PV, _Cid, Tid, EvDesc) ->
1032    d("handle_event -> received"
1033      "~n      EvDesc: ~p"
1034      "~n      Tid:    ~p", [EvDesc, Tid]),
1035    {notifyReply, cre_notifyRep(Tid)}.
1036
1037
1038service_change(CH, _PV, SCR) ->
1039    SCP = SCR#'ServiceChangeRequest'.serviceChangeParms,
1040    #'ServiceChangeParm'{serviceChangeAddress = Address,
1041                         serviceChangeProfile = Profile,
1042                         serviceChangeReason  = [_Reason]} = SCP,
1043    TermId = SCR#'ServiceChangeRequest'.terminationID,
1044    if
1045        TermId == [?megaco_root_termination_id] ->
1046            MyMid = CH#megaco_conn_handle.local_mid,
1047            Res = {serviceChangeResParms,
1048		   cre_serviceChangeResParms(MyMid, Address, Profile)},
1049            cre_serviceChangeReply(TermId, Res);
1050        true ->
1051            Res = {errorDescriptor,
1052                   cre_error_descr(?megaco_not_implemented,
1053				   "Only handled for root")},
1054            cre_serviceChangeReply(TermId, Res)
1055    end.
1056
1057
1058
1059%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1060
1061cre_serviceChangeReply(TermId, Result) ->
1062    #'ServiceChangeReply'{terminationID       = TermId,
1063			  serviceChangeResult = Result}.
1064
1065cre_serviceChangeResParms(Mid, Addr, Prof) ->
1066    #'ServiceChangeResParm'{serviceChangeMgcId   = Mid,
1067			    serviceChangeAddress = Addr,
1068			    serviceChangeProfile = Prof}.
1069
1070
1071cre_notifyRep(Tid) ->
1072    #'NotifyReply'{terminationID = [Tid]}.
1073
1074% cre_notifyRep(Tid,Err) ->
1075%     #'NotifyReply'{terminationID = [Tid], errorDescriptor = Err}.
1076
1077cre_error_descr(Code,Text) ->
1078    #'ErrorDescriptor'{errorCode = Code, errorText = Text}.
1079
1080cre_error_descr(Code,FormatString,Args) ->
1081    Text = lists:flatten(io_lib:format(FormatString,Args)),
1082    cre_error_descr(Code,Text).
1083
1084
1085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1086
1087notify_started(Parent) ->
1088    Parent ! {started, self()}.
1089
1090
1091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1092
1093%% The megaco user callback interface
1094
1095handle_connect(CH, PV, Pid) ->
1096    case CH#megaco_conn_handle.remote_mid of
1097        preliminary_mid ->
1098	    %% Avoids deadlock
1099	    ok;
1100	_ ->
1101	    Reply = request(Pid, {handle_connect, CH, PV}),
1102	    Reply
1103    end.
1104
1105handle_disconnect(_CH, _PV,
1106		  {user_disconnect, {Pid, ignore}},
1107		  Pid) ->
1108    ok;
1109handle_disconnect(CH, _PV,
1110		  {user_disconnect, {Pid, cancel}},
1111		  Pid) ->
1112    megaco:cancel(CH, disconnected),
1113    ok;
1114handle_disconnect(CH, PV, R, Pid) ->
1115    request(Pid, {handle_disconnect, CH, PV, R}).
1116
1117handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor, Pid) ->
1118    Req = {handle_syntax_error, ReceiveHandle, ProtocolVersion,
1119	   ErrorDescriptor},
1120    request(Pid, Req).
1121
1122handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor, Pid) ->
1123    Req = {handle_message_error, ConnHandle, ProtocolVersion, ErrorDescriptor},
1124    request(Pid, Req).
1125
1126handle_trans_request(CH, PV, AR, Pid) ->
1127    Reply = request(Pid, {handle_trans_request, CH, PV, AR}),
1128    Reply.
1129
1130handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Pid) ->
1131    Req = {handle_trans_long_request, ConnHandle, ProtocolVersion, ReqData},
1132    request(Pid, Req).
1133
1134handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData, Pid) ->
1135    Req = {handle_trans_reply, ConnHandle, ProtocolVersion,
1136	   ActualReply, ReplyData},
1137    request(Pid, Req).
1138
1139handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Pid) ->
1140    Req = {handle_trans_ack, ConnHandle, ProtocolVersion, AckStatus, AckData},
1141    request(Pid, Req).
1142
1143handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Pid) ->
1144    Req = {handle_unexpected_trans, ConnHandle, ProtocolVersion, Trans},
1145    request(Pid, Req).
1146
1147handle_trans_request_abort(ConnHandle, ProtocolVersion, TransId,
1148			   Handler, Pid) ->
1149    Req = {handle_trans_request_abort,
1150	   ConnHandle, ProtocolVersion, TransId, Handler},
1151    request(Pid, Req).
1152
1153
1154request(Pid, Request) ->
1155    Pid ! {request, Request, self()},
1156    receive
1157	{reply, {delay_reply, To, Reply}, Pid} ->
1158	    megaco:report_event(ignore, self(), Pid,
1159				"reply: delay_reply", [To, Reply]),
1160	    sleep(To),
1161	    megaco:report_event(ignore, self(), Pid,
1162				"reply: delay done now return", []),
1163	    Reply;
1164	{reply, {exit, To, Reason}, Pid} ->
1165	    megaco:report_event(ignore, self(), Pid,
1166				"reply: exit", [To, Reason]),
1167	    sleep(To),
1168	    megaco:report_event(ignore, self(), Pid,
1169				"reply: sleep done now exit", []),
1170	    exit(Reason);
1171	{reply, Reply, Pid} ->
1172	    megaco:report_event(ignore, self(), Pid, "reply", [Reply]),
1173	    Reply
1174    end.
1175
1176
1177reply(To, Reply) ->
1178    To ! {reply, Reply, self()}.
1179
1180
1181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1182
1183sleep(X) ->
1184    d("sleep -> ~w", [X]),
1185    receive after X -> ok end.
1186
1187
1188info_msg(F,A)  -> error_logger:info_msg("MGC: " ++ F ++ "~n",A).
1189error_msg(F,A) -> error_logger:error_msg("MGC: " ++ F ++ "~n",A).
1190
1191
1192get_encoding_module(RI) ->
1193    case (catch get_conf(encoding_module, RI)) of
1194	{error, _} ->
1195	    undefined;
1196	Val ->
1197	    Val
1198    end.
1199
1200get_encoding_config(RI, EM) ->
1201    case text_codec(EM) of
1202	true ->
1203	    case megaco:system_info(text_config) of
1204		[Conf] when is_list(Conf) ->
1205		    Conf;
1206		_ ->
1207		    []
1208	    end;
1209
1210	false ->
1211	    get_conf(encoding_config, RI)
1212    end.
1213
1214text_codec(megaco_compact_text_encoder) ->
1215    true;
1216text_codec(megaco_pretty_text_encoder) ->
1217    true;
1218text_codec(_) ->
1219    false.
1220
1221
1222get_transport_module(RI) ->
1223    get_conf(transport_module, RI).
1224
1225get_transport_port(RI) ->
1226    get_conf(port, RI).
1227
1228get_transport_opts(RI) ->
1229    get_conf(transport_opts, RI, []).
1230
1231
1232get_conf(Key, Config) ->
1233    case lists:keysearch(Key, 1, Config) of
1234	{value, {Key, Val}} ->
1235	    Val;
1236	_ ->
1237	    exit({error, {not_found, Key, Config}})
1238    end.
1239
1240get_conf(Key, Config, Default) ->
1241    case lists:keysearch(Key, 1, Config) of
1242	{value, {Key, Val}} ->
1243	    Val;
1244	_ ->
1245	    Default
1246    end.
1247
1248
1249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1250
1251random_init() ->
1252    ok.
1253
1254random(N) ->
1255    rand:uniform(N).
1256
1257
1258display_system_info(Mid) ->
1259    display_system_info(Mid, "").
1260
1261display_system_info(Mid, Pre) ->
1262    TimeStr = ?FTS(),
1263    MibStr  = lists:flatten(io_lib:format("~p ", [Mid])),
1264    megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr).
1265
1266
1267create_timer(Time, Event) ->
1268    erlang:send_after(Time, self(), {Event, Time}).
1269
1270cancel_timer(undefined) ->
1271    ok;
1272cancel_timer(Ref) ->
1273    erlang:cancel_timer(Ref).
1274
1275
1276%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1277
1278e(F, A) ->
1279    print(error, get(verbosity), "ERROR", F, A).
1280
1281i(F) ->
1282    i(F, []).
1283
1284i(F, A) ->
1285    print(info, get(verbosity), "INFO", F, A).
1286
1287
1288d(F) ->
1289    d(F, []).
1290
1291d(F, A) ->
1292    print(debug, get(verbosity), "DBG", F, A).
1293
1294
1295printable(error, _)   -> true;
1296printable(_, debug)   -> true;
1297printable(info, info) -> true;
1298printable(_,_)        -> false.
1299
1300print(Severity, Verbosity, P, F, A) ->
1301    print(printable(Severity,Verbosity), P, F, A).
1302
1303print(true, P, F, A) ->
1304    print(P, F, A);
1305print(_, _, _, _) ->
1306    ok.
1307
1308print(P, F, A) ->
1309    io:format("*** [~s] [~s] ~p ~s ***"
1310	      "~n   " ++ F ++ "~n~n",
1311	      [?FTS(), P, self(), get(sname) | A]).
1312
1313
1314