1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2007-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: megaco sequence generator for the megaco test suite
24%%----------------------------------------------------------------------
25
26-module(megaco_test_megaco_generator).
27
28-behaviour(megaco_test_generator).
29
30-compile({no_auto_import,[error/1]}).
31
32%% API
33-export([
34         start_link/1, start_link/2,
35	 stop/1,
36         exec/2, exec/3
37        ]).
38
39%% genarator behaviour callback exports
40-export([
41         init/1,
42         handle_parse/2,
43         handle_exec/2,
44         terminate/2
45        ]).
46
47%% Megaco callback api
48-export([
49         handle_connect/3, handle_connect/4,
50         handle_disconnect/4,
51         handle_syntax_error/4,        handle_syntax_error/5,
52         handle_message_error/4,       handle_message_error/5,
53         handle_trans_request/4,       handle_trans_request/5,
54         handle_trans_long_request/4,  handle_trans_long_request/5,
55         handle_trans_reply/5,         handle_trans_reply/6,
56         handle_trans_ack/5,           handle_trans_ack/6,
57         handle_trans_request_abort/5, handle_trans_request_abort/6,
58         handle_unexpected_trans/4,    handle_unexpected_trans/5
59        ]).
60
61
62%%----------------------------------------------------------------------
63
64-include_lib("megaco/include/megaco.hrl").
65
66
67%%----------------------------------------------------------------------
68
69-define(DELIVER_MOD, megaco_test_deliver).
70
71
72%%----------------------------------------------------------------------
73
74-record(state,
75	{
76	  mid,
77	  recv_handle,
78	  port,
79	  send_handle,
80	  conn_handle,
81
82	  transport_sup,
83	  ctrl_pid,
84
85	  result = [] % Accumulated results from verification
86	 }).
87
88
89%%----------------------------------------------------------------------
90%% API
91%%----------------------------------------------------------------------
92
93start_link(Name) ->
94    megaco_test_generator:start_link(?MODULE, [], Name).
95
96start_link(Name, Node) ->
97    megaco_test_generator:start_link(?MODULE, [], Name, Node).
98
99stop(Server) ->
100    megaco_test_generator:stop(Server).
101
102exec(Server, Instructions) when is_list(Instructions) ->
103    megaco_test_generator:exec(Server, Instructions).
104
105exec(Server, Instructions, Timeout) when is_list(Instructions) ->
106    megaco_test_generator:exec(Server, Instructions, Timeout).
107
108
109%%----------------------------------------------------------------------
110%% generator callback functions
111%%----------------------------------------------------------------------
112
113init([]) ->
114    random_init(),
115    {ok, #state{}}.
116
117
118%% ----- instruction parser -----
119
120handle_parse({debug, Debug} = Instruction, State)
121  when is_boolean(Debug) ->
122    {ok, Instruction, State};
123
124handle_parse({expect_nothing, To} = Instruction, State)
125  when is_integer(To) andalso (To > 0) ->
126    {ok, Instruction, State};
127
128handle_parse({megaco_trace, Level} = Instruction, State)
129  when (Level =:= disable) orelse
130       (Level =:= max)     orelse
131       (Level =:= min)     orelse
132       is_integer(Level) ->
133    {ok, Instruction, State};
134
135handle_parse({sleep, To} = Instruction, State)
136  when is_integer(To) andalso (To > 0) ->
137    {ok, Instruction, State};
138
139handle_parse(megaco_start = Instruction, State) ->
140    {ok, Instruction, State};
141
142handle_parse(megaco_stop = Instruction, State) ->
143    {ok, Instruction, State};
144
145handle_parse({megaco_start_user, _Mid, _RecvInfo, Conf} = Instruction, State)
146  when is_list(Conf) ->
147    {ok, Instruction, State};
148
149handle_parse(megaco_stop_user = Instruction, State) ->
150    {ok, Instruction, State};
151
152handle_parse(megaco_info = Instruction, State) ->
153    {ok, Instruction, State};
154
155handle_parse(megaco_system_info, State) ->
156    Verify = fun(_) -> ok end,
157    Instruction = {megaco_system_info, internal_system_info_tag, Verify},
158    {ok, Instruction, State};
159
160handle_parse({megaco_system_info, Tag}, State)
161  when is_atom(Tag) ->
162    Verify = fun(_) -> ok end,
163    Instruction = {megaco_system_info, Tag, Verify},
164    {ok, Instruction, State};
165
166handle_parse({megaco_system_info, Tag, Verify} = Instruction, State)
167  when is_atom(Tag) andalso is_function(Verify) ->
168    {ok, Instruction, State};
169
170handle_parse({megaco_user_info, Tag} = Instruction, State)
171  when is_atom(Tag) ->
172    {ok, Instruction, State};
173
174handle_parse({megaco_update_user_info, Tag, _Val} = Instruction, State)
175  when is_atom(Tag) ->
176    {ok, Instruction, State};
177
178handle_parse({megaco_conn_info, Tag} = Instruction, State)
179  when is_atom(Tag) ->
180    {ok, Instruction, State};
181
182handle_parse({megaco_update_conn_info, Tag, _Val} = Instruction, State)
183  when is_atom(Tag) ->
184    {ok, Instruction, State};
185
186handle_parse(start_transport = Instruction, State) ->
187    {ok, Instruction, State};
188
189handle_parse(listen = _Instruction, State) ->
190    MeybeRetry  = make_connect_retry_fun2(),
191    Instruction = {listen, [], MeybeRetry},
192    {ok, Instruction, State};
193
194handle_parse({listen, Opts} = _Instruction, State)
195  when is_list(Opts) ->
196    MeybeRetry  = make_connect_retry_fun2(),
197    Instruction = {listen, Opts, MeybeRetry},
198    {ok, Instruction, State};
199
200handle_parse({listen, Opts, MeybeRetry} = Instruction, State)
201  when is_list(Opts) andalso is_function(MeybeRetry) ->
202    {ok, Instruction, State};
203
204handle_parse(connect = _Instruction, State) ->
205    case inet:gethostname() of
206	{ok, LocalHost} ->
207	    MeybeRetry  = make_connect_retry_fun2(),
208	    Instruction = {connect, LocalHost, [], MeybeRetry},
209	    {ok, Instruction, State};
210	Error ->
211	    Error
212    end;
213
214handle_parse({connect, Opts} = _Instruction, State)
215  when is_list(Opts) ->
216    verify_connect_opts(Opts),
217    case inet:gethostname() of
218	{ok, LocalHost} ->
219	    MeybeRetry  = make_connect_retry_fun2(),
220	    Instruction = {connect, LocalHost, Opts, MeybeRetry},
221	    {ok, Instruction, State};
222	Error ->
223	    Error
224    end;
225
226handle_parse({connect, Host} = _Instruction, State)
227  when is_atom(Host) ->
228    MeybeRetry  = make_connect_retry_fun2(),
229    Instruction = {connect, Host, [], MeybeRetry},
230    {ok, Instruction, State};
231
232handle_parse({connect, Host, Opts} = _Instruction, State)
233  when (is_atom(Host) orelse is_list(Host)) andalso is_list(Opts) ->
234    verify_connect_opts(Opts),
235    MeybeRetry  = make_connect_retry_fun2(),
236    Instruction = {connect, Host, Opts, MeybeRetry},
237    {ok, Instruction, State};
238
239handle_parse({connect, Host, Opts, MeybeRetry} = Instruction, State)
240  when (is_atom(Host) orelse is_list(Host)) andalso
241       is_list(Opts) andalso
242       is_function(MeybeRetry) ->
243    verify_connect_opts(Opts),
244    {ok, Instruction, State};
245
246handle_parse(disconnect = Instruction, State) ->
247    {ok, Instruction, State};
248
249handle_parse(megaco_connect = Instruction, State) ->
250    {ok, Instruction, State};
251
252handle_parse({megaco_connect, _} = Instruction, State) ->
253    {ok, Instruction, State};
254
255handle_parse(megaco_disconnect = Instruction, State) ->
256    {ok, Instruction, State};
257
258handle_parse({megaco_disconnect, _Reason} = Instruction, State) ->
259    {ok, Instruction, State};
260
261handle_parse({megaco_call, ARs, Opts} = Instruction, State)
262  when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
263    {ok, Instruction, State};
264
265handle_parse({megaco_call, _Mid, ARs, Opts} = Instruction, State)
266  when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
267    {ok, Instruction, State};
268
269handle_parse({megaco_cast, ARs, Opts} = Instruction, State)
270  when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
271    {ok, Instruction, State};
272
273handle_parse({megaco_cast, _Mid, ARs, Opts} = Instruction, State)
274  when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
275    {ok, Instruction, State};
276
277handle_parse({megaco_cancel, _Reason} = Instruction, State) ->
278    {ok, Instruction, State};
279
280handle_parse({megaco_callback, Tag, TimeoutOrVerify} = Instruction, State)
281  when (is_atom(Tag) andalso
282	((is_integer(TimeoutOrVerify) andalso
283	  (TimeoutOrVerify > 0)) orelse
284	 is_function(TimeoutOrVerify))) ->
285    {ok, Instruction, State};
286
287handle_parse({megaco_callback, Tag, Verify, Timeout} = Instruction, State)
288  when (is_atom(Tag) andalso
289	is_function(Verify) andalso
290	(is_integer(Timeout) andalso (Timeout > 0))) ->
291    {ok, Instruction, State};
292
293handle_parse({megaco_callback, Tag, {VMod, VFunc, VArgs}} = _Instruction,
294	     State)
295  when (is_atom(Tag) andalso
296	(is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs))) ->
297    Verify = fun(X) ->
298                     io:format("[megaco_callback ~w] calling ~w:~w with"
299                               "~n   X:     ~p"
300                               "~n   VArgs: ~w"
301                               "~n", [Tag, VMod, VFunc, X, VArgs]),
302                     (catch apply(VMod, VFunc, [X|VArgs]))
303             end,
304    Instruction = {megaco_callback, Tag, Verify},
305    {ok, Instruction, State};
306
307handle_parse({megaco_callback, Verifiers0} = _Instruction, State)
308  when is_list(Verifiers0) ->
309    Verifiers = [make_verifier(Verifier) || Verifier <- Verifiers0],
310    Instruction = {megaco_callback, Verifiers},
311    {ok, Instruction, State};
312
313handle_parse({trigger, Trigger} = Instruction, State)
314  when is_function(Trigger) ->
315    {ok, Instruction, State};
316
317handle_parse(Instruction, _State) ->
318    error({invalid_instruction, Instruction}).
319
320
321make_verifier({Tag, No, VerifyFunc} = Verify)
322  when is_atom(Tag) andalso is_integer(No) andalso is_function(VerifyFunc) ->
323    Verify;
324make_verifier({Tag, No, {VMod, VFunc, VArgs}})
325  when is_atom(Tag) andalso is_integer(No) andalso
326       (is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs)) ->
327    VerifyFunc = fun(X) ->
328                         io:format("[megaco_callback ~w] calling ~w:~w with"
329                                   "~n   X: ~p"
330                                   "~n   VArgs: ~w"
331                                   "~n", [Tag, VMod, VFunc, X, VArgs]),
332                         (catch apply(VMod, VFunc, [X|VArgs]))
333                 end,
334    Verify = {Tag, No, VerifyFunc},
335    Verify;
336make_verifier(BadVerifier) ->
337    error({bad_verifier, BadVerifier}).
338
339
340verify_connect_opts([]) ->
341    ok;
342verify_connect_opts([{Key, _}|Opts]) when is_atom(Key) ->
343    verify_connect_opts(Opts);
344verify_connect_opts([H|_]) ->
345    error({bad_opts_list, H}).
346
347%% make_connect_retry_fun1() ->
348%%      fun(Error, _) ->
349%% 	     {false, Error}
350%%      end.
351
352make_connect_retry_fun2() ->
353     fun(Error, noError) ->
354	     Timeout = 250,
355	     sleep(random(Timeout) + 100),
356	     {true, {3, Timeout*2, Error}};
357	(_Error, {0, _Timeout, OriginalError}) ->
358	     {false, OriginalError};
359	(_Error, {N, Timeout, OriginalError}) ->
360	     sleep(random(Timeout) + 100),
361	     {true, {N-1, Timeout*2, OriginalError}}
362     end.
363
364
365%% ----- instruction exececutor -----
366
367handle_exec({debug, Debug}, State) ->
368    p("debug: ~p", [Debug]),
369    put(debug, Debug),
370    {ok, State};
371
372handle_exec({expect_nothing, To}, State) ->
373    p("expect nothing: ~p", [To]),
374    receive
375        Any ->
376            e("received unexpected: "
377              "~n   ~p", [Any]),
378            error({expect_nothing, Any})
379    after To ->
380            p("go nothing (~p) as expected", [To]),
381            {ok, State}
382    end;
383
384handle_exec({megaco_trace, disable}, State) ->
385    p("megaco trace: disable"),
386    megaco:disable_trace(),
387    {ok, State};
388handle_exec({megaco_trace, Level}, State) ->
389    p("megaco trace: enable [~w]", [Level]),
390    megaco:enable_trace(Level, io),
391    {ok, State};
392
393handle_exec(megaco_start, State) ->
394    p("start megaco"),
395    ok = megaco:start(),
396    {ok, State};
397
398handle_exec(megaco_stop, State) ->
399    p("stop megaco"),
400    ok = megaco:stop(),
401    {ok, State};
402
403handle_exec({megaco_start_user, Mid, RecvInfo, Conf}, State) ->
404    p("start megaco user: ~p", [Mid]),
405
406    d("megaco_start_user -> start user"),
407    ok = megaco:start_user(Mid, Conf),
408
409    d("megaco_start_user -> update user info: user_mod"),
410    ok = megaco:update_user_info(Mid, user_mod,  ?MODULE),
411
412    d("megaco_start_user -> update user info: user_args"),
413    ok = megaco:update_user_info(Mid, user_args,  [self()]),
414
415    Port = get_config(port,             RecvInfo),
416    EM   = get_config(encoding_module,  RecvInfo),
417    EC   = get_config(encoding_config,  RecvInfo),
418    TM   = get_config(transport_module, RecvInfo),
419    RH0  = megaco:user_info(Mid, receive_handle),
420
421    RH1  = RH0#megaco_receive_handle{send_mod        = TM,
422				     encoding_mod    = EM,
423				     encoding_config = EC},
424
425    State1 = State#state{mid = Mid, recv_handle = RH1, port = Port},
426    {ok, State1};
427
428handle_exec(megaco_stop_user, #state{mid = Mid} = State)
429  when Mid /= undefined ->
430    p("stop megaco user: ~p", [Mid]),
431    megaco_cleanup(State),
432    ok = megaco:stop_user(Mid),
433    {ok, State#state{mid = undefined}};
434
435handle_exec(start_transport,
436            #state{recv_handle = #megaco_receive_handle{send_mod = TM}} = State) ->
437    p("start transport ~p", [TM]),
438    case (catch TM:start_transport()) of
439	{ok, Sup} ->
440	    d("transport started: Sup: ~p", [Sup]),
441	    {ok, State#state{transport_sup = Sup}};
442	{error, Reason} ->
443	    e("failed starting transport (~w): "
444	      "~n   ~p", [TM, Reason]),
445	    error({failed_starting_transport, TM, Reason});
446	Crap ->
447	    e("failed starting transport (~w): "
448	      "~n   ~p", [TM, Crap]),
449	    error({failed_starting_transport, TM, Crap})
450    end;
451
452handle_exec({listen, Opts0, MaybeRetry},
453     #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
454  when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
455    p("listen(tcp)"),
456    Opts = [{module,         ?DELIVER_MOD},
457	    {port,           Port},
458	    {receive_handle, RH},
459	    {tcp_options,    [{nodelay, true}]} | Opts0],
460    case (catch handle_exec_listen_tcp(Pid, Opts, MaybeRetry)) of
461        ok ->
462            {ok, State};
463        Else ->
464            error({tcp_listen_failed, Opts0, Else})
465    end;
466handle_exec({listen, Opts0, _MaybeRetry},
467     #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
468  when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
469    p("listen(udp) - open"),
470    Opts = [{module, ?DELIVER_MOD}, {port, Port}, {receive_handle, RH}|Opts0],
471    case (catch megaco_udp:open(Pid, Opts)) of
472        {ok, _SH, _CtrlPid} ->
473            {ok, State};
474        Else ->
475            error({udp_open, Opts0, Else})
476    end;
477handle_exec({listen, Opts0, _MaybeRetry},
478            #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
479  when RH#megaco_receive_handle.send_mod =:= megaco_test_generic_transport ->
480    p("listen(generic)"),
481    Opts = [{module, ?DELIVER_MOD}, {port, Port}, {receive_handle, RH}|Opts0],
482    case (catch megaco_test_generic_transport:listen(Pid, Opts)) of
483        {ok, _SH, _CtrlPid} ->
484            {ok, State};
485        Else ->
486            error({udp_open, Opts0, Else})
487    end;
488
489handle_exec({connect, Host, Opts0, MaybeRetry},
490     #state{transport_sup = Sup,
491	     recv_handle  = RH,
492	     port         = Port} = State)
493  when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
494    p("connect(tcp) to ~p:~p", [Host, Port]),
495    PrelMid = preliminary_mid,
496    Opts = [{host,           Host},
497	    {port,           Port},
498	    {receive_handle, RH},
499	    {tcp_options,    [{nodelay, true}]} | Opts0],
500    case (catch handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry)) of
501	{ok, SH, ControlPid} ->
502	    d("connected(tcp): ~p, ~p", [SH, ControlPid]),
503	    megaco_connector_start(RH, PrelMid, SH, ControlPid),
504	    {ok, State#state{send_handle = SH,
505			      ctrl_pid    = ControlPid}};
506	Error ->
507	    error({tcp_connect_failed, Host, Opts0, Error})
508    end;
509
510handle_exec({connect, Host, Opts0, _MaybeRetry},
511     #state{transport_sup = Sup,
512	     recv_handle   = RH,
513	     port          = Port} = State)
514  when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
515    p("connect(udp) to ~p", [Host]),
516    PrelMid = preliminary_mid,
517    Opts = [{port, 0}, {receive_handle, RH}|Opts0],
518    d("udp open", []),
519    case (catch megaco_udp:open(Sup, Opts)) of
520	{ok, Handle, ControlPid} ->
521	    d("opened(udp): ~p, ~p", [Handle, ControlPid]),
522	    SH = megaco_udp:create_send_handle(Handle, Host, Port),
523	    megaco_connector_start(RH, PrelMid, SH, ControlPid),
524	    {ok, State#state{send_handle = SH,
525			      ctrl_pid    = ControlPid}};
526	Error ->
527	    error({udp_connect_failed, Host, Opts0, Error})
528    end;
529
530handle_exec({connect, Host, Opts0, _MaybeRetry},
531     #state{transport_sup = Sup,
532	     recv_handle   = RH,
533	     port          = Port} = State)
534  when RH#megaco_receive_handle.send_mod =:= megaco_test_generic_transport ->
535    p("connect(generic) to ~p", [Host]),
536    PrelMid = preliminary_mid,
537    Opts = [{host, Host}, {port, Port}, {receive_handle, RH}|Opts0],
538    case (catch megaco_test_generic_transport:connect(Sup, Opts)) of
539	{ok, SH, ControlPid} ->
540	    d("connected(generic): ~p, ~p", [SH, ControlPid]),
541	    megaco_connector_start(RH, PrelMid, SH, ControlPid),
542	    {ok, State#state{send_handle = SH,
543			      ctrl_pid    = ControlPid}};
544	Error ->
545	    error({generic_connect_failed, Host, Opts0, Error})
546    end;
547
548handle_exec(megaco_connect, State) ->
549    p("expect megaco_connect"),
550    receive
551        {megaco_connect_result, {ok, CH}} ->
552            p("received successful megaco_connect: ~p", [CH]),
553            {ok, State#state{conn_handle = CH}};
554        {megaco_connect_result, Error} ->
555            p("received failed megaco_connect: ~p", [Error]),
556            #state{result = Res} = State,
557            {ok, State#state{result = [Error|Res]}}
558    end;
559
560handle_exec({megaco_connect, Mid},
561	    #state{recv_handle = RH,
562		   send_handle = SH,
563		   ctrl_pid    = ControlPid} = State) ->
564    p("megaco connect: ~p", [Mid]),
565    megaco_connector_start(RH, Mid, SH, ControlPid),
566    {ok, State};
567
568handle_exec({megaco_user_info, Tag}, #state{mid = Mid, result = Res} = State)
569  when Mid /= undefined ->
570    p("megaco user-info: ~w", [Tag]),
571    Val = (catch megaco:user_info(Mid, Tag)),
572    d("megaco_user_info: ~p", [Val]),
573    {ok, State#state{result = [Val|Res]}};
574
575handle_exec({megaco_update_user_info, Tag, Val}, #state{mid = Mid} = State)
576  when Mid /= undefined ->
577    p("update megaco user-info: ~w -> ~p", [Tag, Val]),
578    ok = megaco:update_user_info(Mid, Tag, Val),
579    {ok, State};
580
581handle_exec({megaco_conn_info, Tag},
582     #state{conn_handle = CH, result = Res} = State)
583  when CH /= undefined ->
584    p("megaco conn-info: ~w", [Tag]),
585    Val = (catch megaco:conn_info(CH, Tag)),
586    d("megaco_conn_info: ~p", [Val]),
587    {ok, State#state{result = [Val|Res]}};
588
589handle_exec({megaco_update_conn_info, Tag, Val},
590     #state{conn_handle = CH} = State)
591  when CH /= undefined ->
592    p("update megaco conn-info: ~w -> ~p", [Tag, Val]),
593    case megaco:update_conn_info(CH, Tag, Val) of
594        ok ->
595            {ok, State};
596        Error ->
597            error({failed_updating_conn_info, Tag, Val, Error})
598    end;
599
600handle_exec(megaco_info, #state{result = Res} = State) ->
601    p("megaco info", []),
602    Val = (catch megaco:info()),
603    d("megaco_info: ~p", [Val]),
604    {ok, State#state{result = [Val|Res]}};
605
606handle_exec({megaco_system_info, Tag, Verify}, #state{result = Res} = State) ->
607    p("megaco system-info: ~w", [Tag]),
608    Val = (catch megaco:system_info(Tag)),
609    d("megaco system-info: ~p", [Val]),
610    case Verify(Val) of
611	ok ->
612	    {ok, State#state{result = [Val|Res]}};
613	Error ->
614	    {error, State#state{result = [Error|Res]}}
615    end;
616
617%% This is either a MG or a MGC which is only connected to one MG
618handle_exec({megaco_call, ARs, Opts}, #state{conn_handle = CH} = State)
619  when CH /= undefined ->
620    p("megaco_call: "
621      "~n      CH:   ~p"
622      "~n      ARs:  ~p"
623      "~n      Opts: ~p", [CH, ARs, Opts]),
624    {_PV, UserReply} = megaco:call(CH, ARs, Opts),
625    d("megaco_call -> UserReply: ~n~p", [UserReply]),
626    {ok, State};
627
628handle_exec({megaco_call, RemoteMid, ARs, Opts}, #state{mid = Mid} = State) ->
629    p("megaco_call: ~p", [RemoteMid]),
630    %% First we have to find the CH for this Mid
631    Conns = megaco:user_info(Mid, connections),
632    {value, {_, CH}} =
633        lists:keysearch(RemoteMid, #megaco_conn_handle.remote_mid, Conns),
634    p("megaco_call: "
635      "~n      CH:   ~p"
636      "~n      ARs:  ~p"
637      "~n      Opts: ~p", [CH, ARs, Opts]),
638    {_PV, UserReply} = megaco:call(CH, ARs, Opts),
639    d("megaco_call -> UserReply: ~n~p", [UserReply]),
640    {ok, State};
641
642%% This is either a MG or a MGC which is only connected to one MG
643handle_exec({megaco_cast, ARs, Opts}, #state{conn_handle = CH} = State)
644  when CH =/= undefined ->
645    p("megaco_cast: "
646      "~n      CH:  ~p"
647      "~n      ARs: ~p", [CH, ARs]),
648    case megaco:cast(CH, ARs, Opts) of
649        ok ->
650            {ok, State};
651        Error ->
652            e("failed sending (cast) message: ~n~p", [Error]),
653            #state{result = Acc} = State,
654            {error, State#state{result = [Error|Acc]}}
655    end;
656
657handle_exec({megaco_cast, RemoteMid, ARs, Opts}, #state{mid = Mid} = State) ->
658    p("megaco_cast with ~p", [RemoteMid]),
659    %% First we have to find the CH for this Mid
660    Conns = megaco:user_info(Mid, connections),
661    {value, {_, CH}} =
662        lists:keysearch(RemoteMid, #megaco_conn_handle.remote_mid, Conns),
663    p("megaco_cast: "
664      "~n      CH:   ~p"
665      "~n      ARs:  ~p"
666      "~n      Opts: ~p", [CH, ARs, Opts]),
667    case megaco:cast(CH, ARs, Opts) of
668        ok ->
669            {ok, State};
670        Error ->
671            e("failed sending (cast) message: "
672              "~n      ~p", [Error]),
673            #state{result = Acc} = State,
674            {error, State#state{result = [Error|Acc]}}
675    end;
676
677%% Nothing shall happen for atleast Timeout time
678handle_exec({megaco_callback, nocall, Timeout}, State) ->
679    p("expect no megaco_callback for ~w", [Timeout]),
680    receive
681        {handle_megaco_callback, Type, Msg, Pid} ->
682            e("received unexpected megaco callback: ~n~p", [Msg]),
683            #state{result = Res} = State,
684            Err = {unexpected_callback, Type, Msg, Pid},
685            {error, State#state{result = [Err|Res]}}
686    after Timeout ->
687            p("got no callback (~p) as expected", [Timeout]),
688            {ok, State}
689    end;
690
691handle_exec({megaco_callback, Tag, Verify}, State) when is_function(Verify) ->
692    p("expect megaco_callback ~w", [Tag]),
693    receive
694        {handle_megaco_callback, Type, Msg, Pid} ->
695            d("received megaco callback:"
696              "~n      ~p", [Msg]),
697            case Verify(Msg) of
698                {VRes, Res, Reply} ->
699                    d("megaco_callback [~w] ~w", [Tag, VRes]),
700                    handle_megaco_callback_reply(Pid, Type, Reply),
701                    validate(VRes, Tag, Res, State);
702                {VRes, Delay, Res, Reply} ->
703                    d("megaco_callback [~w] ~w, ~w", [Tag,Delay,VRes]),
704                    handle_megaco_callback_reply(Pid, Type, Delay, Reply),
705                    validate(VRes, Tag, Res, State)
706            end
707    end;
708
709handle_exec({megaco_callback, Tag, {VMod, VFunc, VArgs}}, State)
710  when is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs) ->
711    p("expect megaco_callback ~w", [Tag]),
712    receive
713        {handle_megaco_callback, Type, Msg, Pid} ->
714            d("received megaco callback: ~n~p"
715              "~n   VMod:  ~w"
716              "~n   VFunc: ~w"
717              "~n   VArgs: ~p", [Msg, VMod, VFunc, VArgs]),
718            case apply(VMod, VFunc, [Msg|VArgs]) of
719                {VRes, Res, Reply} ->
720                    d("megaco_callback [~w] ~w",[Tag, VRes]),
721                    handle_megaco_callback_reply(Pid, Type, Reply),
722                    validate(VRes, Tag, Res, State);
723                {VRes, Delay, Res, Reply} ->
724                    d("megaco_callback [~w] ~w, ~w",[Tag,Delay,VRes]),
725                    handle_megaco_callback_reply(Pid, Type, Delay, Reply),
726                    validate(VRes, Tag, Res, State)
727            end
728    end;
729
730handle_exec({megaco_callback, Tag, Verify, Timeout}, State)
731  when (is_function(Verify) andalso
732	(is_integer(Timeout) andalso (Timeout > 0))) ->
733    p("expect megaco_callback ~w (with ~w)", [Tag, Timeout]),
734    receive
735        {handle_megaco_callback, Type, Msg, Pid} ->
736            d("received megaco callback: ~n~p", [Msg]),
737            case Verify(Msg) of
738                {VRes, Res, Reply} ->
739                    d("megaco_callback [~w] ~w",[Tag,VRes]),
740                    handle_megaco_callback_reply(Pid, Type, Reply),
741                    validate(VRes, Tag, Res, State);
742                {VRes, Delay, Res, Reply} ->
743                    d("megaco_callback [~w] ~w, ~w",[Tag,Delay,VRes]),
744                    handle_megaco_callback_reply(Pid, Type, Delay, Reply),
745                    validate(VRes, Tag, Res, State)
746            end
747    after Timeout ->
748            e("megaco_callback ~w timeout", [Tag]),
749            #state{result = Res} = State,
750            Err = {callback_timeout, Tag, Timeout},
751            {error, State#state{result = [Err|Res]}}
752    end;
753
754handle_exec({megaco_callback, Verifiers}, State) ->
755    p("expect megaco_callback(s)"),
756    megaco_callback_verify(Verifiers, State);
757
758handle_exec({megaco_cancel, Reason}, #state{conn_handle = CH} = State) ->
759    p("megaco_cancel: ~w", [Reason]),
760    case megaco:cancel(CH, Reason) of
761        ok ->
762            {ok, State};
763        Error ->
764            e("failed cancel: ~n~p", [Error]),
765            #state{result = Acc} = State,
766            {error, State#state{result = [Error|Acc]}}
767    end;
768
769handle_exec({trigger, Trigger}, State) when is_function(Trigger) ->
770    p("trigger"),
771    (catch Trigger()),
772    {ok, State};
773
774handle_exec({sleep, To}, State) ->
775    p("sleep ~p", [To]),
776    megaco_test_generator:sleep(To),
777    {ok, State};
778
779handle_exec(BadInstruction, _State) ->
780    error({invalid_instruction, BadInstruction}).
781
782
783%% --- cleanup ---
784
785megaco_cleanup(#state{mid = Mid}) ->
786    Close = fun(CH) -> do_megaco_cleanup(CH) end,
787    Conns =
788	case (catch megaco:user_info(Mid, connections)) of
789	    Connections when is_list(Connections) ->
790		Connections;
791	    _ ->
792		[]
793	end,
794    lists:foreach(Close, Conns).
795
796do_megaco_cleanup(CH) ->
797    case (catch do_megaco_cleanup2(CH)) of
798        ok ->
799            ok;
800        {'EXIT', {no_such_connection, _}} ->
801            ok;
802        {'EXIT', Reason} ->
803            exit(Reason)
804    end.
805
806do_megaco_cleanup2(CH) ->
807    d("do_megaco_cleanup2 -> entry with"
808      "~n   CH: ~p", [CH]),
809    Reason     = {stopped_by_user,self()},
810    Pid        = megaco:conn_info(CH, control_pid),
811    SendMod    = megaco:conn_info(CH, send_mod),
812    SendHandle = megaco:conn_info(CH, send_handle),
813    d("do_megaco_cleanup2 -> disconnect"),
814    megaco:disconnect(CH, Reason),
815    d("do_megaco_cleanup2 -> disconnected, now cancel"),
816    megaco:cancel(CH, Reason),
817    d("do_megaco_cleanup2 -> canceled, now close"),
818    case SendMod of
819        megaco_tcp -> (catch megaco_tcp:close(SendHandle));
820        megaco_udp -> (catch megaco_udp:close(SendHandle));
821        SendMod    -> exit(Pid, Reason)
822    end,
823    ok.
824
825
826%% --- connector ---
827
828megaco_connector_start(RH, PrelMid, SH, ControlPid) ->
829    Self = self(),
830    Fun  = fun() -> megaco_connect(RH, PrelMid, SH, ControlPid, Self) end,
831    erlang:spawn_opt(Fun, [link]).
832
833megaco_connect(RH, PrelMid, SH, ControlPid, Parent) ->
834    Result = megaco:connect(RH, PrelMid, SH, ControlPid),
835    Parent ! {megaco_connect_result, Result},
836    exit(normal).
837
838
839%% --- megaco callback verify ---
840
841%% This is used when a number of callback's is expected, but where
842%% the specific order is unknown.
843megaco_callback_verify([], State) ->
844    d("megaco_callback_verify -> done"),
845    {ok, State};
846megaco_callback_verify(Verifiers0, State0) ->
847    d("megaco_callback_verify -> entry when"
848      "~n   length(Verifiers0): ~w", [length(Verifiers0)]),
849    receive
850        {handle_megaco_callback, Type, Msg, Pid} ->
851            d("megaco_callback_verify -> received megaco callback: ~w"
852	      "~n   Msg: ~p", [Type, Msg]),
853            case megaco_callback_verify(Verifiers0, Type, Msg, Pid, State0) of
854                {ok, Verifiers, State} ->
855                    megaco_callback_verify(Verifiers, State);
856                Error ->
857                    Error
858            end
859    end.
860
861megaco_callback_verify(Verifiers0, Type, Msg, Pid, State0) ->
862    d("megaco_callback_verify -> entry"),
863    Tag = element(1, Msg),
864    d("megaco_callback_verify -> Tag: ~w", [Tag]),
865    case lists:keysearch(Tag, 1, Verifiers0) of
866        {value, {Tag, N, Verify}} when (N > 0) andalso is_function(Verify) ->
867            d("megaco_callback_verify -> N: ~w",[N]),
868            case Verify(Msg) of
869                {VRes, Res, Reply} ->
870                    d("megaco_callback_verify -> VRes: ~w",[VRes]),
871                    handle_megaco_callback_reply(Pid, Type, Reply),
872                    case validate(VRes, Tag, Res, State0) of
873                        {error, _} = EState ->
874                            e("megaco_callback_verify -> (1) error", []),
875                            throw(EState);
876                        {ok, State} when N > 1 ->
877                            d("megaco_callback_verify -> (1) validated"),
878                            Rec = {Tag, N-1, Verify},
879                            Verifiers =
880                                lists:keyreplace(Tag, 1, Verifiers0, Rec),
881                            {ok, Verifiers, State};
882                        {ok, State} ->
883                            d("megaco_callback_verify -> (2) validated"),
884                            Verifiers = lists:keydelete(Tag, 1, Verifiers0),
885                            {ok, Verifiers, State}
886                    end;
887                {VRes, Delay, Res, Reply} ->
888                    d("megaco_callback_verify -> Delay: ~w, VRes: ~w",
889                      [Delay,VRes]),
890                    handle_megaco_callback_reply(Pid, Type, Delay, Reply),
891                    case validate(VRes, Tag, Res, State0) of
892                        {error, _} = EState ->
893                            e("megaco_callback_verify -> (2) error", []),
894                            throw(EState);
895                        {ok, State} when N > 1 ->
896                            d("megaco_callback_verify -> (3) validated"),
897                            Rec = {Tag, N-1, Verify},
898                            Verifiers =
899                                lists:keyreplace(Tag, 1, Verifiers0, Rec),
900                            {ok, Verifiers, State};
901                        {ok, State} ->
902                            d("megaco_callback_verify -> (4) validated"),
903                            Verifiers = lists:keydelete(Tag, 1, Verifiers0),
904                            {ok, Verifiers, State}
905                    end
906            end;
907        false ->
908            e("megaco_callback_verify -> no such tag ~w~n~p",
909              [Tag, Verifiers0]),
910            #state{result = Res} = State0,
911            State = State0#state{result = [{Type, error, Msg}|Res]},
912            error(State)
913    end.
914
915
916%% --- validate verify result ---
917
918validate(ok, handle_connect = Tag, CH, #state{result = Acc} = S) ->
919    {ok, S#state{conn_handle = CH, result = [{Tag, ok, CH}|Acc]}};
920validate(ok, Tag, Res, #state{result = Acc} = S) ->
921    {ok, S#state{result = [{Tag, ok, Res}|Acc]}};
922validate(error, Tag, Res, #state{result = Acc} = S) ->
923    {error, S#state{result = [{Tag, error, Res}|Acc]}}.
924
925
926%% ----- termination -----
927
928terminate(normal, #state{result = Result} = _State) ->
929    d("terminate -> entry when normal with"
930      "~n   Result: ~p", [Result]),
931    %% megaco_cleanup(State),
932    {ok, Result};
933
934terminate(Reason, #state{result = Result} = State) ->
935    d("terminate -> entry with"
936      "~n   Reason: ~p"
937      "~n   Result: ~p", [Reason, Result]),
938    megaco_cleanup(State),
939    {error, {Reason, Result}}.
940
941
942%%----------------------------------------------------------------------
943
944handle_exec_listen_tcp(Sup, Opts, MaybeRetry) ->
945    handle_exec_listen_tcp(Sup, Opts, MaybeRetry, noError).
946
947handle_exec_listen_tcp(Sup, Opts, MaybeRetry, Error0) ->
948    case (catch megaco_tcp:listen(Sup, Opts)) of
949        ok ->
950            ok;
951	Error1 ->
952	    case (catch MaybeRetry(Error1, Error0)) of
953		{true, Error2} ->
954		    handle_exec_listen_tcp(Sup, Opts, MaybeRetry, Error2);
955		{false, Error3} ->
956		    {error, Error3}
957	    end
958    end.
959
960
961handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry)
962  when is_function(MaybeRetry) ->
963    handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry, noError).
964
965handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry, Error0) ->
966    case (catch megaco_tcp:connect(Sup, Opts)) of
967	{ok, SH, ControlPid} ->
968	    d("tcp connected: ~p, ~p", [SH, ControlPid]),
969	    {ok, SH, ControlPid};
970	Error1 ->
971	    case (catch MaybeRetry(Error1, Error0)) of
972		{true, Error2} ->
973		    handle_exec_connect_tcp(Host, Opts, Sup,
974					    MaybeRetry, Error2);
975		{false, Error3} ->
976		    {error, Error3}
977	    end
978    end.
979
980
981
982%%----------------------------------------------------------------------
983%% megaco_user callback functions
984%%----------------------------------------------------------------------
985
986handle_connect(CH, PV, P) ->
987    Req = {handle_connect, CH, PV},
988    handle_megaco_callback_call(P, Req).
989
990handle_connect(CH, PV, Extra, P) ->
991    Req = {handle_connect, CH, PV, Extra},
992    handle_megaco_callback_call(P, Req).
993
994handle_disconnect(CH, PV, R, P) ->
995    Msg   = {handle_disconnect, CH, PV, R},
996    Reply = ok,
997    handle_megaco_callback_cast(P, Msg, Reply).
998
999handle_syntax_error(RH, PV, ED, P) ->
1000    Req = {handle_syntax_error, RH, PV, ED},
1001    handle_megaco_callback_call(P, Req).
1002
1003handle_syntax_error(RH, PV, ED, Extra, P) ->
1004    Req = {handle_syntax_error, RH, PV, ED, Extra},
1005    handle_megaco_callback_call(P, Req).
1006
1007handle_message_error(CH, PV, ED, P) ->
1008    Msg   = {handle_message_error, CH, PV, ED},
1009    Reply = ok,
1010    handle_megaco_callback_cast(P, Msg, Reply).
1011
1012handle_message_error(CH, PV, ED, Extra, P) ->
1013    Msg   = {handle_message_error, CH, PV, ED, Extra},
1014    Reply = ok,
1015    handle_megaco_callback_cast(P, Msg, Reply).
1016
1017handle_trans_request(CH, PV, AR, P) ->
1018    Req = {handle_trans_request, CH, PV, AR},
1019    handle_megaco_callback_call(P, Req).
1020
1021handle_trans_request(CH, PV, AR, Extra, P) ->
1022    Req = {handle_trans_request, CH, PV, AR, Extra},
1023    handle_megaco_callback_call(P, Req).
1024
1025handle_trans_long_request(CH, PV, RD, P) ->
1026    Req = {handle_trans_long_request, CH, PV, RD},
1027    handle_megaco_callback_call(P, Req).
1028
1029handle_trans_long_request(CH, PV, RD, Extra, P) ->
1030    Req = {handle_trans_long_request, CH, PV, RD, Extra},
1031    handle_megaco_callback_call(P, Req).
1032
1033handle_trans_reply(CH, PV, AR, RD, P) ->
1034    Msg = {handle_trans_reply, CH, PV, AR, RD},
1035    Reply = ok,
1036    handle_megaco_callback_cast(P, Msg, Reply).
1037
1038handle_trans_reply(CH, PV, AR, RD, Extra, P) ->
1039    Msg = {handle_trans_reply, CH, PV, AR, RD, Extra},
1040    Reply = ok,
1041    handle_megaco_callback_cast(P, Msg, Reply).
1042
1043handle_trans_ack(CH, PV, AS, AD, P) ->
1044    Msg = {handle_trans_ack, CH, PV, AS, AD},
1045    Reply = ok,
1046    handle_megaco_callback_cast(P, Msg, Reply).
1047
1048handle_trans_ack(CH, PV, AS, AD, Extra, P) ->
1049    Msg = {handle_trans_ack, CH, PV, AS, AD, Extra},
1050    Reply = ok,
1051    handle_megaco_callback_cast(P, Msg, Reply).
1052
1053handle_unexpected_trans(CH, PV, T, P) ->
1054    Msg = {handle_unexpected_trans, CH, PV, T},
1055    Reply = ok,
1056    handle_megaco_callback_cast(P, Msg, Reply).
1057
1058handle_unexpected_trans(CH, PV, T, Extra, P) ->
1059    Msg = {handle_unexpected_trans, CH, PV, T, Extra},
1060    Reply = ok,
1061    handle_megaco_callback_cast(P, Msg, Reply).
1062
1063handle_trans_request_abort(RH, PV, TransNo, Pid, P) ->
1064    Msg = {handle_trans_request_abort, RH, PV, TransNo, Pid},
1065    Reply = ok,
1066    handle_megaco_callback_cast(P, Msg, Reply).
1067
1068handle_trans_request_abort(RH, PV, TransNo, Pid, Extra, P) ->
1069    Msg = {handle_trans_request_abort, RH, PV, TransNo, Pid, Extra},
1070    Reply = ok,
1071    handle_megaco_callback_cast(P, Msg, Reply).
1072
1073handle_megaco_callback_cast(P, Msg, Reply) ->
1074    d("handle_megaco_callback_cast -> entry with Msg: ~n~p", [Msg]),
1075    P ! {handle_megaco_callback, cast, Msg, self()},
1076    Reply.
1077
1078handle_megaco_callback_call(P, Msg) ->
1079    d("handle_megaco_callback_call -> entry with"
1080      "~n   P:   ~p"
1081      "~n   Msg: ~p", [P, Msg]),
1082    P ! {handle_megaco_callback, call, Msg, self()},
1083    receive
1084        {handle_megaco_callback_reply, Reply} ->
1085            d("handle_megaco_callback_call -> received reply: ~n~p", [Reply]),
1086            Reply;
1087        {handle_megaco_callback_reply, Delay, Reply} when is_integer(Delay) ->
1088            d("handle_megaco_callback_call -> "
1089              "received reply [~w]: "
1090              "~n   ~p", [Delay, Reply]),
1091            sleep(Delay),
1092            d("handle_megaco_callback_call -> deliver reply after delay [~w]",
1093              [Delay]),
1094            Reply;
1095        {'EXIT', Pid, Reason} when (Pid =:= P) ->
1096            d("handle_megaco_callback_call -> "
1097              "received unexpected EXIT signal (from ~p): "
1098              "~n   Reason: ~p", [Pid, Reason]),
1099            exit({unexpected_EXIT_signal, Pid, Reason});
1100        {'EXIT', SomePid, SomeReason} ->
1101            d("handle_megaco_callback_call -> "
1102              "received unexpected EXIT signal from unknown process: "
1103              "~n   Pid:    ~p"
1104              "~n   Reason: ~p", [SomePid, SomeReason]),
1105            exit({unexpected_EXIT_signal, SomePid, SomeReason})
1106    end.
1107
1108
1109handle_megaco_callback_reply(P, call, Reply) ->
1110    P ! {handle_megaco_callback_reply, Reply};
1111handle_megaco_callback_reply(_, _, _) ->
1112    ok.
1113
1114handle_megaco_callback_reply(P, call, Delay, Reply) ->
1115    P ! {handle_megaco_callback_reply, Delay, Reply};
1116handle_megaco_callback_reply(_, _, _, _) ->
1117    ok.
1118
1119
1120%%----------------------------------------------------------------------
1121%% internal utility functions
1122%%----------------------------------------------------------------------
1123
1124random_init() ->
1125    ok.
1126
1127random(N) ->
1128    rand:uniform(N).
1129
1130
1131get_config(Key, Opts) ->
1132    {value, {Key, Val}} = lists:keysearch(Key, 1, Opts),
1133    Val.
1134
1135sleep(X) -> megaco_test_generator:sleep(X).
1136
1137d(F)    -> megaco_test_generator:debug(F).
1138d(F, A) -> megaco_test_generator:debug(F, A).
1139
1140e(F, A) -> megaco_test_generator:error(F, A).
1141
1142p(F      ) -> p("", F, []).
1143p(F,    A) -> p("", F, A).
1144p(P, F, A) -> megaco_test_generator:print(P,    F, A).
1145
1146error(Reason) ->
1147    throw({error, Reason}).
1148
1149