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