1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-2019. 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: Test encoding/decoding of the sample call flows Megaco/H.248
24%%----------------------------------------------------------------------
25%% megaco_call_flow_test:pretty_text().
26%% megaco_call_flow_test:compact_text().
27%% megaco_call_flow_test:bin().
28%% megaco_call_flow_test:asn1_ber().
29%% megaco_call_flow_test:asn1_per().
30%% megaco_call_flow_test:erl_dist().
31%% megaco_call_flow_test:compressed_erl_dist().
32%% megaco_call_flow_test:gnuplot_gif().
33%% megaco_call_flow_test:gnuplot_size_gif().
34%% megaco_call_flow_test:gnuplot_enc_time_gif().
35%% megaco_call_flow_test:gnuplot_dec_time_gif().
36%% megaco_call_flow_test:gnuplot_code_time_gif().
37%%----------------------------------------------------------------------
38
39-module(megaco_call_flow_SUITE).
40
41-export([
42 	 suite/0, all/0, groups/0,
43         init_per_suite/1, end_per_suite/1,
44         init_per_group/2, end_per_group/2,
45         init_per_testcase/2, end_per_testcase/2,
46
47         pretty/1,
48         compact/1,
49         pretty_flex/1,
50         compact_flex/1,
51         bin/1,
52         ber/1,
53         per/1,
54         standard_erl/1,
55         compressed_erl/1
56        ]).
57
58-export([
59         msg1/0,   msg1/1,
60         msg2/0,   msg2/1,
61         msg3/0,   msg3/1,
62         msg4/0,   msg4/1,
63         msg5a/0,  msg5a/1,
64         msg5b/0,  msg5b/1,
65         msg6/0,   msg6/1,
66         msg7/0,   msg7/1,
67         msg9/0,   msg9/1,
68         msg10/0,  msg10/1,
69         msg11/0,  msg11/1,
70         msg12/0,  msg12/1,
71         msg13/0,  msg13/1,
72         msg14/0,  msg14/1,
73         msg15/0,  msg15/1,
74         msg16/0,  msg16/1,
75         msg17a/0, msg17a/1,
76         msg17b/0, msg17b/1,
77         msg18a/0, msg18a/1,
78         msg18b/0, msg18b/1,
79         msg18c/0, msg18c/1,
80         msg18d/0, msg18d/1,
81         msg19a/0, msg19a/1,
82         msg19b/0, msg19b/1,
83         msg20/0,  msg20/1,
84         msg21/0,  msg21/1,
85         msg22a/0, msg22a/1,
86         msg22b/0, msg22b/1,
87         msg23a/0, msg23a/1,
88         msg23b/0, msg23b/1
89
90        ]).
91
92-export([
93         encoders/0,
94         msg_sizes/0,
95         coding_times/0,
96         encoding_times/0,
97         decoding_times/0,
98         coding_times_stat/0,
99         encoding_times_stat/0,
100         decoding_times_stat/0,
101         size_stat/0,
102         gnuplot_gif/0,
103         gnuplot_size_gif/0,
104
105         gen_byte_msg/2,
106         gen_header_file_binary/1,
107         gen_ber_header/0,
108         gen_ber_bin_header/0,
109         gen_per_header/0,
110         single_meter/4,
111         count/2
112        ]).
113
114-include_lib("megaco/include/megaco.hrl").
115-include_lib("megaco/include/megaco_message_v1.hrl").
116-include("megaco_test_lib.hrl").
117
118
119%%======================================================================
120%% Common Test interface functions
121%%======================================================================
122
123suite() ->
124    [{ct_hooks, [ts_install_cth]}].
125
126all() ->
127    [
128     {group, text},
129     {group, binary},
130     {group, erl}
131    ].
132
133groups() ->
134    [
135     {text,   [], text_cases()},
136     {flex,   [], flex_cases()},
137     {binary, [], binary_cases()},
138     {erl,    [], erl_cases()}
139    ].
140
141text_cases() ->
142    [
143     pretty,
144     compact
145    ].
146
147flex_cases() ->
148    [
149     pretty_flex,
150     compact_flex
151    ].
152
153binary_cases() ->
154    [
155     bin,
156     ber,
157     per
158    ].
159
160erl_cases() ->
161    [
162     standard_erl,
163     compressed_erl
164    ].
165
166
167
168%%
169%% -----
170%%
171
172init_per_suite(suite) ->
173    [];
174init_per_suite(doc) ->
175    [];
176init_per_suite(Config0) when is_list(Config0) ->
177
178    ?ANNOUNCE_SUITE_INIT(),
179
180    p("init_per_suite -> entry with"
181      "~n      Config: ~p"
182      "~n      Nodes:  ~p", [Config0, erlang:nodes()]),
183
184    case ?LIB:init_per_suite(Config0) of
185        {skip, _} = SKIP ->
186            SKIP;
187
188        Config1 when is_list(Config1) ->
189
190            %% We need a (local) monitor on this node also
191            megaco_test_sys_monitor:start(),
192
193            p("init_per_suite -> end when"
194              "~n      Config: ~p"
195              "~n      Nodes:  ~p", [Config1, erlang:nodes()]),
196
197            Config1
198    end.
199
200end_per_suite(suite) -> [];
201end_per_suite(doc) -> [];
202end_per_suite(Config0) when is_list(Config0) ->
203
204    p("end_per_suite -> entry with"
205      "~n      Config: ~p"
206      "~n      Nodes:  ~p", [Config0, erlang:nodes()]),
207
208    megaco_test_sys_monitor:stop(),
209    Config1 = ?LIB:end_per_suite(Config0),
210
211    p("end_per_suite -> end when"
212      "~n      Nodes:  ~p", [erlang:nodes()]),
213
214    Config1.
215
216
217%%
218%% -----
219%%
220
221init_per_group(Group, Config) ->
222    ?ANNOUNCE_GROUP_INIT(Group),
223    Config.
224
225end_per_group(_Group, Config) ->
226    Config.
227
228
229%%
230%% -----
231%%
232
233init_per_testcase(Case, Config) ->
234    %% We do *not* reset events with each test case
235    %% The test cases are so short we don't bother,
236    %% and also we would drown in mprintouts...
237    megaco_test_lib:init_per_testcase(Case, Config).
238
239end_per_testcase(Case, Config) ->
240    megaco_test_lib:end_per_testcase(Case, Config).
241
242
243
244%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
245
246pretty(suite) ->
247    [];
248pretty(Config) when is_list(Config) ->
249    ?ACQUIRE_NODES(1, Config),
250    pretty_text().
251
252compact(suite) ->
253    [];
254compact(Config) when is_list(Config) ->
255    ?ACQUIRE_NODES(1, Config),
256    compact_text().
257
258pretty_flex(suite) ->
259    [];
260pretty_flex(Config) when is_list(Config) ->
261    ?ACQUIRE_NODES(1, Config),
262    pretty_flex().
263
264compact_flex(suite) ->
265    [];
266compact_flex(Config) when is_list(Config) ->
267    ?ACQUIRE_NODES(1, Config),
268    compact_flex().
269
270bin(suite) ->
271    [];
272bin(Config) when is_list(Config) ->
273    ?ACQUIRE_NODES(1, Config),
274    bin().
275
276ber(suite) ->
277    [];
278ber(Config) when is_list(Config) ->
279    ?ACQUIRE_NODES(1, Config),
280    asn1_ber().
281
282per(suite) ->
283    [];
284per(Config) when is_list(Config) ->
285    ?ACQUIRE_NODES(1, Config),
286    asn1_per().
287
288standard_erl(suite) ->
289    [];
290standard_erl(Config) when is_list(Config) ->
291    ?ACQUIRE_NODES(1, Config),
292    standard_erl().
293
294compressed_erl(suite) ->
295    [];
296compressed_erl(Config) when is_list(Config) ->
297    ?ACQUIRE_NODES(1, Config),
298    compressed_erl().
299
300%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
301
302-define(PP(Name, Val), #'PropertyParm'{name = Name, value = [Val]}).
303-define(SP(Name, Val), #'StatisticsParameter'{statName = Name, statValue = [Val]}) .
304
305names() ->
306    [
307     msg1, msg2, msg3, msg4, msg5a, msg5b, msg6, msg7, msg9, msg10,
308     msg11, msg12, msg13, msg14, msg15, msg16, msg17a, msg17b,
309     msg18a, msg18b, msg18c, msg18d, msg19a, msg19b, msg20, msg21,
310     msg22a, msg22b, msg23a, msg23b
311    ].
312
313%% In this example, MG1 has the IP address 124.124.124.222, MG2 is
314%% 125.125.125.111, and the MGC is 123.123.123.4. The default Megaco port
315%% is 55555 for all three.
316
317-define(DEFAULT_PORT, 55555).
318-define(MG1_MID_NO_PORT, {ip4Address,
319                          #'IP4Address'{address = [124, 124, 124, 222]}}).
320-define(MG1_MID, {ip4Address, #'IP4Address'{address = [124, 124, 124, 222],
321                                            portNumber = ?DEFAULT_PORT}}).
322-define(MG2_MID, {ip4Address, #'IP4Address'{address = [125, 125, 125, 111],
323                                            portNumber = ?DEFAULT_PORT}}).
324-define(MGC_MID, {ip4Address, #'IP4Address'{address = [123, 123, 123, 4],
325                                            portNumber = ?DEFAULT_PORT}}).
326
327-define(A4444, ["11111111", "00000000", "00000000"]).
328-define(A4445, ["11111111", "00000000", "11111111"]).
329-define(A5555, ["11111111", "11111111", "00000000"]).
330-define(A5556, ["11111111", "11111111", "11111111"]).
331
332%% -define(A4444, ["00000000"]).
333%% -define(A4445, ["00001111"]).
334%% -define(A5555, ["11110000"]).
335%% -define(A5556, ["11111111"]).
336
337request(Mid, TransId, ContextId, CmdReq) when is_list(CmdReq) ->
338    Actions = [#'ActionRequest'{contextId = ContextId,
339                                commandRequests = CmdReq}],
340    Req = {transactions,
341           [{transactionRequest,
342             #'TransactionRequest'{transactionId = TransId,
343                                   actions = Actions}}]},
344    #'MegacoMessage'{mess = #'Message'{version = 1,
345                                       mId = Mid,
346                                       messageBody = Req}}.
347
348reply(Mid, TransId, ContextId, CmdReply) when is_list(CmdReply) ->
349    Actions = [#'ActionReply'{contextId = ContextId,
350                              commandReply = CmdReply}],
351    Req = {transactions,
352           [{transactionReply,
353             #'TransactionReply'{transactionId = TransId,
354                                 transactionResult = {actionReplies, Actions}}}]},
355    #'MegacoMessage'{mess = #'Message'{version = 1,
356                                       mId = Mid,
357                                       messageBody = Req}}.
358
359%%----------------------------------------------------------------------
360%% 1. An MG registers with an MGC using the ServiceChange command:
361%%
362%% MG1 to MGC:
363%% "MEGACO/1 [124.124.124.222]
364%% Transaction = 9998 {
365%%     Context = - {
366%%         ServiceChange = ROOT {Services {
367%%             Method=Restart,
368%%             ServiceChangeAddress=55555, Profile=ResGW/1}
369%%         }
370%%     }
371%% }
372%%----------------------------------------------------------------------
373
374msg1() ->
375    msg1(?MG1_MID_NO_PORT).
376msg1(Mid) ->
377    Address = {portNumber, ?DEFAULT_PORT},
378    Profile = #'ServiceChangeProfile'{profileName = "resgw",
379                                      version = 1},
380    Parm = #'ServiceChangeParm'{serviceChangeMethod = restart,
381                                serviceChangeAddress = Address,
382                                serviceChangeReason  = ["901 mg cold boot"],
383                                %% BUGBUG: Mandatory reason missing in spec
384                                serviceChangeProfile = Profile},
385    Req = #'ServiceChangeRequest'{terminationID = [?megaco_root_termination_id],
386                                  serviceChangeParms = Parm},
387    CmdReq = #'CommandRequest'{command = {serviceChangeReq, Req}},
388    request(Mid, 9998, ?megaco_null_context_id, [CmdReq]).
389
390%%----------------------------------------------------------------------
391%%
392%% 2. The MGC sends a reply:
393%%
394%% MGC to MG1:
395%% MEGACO/1 [123.123.123.4]:55555
396%% Reply = 9998 {
397%%    Context = - {ServiceChange = ROOT {
398%%      Services {ServiceChangeAddress=55555, Profile=ResGW/1} } }
399%% }
400%%----------------------------------------------------------------------
401
402msg2() ->
403    msg2(?MGC_MID).
404msg2(Mid) ->
405    Address = {portNumber, ?DEFAULT_PORT},
406    Profile = #'ServiceChangeProfile'{profileName = "resgw",
407                                      version = 1},
408    Parm = #'ServiceChangeResParm'{serviceChangeAddress = Address,
409                                   serviceChangeProfile = Profile},
410    Reply = #'ServiceChangeReply'{terminationID = [?megaco_root_termination_id],
411                                  serviceChangeResult = {serviceChangeResParms,
412                                                         Parm}},
413    reply(Mid, 9998, ?megaco_null_context_id, [{serviceChangeReply, Reply}]).
414
415%%----------------------------------------------------------------------
416%% 3. The MGC programs a Termination in the NULL context. The
417%%    terminationId is A4444, the streamId is 1, the requestId in the
418%%    Events descriptor is 2222. The mId is the identifier of the sender
419%%    of this message, in this case, it is the IP address and port
420%%    [123.123.123.4]:55555. Mode for this stream is set to
421%%    SendReceive. "al" is the analog line supervision package.
422%%
423%% MGC to MG1:
424%% MEGACO/1 [123.123.123.4]:55555
425%% Transaction = 9999 {
426%%     Context = - {
427%%         Modify = A4444 {
428%%             Media { Stream = 1 {
429%%                      LocalControl {
430%%                          Mode = SendReceive,
431%%                          tdmc/gain=2,  ; in dB,
432%%                          tdmc/ec=G165
433%%                      },
434%%                      Local {
435%% v=0
436%% c=IN IP4 $
437%% m=audio $ RTP/AVP 0
438%% a=fmtp:PCMU VAD=X-NNVAD ; special voice activity
439%%                         ; detection algorithm
440%%                      }
441%%                  }
442%%             },
443%%             Events = 2222 {al/of}
444%%         }
445%%     }
446%% }
447%%----------------------------------------------------------------------
448
449msg3() ->
450    msg3(?MGC_MID).
451msg3(Mid) ->
452    msg3(Mid, ?A4444).
453msg3(Mid, Tid) ->
454    Gain = ?PP("tdmc/gain", "2"),
455    %% Ec   = ?PP("tdmc/ec", "G165"),
456    Ec   = ?PP("tdmc/ec", "g165"), %% BUGBUG: should be case insensitive
457    LCD = #'LocalControlDescriptor'{streamMode = sendRecv,
458                                    propertyParms = [Gain, Ec]},
459    V = ?PP("v", "0"),
460    C = ?PP("c", "IN IP4 $ "),
461    M = ?PP("m", "audio $ RTP/AVP 0"),
462    A = ?PP("a", "fmtp:PCMU VAD=X-NNVAD"),
463    LD = #'LocalRemoteDescriptor'{propGrps = [[V, C, M, A]]},
464    Parms = #'StreamParms'{localControlDescriptor = LCD,
465                           localDescriptor = LD},
466    StreamDesc = #'StreamDescriptor'{streamID = 1,
467                                     streamParms = Parms},
468    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
469    ReqEvent = #'RequestedEvent'{pkgdName = "al/of",
470                                 evParList = []},
471    EventsDesc = #'EventsDescriptor'{requestID = 2222,
472                                     eventList = [ReqEvent]},
473    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = Tid}],
474                           descriptors = [{mediaDescriptor, MediaDesc},
475                                          {eventsDescriptor, EventsDesc}]},
476    CmdReq = #'CommandRequest'{command = {modReq, AmmReq}},
477    request(Mid, 9999, ?megaco_null_context_id, [CmdReq]).
478
479%%----------------------------------------------------------------------
480%% The dialplan script could have been loaded into the MG previously.
481%% Its function would be to wait for the OffHook, turn on dialtone and
482%% start collecting DTMF digits. However in this example, we use the
483%% digit map, which is put into place after the offhook is detected (step
484%% 5 below).
485%%
486%%
487%% Note that the embedded EventsDescriptor could have been used to
488%% combine steps 3 and 4 with steps 8 and 9, eliminating steps 6 and 7.
489%%
490%%
491%% 4. The MG1 accepts the Modify with this reply:
492%%
493%% MG1 to MGC:
494%% MEGACO/1 [124.124.124.222]:55555
495%% Reply = 9999 {
496%%    Context = - {Modify = A4444}
497%% }
498%%----------------------------------------------------------------------
499
500msg4() ->
501    msg4(?MG1_MID).
502msg4(Mid) ->
503    msg4(Mid, ?A4444).
504msg4(Mid, Tid) ->
505    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = Tid}]},
506    reply(Mid, 9999, ?megaco_null_context_id, [{modReply, Reply}]).
507
508%%----------------------------------------------------------------------
509%% 5. A similar exchange happens between MG2 and the MGC, resulting in an
510%%    idle Termination called A5555.
511%%----------------------------------------------------------------------
512
513msg5a() ->
514    msg5a(?MGC_MID).
515msg5a(Mid) ->
516    msg3(Mid, ?A4444).
517
518msg5b() ->
519    msg5b(?MG2_MID).
520msg5b(Mid) ->
521    msg4(Mid, ?A5555).
522
523%%----------------------------------------------------------------------
524%% A.1.2 Collecting Originator Digits and Initiating Termination
525%%
526%% The following builds upon the previously shown conditions.  It
527%% illustrates the transactions from the Media Gateway Controller and
528%% originating Media Gateway (MG1) to get the originating Termination
529%% (A4444) through the stages of digit collection required to initiate a
530%% connection to the terminating Media Gateway (MG2).
531%%
532%% 6. MG1 detects an offhook event from User 1 and reports it to the
533%%    Media Gateway Controller via the Notify Command.
534%%
535%% MG1 to MGC:
536%% MEGACO/1 [124.124.124.222]:55555
537%% Transaction = 10000 {
538%%    Context = - {
539%%        Notify = A4444 {ObservedEvents =2222 {
540%%          19990729T22000000:al/of}}
541%%    }
542%% }
543%%----------------------------------------------------------------------
544
545msg6() ->
546    msg6(?MG1_MID).
547msg6(Mid) ->
548    TimeStamp = #'TimeNotation'{date = "19990729",
549                                time = "22000000"},
550    Event = #'ObservedEvent'{eventName = "al/of",
551                             timeNotation = TimeStamp,
552                             eventParList = []},
553    Desc = #'ObservedEventsDescriptor'{requestId = 2222,
554                                       observedEventLst = [Event]},
555    NotifyReq = #'NotifyRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
556                                 observedEventsDescriptor = Desc},
557    CmdReq = #'CommandRequest'{command = {notifyReq, NotifyReq}},
558    request(Mid, 10000, ?megaco_null_context_id, [CmdReq]).
559
560%%----------------------------------------------------------------------
561%% 7. And the Notify is acknowledged.
562%%
563%% MGC to MG1:
564%% MEGACO/1 [123.123.123.4]:55555
565%% Reply = 10000 {
566%%     Context = - {Notify = A4444}
567%% }
568%%----------------------------------------------------------------------
569
570msg7() ->
571    msg7(?MGC_MID).
572msg7(Mid) ->
573    Reply = #'NotifyReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
574    reply(Mid, 10000, ?megaco_null_context_id, [{notifyReply, Reply}]).
575
576%%----------------------------------------------------------------------
577%% 8. The MGC Modifies the termination to play dial tone, and to look for
578%%    digits now. There is also an embedded event to stop dialtone upon
579%%    detection of the first digit. dd is the DTMF Detection package, and
580%%    ce is the completion event.
581%%----------------------------------------------------------------------
582
583%% BUGBUG: Example missing in spec
584
585%%----------------------------------------------------------------------
586%% 9.
587%%
588%% MGC to MG1:
589%% MEGACO/1 [123.123.123.4]:55555
590%% Transaction = 10001 {
591%%     Context = - {
592%%         Modify = A4444 {
593%%             Events = 2223 {
594%%                 al/on, dd/ce {DigitMap=Dialplan0}
595%%             },
596%%             Signals {cg/dt},
597%%             DigitMap= Dialplan0{
598%% (0S| 00S|[1-7]xLxx|8Lxxxxxxx|#xxxxxxx|*xx|9L1xxxxxxxxxx|9L011x.S)}
599%%         }
600%%     }
601%% }
602%%----------------------------------------------------------------------
603
604msg9() ->
605    msg9(?MGC_MID).
606msg9(Mid) ->
607    Name = "dialplan00",
608    Body = "(0s| 00s|[1-7]xlxx|8lxxxxxxx|#xxxxxxx|*xx|9l1xxxxxxxxxx|9l011x.s)",
609    Value = #'DigitMapValue'{digitMapBody = Body},
610    On = #'RequestedEvent'{pkgdName = "al/on", evParList = []},
611    Action = #'RequestedActions'{eventDM = {digitMapName, Name}},
612    Ce = #'RequestedEvent'{pkgdName = "dd/ce",
613                           eventAction = Action,
614                           evParList = []},
615    EventsDesc = #'EventsDescriptor'{requestID = 2223,
616                                     eventList = [On, Ce]},
617    Signal = #'Signal'{signalName = "cg/rt", sigParList = []},
618    DigMapDesc = #'DigitMapDescriptor'{digitMapName = Name,
619                                       digitMapValue = Value},
620    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
621                           descriptors = [{eventsDescriptor, EventsDesc},
622                                          {signalsDescriptor, [{signal, Signal}]},
623                                          {digitMapDescriptor, DigMapDesc}]},
624    CmdReq = #'CommandRequest'{command = {modReq, AmmReq}},
625    request(Mid, 10001, ?megaco_null_context_id, [CmdReq]).
626
627%%----------------------------------------------------------------------
628%% 10. And the Modify is acknowledged.
629%%
630%% MG1 to MGC:
631%% MEGACO/1 [124.124.124.222]:55555
632%% Reply = 10001 {
633%%     Context = - {Modify = A4444}
634%% }
635%%----------------------------------------------------------------------
636
637msg10() ->
638    msg10(?MG1_MID).
639msg10(Mid) ->
640    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
641    reply(Mid, 10001, ?megaco_null_context_id, [{modReply, Reply}]).
642
643%%----------------------------------------------------------------------
644%% 11. Next, digits are accumulated by MG1 as they are dialed by User 1.
645%%     Dialtone is stopped upon detection of the first digit, using the
646%%     embedded event in step 8. When an appropriate match is made of
647%%     collected digits against the currently programmed Dialplan for
648%%     A4444, another Notify is sent to the Media Gateway Controller.
649%%
650%% MG1 to MGC:
651%% MEGACO/1 [124.124.124.222]:55555
652%% Transaction = 10002 {
653%%    Context = - {
654%%        Notify = A4444 {ObservedEvents =2223 {
655%%          19990729T22010001:dd/ce{ds="916135551212"}}}
656%%    }
657%% }
658%%----------------------------------------------------------------------
659
660msg11() ->
661    msg11(?MG1_MID).
662msg11(Mid) ->
663    TimeStamp = #'TimeNotation'{date = "19990729",
664                                time = "22010001"},
665    Parm = #'EventParameter'{eventParameterName = "ds",
666                             value = ["916135551212"]},
667    %% BUGBUG: Quoted string or safe char?
668    Event = #'ObservedEvent'{eventName = "dd/ce",
669                             timeNotation = TimeStamp,
670                             eventParList = [Parm]},
671    Desc = #'ObservedEventsDescriptor'{requestId = 2223,
672                                       observedEventLst = [Event]},
673    NotifyReq = #'NotifyRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
674                                 observedEventsDescriptor = Desc},
675    CmdReq = #'CommandRequest'{command = {notifyReq, NotifyReq}},
676    request(Mid, 10002, ?megaco_null_context_id, [CmdReq]).
677
678%%----------------------------------------------------------------------
679%% 12. And the Notify is acknowledged.
680%%
681%% MGC to MG1:
682%% MEGACO/1 [123.123.123.4]:55555
683%% Reply = 10002 {
684%%     Context = - {Notify = A4444}
685%% }
686%%----------------------------------------------------------------------
687
688msg12() ->
689    msg12(?MGC_MID).
690msg12(Mid) ->
691    Reply = #'NotifyReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
692    reply(Mid, 10002, ?megaco_null_context_id, [{notifyReply, Reply}]).
693
694%%----------------------------------------------------------------------
695%% 13. The controller then analyses the digits and determines that a
696%%     connection needs to be made from MG1 to MG2. Both the TDM
697%%     termination A4444, and an RTP termination are added to a new
698%%     context in MG1. Mode is ReceiveOnly since Remote descriptor values
699%%     are not yet specified. Preferred codecs are in the MGC's preferred
700%%     order of choice.
701%%
702%% MGC to MG1:
703%% MEGACO/1 [123.123.123.4]:55555
704%% Transaction = 10003 {
705%%     Context = $ {
706%%        Add = A4444,
707%%        Add = $ {
708%%            Media {
709%%              Stream = 1 {
710%%                   LocalControl {
711%%                       Mode = ReceiveOnly,
712%%
713%%                       nt/jit=40, ; in ms
714%%                   },
715%%                   Local {
716%% v=0
717%% c=IN IP4 $
718%% m=audio $ RTP/AVP 4
719%% a=ptime:30
720%% v=0
721%% c=IN IP4 $
722%% m=audio $ RTP/AVP 0
723%%                   }
724%%              }
725%%           }
726%%        }
727%%     }
728%% }
729%%
730%% NOTE - The MGC states its prefrred parameter values as a series of
731%% sdp blocks in Local. The MG fills in the Local Descriptor in the
732%% Reply.
733%%----------------------------------------------------------------------
734
735msg13() ->
736    msg13(?MGC_MID).
737msg13(Mid) ->
738    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
739                           descriptors    = []},
740    CmdReq = #'CommandRequest'{command = {addReq, AmmReq}},
741    Jit = ?PP("nt/jit", "40"),
742    LCD = #'LocalControlDescriptor'{streamMode = recvOnly,
743                                    propertyParms = [Jit]},
744    V = ?PP("v", "0"),
745    C = ?PP("c", "IN IP4 $ "),
746    M = ?PP("m", "audio $ RTP/AVP 4"),
747    A = ?PP("a", "ptime:30"),
748    V2 = ?PP("v", "0"),
749    C2 = ?PP("c", "IN IP4 $ "),
750    M2 = ?PP("m", "audio $ RTP/AVP 0"),
751    LD = #'LocalRemoteDescriptor'{propGrps = [[V, C, M, A], [V2, C2, M2]]},
752    Parms = #'StreamParms'{localControlDescriptor = LCD,
753                           localDescriptor = LD},
754    StreamDesc = #'StreamDescriptor'{streamID = 1,
755                                     streamParms = Parms},
756    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
757    ChooseTid = #megaco_term_id{contains_wildcards = true,
758                                id = [[?megaco_choose]]},
759    AmmReq2 = #'AmmRequest'{terminationID = [ChooseTid],
760                            descriptors   = [{mediaDescriptor, MediaDesc}]},
761    CmdReq2 = #'CommandRequest'{command = {addReq, AmmReq2}},
762    request(Mid, 10003, ?megaco_choose_context_id, [CmdReq, CmdReq2]).
763
764%%----------------------------------------------------------------------
765%% 14. MG1 acknowledges the new Termination and fills in the Local IP
766%%     address and UDP port. It also makes a choice for the codec based
767%%     on the MGC preferences in Local. MG1 sets the RTP port to 2222.
768%%
769%% MEGACO/1 [124.124.124.222]:55555
770%% Reply = 10003 {
771%%    Context = 2000 {
772%%       Add = A4444,
773%%       Add=A4445{
774%%          Media {
775%%              Stream = 1 {
776%%                  Local {
777%% v=0
778%% c=IN IP4 124.124.124.222
779%% m=audio 2222 RTP/AVP 4
780%% a=ptime:30
781%% a=recvonly
782%%                  } ; RTP profile for G.723 is 4
783%%              }
784%%          }
785%%       }
786%%    }
787%% }
788%%----------------------------------------------------------------------
789
790msg14() ->
791    msg14(?MG1_MID).
792msg14(Mid) ->
793    V = ?PP("v", "0"),
794    C = ?PP("c", "IN IP4 124.124.124.222"),
795    M = ?PP("m", "audio 2222 RTP/AVP 4"),
796    A = ?PP("a", "a=ptime:30"),
797    A2= ?PP("a", "recvonly"),
798    LD = #'LocalRemoteDescriptor'{propGrps = [[V, C, M, A, A2]]},
799    Parms = #'StreamParms'{localDescriptor = LD},
800    StreamDesc = #'StreamDescriptor'{streamID = 1,
801                                     streamParms = Parms},
802    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
803    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
804    Reply2 = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4445}],
805                          terminationAudit = [{mediaDescriptor, MediaDesc}]},
806    reply(Mid, 10003, 2000, [{addReply, Reply}, {addReply, Reply2}]).
807
808%%----------------------------------------------------------------------
809%% 15. The MGC will now associate A5555 with a new Context on MG2, and
810%%     establish an RTP Stream (i.e, A5556 will be assigned), SendReceive
811%%     connection through to the originating user, User 1. The MGC also
812%%     sets ring on A5555.
813%%
814%% MGC to MG2:
815%% MEGACO/1 [123.123.123.4]:55555
816%% Transaction = 50003 {
817%%     Context = $ {
818%%        Add = A5555  { Media {
819%%             Stream = 1 {
820%%                  LocalControl {Mode = SendReceive} }},
821%%             Signals {al/ri}
822%%             },
823%%        Add  = $ {Media {
824%%             Stream = 1 {
825%%                  LocalControl {
826%%                     Mode = SendReceive,
827%%                     nt/jit=40 ; in ms
828%%                  },
829%%                  Local {
830%% v=0
831%% c=IN IP4 $
832%% m=audio $ RTP/AVP 4
833%% a=ptime:30
834%%                  },
835%%                  Remote {
836%% v=0
837%% c=IN IP4 124.124.124.222
838%% m=audio 2222 RTP/AVP 4
839%% a=ptime:30
840%%                  } ; RTP profile for G.723 is 4
841%%              }
842%%           }
843%%       }
844%%    }
845%% }
846%%----------------------------------------------------------------------
847
848msg15() ->
849    msg15(?MGC_MID).
850msg15(Mid) ->
851    LCD = #'LocalControlDescriptor'{streamMode = sendRecv,
852                                    propertyParms = []},
853    Parms = #'StreamParms'{localControlDescriptor = LCD},
854    StreamDesc = #'StreamDescriptor'{streamID = 1,
855                                     streamParms = Parms},
856    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
857    Signal = #'Signal'{signalName = "al/ri",
858                       sigParList = []},
859    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A5555}],
860                           descriptors   = [{mediaDescriptor, MediaDesc},
861                                            {signalsDescriptor, [{signal, Signal}]}]},
862    CmdReq = #'CommandRequest'{command = {addReq, AmmReq}},
863    Jit = ?PP("nt/jit", "40"),
864    LCD2 = #'LocalControlDescriptor'{streamMode = sendRecv,
865                                     propertyParms = [Jit]},
866    V = ?PP("v", "0"),
867    C = ?PP("c", "IN IP4 $ "),
868    M = ?PP("m", "audio $ RTP/AVP 4"),
869    A = ?PP("a", "ptime:30"),
870    LD2 = #'LocalRemoteDescriptor'{propGrps = [[V, C, M, A]]},
871    V2 = ?PP("v", "0"),
872    C2 = ?PP("c", "IN IP4 124.124.124.222"),
873    M2 = ?PP("m", "audio 2222 RTP/AVP 4"),
874    RD2 = #'LocalRemoteDescriptor'{propGrps = [[V2, C2, M2]]},
875    Parms2 = #'StreamParms'{localControlDescriptor = LCD2,
876                            localDescriptor = LD2,
877                            remoteDescriptor = RD2},
878    StreamDesc2 = #'StreamDescriptor'{streamID = 1,
879                                      streamParms = Parms2},
880    MediaDesc2 = #'MediaDescriptor'{streams = {multiStream, [StreamDesc2]}},
881    ChooseTid = #megaco_term_id{contains_wildcards = true,
882                                id = [[?megaco_choose]]},
883    AmmReq2 = #'AmmRequest'{terminationID = [ChooseTid],
884                            descriptors   = [{mediaDescriptor, MediaDesc2}]},
885    CmdReq2 = #'CommandRequest'{command = {addReq, AmmReq2}},
886    request(Mid, 50003, ?megaco_choose_context_id, [CmdReq, CmdReq2]).
887
888%%----------------------------------------------------------------------
889%% 16. This is acknowledged. The stream port number is different from the
890%%     control port number. In this case it is 1111 (in the SDP).
891%%
892%% MG2 to MGC:
893%% MEGACO/1 [124.124.124.222]:55555
894%% Reply = 50003 {
895%%    Context = 5000 {
896%%       Add = A5556{
897%%          Media {
898%%             Stream = 1 {
899%%                 Local {
900%% v=0
901%% c=IN IP4 125.125.125.111
902%% m=audio 1111 RTP/AVP 4
903%% }
904%%             } ; RTP profile for G723 is 4
905%%          }
906%%        }
907%%    }
908%% }
909%%----------------------------------------------------------------------
910
911msg16() ->
912    msg16(?MG2_MID).
913msg16(Mid) ->
914    V = ?PP("v", "0"),
915    C = ?PP("c", "IN IP4 125.125.125.111"),
916    M = ?PP("m", "audio 1111 RTP/AVP 4"),
917    LD = #'LocalRemoteDescriptor'{propGrps = [[V, C, M]]},
918    Parms = #'StreamParms'{localDescriptor = LD},
919    StreamDesc = #'StreamDescriptor'{streamID = 1,
920                                     streamParms = Parms},
921    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
922    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A5556}],
923                         terminationAudit = [{mediaDescriptor, MediaDesc}]},
924    reply(Mid, 50003, 5000, [{addReply, Reply}]).
925
926%%----------------------------------------------------------------------
927%% 17. The above IPAddr and UDPport need to be given to MG1 now.
928%%
929%% MGC to MG1:
930%% MEGACO/1 [123.123.123.4]:55555
931%% Transaction = 10005 {
932%%   Context = 2000 {
933%%     Modify = A4444 {
934%%       Signals {cg/rt}
935%%     },
936%%     Modify = A4445 {
937%%        Media {
938%%             Stream = 1 {
939%%                 Remote {
940%% v=0
941%% c=IN IP4 125.125.125.111
942%% m=audio 1111 RTP/AVP 4
943%%                 }
944%%             } ; RTP profile for G723 is 4
945%%         }
946%%     }
947%%   }
948%% }
949%%----------------------------------------------------------------------
950
951msg17a() ->
952    msg17a(?MGC_MID).
953msg17a(Mid) ->
954    Signal = #'Signal'{signalName = "cg/rt", sigParList = []},
955    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
956                           descriptors   = [{signalsDescriptor, [{signal, Signal}]}]},
957    CmdReq = #'CommandRequest'{command = {modReq, AmmReq}},
958
959    V = ?PP("v", "0"),
960    C = ?PP("c", "IN IP4 125.125.125.111"),
961    M = ?PP("m", "audio 1111 RTP/AVP 4"),
962    RD2 = #'LocalRemoteDescriptor'{propGrps = [[V, C, M]]},
963    Parms2 = #'StreamParms'{remoteDescriptor = RD2},
964    StreamDesc2 = #'StreamDescriptor'{streamID = 1,
965                                      streamParms = Parms2},
966    MediaDesc2 = #'MediaDescriptor'{streams = {multiStream, [StreamDesc2]}},
967    AmmReq2 = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4445}],
968                            descriptors   = [{mediaDescriptor, MediaDesc2}]},
969    CmdReq2 = #'CommandRequest'{command = {modReq, AmmReq2}},
970    request(Mid, 10005, 2000, [CmdReq, CmdReq2]).
971
972%%----------------------------------------------------------------------
973%% MG1 to MGC:
974%% MEGACO/1 [124.124.124.222]:55555
975%% Reply = 10005 {
976%%    Context = 2000 {Modify = A4444, Modify = A4445}
977%% }
978%%----------------------------------------------------------------------
979
980msg17b() ->
981    msg17b(?MG1_MID).
982msg17b(Mid) ->
983    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
984    Reply2 = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4445}]},
985    reply(Mid, 10005, 2000, [{modReply, Reply}, {modReply, Reply2}]).
986
987%%----------------------------------------------------------------------
988%% 18. The two gateways are now connected and User 1 hears the
989%%     RingBack. The MG2 now waits until User2 picks up the receiver and
990%%     then the two-way call is established.
991%%
992%% MG2 to MGC:
993%% MEGACO/1 [125.125.125.111]:55555
994%% Transaction = 50005 {
995%%    Context = 5000 {
996%%        Notify = A5555 {ObservedEvents =1234 {
997%%          19990729T22020002:al/of}}
998%%    }
999%% }
1000%%----------------------------------------------------------------------
1001
1002msg18a() ->
1003    msg18a(?MG2_MID).
1004msg18a(Mid) ->
1005    TimeStamp = #'TimeNotation'{date = "19990729",
1006                                time = "22020002"},
1007    Event = #'ObservedEvent'{eventName = "al/of",
1008                             timeNotation = TimeStamp,
1009                             eventParList = []},
1010    Desc = #'ObservedEventsDescriptor'{requestId = 1234,
1011                                       observedEventLst = [Event]},
1012    NotifyReq = #'NotifyRequest'{terminationID = [#megaco_term_id{id = ?A5555}],
1013                                 observedEventsDescriptor = Desc},
1014    CmdReq = #'CommandRequest'{command = {notifyReq, NotifyReq}},
1015    request(Mid, 50005, 5000, [CmdReq]).
1016
1017%%----------------------------------------------------------------------
1018%% MGC to MG2:
1019%% MEGACO/1 [123.123.123.4]:55555
1020%% Reply = 50005 {
1021%%     Context = - {Notify = A5555}
1022%% }
1023%%----------------------------------------------------------------------
1024
1025msg18b() ->
1026    msg18b(?MGC_MID).
1027msg18b(Mid) ->
1028    Reply = #'NotifyReply'{terminationID = [#megaco_term_id{id = ?A5555}]},
1029    reply(Mid, 50005, ?megaco_null_context_id, [{notifyReply, Reply}]).
1030
1031%%----------------------------------------------------------------------
1032%% MGC to MG2:
1033%% MEGACO/1 [123.123.123.4]:55555
1034%% Transaction = 50006 {
1035%%    Context = 5000 {
1036%%       Modify = A5555 {
1037%%          Events = 1235 {al/on},
1038%%          Signals { } ; to turn off ringing
1039%%       }
1040%%    }
1041%% }
1042%%----------------------------------------------------------------------
1043
1044msg18c() ->
1045    msg18c(?MGC_MID).
1046msg18c(Mid) ->
1047    On = #'RequestedEvent'{pkgdName = "al/on", evParList = []},
1048    EventsDesc = #'EventsDescriptor'{requestID = 1235,
1049                                     eventList = [On]},
1050    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A5555}],
1051                           descriptors   = [{eventsDescriptor, EventsDesc},
1052                                            {signalsDescriptor, []}]},
1053    CmdReq = #'CommandRequest'{command = {modReq, AmmReq}},
1054    request(Mid, 50006, 5000, [CmdReq]).
1055
1056%%----------------------------------------------------------------------
1057%% MG2 to MGC:
1058%% MEGACO/1 [125.125.125.111]:55555
1059%% Reply = 50006 {
1060%%  Context = 5000 {Modify = A4445}
1061%% }
1062%%----------------------------------------------------------------------
1063
1064msg18d() ->
1065    msg18d(?MG2_MID).
1066msg18d(Mid) ->
1067    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4445}]},
1068    reply(Mid, 50006, 5000, [{modReply, Reply}]).
1069
1070%%----------------------------------------------------------------------
1071%% 19. Change mode on MG1 to SendReceive, and stop the ringback.
1072%%
1073%% MGC to MG1:
1074%% MEGACO/1 [123.123.123.4]:55555
1075%% Transaction = 10006 {
1076%%    Context = 2000 {
1077%%       Modify = A4445 {
1078%%          Media {
1079%%             Stream = 1 {
1080%%                LocalControl {
1081%%                   Mode=SendReceive
1082%%                }
1083%%             }
1084%%          }
1085%%       },
1086%%       Modify = A4444 {
1087%%          Signals { }
1088%%       }
1089%%    }
1090%% }
1091%%----------------------------------------------------------------------
1092
1093msg19a() ->
1094    msg19a(?MGC_MID).
1095msg19a(Mid) ->
1096    LCD = #'LocalControlDescriptor'{streamMode = sendRecv,
1097                                    propertyParms = []},
1098    Parms = #'StreamParms'{localControlDescriptor = LCD},
1099    StreamDesc = #'StreamDescriptor'{streamID = 1,
1100                                     streamParms = Parms},
1101    MediaDesc = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
1102    AmmReq = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4445}],
1103                           descriptors   = [{mediaDescriptor, MediaDesc}]},
1104    CmdReq = #'CommandRequest'{command = {modReq, AmmReq}},
1105    AmmReq2 = #'AmmRequest'{terminationID = [#megaco_term_id{id = ?A4444}],
1106                            descriptors   = [{signalsDescriptor, []}]},
1107    CmdReq2 = #'CommandRequest'{command = {modReq, AmmReq2}},
1108    request(Mid, 10006, 2000, [CmdReq, CmdReq2]).
1109
1110%%----------------------------------------------------------------------
1111%% MG1 to MGC:
1112%% MEGACO/1 [124.124.124.222]:55555
1113%% Reply = 10006 {
1114%%    Context = 2000 {Modify = A4445, Modify = A4444}}
1115%%----------------------------------------------------------------------
1116
1117msg19b() ->
1118    msg19b(?MG1_MID).
1119msg19b(Mid) ->
1120    Reply = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4445}]},
1121    Reply2= #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A4444}]},
1122    reply(Mid, 10006, 2000, [{modReply, Reply}, {modReply, Reply2}]).
1123
1124%%----------------------------------------------------------------------
1125%% 20. The MGC decides to Audit the RTP termination on MG2.
1126%%
1127%% MEGACO/1 [123.123.123.4]:55555
1128%% Transaction = 50007 {
1129%%    Context = - {AuditValue = A5556{
1130%%       Audit{Media, DigitMap, Events, Signals, Packages, Statistics }}
1131%%    }
1132%% }
1133%%----------------------------------------------------------------------
1134
1135msg20() ->
1136    msg20(?MGC_MID).
1137msg20(Mid) ->
1138    Tokens = [mediaToken, eventsToken, signalsToken,
1139              digitMapToken, statsToken, packagesToken],
1140    AuditDesc = #'AuditDescriptor'{auditToken = Tokens},
1141    Req = #'AuditRequest'{terminationID = #megaco_term_id{id = ?A5556},
1142                          auditDescriptor = AuditDesc},
1143    CmdReq = #'CommandRequest'{command = {auditValueRequest, Req}},
1144    request(Mid, 50007, ?megaco_null_context_id, [CmdReq]).
1145
1146%%----------------------------------------------------------------------
1147%% 21. The MG2 replies. An RTP termination has no events nor signals, so
1148%%     these are left out in the reply .
1149%%
1150%% MEGACO/1 [125.125.125.111]:55555
1151%% Reply = 50007 {
1152%%    Context = - {
1153%% AuditValue = A5556 {
1154%%           Media {
1155%%              Stream = 1 {
1156%%                  LocalControl { Mode = SendReceive,
1157%%                     nt/jit=40 },
1158%%                  Local {
1159%% v=0
1160%% c=IN IP4 125.125.125.111
1161%% m=audio 1111 RTP/AVP  4
1162%% a=ptime:30
1163%%                 },
1164%%                  Remote {
1165%% v=0
1166%% c=IN IP4 124.124.124.222
1167%% m=audio 2222 RTP/AVP  4
1168%% a=ptime:30
1169%%                  } } },
1170%%           Packages {nt-1, rtp-1},
1171%%           Statistics { rtp/ps=1200,  ; packets sent
1172%%                        nt/os=62300, ; octets sent
1173%%                        rtp/pr=700, ; packets received
1174%%                        nt/or=45100, ; octets received
1175%%                        rtp/pl=0.2,  ; % packet loss
1176%%                        rtp/jit=20,
1177%%                        rtp/delay=40 } ; avg latency
1178%%        }
1179%%     }
1180%% }
1181%%----------------------------------------------------------------------
1182
1183msg21() ->
1184    msg21(?MG2_MID).
1185msg21(Mid) ->
1186    Jit = ?PP("nt/jit", "40"),
1187    LCD = #'LocalControlDescriptor'{streamMode = sendRecv,
1188                                    propertyParms = [Jit]},
1189    LDV = ?PP("v", "0"),
1190    LDC = ?PP("c", "IN IP4 125.125.125.111"),
1191    LDM = ?PP("m", "audio 1111 RTP/AVP  4"),
1192    LDA = ?PP("a", "ptime:30"),
1193    LD  = #'LocalRemoteDescriptor'{propGrps = [[LDV, LDC, LDM, LDA]]},
1194    RDV = ?PP("v", "0"),
1195    RDC = ?PP("c", "IN IP4 124.124.124.222"),
1196    RDM = ?PP("m", "audio 2222 RTP/AVP  4"),
1197    RDA = ?PP("a", "ptime:30"),
1198    RD  = #'LocalRemoteDescriptor'{propGrps = [[RDV, RDC, RDM, RDA]]},
1199    StreamParms = #'StreamParms'{localControlDescriptor = LCD,
1200                                 localDescriptor = LD,
1201                                 remoteDescriptor = RD},
1202    StreamDesc = #'StreamDescriptor'{streamID = 1,
1203                                     streamParms = StreamParms},
1204    Media = #'MediaDescriptor'{streams = {multiStream, [StreamDesc]}},
1205    PackagesItem  = #'PackagesItem'{packageName = "nt",
1206                                    packageVersion = 1},
1207    PackagesItem2 = #'PackagesItem'{packageName = "rtp",
1208                                    packageVersion = 1},
1209    Stat  = ?SP("rtp/ps","1200"),
1210    Stat2 = ?SP("nt/os","62300"),
1211    Stat3 = ?SP("rtp/pr","700"),
1212    Stat4 = ?SP("nt/or","45100"),
1213    Stat5 = ?SP("rtp/pl","0.2"),
1214    Stat6 = ?SP("rtp/jit","20"),
1215    Stat7 = ?SP("rtp/delay","40"),
1216    Statistics = [Stat, Stat2, Stat3, Stat4, Stat5, Stat6, Stat7],
1217    Audits = [{mediaDescriptor, Media},
1218              {packagesDescriptor, [PackagesItem, PackagesItem2]},
1219              {statisticsDescriptor, Statistics}],
1220    Reply = {auditResult, #'AuditResult'{terminationID = #megaco_term_id{id = ?A5556},
1221					 terminationAuditResult = Audits}},
1222    reply(Mid, 50007, ?megaco_null_context_id, [{auditValueReply, Reply}]).
1223
1224%%----------------------------------------------------------------------
1225%% 22. When the MGC receives an onhook signal from one of the MGs, it
1226%% brings down the call. In this example, the user at MG2 hangs up first.
1227%%
1228%% MG2 to MGC:
1229%% MEGACO/1 [125.125.125.111]:55555
1230%% Transaction = 50008 {
1231%%    Context = 5000 {
1232%%        Notify = A5555 {ObservedEvents =1235 {
1233%%           19990729T24020002:al/on}
1234%%        }
1235%%    }
1236%% }
1237%%----------------------------------------------------------------------
1238
1239msg22a() ->
1240    msg22a(?MG2_MID).
1241msg22a(Mid) ->
1242    TimeStamp = #'TimeNotation'{date = "19990729",
1243                                time = "24020002"},
1244    Event = #'ObservedEvent'{eventName = "al/on",
1245                             timeNotation = TimeStamp,
1246                             eventParList = []},
1247    Desc = #'ObservedEventsDescriptor'{requestId = 1235,
1248                                       observedEventLst = [Event]},
1249    NotifyReq = #'NotifyRequest'{terminationID = [#megaco_term_id{id = ?A5555}],
1250                                 observedEventsDescriptor = Desc},
1251    CmdReq = #'CommandRequest'{command = {notifyReq, NotifyReq}},
1252    request(Mid, 50008, 5000, [CmdReq]).
1253
1254%%----------------------------------------------------------------------
1255%% MGC to MG2:
1256%% MEGACO/1 [123.123.123.4]:55555
1257%% Reply = 50008 {
1258%%     Context = - {Notify = A5555}
1259%% }
1260%%----------------------------------------------------------------------
1261
1262msg22b() ->
1263    msg22b(?MGC_MID).
1264msg22b(Mid) ->
1265    Reply = #'NotifyReply'{terminationID = [#megaco_term_id{id = ?A5555}]},
1266    reply(Mid, 50008, ?megaco_null_context_id, [{notifyReply, Reply}]).
1267
1268%%----------------------------------------------------------------------
1269%% 23. The MGC now sends both MGs a Subtract to take down the call. Only
1270%%     the subtracts to MG2 are shown here. Each termination has its own
1271%%     set of statistics that it gathers. An MGC may not need to request
1272%%     both to be returned. A5555 is a physical termination, and A5556 is
1273%%     an RTP termination.
1274%%
1275%% MGC to MG2:
1276%%
1277%% MEGACO/1 [123.123.123.4]:55555
1278%% Transaction = 50009 {
1279%%    Context = 5000 {
1280%%       Subtract = A5555 {Audit{Statistics}},
1281%%       Subtract = A5556 {Audit{Statistics}}
1282%%    }
1283%% }
1284%%----------------------------------------------------------------------
1285
1286msg23a() ->
1287    msg23a(?MGC_MID).
1288msg23a(Mid) ->
1289    CommonAuditDesc = #'AuditDescriptor'{auditToken = [statsToken]},
1290    SubReq = #'SubtractRequest'{terminationID = [#megaco_term_id{id = ?A5555}],
1291                                auditDescriptor = CommonAuditDesc},
1292    SubReq2 = #'SubtractRequest'{terminationID = [#megaco_term_id{id = ?A5556}],
1293                                 auditDescriptor = CommonAuditDesc},
1294    CmdReq = #'CommandRequest'{command = {subtractReq, SubReq}},
1295    CmdReq2 = #'CommandRequest'{command = {subtractReq, SubReq2}},
1296    request(Mid, 50009, 5000, [CmdReq, CmdReq2]).
1297%%
1298%%----------------------------------------------------------------------
1299%% MG2 to MGC:
1300%%
1301%% MEGACO/1 [125.125.125.111]:55555
1302%% Reply = 50009 {
1303%%    Context = 5000 {
1304%%      Subtract = A5555 {
1305%%           Statistics {
1306%%              nt/os=45123, ; Octets Sent
1307%%              nt/dur=40 ; in seconds
1308%%              }
1309%%        },
1310%%        Subtract = A5556 {
1311%%           Statistics {
1312%%              rtp/ps=1245, ; packets sent
1313%%              nt/os=62345, ; octets sent
1314%%              rtp/pr=780, ; packets received
1315%%              nt/or=45123, ; octets received
1316%%              rtp/pl=10, ;  % packets lost
1317%%              rtp/jit=27,
1318%%              rtp/delay=48 ; average latency
1319%%           }
1320%%        }
1321%%    }
1322%% }
1323%%----------------------------------------------------------------------
1324
1325msg23b() ->
1326    msg23b(?MG2_MID).
1327msg23b(Mid) ->
1328    Stat11 = ?SP("nt/os","45123"),
1329    Stat12 = ?SP("nt/dur", "40"),
1330    Stats1 = [Stat11, Stat12],
1331    Reply1 = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A5555}],
1332                          terminationAudit = [{statisticsDescriptor, Stats1}]},
1333    Stat21 = ?SP("rtp/ps","1245"),
1334    Stat22 = ?SP("nt/os", "62345"),
1335    Stat23 = ?SP("rtp/pr", "780"),
1336    Stat24 = ?SP("nt/or", "45123"),
1337    Stat25 = ?SP("rtp/pl", "10"),
1338    Stat26 = ?SP("rtp/jit", "27"),
1339    Stat27 = ?SP("rtp/delay","48"),
1340    Stats2 = [Stat21, Stat22, Stat23, Stat24, Stat25, Stat26, Stat27],
1341    Reply2 = #'AmmsReply'{terminationID = [#megaco_term_id{id = ?A5556}],
1342                          terminationAudit = [{statisticsDescriptor, Stats2}]},
1343    reply(Mid, 50009, 5000, [{subtractReply, Reply1}, {subtractReply, Reply2}]).
1344
1345%%----------------------------------------------------------------------
1346%% 24. The MGC now sets up both MG1 and MG2 to be ready to detect the
1347%%     next off-hook event. See step 1. Note that this could be the
1348%%     default state of a termination in the null context, and if this
1349%%     were the case, no message need be sent from the MGC to the
1350%%     MG. Once a termination returns to the null context, it goes back
1351%%     to the default termination values for that termination.
1352%%----------------------------------------------------------------------
1353
1354%% BUGBUG: Example missing in spec
1355
1356%%----------------------------------------------------------------------
1357%% Testing
1358%%----------------------------------------------------------------------
1359
1360messages() ->
1361    [{Slogan, catch ?MODULE:Slogan()} || Slogan <- names()].
1362
1363encoders() ->
1364    [
1365     {megaco_pretty_text_encoder,  [], 	         []},
1366     {megaco_compact_text_encoder, [], 	         []},
1367     {megaco_binary_encoder,       [], 	         [native]},
1368     {megaco_ber_encoder,          [],           [native]},
1369     {megaco_per_encoder,          [], 	         [native]},
1370     {megaco_erl_dist_encoder,     [],      	 []},
1371     {megaco_erl_dist_encoder,     [compressed], [compressed]}
1372    ].
1373
1374
1375pretty_mod({Mod, Opt, _Opt2}) ->
1376    case Mod of
1377	megaco_pretty_text_encoder   when Opt == [flex] -> pretty_flex;
1378	megaco_compact_text_encoder  when Opt == [flex] -> compact_flex;
1379	megaco_pretty_text_encoder  -> pretty_text;
1380	megaco_compact_text_encoder -> compact_text;
1381	megaco_binary_encoder       -> asn1_ber;
1382	megaco_ber_encoder          -> asn1_ber_old;
1383	megaco_per_encoder          -> asn1_per;
1384	megaco_erl_dist_encoder when Opt == []           -> standard_erl;
1385	megaco_erl_dist_encoder when Opt == [compressed] -> compressed_erl;
1386	Ugly                        -> Ugly
1387    end.
1388
1389%%----------------------------------------------------------------------
1390%% Run specific encoder for all test cases
1391%%----------------------------------------------------------------------
1392
1393pretty_text() ->
1394    Default = [],
1395    Encoder = {megaco_pretty_text_encoder, Default, Default},
1396    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1397    compute_res(All).
1398
1399compact_text() ->
1400    Default = [],
1401    Encoder = {megaco_compact_text_encoder, Default, Default},
1402    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1403    compute_res(All).
1404
1405pretty_flex() ->
1406    Default = [flex],
1407    Encoder = {megaco_pretty_text_encoder, Default, Default},
1408    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1409    compute_res(All).
1410
1411compact_flex() ->
1412    Default = [flex],
1413    Encoder = {megaco_compact_text_encoder, Default, Default},
1414    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1415    compute_res(All).
1416
1417bin() ->
1418    Default = [],
1419    Native = [native],
1420    Encoder = {megaco_binary_encoder, Default, Native},
1421    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1422    compute_res(All).
1423
1424asn1_ber() ->
1425    Default = [],
1426    Native = [native],
1427    Encoder = {megaco_ber_encoder, Default, Native},
1428    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1429    compute_res(All).
1430
1431asn1_per() ->
1432    Default = [],
1433    Native = [native],
1434    Encoder = {megaco_per_encoder, Default, Native},
1435    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1436    compute_res(All).
1437
1438standard_erl() ->
1439    Config = [],
1440    Encoder = {megaco_erl_dist_encoder, Config, Config},
1441    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1442    compute_res(All).
1443
1444compressed_erl() ->
1445    Config = [compressed],
1446    Encoder = {megaco_erl_dist_encoder, Config, Config},
1447    All = [encode(Slogan, Msg, Encoder) || {Slogan, Msg} <- messages()],
1448    compute_res(All).
1449
1450encode(Slogan, DecodedMsg, {Mod, Opt, _Opt2} = _Encoder) ->
1451    Main = "==================================================",
1452    Sub  = "--------------------------------------------------",
1453    case catch Mod:encode_message(Opt, DecodedMsg) of
1454        {ok, EncodedMsg} when is_binary(EncodedMsg) ->
1455            Sz = size(EncodedMsg),
1456            ok = io:format("~w ~s ~w bytes~n",
1457                           [Slogan, Main, Sz]),
1458            catch io:format("~n~s~n", [(catch binary_to_list(EncodedMsg))]),
1459            case catch Mod:decode_message(Opt, EncodedMsg) of
1460                {ok, ReDecodedMsg} ->
1461                    fmt(Slogan, DecodedMsg, ReDecodedMsg);
1462                {error, {Line, _, Reason}} when is_integer(Line)->
1463                    ?ERROR([{Slogan, Line, decode_failed}, {error,  Reason}, {encoded, EncodedMsg}, DecodedMsg]),
1464                    io:format("~n~w <ERROR> #~w: ~w~n",
1465                              [Slogan, Line, Reason]);
1466                Other ->
1467                    ?ERROR([{Slogan, 0, decode_failed}, Other, {encoded, EncodedMsg}, DecodedMsg]),
1468                    io:format("~n~w <ERROR> ~w~n", [Slogan, Other])
1469            end,
1470            Sz;
1471        Other ->
1472            ?ERROR([{Slogan, encode_failed}, Other, DecodedMsg]),
1473            ok = io:format("~w ~s~n~p~n<ERROR> ~s~n~p~n",
1474                           [Slogan, Main, DecodedMsg, Sub, Other]),
1475            {Slogan, {encode_message, Other}}
1476    end.
1477
1478fmt(_Slogan, Msg, Msg) ->
1479    ok;
1480fmt(Slogan, {'MegacoMessage', A, {'Message', V, MID, {transactions, [{T, Old}]}}},
1481    {'MegacoMessage', A, {'Message', V, MID, {transactions, [{T, New}]}}}) ->
1482    fmt(Slogan, Old, New);
1483fmt(Slogan, Old, New) ->
1484    PrettyOld = lists:flatten(io_lib:format("~p", [Old])),
1485    PrettyNew = lists:flatten(io_lib:format("~p", [New])),
1486    fmt_diff(Slogan, Old, New, PrettyOld, PrettyNew, []).
1487
1488fmt_diff(Slogan, Old, New, [H |  OldRest], [H |  NewRest], Common) ->
1489    fmt_diff(Slogan, Old, New, OldRest, NewRest, [H | Common]);
1490fmt_diff(Slogan, Old, New, OldRest, NewRest, Common) ->
1491    RevCommon = lists:reverse(Common),
1492    ?ERROR([{Slogan, decode_mismatch}, {old, Old}, {new, New}]),
1493    Sub = "--------------------------------------------------",
1494    io:format("~n~w COMMON ~s~n~s~n", [Slogan, Sub, RevCommon]),
1495    io:format("~n~w OLD    ~s~n~s~n", [Slogan, Sub, OldRest]),
1496    io:format("~n~w NEW    ~s~n~s~n", [Slogan, Sub, NewRest]).
1497
1498compute_res(All) ->
1499    compute_res(All, [], 0).
1500
1501compute_res([H | T], Bad, Sum) when is_integer(H) ->
1502    compute_res(T, Bad, Sum + H);
1503compute_res([H | T], Bad, Sum) ->
1504    compute_res(T, [H | Bad], Sum);
1505compute_res([], Bad, Sum) ->
1506    ok = io:format("#bytes: ~w; errors: ~p~n", [Sum, Bad]).
1507
1508%%----------------------------------------------------------------------
1509%% Compute sizes of encoded messages
1510%%----------------------------------------------------------------------
1511
1512msg_sizes() ->
1513    Encoders = encoders(),
1514    msg_sizes(Encoders).
1515
1516%% Returns a list of {MessageSlogan, MessageSizes} where
1517%% MessageSizes is the result from msg_sizes/2
1518msg_sizes(Encoders) ->
1519    [{S, msg_sizes(Msg, Encoders)} ||  {S, Msg} <- messages()].
1520
1521%% Returns a list of {Encoder, Res} tuples
1522%% where Res either is the message size (integer)
1523%% or an error atom
1524msg_sizes(DecodedMsg, Encoders) ->
1525    [abs_msg_size(DecodedMsg, E) || E <- Encoders].
1526
1527abs_msg_size(DecodedMsg, {Mod, Opt, _Opt2} = Encoder) ->
1528    case catch Mod:encode_message(Opt, DecodedMsg) of
1529        {ok, EncodedMsg} when is_binary(EncodedMsg) ->
1530	    {Encoder, size(EncodedMsg)};
1531        Error ->
1532            {Encoder, {bad_encoder, Error}}
1533    end.
1534
1535%%----------------------------------------------------------------------
1536%% Compute time for encoding messages
1537%%----------------------------------------------------------------------
1538
1539encoding_times() ->
1540    Encoders = encoders(),
1541    encoding_times(Encoders).
1542
1543%% Returns a list of {MessageSlogan, EncodingTimes} where
1544%% EncodingTimes is the result from encoding_times/2
1545encoding_times(Encoders) ->
1546    [{Slogan, encoding_times(Msg, Encoders)} ||  {Slogan, Msg} <- messages()].
1547
1548%% Returns a list of {Encoder, Res} tuples
1549%% where Res either is the encoding time (integer)
1550%% or an error atom
1551encoding_times(DecodedMsg, Encoders) ->
1552    [{E, encoding_time(encoding_msg(DecodedMsg, E))} || E <- Encoders].
1553
1554encoding_msg(DecodedMsg, {Mod, Opt, Opt2} = Encoder) ->
1555    {ok, EncodedMsg}  = Mod:encode_message(Opt,  DecodedMsg),
1556    {ok, DecodedMsg2} = Mod:decode_message(Opt2, EncodedMsg),
1557    {Encoder, DecodedMsg2}.
1558
1559encoding_time({{Mod, _Opt, Opt2} = Encoder, DecodedMsg}) ->
1560    meter(fun() -> {ok, _} = Mod:encode_message(Opt2, DecodedMsg) end, Encoder).
1561
1562%%----------------------------------------------------------------------
1563%% Compute time for decoding messages
1564%%----------------------------------------------------------------------
1565
1566decoding_times() ->
1567    Decoders = encoders(),
1568    decoding_times(Decoders).
1569
1570%% Returns a list of {MessageSlogan, DecodingTimes} where
1571%% DecodingTimes is the result from decoding_times/2
1572decoding_times(Decoders) ->
1573    [{Slogan, decoding_times(Msg, Decoders)} ||  {Slogan, Msg} <- messages()].
1574
1575%% Returns a list of {Decoder, Res} tuples
1576%% where Res either is the decoding time (integer)
1577%% or an error atom
1578decoding_times(DecodedMsg, Encoders) ->
1579    [{E, decoding_time(decoding_msg(DecodedMsg, E))} || E <- Encoders].
1580
1581decoding_msg(DecodedMsg, {Mod, Opt, _Opt2} = Encoder) ->
1582    {ok, EncodedMsg} = Mod:encode_message(Opt, DecodedMsg),
1583    {Encoder, EncodedMsg}.
1584
1585decoding_time({{Mod, _Opt, Opt2} = Encoder, EncodedMsg}) ->
1586    meter(fun() -> {ok, _} = Mod:decode_message(Opt2, EncodedMsg) end, Encoder).
1587
1588%%----------------------------------------------------------------------
1589%%----------------------------------------------------------------------
1590
1591coding_times() ->
1592    Coders = encoders(),
1593    coding_times(Coders).
1594
1595%% Returns a list of {MessageSlogan, DecodingTimes} where
1596%% DecodingTimes is the result from decoding_times/2
1597coding_times(Coders) ->
1598    [{Slogan, coding_times(Msg, Coders)} ||  {Slogan, Msg} <- messages()].
1599
1600%% Returns a list of {Decoder, Res} tuples
1601%% where Res either is the decoding time (integer)
1602%% or an error atom
1603coding_times(DecodedMsg, Coders) ->
1604    [{E, coding_time(coding_msg(DecodedMsg, E))} || E <- Coders].
1605
1606coding_msg(DecodedMsg, {Mod, Opt, _Opt2} = Encoder) ->
1607    {ok, EncodedMsg} = Mod:encode_message(Opt, DecodedMsg),
1608    {Encoder, EncodedMsg}.
1609
1610coding_time({{Mod, _Opt, Opt2} = Encoder, EncodedMsg}) ->
1611    Fun = fun() ->
1612		  {ok, DecodedMsg} = Mod:decode_message(Opt2, EncodedMsg),
1613		  {ok, _}          = Mod:encode_message(Opt2, DecodedMsg)
1614	  end,
1615    meter(Fun, Encoder).
1616
1617%%----------------------------------------------------------------------
1618%% Return size statistics as term
1619%%----------------------------------------------------------------------
1620
1621size_stat() ->
1622    Encoders = encoders(),
1623    MsgSizes = msg_sizes(Encoders),
1624    stat(Encoders, MsgSizes).
1625
1626%%----------------------------------------------------------------------
1627%%----------------------------------------------------------------------
1628
1629gnuplot_gif() ->
1630    [
1631     {size,     gnuplot_size_gif()},
1632     {encoding, gnuplot_enc_time_gif()},
1633     {decoding, gnuplot_dec_time_gif()},
1634     {coding,   gnuplot_code_time_gif()}
1635    ].
1636
1637%%----------------------------------------------------------------------
1638%% Generate GIF picture from size statistics with gnuplot
1639%%----------------------------------------------------------------------
1640
1641gnuplot_size_gif() ->
1642    {ok, _Cwd} = file:get_cwd(),
1643    TmpDir = "megaco_encoded_size.tmp",
1644    GifFile = "megaco_encoded_size.gif",
1645    Header =
1646        ["set title \"Size comparison of Megaco/H.248 encoding formats\"\n",
1647         "set timestamp top\n",
1648         "set terminal gif\n",
1649         "set xlabel \"Test cases from Appendix A\"\n",
1650         "set ylabel \"Message size in bytes\"\n",
1651         "set rmargin 10\n",
1652         "set key left top Left\n",
1653         "set output \"", GifFile, "\"\n\n"],
1654    Encoders = encoders(),
1655    Stat = msg_sizes(Encoders),
1656    BatchFile = gnuplot_dir(TmpDir, Header, Encoders, Stat),
1657    Cmd = "cd " ++ TmpDir ++ "; gnuplot " ++ BatchFile,
1658    os:cmd(Cmd),
1659    ok = io:format("~n~s~nxv ~s~n", [Cmd, filename:join([TmpDir, GifFile])]),
1660    stat(Encoders, Stat).
1661
1662%%----------------------------------------------------------------------
1663%% Return encoding time statistics as term
1664%%----------------------------------------------------------------------
1665
1666encoding_times_stat() ->
1667    Encoders = encoders(),
1668    EncodingTimes = encoding_times(Encoders),
1669    stat(Encoders, EncodingTimes).
1670
1671%%----------------------------------------------------------------------
1672%% Return encoding time statistics as term
1673%%----------------------------------------------------------------------
1674
1675decoding_times_stat() ->
1676    Encoders = encoders(),
1677    DecodingTimes = decoding_times(Encoders),
1678    stat(Encoders, DecodingTimes).
1679
1680%%----------------------------------------------------------------------
1681%% Return encoding time statistics as term
1682%%----------------------------------------------------------------------
1683
1684coding_times_stat() ->
1685    Encoders = encoders(),
1686    CodingTimes = coding_times(Encoders),
1687    stat(Encoders, CodingTimes).
1688
1689%%----------------------------------------------------------------------
1690%% Generate GIF picture from encoding time statistics with gnuplot
1691%%----------------------------------------------------------------------
1692
1693gnuplot_enc_time_gif() ->
1694    {ok, _Cwd} = file:get_cwd(),
1695    TmpDir = "megaco_encoding_time.tmp",
1696    GifFile = "megaco_encoding_time.gif",
1697    Header =
1698        ["set title \"Encoding time comparison of Megaco/H.248 encoding formats\"\n",
1699         "set timestamp top\n",
1700         "set terminal gif\n",
1701         "set xlabel \"Test cases from Appendix A\"\n",
1702         "set ylabel \"Time for encoding in micro seconds\"\n",
1703         "set rmargin 10\n",
1704         "set key left top Left\n",
1705         "set output \"", GifFile, "\"\n\n"],
1706    Encoders = encoders(),
1707    Stat = encoding_times(Encoders),
1708    BatchFile = gnuplot_dir(TmpDir, Header, Encoders, Stat),
1709    Cmd = "cd " ++ TmpDir ++ "; gnuplot " ++ BatchFile,
1710    os:cmd(Cmd),
1711    ok = io:format("~n~s~nxv ~s~n", [Cmd, filename:join([TmpDir, GifFile])]),
1712    stat(Encoders, Stat).
1713
1714%%----------------------------------------------------------------------
1715%% Generate GIF picture from decoding time statistics with gnuplot
1716%%----------------------------------------------------------------------
1717
1718gnuplot_dec_time_gif() ->
1719    {ok, _Cwd} = file:get_cwd(),
1720    TmpDir = "megaco_decoding_time.tmp",
1721    GifFile = "megaco_decoding_time.gif",
1722    Header =
1723        ["set title \"Decoding time comparison of Megaco/H.248 encoding formats\"\n",
1724         "set timestamp top\n",
1725         "set terminal gif\n",
1726         "set xlabel \"Test cases from Appendix A\"\n",
1727         "set ylabel \"Time for decoding in micro seconds\"\n",
1728         "set rmargin 10\n",
1729         "set key left top Left\n",
1730         "set output \"", GifFile, "\"\n\n"],
1731    Encoders = encoders(),
1732    Stat = decoding_times(Encoders),
1733    BatchFile = gnuplot_dir(TmpDir, Header, Encoders, Stat),
1734    Cmd = "cd " ++ TmpDir ++ "; gnuplot " ++ BatchFile,
1735    os:cmd(Cmd),
1736    ok = io:format("~n~s~nxv ~s~n", [Cmd, filename:join([TmpDir, GifFile])]),
1737    stat(Encoders, Stat).
1738
1739%%----------------------------------------------------------------------
1740%% Generate GIF picture from decoding time statistics with gnuplot
1741%%----------------------------------------------------------------------
1742
1743gnuplot_code_time_gif() ->
1744    {ok, _Cwd} = file:get_cwd(),
1745    TmpDir = "megaco_coding_time.tmp",
1746    GifFile = "megaco_coding_time.gif",
1747    Header =
1748        ["set title \"Encoding + decoding time comparison of Megaco/H.248 encoding formats\"\n",
1749         "set timestamp top\n",
1750         "set terminal gif\n",
1751         "set xlabel \"Test cases from Appendix A\"\n",
1752         "set ylabel \"Time for encoding+decoding in micro seconds\"\n",
1753         "set rmargin 10\n",
1754         "set key left top Left\n",
1755         "set output \"", GifFile, "\"\n\n"],
1756    Encoders = encoders(),
1757    Stat = coding_times(Encoders),
1758    BatchFile = gnuplot_dir(TmpDir, Header, Encoders, Stat),
1759    Cmd = "cd " ++ TmpDir ++ "; gnuplot " ++ BatchFile,
1760    os:cmd(Cmd),
1761    ok = io:format("~n~s~nxv ~s~n", [Cmd, filename:join([TmpDir, GifFile])]),
1762    stat(Encoders, Stat).
1763
1764%%----------------------------------------------------------------------
1765%% Encode asn.1 messages
1766%%----------------------------------------------------------------------
1767
1768gen_byte_msg(Msg, {Mod, Opt, _Opt2} = _Encoder) ->
1769    {ok, EncodedMsg} = Mod:encode_message(Opt, Msg),
1770    EncodedMsg.
1771
1772%%----------------------------------------------------------------------
1773%% Gen the C header file content as a binary
1774%%----------------------------------------------------------------------
1775
1776gen_header_file_binary([]) ->
1777    ok;
1778gen_header_file_binary([{S, B}| Rest]) ->
1779    file:write_file(atom_to_list(S), B),
1780    gen_header_file_binary(Rest).
1781
1782%%----------------------------------------------------------------------
1783%% Generate headerfile for asn.1 BER test in C
1784%%----------------------------------------------------------------------
1785
1786gen_ber_header() ->
1787    Encoder = {megaco_ber_encoder, [], []},
1788    L = [{S, gen_byte_msg(Msg, Encoder)} ||  {S, Msg} <- messages()],
1789    gen_header_file_binary(L).
1790
1791%%----------------------------------------------------------------------
1792%% Generate headerfile for asn.1 BER test in C
1793%%----------------------------------------------------------------------
1794gen_ber_bin_header() ->
1795    Encoder = {megaco_ber_encoder, [], []},
1796    L = [{S, gen_byte_msg(Msg, Encoder)} ||  {S, Msg} <- messages()],
1797    gen_header_file_binary(L).
1798
1799%%----------------------------------------------------------------------
1800%% Generate headerfile for asn.1 PER test in C
1801%%----------------------------------------------------------------------
1802gen_per_header() ->
1803    Encoder = {megaco_per_encoder, [], []},
1804    L = [{S, gen_byte_msg(Msg, Encoder)} ||  {S, Msg} <- messages()],
1805    gen_header_file_binary(L).
1806
1807%%----------------------------------------------------------------------
1808%% Execute a fun a number of times and return avg in millis
1809%%----------------------------------------------------------------------
1810
1811meter(Fun, Encoder) ->
1812    MaxTime = timer:seconds(5),
1813    Rep = 2,
1814    M = pretty_mod(Encoder),
1815    io:format("~p:\t", [M]),
1816    Times  = [single_meter(Fun, MaxTime, {M, N}) || N <- lists:seq(1, Rep)],
1817    Min = lists:min(Times),
1818    Max = lists:max(Times),
1819    Median = lists:nth((Rep + 1) div 2, lists:sort(Times)),
1820    io:format("(median=~p, diff=~p)~n", [Median, Max - Min]),
1821    Median.
1822
1823single_meter(Fun, MaxTime, Tag) ->
1824    Pid = spawn_link(?MODULE, single_meter, [self(), Fun, MaxTime, Tag]),
1825    receive
1826	{meter, Pid, Time} ->
1827	    io:format("~p \t", [Time]),
1828	    Time;
1829	{'EXIT', Pid, Reason} ->
1830	    {bad_result, Reason}
1831    end.
1832
1833single_meter(Parent, Fun, Expected, _Tag) ->
1834    erlang:statistics(runtime),
1835    erlang:send_after(Expected, self(), return_count),
1836    Count = count(Fun, 1),
1837    {_, Actual} = erlang:statistics(runtime),
1838    %% Diff = Actual - Expected,
1839    Micros = trunc((Actual / Count) * 1000),
1840    Parent ! {meter, self(), Micros},
1841    unlink(Parent),
1842    exit(normal).
1843
1844count(Fun, N) ->
1845    Fun(),
1846    receive
1847	return_count ->
1848	    N
1849    after 0 ->
1850	    count(Fun, N + 1)
1851    end.
1852
1853stat(Encoders, Stat) ->
1854    Fun =
1855	fun({_Mod, _Opt, _Opt2} = E) ->
1856		List = lists:flatten([[Val || {E2, Val} <- Info, E2 == E] ||
1857					 {_Slogan, Info} <- Stat]),
1858		Max = lists:max(List),
1859		Min = lists:min(List),
1860		case catch lists:sum(List) of
1861		    {'EXIT', _} ->
1862			{E, bad, List};
1863		    Sum when is_integer(Sum) ->
1864			N   = length(List),
1865			{E, [{min, Min}, {avg, Sum div N}, {max, Max}]}
1866		end
1867	end,
1868    lists:map(Fun, Encoders).
1869
1870gnuplot_dir(Dir, Header, Encoders, Stat) ->
1871    file:make_dir(Dir),
1872    {Names, Arrows} = gnuplot_data(Encoders, Dir, Stat, 1, [], []),
1873    [H | T] = [lists:concat(["\"", N, "\" with linespoints"]) || N <- Names],
1874    Plots = [[", ", Plot] || Plot <- T],
1875    IoList = [Header, Arrows, "\nplot ", H, Plots, "\n\nshow output\n"],
1876    Bin = list_to_binary(IoList),
1877    BatchFile = "gnuplot.batch",
1878    file:write_file(filename:join(Dir, BatchFile), Bin),
1879    BatchFile.
1880
1881gnuplot_data([], _Dir, _Stat, _Pos, Names, Arrows) ->
1882    {lists:reverse(Names), lists:reverse(Arrows)};
1883gnuplot_data([{Mod, _Opt, _Opt2} = E | Encoders], Dir, Stat, Pos, Names, Arrows) ->
1884    Plot = fun({Msg, List}, {N, AccSz}) ->
1885                   {value, {_, Sz}} = lists:keysearch(E, 1, List),
1886                   ActualSz =
1887                       if
1888                           is_integer(Sz) ->
1889                               Sz;
1890                           true        ->
1891                               ok = io:format("<ERROR> ~p(~p) -> ~p~n",
1892                                              [Mod, Msg, Sz]),
1893                               0
1894                       end,
1895                   Acc = {N + 1, [ActualSz | AccSz]},
1896                   {lists:concat([N + 1, " ", ActualSz, "\n"]), Acc}
1897           end,
1898    {Data, {N, Sizes}} = lists:mapfoldl(Plot, {0, []}, Stat),
1899    Min = lists:min(Sizes),
1900    Max = lists:max(Sizes),
1901    Sum = lists:sum(Sizes),
1902    Avg = Sum div N,
1903    Len = length(Stat),
1904    Pretty = pretty_mod(E),
1905    Name = lists:concat([Pretty, " (", Min, ",", Avg, ",", Max, ")"]),
1906    file:write_file(filename:join(Dir, Name), list_to_binary(Data)),
1907    %%"plot \"-\" title \"", E, "\" with linespoints\n", Data, "e\n",
1908    Arrow =
1909	lists:concat(["set arrow from 1,", Avg,
1910		      " to ", Len, ", ", Avg,
1911		      " nohead lt ", Pos, "\n",
1912		      "set label \" ", Avg, " (avg)\" at ", Len, ",", Avg + 10, "\n"]),
1913    gnuplot_data(Encoders, Dir, Stat, Pos + 1, [Name | Names], [Arrow | Arrows]).
1914
1915
1916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1917
1918p(F, A) ->
1919    io:format("*** [~s] ***"
1920	      "~n   " ++ F ++ "~n",
1921	      [?FTS() | A]).
1922
1923