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