PageRenderTime 104ms CodeModel.GetById 5ms app.highlight 79ms RepoModel.GetById 1ms app.codeStats 2ms

/lib/megaco/src/engine/megaco_messenger.erl

https://github.com/bsmr-erlang/otp
Erlang | 5371 lines | 3901 code | 623 blank | 847 comment | 25 complexity | a23706a7dfa9de93cd45527b8c44eb56 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1%%
   2%% %CopyrightBegin%
   3%%
   4%% Copyright Ericsson AB 1999-2016. 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: Send and process a (sequence of) Megaco/H.248 transactions
  24%%----------------------------------------------------------------------
  25
  26-module(megaco_messenger).
  27
  28%% Application internal export
  29-export([
  30         process_received_message/4, process_received_message/5,
  31         receive_message/4, receive_message/5,
  32         connect/4, connect/5, 
  33         disconnect/2,
  34         encode_actions/3, 
  35         call/3,
  36         cast/3,
  37         cancel/2,
  38         request_timeout/2,
  39	 request_keep_alive_timeout/2, 
  40         pending_timeout/3,
  41         reply_timeout/3,
  42         segment_timeout/3,
  43         %% segment_reply_timeout/4,
  44
  45	 test_request/5,
  46	 test_reply/5
  47        ]).
  48
  49%% MIB stat functions
  50-export([
  51	 get_stats/0, get_stats/1, get_stats/2,
  52	 reset_stats/0, reset_stats/1
  53	]).
  54
  55%% Misc functions
  56-export([
  57	 cleanup/2, 
  58	 which_requests/1, which_replies/1
  59	]).
  60
  61%% Module internal export
  62-export([
  63	 process_received_message/6,
  64         handle_request/2,
  65         handle_long_request/2,
  66         connect_remote/3,
  67         disconnect_local/2,
  68         disconnect_remote/3,
  69         send_request_remote/4,
  70         receive_reply_remote/2, receive_reply_remote/3
  71        ]).
  72
  73-include_lib("megaco/include/megaco.hrl").
  74-include("megaco_message_internal.hrl").
  75-include_lib("megaco/src/app/megaco_internal.hrl").
  76
  77%% N.B. Update cancel/1 with '_' when a new field is added
  78-record(request,
  79        {trans_id,
  80	 remote_mid,
  81         timer_ref,  % {short, Ref} | {long, Ref}
  82         init_timer,
  83         init_long_timer,
  84         curr_timer,
  85         version,
  86         bytes,     % {send, Data} | {no_send, Data}, Data = binary() | tuple()
  87         send_handle,
  88         user_mod,
  89         user_args,
  90         reply_action, % call | cast
  91         reply_data,
  92	 seg_recv = [],     % [integer()] (received segments)
  93	 init_seg_timer,
  94	 seg_timer_ref,
  95	 keep_alive_timer,  % plain | integer() >= 0
  96	 keep_alive_ref     % undefined | ref()
  97        }).
  98
  99
 100%% N.B. Update cancel/1 with '_' when a new field is added
 101-record(reply,
 102        {
 103	 trans_id,
 104	 local_mid,
 105	 state = prepare,     % prepare | eval_request | waiting_for_ack | aborted
 106	 pending_timer_ref,
 107	 handler = undefined, % pid of the proc executing the callback func
 108	 timer_ref,
 109	 version,
 110	 %% bytes: Sent reply data: not acknowledged
 111	 bytes,               % binary() | [{integer(), binary(), timer_ref()}]
 112	 ack_action,          % discard_ack | {handle_ack, Data}
 113	 send_handle,
 114	 %% segments: Not sent reply data (segments)
 115	 segments = [],       % [{integer(), binary()}]
 116	 user_mod,
 117	 user_args
 118	}).
 119
 120-record(trans_id,
 121        {
 122          mid,
 123          serial
 124         }).
 125
 126
 127-ifdef(MEGACO_TEST_CODE).
 128-define(SIM(Other,Where),
 129	fun(Afun,Bfun) ->
 130		Kfun = {?MODULE,Bfun},
 131		case (catch ets:lookup(megaco_test_data, Kfun)) of
 132		    [{Kfun,Cfun}] ->
 133			Cfun(Afun);
 134		    _ ->
 135			Afun
 136		end
 137	end(Other,Where)).
 138-define(TC_AWAIT_CANCEL_EVENT(),
 139	case megaco_tc_controller:lookup(block_on_cancel) of
 140	    {value, {Tag, Pid}} when is_pid(Pid) ->
 141		Pid ! {Tag, self()},
 142		receive
 143		    {Tag, Pid} ->
 144			ok
 145		end;
 146	    {value, {sleep, To}} when is_integer(To) andalso (To > 0) ->
 147		receive after To -> ok end;
 148	    _ ->
 149		ok
 150	end).
 151-define(TC_AWAIT_REPLY_EVENT(Info),
 152	case megaco_tc_controller:lookup(block_on_reply) of
 153	    {value, {Tag, Pid}} when is_pid(Pid) ->
 154		Pid ! {Tag, self(), Info},
 155		receive
 156		    {Tag, Pid} ->
 157			ok
 158		end;
 159	    _Whatever ->
 160		%% io:format("Whatever: ~p~n", [Whatever]),
 161		ok
 162	end).
 163-else.
 164-define(SIM(Other,Where),Other).
 165-define(TC_AWAIT_CANCEL_EVENT(),ok).
 166-define(TC_AWAIT_REPLY_EVENT(_),ok).
 167-endif.
 168
 169
 170-define(report_pending_limit_exceeded(ConnData),
 171	?report_important(ConnData, "<ERROR> pending limit exceeded", [])).
 172
 173-ifdef(megaco_extended_trace).
 174-define(rt1(T,F,A),?report_trace(T,F,A)).
 175-define(rt2(F,A),  ?rt1(ignore,F,A)).
 176-define(rt3(F),    ?rt2(F,[])).
 177-else.
 178-define(rt1(T,F,A),ok).
 179-define(rt2(F,A),  ok).
 180-define(rt3(F),    ok).
 181-endif.
 182
 183
 184%%----------------------------------------------------------------------
 185%% SNMP statistics handling functions
 186%%----------------------------------------------------------------------
 187
 188%%-----------------------------------------------------------------
 189%% Func: get_stats/0, get_stats/1, get_stats/2
 190%% Description: Retreive statistics (counters) for TCP
 191%%-----------------------------------------------------------------
 192
 193get_stats() ->
 194    megaco_stats:get_stats(megaco_stats).
 195
 196get_stats(ConnHandleOrCounter) ->
 197    megaco_stats:get_stats(megaco_stats, ConnHandleOrCounter).
 198
 199get_stats(ConnHandle, Counter) ->
 200    megaco_stats:get_stats(megaco_stats, ConnHandle, Counter).
 201
 202
 203%%-----------------------------------------------------------------
 204%% Func: reset_stats/0, reaet_stats/1
 205%% Description: Reset statistics (counters)
 206%%-----------------------------------------------------------------
 207
 208reset_stats() ->
 209    megaco_stats:reset_stats(megaco_stats).
 210
 211reset_stats(ConnHandleOrCounter) ->
 212    megaco_stats:reset_stats(megaco_stats, ConnHandleOrCounter).
 213
 214
 215
 216%%----------------------------------------------------------------------
 217%% cleanup utility functions
 218%%----------------------------------------------------------------------
 219
 220cleanup(#megaco_conn_handle{local_mid = LocalMid}, Force) 
 221  when (Force =:= true) orelse (Force =:= false) ->
 222    Pat = #reply{trans_id  = '$1', 
 223		 local_mid = LocalMid, 
 224		 state     = '$2',
 225		 _         = '_'},
 226    do_cleanup(Pat, Force);
 227cleanup(LocalMid, Force) 
 228  when (Force =:= true) orelse (Force =:= false) ->
 229    Pat = #reply{trans_id  = '$1', 
 230		 local_mid = LocalMid, 
 231		 state     = '$2',
 232		 _         = '_'},
 233    do_cleanup(Pat, Force).
 234    
 235do_cleanup(Pat, Force) ->
 236    Match = megaco_monitor:which_replies(Pat),
 237    Reps  = [{V1, V2} || [V1, V2] <- Match],
 238    do_cleanup2(Reps, Force).
 239
 240do_cleanup2([], _) ->
 241    ok;
 242do_cleanup2([{TransId, aborted}|T], Force = false) ->
 243    megaco_monitor:delete_reply(TransId),
 244    do_cleanup2(T, Force);
 245do_cleanup2([_|T], Force = false) ->
 246    do_cleanup2(T, Force);
 247do_cleanup2([{TransId, _State}|T], Force = true) ->
 248    megaco_monitor:delete_reply(TransId),
 249    do_cleanup2(T, Force).
 250		  
 251
 252%%----------------------------------------------------------------------
 253%% which_requests and which_replies utility functions
 254%%----------------------------------------------------------------------
 255
 256which_requests(#megaco_conn_handle{local_mid  = LocalMid, 
 257				   remote_mid = RemoteMid}) ->
 258    Pat1 = #trans_id{mid    = LocalMid, 
 259		     serial = '$1', _ = '_'},
 260    Pat2 = #request{trans_id   = Pat1, 
 261		    remote_mid = RemoteMid, 
 262		    _ = '_'},
 263    Match = megaco_monitor:which_requests(Pat2),
 264    [S || [S] <- Match];
 265which_requests(LocalMid) ->
 266    Pat1 = #trans_id{mid    = LocalMid, 
 267		     serial = '$1', _ = '_'},
 268    Pat2 = #request{trans_id   = Pat1, 
 269		    remote_mid = '$2', _ = '_'},
 270    Match0 = megaco_monitor:which_requests(Pat2),
 271    Match1 = [{mk_ch(LocalMid, V2), V1} || [V1, V2] <- Match0],
 272    which_requests1(lists:sort(Match1)).
 273
 274which_requests1([]) ->
 275    [];
 276which_requests1([{CH, S}|T]) ->
 277    which_requests2(T, CH, [S], []).
 278
 279which_requests2([], CH, Serials, Reqs) ->
 280    lists:reverse([{CH, Serials}|Reqs]);
 281which_requests2([{CH, S}|T], CH, Serials, Reqs) ->
 282    which_requests2(T, CH, [S|Serials], Reqs);
 283which_requests2([{CH1, S}|T], CH2, Serials, Reqs) ->
 284    which_requests2(T, CH1, [S], [{CH2, lists:reverse(Serials)}| Reqs]).
 285    
 286
 287which_replies(#megaco_conn_handle{local_mid  = LocalMid, 
 288				  remote_mid = RemoteMid}) ->
 289    Pat1 = #trans_id{mid    = RemoteMid, 
 290		     serial = '$1', _ = '_'},
 291    Pat2 = #reply{trans_id  = Pat1, 
 292  		  local_mid = LocalMid, 
 293  		  state     = '$2', 
 294  		  handler   = '$3', _ = '_'},
 295    Match = megaco_monitor:which_replies(Pat2),
 296    [{V1, V2, V3} || [V1, V2, V3] <- Match];
 297which_replies(LocalMid) ->
 298    Pat1 = #trans_id{mid    = '$1', 
 299		     serial = '$2', _ = '_'},
 300    Pat2 = #reply{trans_id  = Pat1, 
 301		  local_mid = LocalMid, 
 302		  state     = '$3', 
 303		  handler   = '$4', _ = '_'},
 304    Match0 = megaco_monitor:which_replies(Pat2),
 305    Match1 = [{mk_ch(LocalMid,V1),{V2,V3,V4}} || [V1, V2, V3, V4] <- Match0],
 306    which_replies1(lists:sort(Match1)).
 307
 308which_replies1([]) ->
 309    [];
 310which_replies1([{CH, Data}|T]) ->
 311    which_replies2(T, CH, [Data], []).
 312
 313which_replies2([], CH, Data, Reps) ->
 314    lists:reverse([{CH, Data}|Reps]);
 315which_replies2([{CH, Data}|T], CH, Datas, Reps) ->
 316    which_replies2(T, CH, [Data|Datas], Reps);
 317which_replies2([{CH1, Data}|T], CH2, Datas, Reps) ->
 318    which_replies2(T, CH1, [Data], [{CH2, lists:reverse(Datas)}| Reps]).
 319    
 320    
 321mk_ch(LM, RM) ->
 322    #megaco_conn_handle{local_mid = LM, remote_mid = RM}.
 323    
 324
 325%%----------------------------------------------------------------------
 326%% Register/unreister connections
 327%%----------------------------------------------------------------------
 328
 329%% Returns {ok, ConnHandle} | {error, Reason}
 330autoconnect(RH, RemoteMid, SendHandle, ControlPid, Extra)
 331  when is_record(RH, megaco_receive_handle) ->
 332    ?rt2("autoconnect", [RH, RemoteMid, SendHandle, ControlPid]),
 333    case megaco_config:autoconnect(RH, RemoteMid, SendHandle, ControlPid) of
 334        {ok, ConnData} ->
 335            do_connect(ConnData, Extra);
 336        {error, Reason} ->
 337            {error, Reason}
 338    end;
 339autoconnect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) ->
 340    {error, {bad_receive_handle, BadHandle}}.
 341
 342connect(RH, RemoteMid, SendHandle, ControlPid) ->
 343    Extra = ?default_user_callback_extra, 
 344    connect(RH, RemoteMid, SendHandle, ControlPid, Extra).
 345connect(RH, RemoteMid, SendHandle, ControlPid, Extra)
 346  when is_record(RH, megaco_receive_handle) ->
 347    ?rt2("connect", [RH, RemoteMid, SendHandle, ControlPid, Extra]),
 348
 349    %% The purpose of this is to have a temoporary process, to 
 350    %% which one can set up a monitor or link and get a 
 351    %% notification when process exits. The entire connect is 
 352    %% done in the temporary worker process. 
 353    %% When it exits, the connect is either successfully done 
 354    %% or it failed.
 355
 356    ConnectorFun = 
 357	fun() ->
 358
 359		ConnectResult = 
 360		    case megaco_config:connect(RH, RemoteMid, 
 361					       SendHandle, ControlPid) of
 362			{ok, ConnData} ->
 363			    do_connect(ConnData, Extra);
 364			{error, Reason} ->
 365			    {error, Reason}
 366		    end,
 367		?rt2("connector: connected", [self(), ConnectResult]),
 368		exit({result, ConnectResult})
 369	end,
 370    Flag      = process_flag(trap_exit, true),
 371    Connector = erlang:spawn_link(ConnectorFun),
 372    receive
 373	{'EXIT', Connector, {result, ConnectResult}} ->
 374	    ?rt2("connect result: received expected connector exit signal", 
 375		 [Connector, ConnectResult]),
 376	    process_flag(trap_exit, Flag),
 377	    ConnectResult;
 378	{'EXIT', Connector, OtherReason} ->
 379	    ?rt2("connect exit: received unexpected connector exit signal", 
 380		 [Connector, OtherReason]),
 381	    process_flag(trap_exit, Flag),
 382	    {error, OtherReason}
 383    end;
 384connect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) ->
 385    {error, {bad_receive_handle, BadHandle}}.
 386
 387do_connect(CD, Extra) ->
 388    CH       = CD#conn_data.conn_handle,
 389    Version  = CD#conn_data.protocol_version,
 390    UserMod  = CD#conn_data.user_mod,
 391    UserArgs = CD#conn_data.user_args,
 392    Args      = 
 393	case Extra of
 394	    ?default_user_callback_extra ->
 395		[CH, Version | UserArgs];
 396	    _ ->
 397		[CH, Version, Extra | UserArgs]
 398	end,
 399    ?report_trace(CD, "callback: connect", [Args]),
 400    Res = (catch apply(UserMod, handle_connect, Args)),
 401    ?report_debug(CD, "return: connect", [{return, Res}]),
 402    case Res of
 403        ok ->
 404	    ?SIM(ok, do_connect), % do_encode),
 405	    monitor_process(CH, CD#conn_data.control_pid);
 406        error ->
 407            megaco_config:disconnect(CH),
 408            {error, {connection_refused, CD, error}};
 409        {error, ED} when is_record(ED,'ErrorDescriptor') ->
 410            megaco_config:disconnect(CH),
 411            {error, {connection_refused, CD, ED}};
 412        _Error ->
 413	    warning_msg("connect callback failed: ~w", [Res]),
 414            megaco_config:disconnect(CH),
 415            {error, {connection_refused, CD, Res}}
 416    end.
 417
 418finish_connect(#conn_data{control_pid = ControlPid} = CD) 
 419  when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) ->
 420    ?rt1(CD, "finish local connect", [ControlPid]),
 421    do_finish_connect(CD);
 422finish_connect(#conn_data{conn_handle = CH,
 423			  control_pid = ControlPid} = CD) 
 424  when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) ->
 425    ?rt1(CD, "finish remote connect", [ControlPid]),
 426    RemoteNode     = node(ControlPid),
 427    UserMonitorPid = whereis(megaco_monitor),
 428    Args           = [CH, ControlPid, UserMonitorPid],
 429    case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of
 430        {ok, ControlMonitorPid} ->
 431	    do_finish_connect(CD#conn_data{control_pid = ControlMonitorPid});
 432        {error, Reason} ->
 433            disconnect(CH, {connect_remote, Reason}),
 434            {error, Reason};
 435        {badrpc, Reason} ->
 436            Reason2 = {'EXIT', Reason},
 437            disconnect(CH, {connect_remote, Reason2}),
 438            {error, Reason2}
 439    end.
 440
 441do_finish_connect(#conn_data{conn_handle = CH,
 442			     send_handle = SendHandle,
 443			     control_pid = ControlPid} = CD) ->
 444    M   = ?MODULE,
 445    F   = disconnect_local,
 446    A   = [CH],
 447    MFA = {M, F, A}, 
 448    case megaco_config:finish_connect(CH, SendHandle, ControlPid, MFA) of
 449	{ok, Ref} ->
 450	    {ok, CD#conn_data{monitor_ref = Ref}};
 451	{error, Reason} ->
 452            {error, {config_update, Reason}}
 453    end.
 454
 455
 456monitor_process(CH, ControlPid) 
 457  when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) ->
 458    M = ?MODULE,
 459    F = disconnect_local,
 460    A = [CH],
 461    Ref = megaco_monitor:apply_at_exit(M, F, A, ControlPid),
 462    case megaco_config:update_conn_info(CH, monitor_ref, Ref) of
 463        ok ->
 464            ?SIM({ok, CH}, monitor_process_local);
 465        {error, Reason} ->
 466            disconnect(CH, {config_update, Reason}),
 467            {error, Reason}
 468    end;
 469monitor_process(CH, ControlPid) 
 470  when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) ->
 471    RemoteNode = node(ControlPid),
 472    UserMonitorPid = whereis(megaco_monitor),
 473    Args = [CH, ControlPid, UserMonitorPid],
 474    case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of
 475        {ok, ControlMonitorPid} ->
 476            M = ?MODULE,
 477            F = disconnect_local,
 478            A = [CH],
 479            Ref = megaco_monitor:apply_at_exit(M, F, A, ControlMonitorPid),
 480            case megaco_config:update_conn_info(CH, monitor_ref, Ref) of
 481                ok ->
 482		    ?SIM({ok, CH}, monitor_process_remote);
 483                {error, Reason} ->
 484                    disconnect(CH, {config_update, Reason}),
 485                    {error, Reason}
 486            end;
 487        {error, Reason} ->
 488            disconnect(CH, {connect_remote, Reason}),
 489            {error, Reason};
 490        {badrpc, Reason} ->
 491            Reason2 = {'EXIT', Reason},
 492            disconnect(CH, {connect_remote, Reason2}),
 493            {error, Reason2}
 494    end;
 495monitor_process(CH, undefined = _ControlPid) ->
 496    %% We have to do this later (setting up the monitor), 
 497    %% when the first message arrives. The 'connected' atom is
 498    %% the indication for the first arriving message to finish
 499    %% the connect. 
 500    %% This may be the case when an MGC performs a pre-connect
 501    %% in order to speed up the handling of an (expected) connecting
 502    %% MG. 
 503    case megaco_config:update_conn_info(CH, monitor_ref, connected) of
 504        ok ->
 505            ?SIM({ok, CH}, monitor_process_local);
 506        {error, Reason} ->
 507            disconnect(CH, {config_update, Reason}),
 508            {error, Reason}
 509    end.
 510
 511connect_remote(CH, ControlPid, UserMonitorPid)
 512  when node(ControlPid) =:= node() andalso node(UserMonitorPid) =/= node() ->
 513    case megaco_config:lookup_local_conn(CH) of
 514        [_ConnData] -> 
 515            UserNode = node(UserMonitorPid),
 516            M = ?MODULE,
 517            F = disconnect_remote,
 518            A = [CH, UserNode],
 519            Ref = megaco_monitor:apply_at_exit(M, F, A, UserMonitorPid),
 520            case megaco_config:connect_remote(CH, UserNode, Ref) of
 521		ok ->
 522		    ControlMonitorPid = whereis(megaco_monitor),
 523		    ?SIM({ok, ControlMonitorPid}, connect_remote);
 524		{error, Reason} ->
 525		    {error, Reason}
 526	    end;
 527        [] ->
 528            {error, {no_connection, CH}}
 529    end.
 530
 531cancel_apply_at_exit({connecting, _ConnectorPid}) ->
 532    ok;
 533cancel_apply_at_exit(connected) ->
 534    ok;
 535cancel_apply_at_exit(ControlRef) ->
 536    megaco_monitor:cancel_apply_at_exit(ControlRef).
 537
 538node_of_control_pid(Pid) when is_pid(Pid) ->
 539    node(Pid);
 540node_of_control_pid(_) ->
 541    node().
 542
 543disconnect(ConnHandle, DiscoReason)
 544  when is_record(ConnHandle, megaco_conn_handle) ->
 545    case megaco_config:disconnect(ConnHandle) of
 546        {ok, ConnData, RemoteConnData} ->
 547            ControlRef = ConnData#conn_data.monitor_ref,
 548            cancel_apply_at_exit(ControlRef),
 549            handle_disconnect_callback(ConnData, DiscoReason),
 550            ControlNode = node_of_control_pid(ConnData#conn_data.control_pid),
 551            case ControlNode =:= node() of
 552                true ->
 553                    %% Propagate to remote users
 554                    CancelFun =
 555                        fun(RCD) ->
 556                                UserRef = RCD#remote_conn_data.monitor_ref,
 557                                cancel_apply_at_exit(UserRef),
 558                                RCD#remote_conn_data.user_node
 559                          end,
 560                    Nodes = lists:map(CancelFun, RemoteConnData),
 561		    %% io:format("NODES: ~p~n", [Nodes]),
 562                    M = ?MODULE,
 563                    F = disconnect,
 564                    A = [ConnHandle, DiscoReason],
 565                    case rpc:multicall(Nodes, M, F, A) of
 566                        {Res, []} ->
 567			    Check = fun(ok) -> false;
 568				       ({error, {no_connection, _CH}}) -> false;
 569				       (_) -> true
 570				    end,
 571                            case lists:filter(Check, Res) of
 572                                [] ->
 573                                    ok;
 574                                Bad ->
 575                                    {error, {remote_disconnect_error, ConnHandle, Bad}}
 576                            end;
 577                        {_Res, Bad} ->
 578                            {error, {remote_disconnect_crash, ConnHandle, Bad}}
 579                    end;
 580                false when (RemoteConnData =:= []) ->
 581                    %% Propagate to remote control node
 582                    M = ?MODULE,
 583                    F = disconnect_remote,
 584                    A = [DiscoReason, ConnHandle, node()],
 585                    case rpc:call(ControlNode, M, F, A) of
 586                        {badrpc, Reason} ->
 587                            {error, {'EXIT', Reason}};
 588                        Other ->
 589                            Other
 590                    end
 591            end;
 592        {error, Reason} ->
 593            {error, Reason}
 594    end;
 595disconnect(BadHandle, Reason) ->
 596    {error, {bad_conn_handle, BadHandle, Reason}}.
 597
 598disconnect_local(Reason, ConnHandle) ->
 599    disconnect(ConnHandle, {no_controlling_process, Reason}).
 600
 601disconnect_remote(_Reason, ConnHandle, UserNode) ->
 602    case megaco_config:disconnect_remote(ConnHandle, UserNode) of
 603        [RCD] ->
 604            Ref = RCD#remote_conn_data.monitor_ref,
 605            cancel_apply_at_exit(Ref),
 606            ok;
 607        [] ->
 608            {error, {no_connection, ConnHandle}}
 609    end.
 610
 611
 612%%----------------------------------------------------------------------
 613%% Handle incoming message
 614%%----------------------------------------------------------------------
 615
 616receive_message(ReceiveHandle, ControlPid, SendHandle, Bin) ->
 617    Extra = ?default_user_callback_extra, 
 618    receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra).
 619
 620receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) ->
 621    Opts = [link , {min_heap_size, 5000}],
 622    spawn_opt(?MODULE,
 623               process_received_message,
 624               [ReceiveHandle, ControlPid, SendHandle, Bin, self(), Extra], Opts),
 625    ok.
 626
 627%% This function is called via the spawn_opt function with the link
 628%% option, therefor the unlink before the exit.
 629process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Receiver, 
 630			 Extra) ->
 631    process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra),
 632    unlink(Receiver),
 633    exit(normal).
 634
 635process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin) ->
 636    Extra = ?default_user_callback_extra, 
 637    process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra).
 638
 639process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) ->
 640    Flag = process_flag(trap_exit, true),
 641    case prepare_message(ReceiveHandle, SendHandle, Bin, ControlPid, Extra) of
 642        {ok, ConnData, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
 643	    ?rt1(ConnData, "message prepared", [MegaMsg]),
 644            Mess = MegaMsg#'MegacoMessage'.mess,
 645            case Mess#'Message'.messageBody of
 646                {transactions, Transactions} ->
 647                    {AckList, ReqList} = 
 648			prepare_trans(ConnData, Transactions, [], [], Extra),
 649                    handle_acks(AckList, Extra),
 650		    case ReqList of
 651			[] ->
 652			    ?rt3("no transaction requests"),
 653			    ignore;
 654			[Req|Reqs] when (ConnData#conn_data.threaded =:= true) ->
 655			    ?rt3("handle requests (spawned)"),
 656 			    lists:foreach(
 657 			      fun(R) -> 
 658 				      spawn(?MODULE, handle_request, [R, Extra]) 
 659 			      end, 
 660 			      Reqs),
 661			    handle_request(Req, Extra);
 662			_ ->
 663			    ?rt3("handle requests"),
 664			    case handle_requests(ReqList, [], Extra) of
 665				[] ->
 666				    ignore;
 667				[LongRequest | More] ->
 668				    lists:foreach(
 669				      fun(LR) ->
 670					      spawn(?MODULE, handle_long_request, [LR, Extra])
 671				      end,
 672				      More),
 673				    handle_long_request(LongRequest, Extra)
 674			    end
 675		    end;
 676                {messageError, Error} ->
 677                    handle_message_error(ConnData, Error, Extra)
 678            end;
 679        {silent_fail, ConnData, {_Code, Reason, Error}} ->
 680            ?report_debug(ConnData, Reason, [no_reply, Error]),
 681            ignore;
 682        {verbose_fail, ConnData, {Code, Reason, Error}} ->
 683            ?report_debug(ConnData, Reason, [Error]),
 684            send_message_error(ConnData, Code, Reason)
 685    end,
 686    process_flag(trap_exit, Flag),
 687    ok.
 688
 689prepare_message(RH, SH, Bin, Pid, Extra)
 690  when is_record(RH, megaco_receive_handle) andalso is_pid(Pid) ->
 691    ?report_trace(RH, "receive bytes", [{bytes, Bin}]),
 692    EncodingMod    = RH#megaco_receive_handle.encoding_mod,
 693    EncodingConfig = RH#megaco_receive_handle.encoding_config,
 694    ProtVersion    = RH#megaco_receive_handle.protocol_version,
 695    case (catch EncodingMod:decode_message(EncodingConfig, ProtVersion, Bin)) of
 696        {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
 697	    ?report_trace(RH, "receive message", [{message, MegaMsg}]),
 698            Mess       = MegaMsg#'MegacoMessage'.mess,
 699            RemoteMid  = Mess#'Message'.mId,
 700            Version    = Mess#'Message'.version,
 701            LocalMid   = RH#megaco_receive_handle.local_mid,
 702            CH         = #megaco_conn_handle{local_mid  = LocalMid,
 703                                             remote_mid = RemoteMid},
 704            case megaco_config:lookup_local_conn(CH) of
 705
 706		%% 
 707		%% Message is not of the negotiated version
 708		%% 
 709
 710                [#conn_data{protocol_version = NegVersion, 
 711			    strict_version   = true} = ConnData] 
 712		when NegVersion =/= Version ->
 713		    %% Use already established connection, 
 714		    %% but incorrect version
 715		    ?rt1(ConnData, "not negotiated version", [Version]),
 716		    Error = {error, {not_negotiated_version, 
 717				     NegVersion, Version}}, 
 718		    handle_syntax_error_callback(RH, ConnData, 
 719						 prepare_error(Error),
 720						 Extra);
 721
 722
 723                [ConnData] ->
 724
 725		    %% 
 726                    %% Use an already established connection
 727		    %% 
 728		    %% This *may* have been set up in the
 729		    %% "non-official" way, so we may need to 
 730		    %% create the monitor to the control process
 731		    %% and store the SendHandle (which is normally 
 732		    %% done when creating the "temporary" connection). 
 733		    %% 
 734
 735		    ?rt1(ConnData, "use already established connection", []),
 736                    ConnData2 = ConnData#conn_data{send_handle      = SH,
 737						   control_pid      = Pid, 
 738                                                   protocol_version = Version},
 739                    check_message_auth(CH, ConnData2, MegaMsg, Bin);
 740
 741                [] ->
 742                    %% Setup a temporary connection
 743		    ?rt3("setup a temporary connection"),
 744                    case autoconnect(RH, RemoteMid, SH, Pid, Extra) of
 745                        {ok, _} ->
 746			    do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin);
 747			{error, {already_connected, _ConnHandle}} ->
 748			    do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin);
 749			{error, {connection_refused, ConnData, Reason}} ->
 750			    Error = prepare_error({error, {connection_refused, Reason}}),
 751                            {verbose_fail, ConnData, Error};
 752                        {error, Reason} ->
 753                            ConnData = fake_conn_data(RH, RemoteMid, SH, Pid),
 754			    ConnData2 = ConnData#conn_data{protocol_version = Version},
 755			    Error    = prepare_error({error, Reason}),
 756                            {verbose_fail, ConnData2, Error}
 757                    end
 758            end;
 759        Error ->
 760	    ?rt2("decode error", [Error]),
 761	    ConnData = handle_decode_error(Error, 
 762					   RH, SH, Bin, Pid, 
 763					   EncodingMod, 
 764					   EncodingConfig, 
 765					   ProtVersion),
 766            handle_syntax_error_callback(RH, ConnData, prepare_error(Error), Extra)
 767    end;
 768prepare_message(RH, SendHandle, _Bin, ControlPid, _Extra) ->
 769    ConnData = fake_conn_data(RH, SendHandle, ControlPid),
 770    Error    = prepare_error({'EXIT', {bad_receive_handle, RH}}),
 771    {verbose_fail, ConnData, Error}.
 772
 773
 774handle_decode_error({error, {unsupported_version, _}},
 775		    #megaco_receive_handle{local_mid = LocalMid} = RH, SH, 
 776		    Bin, Pid,
 777		    EM, EC, V) ->
 778    case (catch EM:decode_mini_message(EC, V, Bin)) of
 779	{ok, #'MegacoMessage'{mess = #'Message'{version = _Ver, 
 780						mId     = RemoteMid}}} ->
 781	    ?rt2("erroneous message received", [SH, RemoteMid, _Ver]),
 782            CH = #megaco_conn_handle{local_mid  = LocalMid,
 783				     remote_mid = RemoteMid},
 784	    incNumErrors(CH),
 785	    %% We cannot put the version into conn-data, that will
 786	    %% make the resulting error message impossible to sent
 787	    %% (unsupported version)
 788	    case megaco_config:lookup_local_conn(CH) of
 789                [ConnData] ->
 790		    ?rt3("known to us"),
 791		    ConnData#conn_data{send_handle = SH};
 792		[] ->
 793		    ?rt3("unknown to us"),
 794		    ConnData = fake_conn_data(RH, SH, Pid),
 795		    ConnData#conn_data{conn_handle = CH}
 796	    end;
 797
 798	_ ->
 799	    ?rt2("erroneous message received", [SH]),
 800	    incNumErrors(),
 801	    fake_conn_data(RH, SH, Pid)
 802    end;
 803
 804handle_decode_error(_,
 805		    #megaco_receive_handle{local_mid = LocalMid} = RH, SH, 
 806		    Bin, Pid,
 807		    EM, EC, V) ->
 808    case (catch EM:decode_mini_message(EC, V, Bin)) of
 809	{ok, #'MegacoMessage'{mess = #'Message'{version = Ver, 
 810						mId     = RemoteMid}}} ->
 811	    ?rt2("erroneous message received", [SH, Ver, RemoteMid]),
 812            CH = #megaco_conn_handle{local_mid  = LocalMid,
 813				     remote_mid = RemoteMid},
 814	    incNumErrors(CH),
 815	    case megaco_config:lookup_local_conn(CH) of
 816                [ConnData] ->
 817		    ?rt3("known to us"),
 818		    ConnData#conn_data{send_handle      = SH,
 819				       protocol_version = Ver};
 820		[] ->
 821		    ?rt3("unknown to us"),
 822		    ConnData = fake_conn_data(RH, SH, Pid),
 823		    ConnData#conn_data{conn_handle      = CH,
 824				       protocol_version = Ver}
 825	    end;
 826
 827	_ ->
 828	    ?rt2("erroneous message received", [SH]),
 829	    incNumErrors(),
 830	    fake_conn_data(RH, SH, Pid)
 831    end.
 832
 833
 834do_prepare_message(RH, CH, SendHandle, MegaMsg, ControlPid, Bin) ->
 835    case megaco_config:lookup_local_conn(CH) of
 836	[ConnData] ->
 837	    case check_message_auth(CH, ConnData, MegaMsg, Bin) of
 838		{ok, ConnData2, MegaMsg} ->
 839		    %% Let the connection be permanent
 840		    {ok, ConnData2, MegaMsg};
 841		{ReplyTag, ConnData, Reason} ->
 842		    %% Remove the temporary connection
 843		    disconnect(CH, {bad_auth, Reason}),
 844		    {ReplyTag, ConnData, Reason}
 845	    end;
 846	[] ->
 847	    Reason = no_connection,
 848	    disconnect(CH, Reason),
 849	    RemoteMid = CH#megaco_conn_handle.remote_mid,
 850	    ConnData = fake_conn_data(RH, RemoteMid, SendHandle, ControlPid),
 851	    Error = prepare_error({error, Reason}),
 852	    {silent_fail, ConnData, Error}
 853    end.
 854
 855check_message_auth(_ConnHandle, ConnData, MegaMsg, Bin) ->
 856    MsgAuth   = MegaMsg#'MegacoMessage'.authHeader,
 857    Mess      = MegaMsg#'MegacoMessage'.mess,
 858    Version   = Mess#'Message'.version,
 859    ConnData2 = ConnData#conn_data{protocol_version = Version},
 860    ConnAuth  = ConnData2#conn_data.auth_data,
 861    ?report_trace(ConnData2, "check message auth", [{bytes, Bin}]),
 862    if
 863	(MsgAuth =:= asn1_NOVALUE) andalso (ConnAuth =:= asn1_NOVALUE) ->
 864            ?SIM({ok, ConnData2, MegaMsg}, check_message_auth);
 865	true -> 
 866	    ED = #'ErrorDescriptor'{errorCode = ?megaco_unauthorized,
 867				    errorText = "Autentication is not supported"},
 868	    {verbose_fail, ConnData2, prepare_error({error, ED})}
 869    end.
 870
 871handle_syntax_error_callback(ReceiveHandle, ConnData, PrepError, Extra) ->
 872    {Code, Reason, Error} = PrepError,
 873    ErrorDesc = #'ErrorDescriptor'{errorCode = Code, errorText = Reason},
 874    Version   = 
 875	case Error of
 876	    {error, {unsupported_version, UV}} ->
 877		UV;
 878	    _ ->
 879		ConnData#conn_data.protocol_version
 880	end,
 881    UserMod   = ConnData#conn_data.user_mod,
 882    UserArgs  = ConnData#conn_data.user_args,
 883    ?report_trace(ReceiveHandle, "callback: syntax error", [ErrorDesc, Error]),
 884    Args      = 
 885	case Extra of
 886	    ?default_user_callback_extra ->
 887		[ReceiveHandle, Version, ErrorDesc | UserArgs];
 888	    _ ->
 889		[ReceiveHandle, Version, ErrorDesc, Extra | UserArgs]
 890	end,
 891    Res = (catch apply(UserMod, handle_syntax_error, Args)),
 892    ?report_debug(ReceiveHandle, "return: syntax error", 
 893		  [{return, Res}, ErrorDesc, Error]),
 894    case Res of
 895        reply ->
 896            {verbose_fail, ConnData, PrepError};
 897        {reply,#'ErrorDescriptor'{errorCode = Code1, errorText = Reason1}} ->
 898            {verbose_fail, ConnData, {Code1,Reason1,Error}};
 899        no_reply ->
 900            {silent_fail, ConnData, PrepError};
 901        {no_reply,#'ErrorDescriptor'{errorCode=Code2,errorText=Reason2}} ->
 902            {silent_fail, ConnData, {Code2,Reason2,Error}}; %%% OTP-????
 903        _ ->
 904	    warning_msg("syntax error callback failed: ~w", [Res]),
 905            {verbose_fail, ConnData, PrepError}
 906    end.
 907
 908fake_conn_data(CH) when is_record(CH, megaco_conn_handle) ->
 909    case (catch megaco_config:conn_info(CH, receive_handle)) of
 910	RH when is_record(RH, megaco_receive_handle) ->
 911	    RemoteMid = CH#megaco_conn_handle.remote_mid,
 912	    ConnData = 
 913		fake_conn_data(RH, RemoteMid, no_send_handle, no_control_pid),
 914	    ConnData#conn_data{conn_handle = CH};
 915	{'EXIT', _} ->
 916	    UserMid = CH#megaco_conn_handle.local_mid,
 917	    case catch megaco_config:user_info(UserMid, receive_handle) of
 918		{'EXIT', _} -> % No such user
 919		    #conn_data{conn_handle        = CH,
 920			       serial             = undefined_serial,
 921			       control_pid        = no_control_pid,
 922			       monitor_ref        = undefined_monitor_ref,
 923			       send_mod           = no_send_mod,
 924			       send_handle        = no_send_handle,
 925			       encoding_mod       = no_encoding_mod,
 926			       encoding_config    = no_encoding_config,
 927			       reply_action       = undefined,
 928			       sent_pending_limit = infinity,
 929			       recv_pending_limit = infinity};
 930		RH ->
 931		    ConnData = 
 932			fake_conn_data(RH, no_send_handle, no_control_pid),
 933		    ConnData#conn_data{conn_handle = CH}
 934	    end
 935    end.
 936
 937fake_conn_data(RH, SendHandle, ControlPid) ->
 938    fake_conn_data(RH, unknown_remote_mid, SendHandle, ControlPid).
 939
 940fake_conn_data(RH, RemoteMid, SendHandle, ControlPid) ->
 941    case catch megaco_config:init_conn_data(RH, RemoteMid, SendHandle, ControlPid) of
 942	{'EXIT', _} -> % No such user
 943	    fake_user_data(RH, RemoteMid, SendHandle, ControlPid);
 944	ConnData ->
 945	    ConnData
 946    end.
 947
 948fake_user_data(RH, RemoteMid, SendHandle, ControlPid) ->
 949    LocalMid = RH#megaco_receive_handle.local_mid,
 950    RH2 = RH#megaco_receive_handle{local_mid = default},
 951    case catch megaco_config:init_conn_data(RH2, RemoteMid, SendHandle, ControlPid) of
 952	{'EXIT', _} -> % Application stopped?
 953	    ConnHandle     = #megaco_conn_handle{local_mid  = LocalMid,
 954						 remote_mid = RemoteMid},
 955	    EncodingMod    = RH#megaco_receive_handle.encoding_mod,
 956	    EncodingConfig = RH#megaco_receive_handle.encoding_config,
 957	    SendMod        = RH#megaco_receive_handle.send_mod,
 958	    #conn_data{conn_handle        = ConnHandle,
 959		       serial             = undefined_serial,
 960		       control_pid        = ControlPid,
 961		       monitor_ref        = undefined_monitor_ref,
 962		       send_mod           = SendMod,
 963		       send_handle        = SendHandle,
 964		       encoding_mod       = EncodingMod,
 965		       encoding_config    = EncodingConfig,
 966		       reply_action       = undefined,
 967		       sent_pending_limit = infinity,
 968		       recv_pending_limit = infinity};
 969	ConnData ->
 970	    ConnData
 971    end.
 972
 973prepare_error(Error) ->
 974    case Error of
 975        {error, ED} when is_record(ED, 'ErrorDescriptor') ->
 976            Code   = ED#'ErrorDescriptor'.errorCode,
 977            Reason = ED#'ErrorDescriptor'.errorText,
 978            {Code, Reason, Error};
 979        {error, [{reason, {bad_token, [BadToken, _Acc]}, Line}]} when is_integer(Line) ->
 980            Reason = 
 981		lists:flatten(
 982		  io_lib:format("Illegal token (~p) on line ~w", [BadToken, Line])),
 983            Code = ?megaco_bad_request,
 984            {Code, Reason, Error};
 985        {error, [{reason, {bad_token, _}, Line}]} when is_integer(Line) ->
 986            Reason = lists:concat(["Illegal token on line ", Line]),
 987            Code = ?megaco_bad_request,
 988            {Code, Reason, Error};
 989        {error, [{reason, {Line, _ParserMod, RawReasonString}} | _]} when is_integer(Line) andalso is_list(RawReasonString) ->
 990	    Reason = 
 991		case RawReasonString of
 992		    [[$s, $y, $n, $t, $a, $x | _], TokenString] ->
 993			lists:flatten(
 994			  io_lib:format("Syntax error on line ~w before token ~s", [Line, TokenString]));
 995		    _ ->
 996			lists:flatten(io_lib:format("Syntax error on line ~w", [Line]))
 997		end,
 998            Code = ?megaco_bad_request,
 999            {Code, Reason, Error};
1000        {error, [{reason, {Line, _, _}} | _]} when is_integer(Line) ->
1001            Reason = lists:concat(["Syntax error on line ", Line]),
1002            Code = ?megaco_bad_request,
1003            {Code, Reason, Error};
1004        {error, {connection_refused, ED}} when is_record(ED,'ErrorDescriptor') ->
1005            Code   = ED#'ErrorDescriptor'.errorCode,
1006            Reason = ED#'ErrorDescriptor'.errorText,
1007            {Code, Reason, Error};
1008        {error, {connection_refused, _}} ->
1009            Reason = "Connection refused by user",
1010            Code = ?megaco_unauthorized,
1011            {Code, Reason, Error};
1012        {error, {unsupported_version, V}} ->
1013            Reason = 
1014		lists:flatten(io_lib:format("Unsupported version: ~w",[V])),
1015            Code = ?megaco_version_not_supported, 
1016            {Code, Reason, Error};
1017        {error, {not_negotiated_version, NegV, MsgV}} ->
1018            Reason = 
1019		lists:flatten(
1020		  io_lib:format("Not negotiated version: ~w [negotiated ~w]",
1021				[MsgV, NegV])),
1022            Code = ?megaco_version_not_supported, 
1023            {Code, Reason, Error};
1024        {error, _} ->
1025            Reason = "Syntax error",
1026            Code = ?megaco_bad_request,
1027            {Code, Reason, Error};
1028        {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
1029            Reason = "MID does not match config",
1030            Code = ?megaco_incorrect_identifier,
1031            {Code, Reason, Error};
1032        _ ->
1033            Reason = "Fatal syntax error",
1034            Code = ?megaco_internal_gateway_error,
1035            {Code, Reason, Error}
1036    end.
1037
1038prepare_trans(_ConnData, [], AckList, ReqList, _Extra) ->
1039    ?SIM({AckList, ReqList}, prepare_trans_done);
1040
1041prepare_trans(ConnData, Trans, AckList, ReqList, Extra) 
1042  when ConnData#conn_data.monitor_ref =:= undefined_auto_monitor_ref ->
1043
1044    ?rt3("prepare_trans - autoconnect"),
1045
1046    %% <BUGBUG>
1047    %% Do we need something here, if we send more then one 
1048    %% trans per message?
1049    %% </BUGBUG>
1050    
1051    %% May occur if another process has already setup a
1052    %% temporary connection, but the handle_connect callback
1053    %% function has not yet returned before the eager MG
1054    %% re-sends its initial service change message.
1055
1056    prepare_autoconnecting_trans(ConnData, Trans, AckList, ReqList, Extra);
1057
1058prepare_trans(#conn_data{monitor_ref = connected} = ConnData, 
1059	      Trans, AckList, ReqList, Extra) ->
1060
1061    ?rt3("prepare_trans - connected"),
1062
1063    %% 
1064    %% This will happen when the "MGC" user performs a "pre" connect,
1065    %% instead of waiting for the auto-connect (which normally
1066    %% happen when the MGC receives the first message from the
1067    %% MG). 
1068    %% 
1069
1070    %% 
1071    %% The monitor_ref will have this value when the pre-connect 
1072    %% is complete, so we finish it here and then continue with the 
1073    %% normal transaction prepare.
1074    %% 
1075
1076    case finish_connect(ConnData) of
1077	{ok, CD} ->
1078	    prepare_normal_trans(CD, Trans, AckList, ReqList, Extra);
1079	{error, Reason} ->
1080	    disconnect(ConnData#conn_data.conn_handle, Reason),
1081	    {[], []}
1082    end;
1083
1084prepare_trans(#conn_data{monitor_ref = {connecting, _}} = _ConnData, 
1085	      _Trans, _AckList, _ReqList, _Extra) ->
1086    
1087    ?rt3("prepare_trans - connecting"),
1088
1089    %% 
1090    %% This will happen when the "MGC" user performs a "pre" connect,
1091    %% instead of waiting for the auto-connect (which normally
1092    %% happen when the MGC receives the first message from the
1093    %% MG). 
1094    %% 
1095
1096    %% 
1097    %% The monitor_ref will have this value when the pre-connect 
1098    %% is in progress. We drop (ignore) this message and hope the
1099    %% other side (MG) will resend. 
1100    %% 
1101
1102    %% prepare_connecting_trans(ConnData, Trans, AckList, ReqList, Extra);
1103    {[], []};
1104
1105prepare_trans(ConnData, Trans, AckList, ReqList, Extra) ->
1106
1107    ?rt3("prepare_trans - normal"),
1108
1109    %% Handle transaction in the normal case
1110
1111    prepare_normal_trans(ConnData, Trans, AckList, ReqList, Extra).
1112
1113
1114prepare_autoconnecting_trans(_ConnData, [], AckList, ReqList, _Extra) ->
1115    ?SIM({AckList, ReqList}, prepare_autoconnecting_trans_done);
1116
1117prepare_autoconnecting_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) ->
1118    ?rt1(ConnData, "[autoconnecting] prepare trans", [Trans]),
1119    case Trans of
1120        {transactionRequest, T} when is_record(T, 'TransactionRequest') ->
1121	    
1122            Serial = T#'TransactionRequest'.transactionId,
1123	    ConnData2 = ConnData#conn_data{serial = Serial},
1124	    ?report_trace(ConnData2, "Pending handle_connect", [T]),
1125
1126	    %% ------------------------------------------
1127	    %% 
1128	    %%   Check pending limit
1129	    %% 
1130	    %% ------------------------------------------
1131
1132	    Limit = ConnData#conn_data.sent_pending_limit,
1133	    TransId = to_remote_trans_id(ConnData2),
1134	    case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
1135		ok ->
1136		    send_pending(ConnData2);
1137		error ->
1138		    %% Pending limit:
1139		    %% In this (granted, highly hypothetical case)
1140		    %% we would make the user very confused if we 
1141		    %% called the abort callback function, since 
1142		    %% the request callback function has not yet
1143		    %% been called. Alas, we skip this call here.
1144		    send_pending_limit_error(ConnData);
1145		aborted ->
1146		    ignore
1147	    end,
1148	    prepare_autoconnecting_trans(ConnData2, Rest, AckList, ReqList, 
1149					 Extra);
1150	_ ->
1151	    prepare_autoconnecting_trans(ConnData, Rest, AckList, ReqList, 
1152					 Extra)
1153    end.
1154
1155
1156%% =================================================================
1157%% 
1158%% Note that the TransactionReply record was changed i v3 (two 
1159%% new fields where added), and since we don't know which version,
1160%% we cannot use the record definition of TransactionReply.
1161%% Instead we transform the record into our own internal format
1162%% #megaco_transaction_reply{}
1163%%
1164%% =================================================================
1165
1166prepare_normal_trans(_ConnData, [], AckList, ReqList, _Extra) ->
1167    ?SIM({AckList, ReqList}, prepare_normal_trans_done);
1168
1169prepare_normal_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) ->
1170    ?rt1(ConnData, "prepare [normal] trans", [Trans]),
1171    case Trans of
1172        {transactionRequest, #'TransactionRequest'{transactionId = asn1_NOVALUE}} ->
1173            ConnData2 = ConnData#conn_data{serial = 0},
1174	    Code   = ?megaco_bad_request,
1175            Reason = "Syntax error in message: transaction id missing",
1176	    send_trans_error(ConnData2, Code, Reason),
1177            prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
1178        {transactionRequest, T} when is_record(T, 'TransactionRequest') ->
1179            Serial = T#'TransactionRequest'.transactionId,
1180            ConnData2 = ConnData#conn_data{serial = Serial},
1181            prepare_request(ConnData2, T, Rest, AckList, ReqList, Extra);
1182        {transactionPending, T} when is_record(T, 'TransactionPending') ->
1183            Serial = T#'TransactionPending'.transactionId,
1184            ConnData2 = ConnData#conn_data{serial = Serial},
1185            handle_pending(ConnData2, T, Extra),
1186            prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
1187        {transactionReply, T} when is_tuple(T) andalso  
1188				   (element(1, T) == 'TransactionReply') ->
1189	    T2        = transform_transaction_reply_dec(T),
1190            Serial    = T2#megaco_transaction_reply.transactionId, 
1191            ConnData2 = ConnData#conn_data{serial = Serial},
1192            handle_reply(ConnData2, T2, Extra),
1193	    prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
1194        {transactionResponseAck, List} when is_list(List) ->
1195            prepare_ack(ConnData, List, Rest, AckList, ReqList, Extra);
1196        {segmentReply, SR} when is_record(SR, 'SegmentReply') ->
1197	    handle_segment_reply(ConnData, SR, Extra), 
1198            prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra)
1199
1200    end.
1201
1202
1203prepare_request(ConnData, T, Rest, AckList, ReqList, Extra) ->
1204    ?rt2("prepare request", [T]),
1205    ConnHandle = ConnData#conn_data.conn_handle, 
1206    LocalMid   = ConnHandle#megaco_conn_handle.local_mid,
1207    TransId    = to_remote_trans_id(ConnData),
1208    ?rt2("prepare request", [LocalMid, TransId]),
1209    case lookup_reply(ConnData, TransId) of
1210        [] ->
1211	    ?rt3("brand new request"),
1212
1213            %% Brand new request
1214
1215	    %% Check pending limit:
1216	    %% 
1217	    %% We should actually check the pending limit here
1218	    %% but since we have to do it later in the 
1219	    %% handle_request function (just before we call
1220	    %% the handle_trans_request callback function) we
1221	    %% can just as well wait (this is after all a very
1222	    %% unlikely case: see function prepare_trans when 
1223	    %% monitor_ref == undefined_auto_monitor_ref).
1224	    %% 
1225
1226	    #conn_data{send_handle      = SendHandle,
1227		       pending_timer    = InitTimer,
1228		       protocol_version = Version,
1229		       user_mod         = UserMod,
1230		       user_args        = UserArgs} = ConnData,
1231	    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
1232	    M = ?MODULE,
1233	    F = pending_timeout,
1234	    A = [ConnHandle, TransId, CurrTimer],     
1235	    PendingRef = megaco_monitor:apply_after(M, F, A, WaitFor),
1236            Rep = #reply{send_handle       = SendHandle,
1237			 trans_id          = TransId,
1238			 local_mid         = LocalMid,
1239			 pending_timer_ref = PendingRef,
1240			 handler           = self(),
1241			 version           = Version,
1242                         user_mod          = UserMod,
1243                         user_args         = UserArgs},
1244            case megaco_monitor:insert_reply_new(Rep) of
1245		true ->
1246		    prepare_normal_trans(ConnData, Rest, AckList, 
1247					 [{ConnData, TransId, T} | ReqList], 
1248					 Extra);
1249		false ->
1250		    %% Oups - someone got there before we did...
1251		    ?report_debug(ConnData, 
1252				  "prepare request: conflicting requests", 
1253				  [TransId]),
1254		    send_pending(ConnData),
1255		    megaco_monitor:cancel_apply_after(PendingRef),
1256		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
1257					 Extra)
1258	    end;
1259
1260	%% We can ignore the Converted value here as we *know*
1261	%% conn-data to be correct (not faked), so even if 
1262	%% the record was converted, it will now have correct
1263	%% values for user_mod and user_args.
1264        {_Converted, 
1265	 #reply{state             = State, 
1266		handler           = Pid,
1267		pending_timer_ref = Ref} = Rep}
1268	when (State =:= prepare) orelse (State =:= eval_request) ->
1269
1270	    ?rt2("request resend", [State, Pid, Ref]),
1271
1272            %% Pending limit:
1273	    %% We are still preparing/evaluating the request
1274            %% Check if the pending limit has been exceeded...
1275	    %% If the pending limit is _not_ exceeded then
1276	    %% we shall send a pending (and actually restart 
1277	    %% the pending timer, but that we cannot do).
1278	    %% Don't care about Msg and Rep version diff
1279
1280	    #conn_data{sent_pending_limit = Limit} = ConnData,
1281	    
1282	    case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
1283		ok ->
1284
1285		    %% ------------------------------------------
1286		    %% 
1287		    %%   Pending limit not exceeded
1288		    %% 
1289		    %%   1) Increment number of pendings sent
1290		    %%      (done in the check function above)
1291		    %%   2) Send pending message
1292		    %%      (We should really restart the pending 
1293		    %%      timer, but we have no way of doing that).
1294		    %% 
1295		    %% ------------------------------------------
1296
1297		    send_pending(ConnData),
1298		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
1299					 Extra);
1300
1301
1302		error ->
1303
1304		    %% -------------------------------------------
1305		    %% 
1306		    %%   Pending limit exceeded
1307		    %% 
1308		    %%   1) Cancel pending timer
1309		    %%   2) Send 506 error message to other side
1310		    %%   3) Inform user (depends on state)
1311		    %%   4) Set reply in aborted state
1312		    %% 
1313		    %% -------------------------------------------
1314
1315		    %% 
1316		    %% State == eval_request:
1317		    %%   This means that the request is currently beeing 
1318		    %%   evaluated by the user, and the reply timer has 
1319		    %%   not yet been started. 
1320		    %%   Either:
1321		    %%   a) The "other side" will resend (which will 
1322		    %%      trigger a pending message send) until we pass the 
1323		    %%      pending limit 
1324		    %%   b) We will send pending messages (when the pending 
1325		    %%      timer expire) until we pass the pending limit.
1326		    %%   In any event, we cannot delete the reply record
1327		    %%   or the pending counter in this case. Is there
1328		    %%   a risk we accumulate aborted reply records?
1329		    %% 
1330		    %% State == prepare:
1331		    %%   The user does not know about this request
1332		    %%   so we can safely perform cleanup.
1333		    %% 
1334		    megaco_monitor:cancel_apply_after(Ref),
1335		    send_pending_limit_error(ConnData),
1336		    if 
1337			State == eval_request ->
1338			    %% 
1339			    %% What if the user never replies?
1340			    %% In that case we will have a record
1341			    %% (and counters) that is never cleaned up...
1342			    NewFields = 
1343				[{#reply.state,             aborted}, 
1344				 {#reply.pending_timer_ref, undefined}], 
1345			    megaco_monitor:update_reply_fields(TransId, 
1346							       NewFields),
1347			    handle_request_abort_callback(ConnData, 
1348							  TransId, Pid, Extra);
1349			true ->
1350			    %% Since the user does not know about
1351			    %% this call yet, it is safe to cleanup.
1352			    %% Should we inform?
1353			    Rep2 = Rep#reply{state = aborted},
1354			    cancel_reply(ConnData, Rep2, aborted),
1355			    ok
1356		    end,
1357		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
1358					 Extra);
1359
1360
1361		aborted ->
1362
1363		    %% -------------------------------------------
1364		    %% 
1365		    %%   Pending limit already exceeded
1366		    %% 
1367		    %%   Cleanup, just to make sure:
1368		    %%     reply record & pending counter
1369		    %% 
1370		    %% -------------------------------------------
1371
1372		    Rep2 = Rep#reply{state = aborted},
1373		    cancel_reply(ConnData, Rep2, aborted),
1374		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
1375					 Extra)
1376
1377	    end;
1378
1379	%% We can ignore the Converted value here as we *know*
1380	%% conn-data to be correct (not faked), so even if 
1381	%% the record was converted, it will now have correct
1382	%% values for user_mod and user_args.
1383        {_Converted, 
1384	 #reply{state   = waiting_for_ack, 
1385		bytes   = Bin, 
1386		version = Version} = Rep} ->
1387	    ?rt3("request resend when waiting for ack"),
1388
1389            %% We have already sent a reply, but the receiver
1390            %% has obviously not got it. Resend the reply but
1391            %% don't restart the reply_timer.
1392            ConnData2 = ConnData#conn_data{protocol_version = Version},
1393            ?report_trace(ConnData2, 
1394			  "re-send trans reply", [T | {bytes, Bin}]),

Large files files are truncated, but you can click here to view the full file