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 "MG" used by the test suite
24%%----------------------------------------------------------------------
25-module(megaco_test_mg).
26
27-export([start/5, start/6, stop/1,
28	 get_stats/1, reset_stats/1,
29	 user_info/1, user_info/2,
30	 update_user_info/3,
31	 conn_info/1, conn_info/2,
32	 update_conn_info/3,
33	 service_change/1,
34	 ack_info/2, rep_info/2,
35	 group_requests/2,
36	 notify_request/1,
37	 await_notify_reply/1,
38	 notify_request_and_reply/1,
39	 cancel_request/2,
40	 apply_load/2,
41	 apply_multi_load/3,
42	 enable_test_code/4,
43	 encode_ar_first/2,
44	 verbosity/2]).
45
46-export([mg/3, notify_request_handler_main/5]).
47-export([loader_main/4]).
48
49%% Megaco callback api
50-export([
51	 handle_connect/4,
52	 handle_disconnect/5,
53	 handle_syntax_error/5,
54	 handle_message_error/5,
55	 handle_trans_request/5,
56	 handle_trans_long_request/5,
57	 handle_trans_reply/6,
58	 handle_trans_ack/6
59	]).
60
61-include("megaco_test_lib.hrl").
62-include_lib("megaco/include/megaco.hrl").
63-include_lib("megaco/include/megaco_message_v1.hrl").
64
65-define(A4444, tid(255*256*256)                 ).
66-define(A4445, tid(255*256*256 + 255)           ).
67-define(A5555, tid(255*256*256 + 255*256)       ).
68-define(A5556, tid(255*256*256 + 255*256 + 255) ).
69
70-record(mg, {mid             = undefined,
71	     state           = initiated,
72	     req_handler     = undefined,
73	     call_mode       = async,
74	     group_size      = 1,
75	     encode_ar_first = false,
76	     ack_info        = undefined,
77	     rep_info        = undefined,
78	     load_counter    = 0,
79	     reply_counter   = 0,
80	     mload_info      = undefined,
81	     parent          = undefined,
82	     dsi_timer,
83             evs             = []}).
84
85-define(EVS_MAX, 10).
86
87
88%%% --------------------------------------------------------------------
89
90start(Node, Mid, Encoding, Transport, Verbosity) ->
91    %% Conf = [{megaco_trace, io}],
92    %% Conf = [{megaco_trace, "megaco-mg.trace"}],
93    Conf = [{megaco_trace, false}],
94    start(Node, Mid, Encoding, Transport, Conf, Verbosity).
95
96start(Node, Mid, Encoding, Transport, Conf, Verbosity) ->
97    d("start mg[~p]: ~p"
98      "~n      Encoding:  ~p"
99      "~n      Transport: ~p"
100      "~n      Conf:      ~p", [Node, Mid, Encoding, Transport, Conf]),
101    RI1    = encoding_config(Encoding),
102    RI2    = transport_config(Transport),
103    {RI3, Conf1} = transport_opts(Conf),
104    RI     = {receive_info, RI1 ++ RI2 ++ RI3},
105    Config = [{local_mid, Mid}, RI] ++ Conf1,
106    Self   = self(),
107    Fun    =
108	fun() ->
109		i("LOADER(~p,~p) started", [self(),node()]),
110		case (catch mg(Self, Verbosity, Config)) of
111		    {'EXIT', Reason} ->
112			e("LOADER(~p,~p) terminating with exit"
113                          "~n      ~p", [self(), node(), Reason]),
114			exit(Reason);
115		    Else ->
116			i("LOADER(~p,~p) terminating with"
117                          "~n      ~p", [self(), node(), Else]),
118			Else
119		end
120	end,
121    true = erlang:monitor_node(Node, true),
122    Pid = spawn_link(Node, Fun),
123    %% Pid = spawn_link(Node, ?MODULE, mg, [self(), Verbosity, Config]),
124    MonRef = (catch erlang:monitor(process, Pid)),
125    NodePing = net_adm:ping(Node),
126    ProcInfo = (catch proc_info(Pid)),
127    i("start -> "
128      "~n      self():           ~p"
129      "~n      node():           ~p"
130      "~n      net_adm:ping(~p): ~p"
131      "~n      Loader:           ~p"
132      "~n      Monitor ref:      ~p"
133      "~n      Process info:     ~p",
134      [self(), node(),
135       Node, NodePing,
136       Pid,
137       MonRef, ProcInfo]),
138    await_started(Node, MonRef, Pid).
139
140proc_info(Pid) ->
141    rpc:call(node(Pid), erlang, process_info, [Pid]).
142
143encoding_config({Encoding, EC}) when is_atom(Encoding) andalso is_list(EC) ->
144    {Mod, Port} = select_encoding(Encoding),
145    [{encoding_module, Mod},
146     {encoding_config, EC},
147     {port,            Port}];
148encoding_config(Encoding) when is_atom(Encoding) ->
149    {Mod, Port} = select_encoding(Encoding),
150    [{encoding_module, Mod},
151     {encoding_config, []},
152     {port,            Port}];
153encoding_config(Encoding) ->
154    throw({error, {invalid_encoding, Encoding}}).
155
156select_encoding(text) ->
157    {megaco_pretty_text_encoder, 2944};
158select_encoding(pretty_text) ->
159    {megaco_pretty_text_encoder, 2944};
160select_encoding(compact_text) ->
161    {megaco_compact_text_encoder, 2944};
162select_encoding(binary) ->
163    {megaco_ber_encoder, 2945};
164select_encoding(erl_dist) ->
165    {megaco_erl_dist_encoder, 2946};
166select_encoding(Encoding) ->
167    throw({error, {invalid_encoding, Encoding}}).
168
169transport_config(tcp) ->
170    [{transport_module, megaco_tcp}];
171transport_config(udp) ->
172    [{transport_module, megaco_udp}];
173transport_config(TransportConfig) when is_list(TransportConfig) ->
174    {value, {transport, Trans}} =
175	lists:keysearch(transport, 1, TransportConfig),
176    transport_config(Trans) ++
177	case lists:keysearch(host, 1, TransportConfig) of
178	    {value, Value} ->
179		[Value];
180	    false ->
181		[]
182	end.
183
184transport_opts(Config) ->
185    case lists:keysearch(transport_opts, 1, Config) of
186	{value, TO} ->
187	    Config1 = lists:keydelete(transport_opts, 1, Config),
188	    {[TO], Config1};
189	false ->
190	    {[], Config}
191    end.
192
193
194await_started(Node, MonRef, Pid) ->
195    i("await_started -> entry with"
196      "~n      MonRef: ~p"
197      "~n      Pid:    ~p", [MonRef, Pid]),
198    receive
199	{started, Pid} ->
200	    d("await_started ~p - started"
201	      "~n      Process Info: ~p", [Pid, (catch proc_info(Pid))]),
202	    true = erlang:monitor_node(Node, false),
203	    erlang:demonitor(MonRef),
204	    {ok, Pid};
205
206	{nodedown, Node} ->
207	    i("await_started ~p - received node down", [Pid]),
208	    exit({node_down, Node});
209
210	{'DOWN', MonRef, process, Pid, Info} ->
211	    e("await_started ~p - received down signal: ~p",
212	      [Pid, Info]),
213	    true = erlang:monitor_node(Node, false),
214	    exit({failed_starting, Pid, Info});
215
216	{'EXIT', Pid, Reason} ->
217	    e("await_started ~p - received exit signal: ~p", [Pid, Reason]),
218	    true = erlang:monitor_node(Node, false),
219	    exit({failed_starting, Pid, Reason})
220
221    %% This timeout was originally 10 secs, but on some debug compiled
222    %% platforms, it was simply not long enough
223    after 20000 ->
224	    NodePing = net_adm:ping(Node),
225	    ProcInfo = (catch proc_info(Pid)),
226	    FlushQ = megaco_test_lib:flush(),
227	    e("await_started ~p - timeout: "
228	      "~n      net_adm:ping(~p):     ~p"
229	      "~n      Process info:         ~p"
230	      "~n      Messages in my queue: ~p",
231	      [Pid, Node, NodePing, ProcInfo, FlushQ]),
232	    true = erlang:monitor_node(Node, false),
233	    exit({error, timeout})
234    end.
235
236
237verbosity(Pid, V) ->
238    Pid ! {verbosity, V, self()}.
239
240
241stop(Pid) ->
242    server_request(Pid, stop, stopped).
243
244
245get_stats(Pid) ->
246    server_request(Pid, statistics, statistics_reply).
247
248reset_stats(Pid) ->
249    server_request(Pid, reset_stats, reset_stats_ack).
250
251
252user_info(Pid) ->
253    server_request(Pid, {user_info, all}, user_info_ack).
254
255user_info(Pid, Tag) when is_atom(Tag) ->
256    server_request(Pid, {user_info, Tag}, user_info_ack).
257
258
259update_user_info(Pid, Tag, Val) ->
260    server_request(Pid, {update_user_info, Tag, Val}, update_user_info_ack).
261
262
263conn_info(Pid) ->
264    server_request(Pid, {conn_info, all}, conn_info_ack).
265
266conn_info(Pid, Tag) when is_atom(Tag) ->
267    server_request(Pid, {conn_info, Tag}, conn_info_ack).
268
269
270update_conn_info(Pid, Tag, Val) ->
271    server_request(Pid, {update_conn_info, Tag, Val}, update_conn_info_ack).
272
273
274enable_test_code(Pid, Module, Where, Fun)
275  when is_atom(Module) andalso is_atom(Where) andalso is_function(Fun) ->
276    Tag = {Module, Where},
277    server_request(Pid, {enable_test_code, Tag, Fun}, enable_test_code_reply).
278
279encode_ar_first(Pid, New) when is_atom(New) ->
280    server_request(Pid, {encode_ar_first, New}, encode_ar_first_reply).
281
282service_change(Pid) ->
283    server_request(Pid, service_change, service_change_reply).
284
285
286group_requests(Pid, N) ->
287    server_request(Pid, {group_requests, N}, group_requests_reply).
288
289
290ack_info(Pid, InfoPid) ->
291    Pid ! {{ack_info, InfoPid}, self()}.
292
293rep_info(Pid, InfoPid) ->
294    Pid ! {{rep_info, InfoPid}, self()}.
295
296
297notify_request(Pid) ->
298    Pid ! {notify_request, self()}.
299
300
301await_notify_reply(Pid) ->
302    await_reply(Pid, notify_request_reply).
303
304
305notify_request_and_reply(Pid) ->
306    notify_request(Pid),
307    await_notify_reply(Pid).
308
309
310cancel_request(Pid, Reason) ->
311    server_request(Pid, cancel_request, Reason, cancel_request_reply).
312
313
314apply_load(Pid, CounterStart) ->
315    server_request(Pid, apply_load, CounterStart, apply_load_ack).
316
317
318apply_multi_load(Pid, NumLoaders, NumReq) ->
319    server_request(Pid, apply_multi_load, {NumLoaders, NumReq}, apply_multi_load_ack).
320
321
322server_request(Pid, Req, ReplyTag) ->
323    Pid ! {Req, self()},
324    await_reply(Pid, ReplyTag).
325
326server_request(Pid, Req, ReqData, ReplyTag) ->
327    Pid ! {Req, ReqData, self()},
328    await_reply(Pid, ReplyTag).
329
330await_reply(Pid, ReplyTag) ->
331    await_reply(Pid, ReplyTag, infinity).
332
333await_reply(Pid, ReplyTag, Timeout) ->
334    receive
335	{ReplyTag, Reply, Pid} ->
336	    Reply;
337	{'EXIT', Pid, Reason} ->
338	    exit({failed, ReplyTag, Pid, Reason})
339    after Timeout ->
340	    exit({timeout, ReplyTag, Pid})
341    end.
342
343
344server_reply(Pid, ReplyTag, Reply) ->
345    Pid ! {ReplyTag, Reply, self()}.
346
347
348%%% --------------------------------------------------------------------
349
350
351mg(Parent, Verbosity, Config) ->
352    process_flag(trap_exit, true),
353    put(sname, "MG"),
354    %% put(verbosity, Verbosity),
355    put(verbosity, debug),  % Enable debug printouts during init
356    i("mg -> starting"),
357    %% megaco:enable_trace(max, io),
358    case (catch init(Config)) of
359	{error, _} = Error ->
360	    exit(Error);
361	{'EXIT', Reason} ->
362	    exit({init_failed, Reason});
363	{ok, Mid, DSITimer} ->
364	    notify_started(Parent),
365	    MG = #mg{parent = Parent, mid = Mid, dsi_timer = DSITimer},
366	    i("mg -> started"),
367	    put(verbosity, Verbosity),
368	    case (catch loop(evs(MG, started))) of
369		{'EXIT', normal} ->
370		    exit(normal);
371		{'EXIT', Reason} ->
372		    e("mg failed with reason:"
373                      "~n      ~p", [Reason]),
374		    exit(Reason);
375		Else ->
376		    e("mg terminated:"
377                      "~n      ~p", [Else]),
378		    exit({unexpected, Else})
379	    end
380    end.
381
382init(Config) ->
383    d("init -> entry with"
384      "~n      Config: ~p", [Config]),
385    random_init(),
386    d("init -> random initiated"),
387    Mid = get_conf(local_mid, Config),
388    d("init -> Mid: ~p", [Mid]),
389    RI  = get_conf(receive_info, Config),
390    d("init -> RI: ~p", [RI]),
391
392    d("init -> maybe start the display system info timer"),
393    DSITimer =
394	case get_conf(display_system_info, Config, undefined) of
395	    Time when is_integer(Time) ->
396		d("init -> creating display system info timer"),
397		create_timer(Time, display_system_info);
398	    _ ->
399		undefined
400	end,
401    Conf0 = lists:keydelete(display_system_info, 1, Config),
402
403    d("init -> start megaco"),
404    application:start(megaco),
405
406
407    d("init -> possibly enable megaco trace"),
408    case lists:keysearch(megaco_trace, 1, Conf0) of
409	{value, {megaco_trace, true}} ->
410	    megaco:enable_trace(max, io);
411	{value, {megaco_trace, io}} ->
412	    megaco:enable_trace(max, io);
413	{value, {megaco_trace, File}} when is_list(File) ->
414	    megaco:enable_trace(max, File);
415	_ ->
416	    ok
417    end,
418    Conf1 = lists:keydelete(megaco_trace, 1, Conf0),
419
420    d("init -> start megaco user"),
421    Conf2 = lists:keydelete(local_mid,    1, Conf1),
422    Conf3 = lists:keydelete(receive_info, 1, Conf2),
423    ok = megaco:start_user(Mid, Conf3),
424    d("init -> update user info (user_mod)"),
425    ok = megaco:update_user_info(Mid, user_mod,  ?MODULE),
426    d("init -> update user info (user_args)"),
427    ok = megaco:update_user_info(Mid, user_args, [self(), Mid]),
428
429    d("init -> get user info (receive_handle)"),
430    RH = megaco:user_info(Mid, receive_handle),
431    d("init -> parse receive info"),
432    {MgcPort, MgcHost, RH1, TO} = parse_receive_info(RI, RH),
433    d("init -> start transport (with ~p)", [TO]),
434    {ok, _CH} = start_transport(MgcPort, MgcHost, RH1, TO),
435    {ok, Mid, DSITimer}.
436
437
438loop(#mg{parent = Parent, mid = Mid} = S) ->
439    d("loop -> await request", []),
440    receive
441	{display_system_info, Time} ->
442	    display_system_info(S#mg.mid),
443	    NewTimer = create_timer(Time, display_system_info),
444	    loop(evs(S#mg{dsi_timer = NewTimer}, {dsi, Time}));
445
446	{verbosity, V, Parent} ->
447	    i("loop -> received new verbosity: ~p", [V]),
448	    put(verbosity,V),
449	    loop(evs(S, {verb, V}));
450
451
452	{stop, Parent} ->
453	    i("loop -> stopping", []),
454	    display_system_info(S#mg.mid, "at finish "),
455	    cancel_timer(S#mg.dsi_timer),
456	    Res = do_stop(Mid),
457	    d("loop -> stop result: ~p", [Res]),
458	    server_reply(Parent, stopped, {ok, Res}),
459	    done(evs(S, stop), normal);
460
461	{{enable_test_code, Tag, Fun}, Parent} ->
462	    i("loop -> enable_test_code: ~p, ~p", [Tag, Fun]),
463	    Reply = (catch ets:insert(megaco_test_data, {Tag, Fun})),
464	    d("loop -> enable_test_code -> "
465	      "~n      Reply:                          ~p"
466	      "~n      ets:tab2list(megaco_test_data): ~p",
467	      [Reply,ets:tab2list(megaco_test_data)]),
468	    server_reply(Parent, enable_test_code_reply, Reply),
469	    loop(evs(S, {enable_test_code, Tag}));
470
471	{{encode_ar_first, EAF}, Parent} ->
472	    i("loop -> encode_ar_first: ~p", [EAF]),
473	    {Reply, S1} = handle_encode_ar_first(S, EAF),
474	    server_reply(Parent, encode_ar_first_reply, Reply),
475	    loop(evs(S1#mg{encode_ar_first = EAF}, {enc_arf, EAF}));
476
477	%% Give me statistics
478	{statistics, Parent} ->
479	    i("loop -> got request for statistics"),
480	    Stats = do_get_statistics(Mid),
481	    server_reply(Parent, statistics_reply, {ok, Stats}),
482	    loop(evs(S, stats));
483
484	{reset_stats, Parent} ->
485	    i("loop -> got request to reset stats counters"),
486	    do_reset_stats(Mid),
487	    server_reply(Parent, reset_stats_ack, ok),
488	    loop(evs(S, rst_stats));
489
490	{{user_info, Tag}, Parent} ->
491	    i("loop -> got user_info request for ~w", [Tag]),
492	    Res = do_get_user_info(Mid, Tag),
493	    d("loop -> Res: ~p", [Res]),
494	    server_reply(Parent, user_info_ack, Res),
495	    loop(evs(S, {ui, Tag}));
496
497	{{update_user_info, Tag, Val}, Parent} ->
498	    i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]),
499	    Res = do_update_user_info(Mid, Tag, Val),
500	    d("loop -> Res: ~p", [Res]),
501	    server_reply(Parent, update_user_info_ack, Res),
502	    loop(evs(S, {uui, {Tag, Val}}));
503
504	{{conn_info, Tag}, Parent} ->
505	    i("loop -> got conn_info request for ~w", [Tag]),
506	    Res = do_get_conn_info(Mid, Tag),
507	    server_reply(Parent, conn_info_ack, Res),
508	    loop(evs(S, {ci, Tag}));
509
510	{{update_conn_info, Tag, Val}, Parent} ->
511	    i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]),
512	    Res = do_update_conn_info(Mid, Tag, Val),
513	    server_reply(Parent, update_conn_info_ack, Res),
514	    loop(evs(S, {uci, {Tag, Val}}));
515
516
517	%% Do a service change
518	%% No server-reply here. Since the service change is
519	%% async, the reply (from the MGC) will come later.
520	{service_change, Parent} ->
521	    i("loop -> received request to perform service change"),
522	    S1 =
523		case (catch do_service_change(S)) of
524		    {ok, MG} ->
525			d("loop -> service change initiated"),
526			MG;
527		    Error ->
528			d("loop -> service change failed: ~p", [Error]),
529			server_reply(Parent, service_change_reply, Error),
530			S
531		end,
532	    loop(evs(S1, svc_ch));
533
534	{{group_requests, N}, Parent} when N > 0 ->
535	    i("loop -> received group_requests ~p", [N]),
536	    Reply = {ok, S#mg.group_size},
537	    server_reply(Parent, group_requests_reply, Reply),
538	    loop(evs(S#mg{group_size = N}, {grp_reqs, N}));
539
540	{{ack_info, To}, Parent} ->
541	    i("loop -> received request to inform about received ack's"),
542	    loop(evs(S#mg{ack_info = To}, {acki, To}));
543
544	{{rep_info, To}, Parent} ->
545	    i("loop -> received request to inform about received rep's "),
546	    loop(evs(S#mg{rep_info = To}, {repi, To}));
547
548	%% Make a sync-call
549	{notify_request, Parent} ->
550	    i("loop -> received request to send notify request "),
551	    {Res, S1} = do_handle_notify_request(S),
552	    d("loop -> notify request result: ~p", [Res]),
553	    loop(evs(S1, not_req));
554
555	%% sync-call complete
556	{notify_request_complete, NotifyReply, Pid} ->
557	    i("loop -> received notify request complete from "
558	      "~n   ~p with"
559	      "~n   NotifyReply: ~p",
560	      [Pid, NotifyReply]),
561	    server_reply(Parent, notify_request_reply, NotifyReply),
562	    loop(evs(S#mg{req_handler = undefined}, {not_reqc, NotifyReply}));
563
564
565	%% cancel requests
566	{cancel_request, Reason, Parent} ->
567	    i("loop -> received request to cancel (all) megaco requests"),
568	    Res = do_cancel_requests(Mid, Reason),
569	    server_reply(Parent, cancel_request_reply, Res),
570	    loop(evs(S, {creq, Reason}));
571
572
573	%% Apply multi-load
574	{apply_multi_load, {NL, NR}, Parent} ->
575	    i("loop -> received apply_multi_load request: ~w, ~w", [NL, NR]),
576	    S1 = start_loaders(S, NL, NR),
577	    loop(evs(S1, {apply_mload, {NL, NR}}));
578
579
580	%% Apply some load
581	{apply_load, Times, Parent} ->
582	    i("loop -> received apply_load request: ~w", [Times]),
583	    S1 =
584		case update_load_times(S, Times) of
585		    {ok, MG} ->
586			apply_load_timer(),
587			server_reply(Parent, apply_load_ack, ok),
588			MG;
589		    Error ->
590			server_reply(Parent, apply_load_ack, Error),
591			S
592		end,
593	    loop(evs(S1, {apply_load, Times}));
594
595	{apply_load_timeout, _} ->
596	    d("loop -> received apply_load timeout", []),
597	    S1 = do_apply_load(S),
598	    loop(evs(S1, apply_loadto));
599
600
601	%% Megaco callback messages
602	{request, Request, Mid, From} ->
603	    d("loop -> received megaco request: "
604              "~n      ~p"
605	      "~n      Mid:  ~p"
606	      "~n      From: ~p",
607	      [Request, Mid, From]),
608	    {Reply, S1} = handle_megaco_request(S, Request),
609	    d("loop -> send (megaco callback) request reply:"
610              "~n      ~p", [Reply]),
611	    From ! {reply, Reply, self()},
612	    loop(evs(S1, {req, {Request, Mid, From}}));
613
614
615	{'EXIT', Pid, Reason} ->
616	    i("loop -> received exit signal from ~p: "
617              "~n      ~p", [Pid, Reason]),
618	    S1 = handle_exit(S, Pid, Reason),
619	    loop(evs(S1, {exit, {Pid, Reason}}));
620
621
622	Invalid ->
623	    error_msg("received invalid request: "
624                      "~n   ~p", [Invalid]),
625	    loop(evs(S, {invalid, Invalid}))
626
627    end.
628
629
630evs(#mg{evs = EVS} = S, Ev) when (length(EVS) < ?EVS_MAX) ->
631    S#mg{evs = [{?FTS(), Ev}|EVS]};
632evs(#mg{evs = EVS} = S, Ev) ->
633    S#mg{evs = [{?FTS(), Ev}|lists:droplast(EVS)]}.
634
635done(#mg{evs = EVS}, Reason) ->
636    info_msg("Exiting with latest event(s): "
637             "~n   ~p"
638             "~n", [EVS]),
639    exit(Reason).
640
641
642handle_encode_ar_first(#mg{encode_ar_first = Old} = MG, New)
643  when (New =:= true) orelse (New =:= false) ->
644    {{ok, Old}, MG#mg{encode_ar_first = New}};
645handle_encode_ar_first(MG, New) ->
646    {{error, {invalid_value, New}}, MG}.
647
648
649%%
650%% Stop user
651%%
652do_stop(Mid) ->
653    d("do_stop -> stopping user ~p", [Mid]),
654    Disco = fun close_conn/1,
655    lists:map(Disco, megaco:user_info(Mid, connections)),
656    megaco:stop_user(Mid).
657
658close_conn(CH) ->
659    d("do_stop -> closing connection ~p", [CH]),
660    Reason     = {stopped_by_user,self()},
661    Pid        = megaco:conn_info(CH, control_pid),
662    SendMod    = megaco:conn_info(CH, send_mod),
663    SendHandle = megaco:conn_info(CH, send_handle),
664    megaco:disconnect(CH, Reason),
665    megaco:cancel(CH, Reason),
666    case SendMod of
667	megaco_tcp -> megaco_tcp:close(SendHandle);
668	megaco_udp -> megaco_udp:close(SendHandle);
669	SendMod    -> exit(Pid, Reason)
670    end.
671
672
673
674%%
675%% Get statistics
676%%
677do_get_statistics(Mid) ->
678    case megaco:user_info(Mid, connections) of
679	[CH] ->
680	    do_get_conn_statistics(CH);
681	[] ->
682	    []
683    end.
684
685do_get_conn_statistics(CH) ->
686    {ok, Gen}  = megaco:get_stats(),
687    %% Pid        = megaco:conn_info(CH, control_pid),
688    SendMod    = megaco:conn_info(CH, send_mod),
689    SendHandle = megaco:conn_info(CH, send_handle),
690    {ok, Trans} =
691	case SendMod of
692	    megaco_tcp -> megaco_tcp:get_stats(SendHandle);
693	    megaco_udp -> megaco_udp:get_stats(SendHandle)
694	end,
695    [{gen, Gen}, {trans, Trans}].
696
697
698%%
699%% reset user stats
700%%
701do_reset_stats(Mid) ->
702    %% We only have one connection
703    [CH] = megaco:user_info(Mid, connections),
704    do_reset_stats1(CH).
705
706do_reset_stats1(CH) ->
707    megaco:reset_stats(),
708    case (catch megaco:conn_info(CH, send_mod)) of
709	{error, Reason} ->
710	    error_msg("unexpected result when retrieving send module for "
711		      "own connection ~p: ~p. "
712		      "~nexiting...", [CH, Reason]),
713	    exit({invalid_connection, CH, Reason});
714	{'EXIT', Reason} ->
715	    error_msg("exit signal when retrieving send module for "
716		      "own connection ~p: ~p. "
717		      "~nexiting...", [CH, Reason]),
718	    exit({invalid_connection, CH, Reason});
719	SendMod when is_atom(SendMod) ->
720	    SendMod:reset_stats()
721    end.
722
723
724%%
725%% Get user info for user
726%%
727do_get_user_info(Mid, all = Tag) ->
728    case (catch megaco:user_info(Mid, Tag)) of
729	L when is_list(L) ->
730	    lists:sort(L);
731	Else ->
732	    Else
733    end;
734do_get_user_info(Mid, Tag) ->
735    (catch megaco:user_info(Mid, Tag)).
736
737
738%%
739%% Update user info for user
740%%
741do_update_user_info(Mid, Tag, Val) ->
742    (catch megaco:update_user_info(Mid, Tag, Val)).
743
744
745%%
746%% Get conn info
747%%
748do_get_conn_info(CH, all = Tag) when is_record(CH, megaco_conn_handle) ->
749    case (catch megaco:conn_info(CH, Tag)) of
750	L when is_list(L) ->
751	    lists:sort(L);
752	Else ->
753	    Else
754    end;
755do_get_conn_info(CH, Tag) when is_record(CH, megaco_conn_handle) ->
756    (catch megaco:conn_info(CH, Tag));
757do_get_conn_info(Mid, Tag) ->
758    case megaco:user_info(Mid, connections) of
759	[CH|_] ->
760	    do_get_conn_info(CH, Tag);
761	[] ->
762	    []
763    end.
764
765%%
766%% Update conn info for user
767%%
768do_update_conn_info(Mid, Tag, Val) ->
769    %% We only have one connection
770    [CH] = megaco:user_info(Mid, connections),
771    (catch megaco:update_conn_info(CH, Tag, Val)).
772
773
774
775
776%%
777%% Perform service change
778%%
779do_service_change(#mg{mid             = Mid,
780		      state           = initiated,
781		      encode_ar_first = EAF} = MG) ->
782    %% We only have one connection
783    d("do service change for ~p", [Mid]),
784    [CH]   = megaco:user_info(Mid, connections),
785    Method = restart,
786    Reason = ?megaco_cold_boot,
787    case do_service_change(CH, Method, EAF, Reason) of
788	ok ->
789	    {ok, MG#mg{state = connecting}};
790	Error ->
791	    d("service change for ~p failed: ~n~p", [Mid, Error]),
792	    Error
793    end;
794do_service_change(#mg{state = State} = MG) ->
795    {{error, {invalid_state, State}}, MG}.
796
797do_service_change(ConnHandle, Method, EAF, Reason) ->
798    d("send service change using:"
799      "~n      ConnHandle: ~p"
800      "~n      Method:     ~p"
801      "~n      EAF:        ~p"
802      "~n      Reason:     ~p", [ConnHandle, Method, EAF, Reason]),
803    SCP    = cre_serviceChangeParm(Method, [Reason]),
804    TermId = [?megaco_root_termination_id],
805    SCR    = cre_serviceChangeReq(TermId, SCP),
806    CR     = cre_commandReq({serviceChangeReq, SCR}),
807    AR     = cre_actionReq(?megaco_null_context_id,[CR]),
808    send_async(EAF, ConnHandle, [AR], []).
809
810
811%% Make a sync call
812do_handle_notify_request(#mg{mid             = Mid,
813			     group_size      = N,
814			     encode_ar_first = EAF,
815			     state           = connected} = MG) ->
816    d("do_handle_notify_request -> entry"),
817    [CH] = megaco:user_info(Mid, connections),
818    Pid = start_notify_request_handler(EAF, CH, N),
819    {ok, MG#mg{req_handler = Pid}};
820do_handle_notify_request(#mg{state = State} = MG) ->
821    d("do_handle_notify_request -> entry with"
822      "~n      State: ~p", [State]),
823    {{error, {invalid_state, State}}, MG}.
824
825
826
827%%
828%% Cancel requests
829%%
830do_cancel_requests(Mid, Reason) ->
831    [CH] = megaco:user_info(Mid, connections),
832    megaco:cancel(CH, Reason).
833
834
835%%
836%% Apply multi load
837%%
838start_loaders(#mg{mid = Mid, encode_ar_first = EAF} = MG, NumLoaders, Times) ->
839    [CH] = megaco:user_info(Mid, connections),
840    Env  = get(),
841    Loaders = start_loaders1(NumLoaders, [], [Env, EAF, Times, CH]),
842    d("start_loaders -> Loaders: ~n~w", [Loaders]),
843    MG#mg{mload_info = {Loaders, 0, 0}}.
844
845start_loaders1(0, Acc, _) ->
846    Acc;
847start_loaders1(N, Acc, Args) ->
848    Pid = spawn_link(?MODULE, loader_main, Args),
849    start_loaders1(N-1, [Pid|Acc], Args).
850
851loader_main(Env, EAF, N, CH) ->
852    lists:foreach(fun({Tag,Val}) -> put(Tag,Val) end, Env),
853    loader_main(EAF, N, CH).
854
855loader_main(_EAF, 0, _) ->
856    d("loader_main -> done"),
857    exit(loader_done);
858loader_main(EAF, N, CH) ->
859    d("loader_main -> entry with: ~w", [N]),
860    {Act, _} = make_notify_request(),
861    _Res     = send_sync(EAF, CH, Act, []),
862    loader_main(EAF, N-1, CH).
863
864
865
866handle_exit(#mg{parent = Pid} = S, Pid, Reason) ->
867    error_msg("received exit from the parent:"
868	      "~n   ~p", [Reason]),
869    done(S, {parent_terminated, Reason});
870
871handle_exit(#mg{parent = Parent, req_handler = Pid} = MG, Pid, Reason) ->
872    error_msg("received unexpected exit from the request handler:"
873	      "~n      ~p", [Reason]),
874    server_reply(Parent, notify_request_reply,
875		 {error, {request_handler_exit, Reason}}),
876    MG#mg{req_handler = undefined};
877
878handle_exit(#mg{parent = Parent, mload_info = {Loaders0, Ok, Err}} = MG,
879	    Pid, loader_done) ->
880    d("handle_exit(loader_done) -> entry when"
881      "~n      Loaders0: ~p"
882      "~n      Ok:       ~p"
883      "~n      Err:      ~p", [Loaders0, Ok, Err]),
884    Loaders = lists:delete(Pid, Loaders0),
885    LoadInfo =
886	case Loaders of
887	    [] ->
888		d("handle_exit -> multi load done"),
889		server_reply(Parent, apply_multi_load_ack, {ok, Ok+1, Err}),
890		undefined;
891	    _ ->
892		{Loaders, Ok+1, Err}
893	end,
894    MG#mg{mload_info = LoadInfo};
895
896
897handle_exit(#mg{parent = Parent, mload_info = {Loaders, Ok, Err}} = MG,
898	    Pid, Reason)
899  when length(Loaders) > 0 ->
900    d("handle_exit -> entry when"
901      "~n      Reason:  ~p"
902      "~n      Loaders: ~p"
903      "~n      Ok:      ~p"
904      "~n      Err:     ~p", [Reason, Loaders, Ok, Err]),
905    case lists:delete(Pid, Loaders) of
906	[] ->
907	    %% since we cannot be empty prior the delete,
908	    %% the last one exited...
909	    server_reply(Parent, apply_multi_load, {ok, Ok, Err+1}),
910	    MG#mg{mload_info = undefined};
911	Loaders ->
912	    %% Could not be this MG, so go on to the next
913	    error_msg("received unexpected exit signal from ~p:"
914                      "~n   ~p", [Pid, Reason]);
915	Loaders1 ->
916	    %% Not empty, but we removed one
917	    MG#mg{mload_info = {Loaders1,Ok,Err+1}}
918    end;
919handle_exit(_MG, Pid, Reason) ->
920    error_msg("received unexpected exit signal from ~p:"
921              "~n   ~p", [Pid, Reason]).
922
923
924parse_receive_info(RI, RH) ->
925    d("parse_receive_info -> get encoding module"),
926    EM = get_encoding_module(RI),
927    d("parse_receive_info -> get encoding config"),
928    EC = get_encoding_config(RI, EM),
929    d("parse_receive_info -> get transport module"),
930    TM = get_transport_module(RI),
931    d("parse_receive_info -> get transport port"),
932    TP = get_transport_port(RI),
933    d("parse_receive_info -> get transport host"),
934    TH = get_transport_host(RI),
935    d("parse_receive_info -> get transport opts"),
936    TO = get_transport_opts(RI),
937    RH1 = RH#megaco_receive_handle{send_mod        = TM,
938				   encoding_mod    = EM,
939				   encoding_config = EC},
940    {TP, TH, RH1, TO}.
941
942
943start_transport(MgcPort, MgcHost,
944		   #megaco_receive_handle{send_mod = megaco_tcp} = RH, TO) ->
945    start_tcp(MgcPort, MgcHost, RH, TO);
946start_transport(MgcPort, MgcHost,
947		   #megaco_receive_handle{send_mod = megaco_udp} = RH, TO) ->
948    start_udp(MgcPort, MgcHost, RH, TO);
949start_transport(_, _, #megaco_receive_handle{send_mod = Mod}, _TO) ->
950    throw({error, {bad_send_mod, Mod}}).
951
952
953start_tcp(MgcPort, MgcHost, RH, TO) ->
954    d("start tcp transport: "
955      "~n      MGC Port:          ~p"
956      "~n      MGC Host:          ~p"
957      "~n      Receive handle:    ~p"
958      "~n      Transport options: ~p", [MgcPort, MgcHost, RH, TO]),
959    case megaco_tcp:start_transport() of
960	{ok, Sup} ->
961	    d("tcp transport started: ~p", [Sup]),
962	    start_tcp_connect(TO, RH, MgcPort, MgcHost, Sup);
963        {error, Reason} ->
964            throw({error, {megaco_tcp_start_transport, Reason}})
965    end.
966
967start_tcp_connect(TO, RH, Port, Host, Sup) ->
968    d("try tcp connecting to: ~p:~p", [Host, Port]),
969    Opts = [{host,           Host},
970	    {port,           Port},
971	    {receive_handle, RH},
972	    {tcp_options,    [{nodelay, true}]}] ++ TO,
973    try_start_tcp_connect(RH, Sup, Opts, 250, noError).
974
975try_start_tcp_connect(RH, Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
976    Sleep = random(Timeout) + 100,
977    d("try tcp connect (~p,~p)", [Timeout, Sleep]),
978    case megaco_tcp:connect(Sup, Opts) of
979	{ok, SendHandle, ControlPid} ->
980	    d("tcp connected: ~p, ~p", [SendHandle, ControlPid]),
981	    megaco_tcp_connect(RH, SendHandle, ControlPid);
982	Error1 when Error0 =:= noError -> % Keep the first error
983	    d("failed connecting [1]: ~p", [Error1]),
984	    sleep(Sleep),
985	    try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error1);
986	Error2 ->
987	    d("failed connecting [2]: ~p", [Error2]),
988	    sleep(Sleep),
989	    try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error0)
990    end;
991try_start_tcp_connect(_RH, Sup, _Opts, _Timeout, Error) ->
992    megaco_tcp:stop_transport(Sup),
993    throw({error, {megaco_tcp_connect, Error}}).
994
995megaco_tcp_connect(RH, SendHandle, ControlPid) ->
996    PrelMgcMid = preliminary_mid,
997    d("megaco connect", []),
998    {ok, CH} = megaco:connect(RH, PrelMgcMid, SendHandle, ControlPid),
999    d("megaco connected: ~p", [CH]),
1000    {ok, CH}.
1001
1002start_udp(MgcPort, MgcHost, RH, TO) ->
1003    d("start udp transport (~p)", [MgcPort]),
1004    case megaco_udp:start_transport() of
1005	{ok, Sup} ->
1006	    d("udp transport started: ~p", [Sup]),
1007	    Opts = [{port, 0}, {receive_handle, RH}] ++ TO,
1008	    d("udp open", []),
1009	    case megaco_udp:open(Sup, Opts) of
1010		{ok, Handle, ControlPid} ->
1011		    d("udp opened: ~p, ~p", [Handle, ControlPid]),
1012		    megaco_udp_connect(MgcPort, MgcHost,
1013				       RH, Handle, ControlPid);
1014		{error, Reason} ->
1015                    throw({error, {megaco_udp_open, Reason}})
1016	    end;
1017        {error, Reason} ->
1018            throw({error, {megaco_udp_start_transport, Reason}})
1019    end.
1020
1021megaco_udp_connect(MgcPort, MgcHost, RH, Handle, ControlPid) ->
1022    MgcMid     = preliminary_mid,
1023    SendHandle = megaco_udp:create_send_handle(Handle, MgcHost, MgcPort),
1024    d("megaco connect", []),
1025    {ok, CH} = megaco:connect(RH, MgcMid, SendHandle, ControlPid),
1026    d("megaco connected: ~p", [CH]),
1027    {ok, CH}.
1028
1029
1030update_load_times(#mg{load_counter = 0} = MG, Times) ->
1031    d("update_load_times(0) -> entry with"
1032      "~n      Times: ~p", [Times]),
1033    {ok, MG#mg{load_counter = Times}};
1034update_load_times(#mg{load_counter = N}, Times) ->
1035    d("update_load_times(~p) -> entry with"
1036      "~n      Times: ~p", [N, Times]),
1037    {error, {already_counting, N}}.
1038
1039
1040do_apply_load(#mg{mid = Mid} = MG) ->
1041    d("do_apply_load -> entry"),
1042    case megaco:user_info(Mid, connections) of
1043	[CH] ->
1044	    do_apply_load(MG, CH);
1045	[] ->
1046	    i("failed to apply load: no connections for ~p", [Mid]),
1047	    MG
1048    end.
1049
1050do_apply_load(#mg{parent          = Parent,
1051		  encode_ar_first = EAF,
1052		  call_mode       = Mode,
1053		  group_size      = Sz,
1054		  load_counter    = N0} = MG, CH) ->
1055    d("do_apply_load -> entry with"
1056      "~n      Mode: ~p"
1057      "~n      Sz:   ~p"
1058      "~n      N0:   ~p", [Mode, Sz, N0]),
1059    {NofSent, Actions, ReplyData} = make_notify_request(N0, Sz),
1060    d("do_apply_load -> notifications constructed:"
1061      "~n      NofSent:   ~p"
1062      "~n      Actions:   ~p"
1063      "~n      ReplyData: ~p", [NofSent, Actions, ReplyData]),
1064    N = N0 - NofSent,
1065    case Mode of
1066	sync ->
1067	    Result = send_sync(EAF, CH, Actions, []),
1068	    d("do_apply_load -> call result when N = ~p: ~n~p", [N,Result]),
1069	    case N of
1070		0 ->
1071		    d("do_apply_load -> load complete"),
1072		    Parent ! {load_complete, self()},
1073		    MG#mg{call_mode = async, load_counter = 0};
1074		_ ->
1075		    d("do_apply_load -> make another round"),
1076		    apply_load_timer(),
1077		    MG#mg{call_mode = async, load_counter = N}
1078	    end;
1079	async ->
1080	    Result = send_async(EAF, CH, Actions, [{reply_data, ReplyData}]),
1081	    d("do_apply_load -> cast result:~n   ~p", [Result]),
1082	    MG#mg{call_mode     = sync,
1083		  load_counter  = N,
1084		  reply_counter = NofSent} % Outstanding replies
1085    end.
1086
1087
1088start_notify_request_handler(EAF, CH, N) ->
1089    d("start_notify_request_handler -> entry with"
1090      "~n      EAF: ~p"
1091      "~n      CH:  ~p"
1092      "~n      N:   ~p", [EAF, CH, N]),
1093    Env = get(),
1094    spawn_link(?MODULE, notify_request_handler_main, [self(), Env, EAF, CH, N]).
1095
1096notify_request_handler_main(Parent, Env, EAF, CH, N) ->
1097    F = fun({Tag, Val}) -> put(Tag, Val) end,
1098    lists:foreach(F, Env),
1099    d("notify_request_handler_main -> entry with"
1100      "~n      Parent: ~p"
1101      "~n      EAF:    ~p"
1102      "~n      CH:     ~p"
1103      "~n      N:      ~p", [Parent, EAF, CH, N]),
1104    Res = do_notify_request(EAF, CH, N),
1105    d("notify_request_handler_main -> notify complete:"
1106      "~n      Res: ~p", [Res]),
1107    Parent ! {notify_request_complete, {ok, Res}, self()},
1108    unlink(Parent),
1109    exit(normal).
1110
1111do_notify_request(_EAF, _CH, N) when N =< 0 ->
1112    d("do_notify_request(~p) -> ignoring", [N]),
1113    ignore;
1114do_notify_request(EAF, CH, 1) ->
1115    d("do_notify_request(1) -> entry with"),
1116    {Action, _} = make_notify_request(),
1117    send_sync(EAF, CH, Action, []);
1118do_notify_request(EAF, CH, N) ->
1119    d("do_notify_request(~p) -> entry with", [N]),
1120    {N, Actions, _} = make_notify_request(N,N),
1121    send_sync(EAF, CH, Actions, []).
1122
1123make_notify_request(N, Sz) when (N >= Sz) andalso (Sz > 0) ->
1124    {Req, ReplyData} = make_notify_request(N, Sz, [], []),
1125    {Sz, Req, ReplyData};
1126make_notify_request(N, _Sz) when N > 0 ->
1127    {Req, ReplyData} = make_notify_request(N, N, [], []),
1128    {N, Req, ReplyData}.
1129
1130
1131
1132make_notify_request(_Offset, 0, Actions, ReplyDatas) ->
1133    {lists:reverse(Actions), lists:reverse(ReplyDatas)};
1134make_notify_request(Offset, N, Actions, ReplyDatas) when N > 0 ->
1135    TimeStamp = cre_timeNotation(),
1136    Event     = cre_observedEvent("al/of", TimeStamp),
1137    Desc      = cre_observedEventsDesc(2000 + N, [Event]),
1138    TidNum    = 2#10000000 + Offset - N,
1139    NotifyReq = cre_notifyReq([#megaco_term_id{id = tid(TidNum)}],Desc),
1140    CmdReq    = cre_commandReq({notifyReq, NotifyReq}),
1141    ActReq    = cre_actionReq(?megaco_null_context_id, [CmdReq]),
1142    make_notify_request(Offset, N-1, [[ActReq]|Actions], [Desc|ReplyDatas]).
1143
1144make_notify_request() ->
1145    TimeStamp  = cre_timeNotation("19990729", "22000000"),
1146    Event      = cre_observedEvent("al/of",   TimeStamp),
1147    Desc1      = cre_observedEventsDesc(2221, [Event]),
1148    Desc2      = cre_observedEventsDesc(2222, [Event]),
1149    Desc3      = cre_observedEventsDesc(2223, [Event]),
1150    Desc4      = cre_observedEventsDesc(2224, [Event]),
1151    NotifyReq1 = cre_notifyReq([#megaco_term_id{id = ?A4444}], Desc1),
1152    NotifyReq2 = cre_notifyReq([#megaco_term_id{id = ?A4445}], Desc2),
1153    CmdReq1    = cre_commandReq({notifyReq, NotifyReq1}),
1154    CmdReq2    = cre_commandReq({notifyReq, NotifyReq2}),
1155    ActReq     = cre_actionReq(?megaco_null_context_id, [CmdReq1,CmdReq2]),
1156    {[ActReq], [Desc3,Desc4]}.
1157
1158
1159cre_actionReq(Cid, Cmds) ->
1160    #'ActionRequest'{contextId       = Cid,
1161		     commandRequests = Cmds}.
1162
1163cre_commandReq(Cmd) ->
1164    #'CommandRequest'{command = Cmd}.
1165
1166cre_serviceChangeReq(TermId, Parms) ->
1167    #'ServiceChangeRequest'{terminationID      = TermId,
1168			    serviceChangeParms = Parms}.
1169
1170cre_serviceChangeParm(Method, Reason) ->
1171    #'ServiceChangeParm'{serviceChangeMethod = Method,
1172			 serviceChangeReason = Reason}.
1173
1174cre_notifyReq(Tid, EvsDesc) ->
1175    #'NotifyRequest'{terminationID = Tid, observedEventsDescriptor = EvsDesc}.
1176
1177% cre_notifyRep(Tid) ->
1178%     #'NotifyReply'{terminationID = [Tid]}.
1179
1180% cre_notifyRep(Tid,Err) ->
1181%     #'NotifyReply'{terminationID = [Tid], errorDescriptor = Err}.
1182
1183cre_observedEventsDesc(Id, EvList) ->
1184    #'ObservedEventsDescriptor'{requestId = Id, observedEventLst = EvList}.
1185
1186cre_observedEvent(Name, Not) ->
1187    #'ObservedEvent'{eventName = Name, timeNotation = Not}.
1188
1189cre_timeNotation() ->
1190    {{Year,Month,Day},{Hour,Min,Sec}} = calendar:universal_time(),
1191    D = lists:flatten(io_lib:format("~4..0w~2..0w~2..0w", [Year,Month,Day])),
1192    T = lists:flatten(io_lib:format("~2..0w~2..0w~4..0w", [Hour,Min,Sec])),
1193    cre_timeNotation(D, T).
1194
1195cre_timeNotation(D,T) ->
1196    #'TimeNotation'{date = D, time = T}.
1197
1198cre_error_descr(Code,Text) ->
1199    #'ErrorDescriptor'{errorCode = Code, errorText = Text}.
1200
1201% cre_error_descr(Code,FormatString,Args) ->
1202%     Text = lists:flatten(io_lib:format(FormatString,Args)),
1203%     cre_error_descr(Code,Text).
1204
1205
1206%% -----------------------
1207%% Handle megaco callbacks
1208%%
1209
1210handle_megaco_request(#mg{state = connecting} = MG,
1211		      {handle_connect, _CH, _PV}) ->
1212    d("handle_megaco_request(handle_connect,connecting) -> entry"),
1213    {ok, MG};
1214handle_megaco_request(#mg{state = S} = MG,
1215		      {handle_connect, _CH, _PV}) ->
1216    d("handle_megaco_request(handle_connect) -> entry"),
1217    Desc =
1218	lists:flatten(io_lib:format("not ready for connect in state ~p", [S])),
1219    ED   =  cre_error_descr(?megaco_internal_gateway_error, Desc),
1220    {{discard_ack, ED}, MG};
1221
1222handle_megaco_request(#mg{req_handler = Pid} = MG,
1223		      {handle_disconnect, _CH, _PV, R})
1224  when is_pid(Pid) ->
1225    d("handle_megaco_request(handle_disconnect) -> entry with"
1226      "~n      Pid: ~p", [Pid]),
1227    Error = {error, {disconnected, R}},
1228    self() ! {notify_request_complete, Error, Pid},
1229    unlink(Pid),
1230    exit(Pid, kill),
1231    {ok, MG#mg{req_handler = undefined, state = initiated}};
1232handle_megaco_request(MG, {handle_disconnect, _CH, _PV, _R}) ->
1233    d("handle_megaco_request(handle_disconnect) -> entry"),
1234    {ok, MG#mg{state = initiated}};
1235
1236handle_megaco_request(MG,
1237		      {handle_syntax_error, _RH, _PV, _ED}) ->
1238    {reply, MG};
1239
1240handle_megaco_request(#mg{req_handler = Pid} = MG,
1241		      {handle_message_error, CH, PV, ED})
1242  when is_pid(Pid) ->
1243    d("handle_megaco_request(handle_message_error) -> entry with"
1244      "~n      Pid:    ~p"
1245      "~n      CH:     ~p"
1246      "~n      PV:     ~p"
1247      "~n      ED:     ~p", [Pid, CH, PV, ED]),
1248    self() ! {notify_request_complete, ED, Pid},
1249    unlink(Pid),
1250    exit(Pid, kill),
1251    {no_reply, MG#mg{req_handler = undefined}};
1252handle_megaco_request(MG, {handle_message_error, CH, PV, ED}) ->
1253    d("handle_megaco_request(handle_message_error) -> entry with"
1254      "~n      CH:     ~p"
1255      "~n      PV:     ~p"
1256      "~n      ED:     ~p", [CH, PV, ED]),
1257    {no_reply, MG};
1258
1259handle_megaco_request(MG, {handle_trans_request, _CH, _PV, _AR}) ->
1260    ED =  cre_error_descr(?megaco_not_implemented,
1261			  "Transaction requests not handled"),
1262    {{discard_ack, ED}, MG};
1263
1264handle_megaco_request(MG,
1265		      {handle_trans_long_request, _CH, _PV, _RD}) ->
1266    ED = cre_error_descr(?megaco_not_implemented,
1267			 "Long transaction requests not handled"),
1268    {{discard_ack, ED}, MG};
1269
1270handle_megaco_request(#mg{rep_info = P} = MG,
1271		      {handle_trans_reply, CH, PV, AR, RD}) when is_pid(P) ->
1272    P ! {rep_received, self(), AR},
1273    do_handle_trans_reply(MG, CH, PV, AR, RD);
1274handle_megaco_request(MG, {handle_trans_reply, CH, PV, AR, RD}) ->
1275    do_handle_trans_reply(MG, CH, PV, AR, RD);
1276
1277handle_megaco_request(#mg{ack_info = P} = MG,
1278		      {handle_trans_ack, _CH, _PV, AS, _AD}) when is_pid(P) ->
1279    d("handle_megaco_request(handle_trans_ack,~p) -> entry",[P]),
1280    P ! {ack_received, self(), AS},
1281    {ok, MG};
1282handle_megaco_request(MG, {handle_trans_ack, _CH, _PV, _AS, _AD}) ->
1283    d("handle_megaco_request(handle_trans_ack) -> entry"),
1284    {ok, MG}.
1285
1286do_handle_trans_reply(#mg{parent = Parent, state = connecting} = MG,
1287		      CH, _PV, {ok, Rep}, _RD) ->
1288    d("do_handle_trans_reply(connecting) -> entry with"
1289      "~n      CH:     ~p"
1290      "~n      Rep:    ~p", [CH, Rep]),
1291    server_reply(Parent, service_change_reply, ok),
1292    {ok, MG#mg{state = connected}};
1293do_handle_trans_reply(#mg{parent = Parent, load_counter = 0} = MG,
1294		      CH, _PV, {ok, Rep}, _RD) ->
1295    d("do_handle_trans_reply(load_counter = 0) -> entry with"
1296      "~n      CH:     ~p"
1297      "~n      Rep:    ~p", [CH, Rep, Parent]),
1298    handle_trans_reply_verify_act(Rep),
1299    server_reply(Parent, load_complete, ok),
1300    {ok, MG#mg{reply_counter = 0}};
1301do_handle_trans_reply(#mg{reply_counter = 0} = MG,
1302		      CH, _PV, {ok, Rep}, _RD) ->
1303    d("do_handle_trans_reply(reply_counter = 0) -> entry with"
1304      "~n      CH:     ~p"
1305      "~n      Rep:    ~p", [CH, Rep]),
1306    handle_trans_reply_verify_act(Rep),
1307    apply_load_timer(),
1308    {ok, MG};
1309do_handle_trans_reply(#mg{reply_counter = N} = MG,
1310		      CH, _PV, {ok, Rep}, _RD) ->
1311    d("do_handle_trans_reply(reply_counter = ~p) -> entry with"
1312      "~n      CH:     ~p"
1313      "~n      Rep:    ~p", [N, CH, Rep]),
1314    handle_trans_reply_verify_act(Rep),
1315    apply_load_timer(),
1316    {ok, MG#mg{reply_counter = N-1}};
1317do_handle_trans_reply(MG, _CH, _PV, {error, ED}, _RD) ->
1318    i("unexpected error transaction: ~p", [ED]),
1319    {ok, MG}.
1320
1321
1322handle_trans_reply_verify_act([]) ->
1323    ok;
1324handle_trans_reply_verify_act([#'ActionReply'{commandReply = Rep}|Reps]) ->
1325    handle_trans_reply_verify_cmd(Rep),
1326    handle_trans_reply_verify_act(Reps);
1327handle_trans_reply_verify_act([Rep|Reps]) ->
1328    i("received 'propably' unexpected reply: ~n~p", [Rep]),
1329    handle_trans_reply_verify_act(Reps).
1330
1331handle_trans_reply_verify_cmd([]) ->
1332    ok;
1333handle_trans_reply_verify_cmd([Cmd|Cmds]) ->
1334    case Cmd of
1335	{notifyReply, #'NotifyReply'{terminationID = [Tid]}} ->
1336	    d("received expected notification reply from ~n   ~p", [Tid]);
1337	Else ->
1338	    i("received unexpected notification reply ~n~p", [Else])
1339    end,
1340    handle_trans_reply_verify_cmd(Cmds).
1341
1342
1343%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1344
1345notify_started(Parent) ->
1346    Parent ! {started, self()}.
1347
1348
1349%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1350
1351%% The megaco user callback interface
1352
1353handle_connect(CH, PV, Pid, Mid) ->
1354    case CH#megaco_conn_handle.remote_mid of
1355        preliminary_mid ->
1356	    %% Avoids deadlock
1357	    ok;
1358	_ ->
1359	    Reply = request(Pid, {handle_connect, CH, PV}, Mid),
1360	    Reply
1361    end.
1362
1363handle_disconnect(_CH, _PV,
1364		  {user_disconnect, {stopped_by_user, Pid}},
1365		  Pid, _Mid) ->
1366    ok;
1367handle_disconnect(CH, PV, R, Pid, Mid) ->
1368    request(Pid, {handle_disconnect, CH, PV, R}, Mid).
1369
1370handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) ->
1371    Req = {handle_syntax_error, ReceiveHandle, ProtocolVersion,
1372	   ErrorDescriptor},
1373    request(Pid, Req, Mid).
1374
1375handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) ->
1376    Req = {handle_message_error, ConnHandle, ProtocolVersion, ErrorDescriptor},
1377    request(Pid, Req, Mid).
1378
1379handle_trans_request(CH, PV, AR, Pid, Mid) ->
1380    Reply = request(Pid, {handle_trans_request, CH, PV, AR}, Mid),
1381    Reply.
1382
1383handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Pid, Mid) ->
1384    Req = {handle_trans_long_request, ConnHandle, ProtocolVersion, ReqData},
1385    request(Pid, Req, Mid).
1386
1387handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData, Pid, Mid) ->
1388    Req = {handle_trans_reply, ConnHandle, ProtocolVersion,
1389	   ActualReply, ReplyData},
1390    request(Pid, Req, Mid).
1391
1392handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Pid, Mid) ->
1393    Req = {handle_trans_ack, ConnHandle, ProtocolVersion, AckStatus, AckData},
1394    request(Pid, Req, Mid).
1395
1396
1397request(Pid, Request, Mid) ->
1398    Pid ! {request, Request, Mid, self()},
1399    receive
1400	{reply, {delay, To, ED}, Pid} ->
1401	    sleep(To),
1402	    {discard_ack, ED};
1403	{reply, Reply, Pid} ->
1404	    Reply
1405    end.
1406
1407
1408%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1409
1410send_async(true, CH, Actions, Opts) ->
1411    d("send_async(true) -> encode actions first"),
1412    case megaco:encode_actions(CH, Actions, Opts) of
1413	{ok, BinOrBins} ->
1414	    d("send_async(true) -> send message"),
1415	    megaco:cast(CH, BinOrBins, Opts);
1416	Error ->
1417	    d("send_async(true) -> encode failed: ~n~p", [Error]),
1418	    Error
1419    end;
1420send_async(_, CH, Actions, Opts) ->
1421    d("send_async(true) -> send message"),
1422    megaco:cast(CH, Actions, Opts).
1423
1424send_sync(true, CH, Actions, Opts) ->
1425    d("send_sync(true) -> encode actions first"),
1426    case megaco:encode_actions(CH, Actions, Opts) of
1427	{ok, BinOrBins} ->
1428	    d("send_sync(true) -> send message"),
1429	    megaco:call(CH, BinOrBins, Opts);
1430	Error ->
1431	    d("send_sync(true) -> encode failed: ~n~p", [Error]),
1432	    Error
1433    end;
1434send_sync(_, CH, Actions, Opts) ->
1435    d("send_sync(false) -> send message"),
1436    megaco:call(CH, Actions, Opts).
1437
1438
1439%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1440
1441sleep(X) ->
1442    receive after X -> ok end.
1443
1444
1445info_msg(F,A)  -> error_logger:info_msg("MG: " ++ F ++ "~n",A).
1446error_msg(F,A) -> error_logger:error_msg("MG: " ++ F ++ "~n",A).
1447
1448
1449get_encoding_module(RI) ->
1450    case (catch get_conf(encoding_module, RI)) of
1451	{error, _} ->
1452	    undefined;
1453	Val ->
1454	    Val
1455    end.
1456
1457get_encoding_config(RI, EM) ->
1458    case text_codec(EM) of
1459	true ->
1460	    case megaco:system_info(text_config) of
1461		[Conf] when is_list(Conf) ->
1462		    Conf;
1463		_ ->
1464		    []
1465	    end;
1466
1467	false ->
1468	    get_conf(encoding_config, RI)
1469    end.
1470
1471text_codec(megaco_compact_text_encoder) ->
1472    true;
1473text_codec(megaco_pretty_text_encoder) ->
1474    true;
1475text_codec(_) ->
1476    false.
1477
1478
1479get_transport_module(RI) ->
1480    get_conf(transport_module, RI).
1481
1482get_transport_port(RI) ->
1483    get_conf(port, RI).
1484
1485get_transport_host(RI) ->
1486    {ok, LocalHost} = inet:gethostname(),
1487    get_conf(host, RI, LocalHost).
1488
1489get_transport_opts(RI) ->
1490    get_conf(transport_opts, RI, []).
1491
1492
1493get_conf(Key, Config) ->
1494    case lists:keysearch(Key, 1, Config) of
1495	{value, {Key, Val}} ->
1496	    Val;
1497	_ ->
1498	    exit({error, {not_found, Key, Config}})
1499    end.
1500
1501get_conf(Key, Config, Default) ->
1502    case lists:keysearch(Key, 1, Config) of
1503	{value, {Key, Val}} ->
1504	    Val;
1505	_ ->
1506	    Default
1507    end.
1508
1509
1510%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1511
1512tid(N) when N >= 0 ->
1513    {Rem1, Val1} = num2str(N),
1514    {Rem2, Val2} = num2str(Rem1),
1515    {0,    Val3} = num2str(Rem2),
1516    [Val3, Val2, Val1].
1517
1518num2str(N) when N >= 0 ->
1519    num2str(N, []).
1520
1521num2str(Rem, Val) when length(Val) == 8 ->
1522    {Rem, Val};
1523num2str(N, Val) ->
1524    D = N div 2,
1525    case N rem 2 of
1526	1 ->
1527	    num2str(D, [$1|Val]);
1528	0 ->
1529	    num2str(D, [$0|Val])
1530    end.
1531
1532
1533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1534
1535%% e(F) ->
1536%%     i(F, []).
1537
1538e(F, A) ->
1539    print(error, get(verbosity), "ERROR", F, A).
1540
1541
1542i(F) ->
1543    i(F, []).
1544
1545i(F, A) ->
1546    print(info, get(verbosity), "INFO", F, A).
1547
1548
1549d(F) ->
1550    d(F, []).
1551
1552d(F, A) ->
1553    print(debug, get(verbosity), "DBG", F, A).
1554
1555
1556printable(error, _)   -> true;
1557printable(_, debug)   -> true;
1558printable(info, info) -> true;
1559printable(_,_)        -> false.
1560
1561print(Severity, Verbosity, P, F, A) ->
1562    print(printable(Severity, Verbosity), P, F, A).
1563
1564print(true, P, F, A) ->
1565    io:format("*** [~s] [~s] ~p ~s ***"
1566              "~n   " ++ F ++ "~n~n",
1567              [?FTS(), P, self(), get(sname) | A]);
1568print(_, _, _, _) ->
1569    ok.
1570
1571
1572
1573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1574
1575random_init() ->
1576    ok.
1577
1578random() ->
1579    random(50).
1580random(N) ->
1581    rand:uniform(N).
1582
1583
1584display_system_info(Mid) ->
1585    display_system_info(Mid, "").
1586
1587display_system_info(Mid, Pre) ->
1588    TimeStr = ?FTS(),
1589    MibStr  = lists:flatten(io_lib:format("~p ", [Mid])),
1590    megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr).
1591
1592
1593create_timer(Time, Event) ->
1594    erlang:send_after(Time, self(), {Event, Time}).
1595
1596cancel_timer(undefined) ->
1597    ok;
1598cancel_timer(Ref) ->
1599    erlang:cancel_timer(Ref).
1600
1601
1602apply_load_timer() ->
1603    create_timer(random(), apply_load_timeout).
1604
1605
1606