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