%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1999-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
%%----------------------------------------------------------------------
%% Purpose: Send and process a (sequence of) Megaco/H.248 transactions
%%----------------------------------------------------------------------

-module(megaco_messenger).
-moduledoc false.

%% Application internal export
-export([
         process_received_message/4, process_received_message/5,
         receive_message/4, receive_message/5,
         connect/4, connect/5, 
         disconnect/2,
         encode_actions/3, 
         call/3,
         cast/3,
         cancel/2,
         request_timeout/2,
	 request_keep_alive_timeout/2, 
         pending_timeout/3,
         reply_timeout/3,
         segment_timeout/3,
         %% segment_reply_timeout/4,

	 test_request/5,
	 test_reply/5
        ]).

%% MIB stat functions
-export([
	 get_stats/0, get_stats/1, get_stats/2,
	 reset_stats/0, reset_stats/1
	]).

%% Misc functions
-export([
	 cleanup/2, 
	 which_requests/1, which_replies/1
	]).

%% Module internal export
-export([
	 process_received_message/6,
         handle_request/2,
         handle_long_request/2,
         connect_remote/3,
         disconnect_local/2,
         disconnect_remote/3,
         send_request_remote/4,
         receive_reply_remote/2, receive_reply_remote/3
        ]).

-include_lib("megaco/include/megaco.hrl").
-include("megaco_message_internal.hrl").
-include_lib("megaco/src/app/megaco_internal.hrl").


%% N.B. Update cancel/1 with '_' when a new field is added
-record(request,
        {trans_id,
	 remote_mid,
         timer_ref,  % {short, Ref} | {long, Ref}
         init_timer,
         init_long_timer,
         curr_timer,
         version,
         bytes,     % {send, Data} | {no_send, Data}, Data = binary() | tuple()
         send_handle,
         user_mod,
         user_args,
         reply_action, % call | cast
         reply_data,
	 seg_recv = [],     % [integer()] (received segments)
	 init_seg_timer,
	 seg_timer_ref,
	 keep_alive_timer,  % plain | integer() >= 0
	 keep_alive_ref     % undefined | ref()
        }).


%% N.B. Update cancel/1 with '_' when a new field is added
-record(reply,
        {
	 trans_id,
	 local_mid,
	 state = prepare,     % prepare | eval_request | waiting_for_ack | aborted
	 pending_timer_ref,
	 handler = undefined, % pid of the proc executing the callback func
	 timer_ref,
	 version,
	 %% bytes: Sent reply data: not acknowledged
	 bytes,               % binary() | [{integer(), binary(), timer_ref()}]
	 ack_action,          % discard_ack | {handle_ack, Data}
	 send_handle,
	 %% segments: Not sent reply data (segments)
	 segments = [],       % [{integer(), binary()}]
	 user_mod,
	 user_args
	}).

-record(trans_id,
        {
          mid,
          serial
         }).


-ifdef(MEGACO_TEST_CODE).
-define(SIM(Other,Where),
	fun(Afun,Bfun) ->
		Kfun = {?MODULE,Bfun},
		case (catch ets:lookup(megaco_test_data, Kfun)) of
		    [{Kfun,Cfun}] ->
			Cfun(Afun);
		    _ ->
			Afun
		end
	end(Other,Where)).
-define(TC_AWAIT_CANCEL_EVENT(),
	case megaco_tc_controller:lookup(block_on_cancel) of
	    {value, {Tag, Pid}} when is_pid(Pid) ->
		Pid ! {Tag, self()},
		receive
		    {Tag, Pid} ->
			ok
		end;
	    {value, {sleep, To}} when is_integer(To) andalso (To > 0) ->
		receive after To -> ok end;
	    _ ->
		ok
	end).
-define(TC_AWAIT_REPLY_EVENT(Info),
	case megaco_tc_controller:lookup(block_on_reply) of
	    {value, {Tag, Pid}} when is_pid(Pid) ->
		Pid ! {Tag, self(), Info},
		receive
		    {Tag, Pid} ->
			ok
		end;
	    _Whatever ->
		%% io:format("Whatever: ~p~n", [Whatever]),
		ok
	end).
-else.
-define(SIM(Other,Where),Other).
-define(TC_AWAIT_CANCEL_EVENT(),ok).
-define(TC_AWAIT_REPLY_EVENT(_),ok).
-endif.


-define(report_pending_limit_exceeded(ConnData),
	?report_important(ConnData, "<ERROR> pending limit exceeded", [])).

-ifdef(megaco_extended_trace).
-define(rt1(T,F,A),?report_trace(T,F,A)).
-define(rt2(F,A),  ?rt1(ignore,F,A)).
-define(rt3(F),    ?rt2(F,[])).
-else.
-define(rt1(T,F,A),ok).
-define(rt2(F,A),  ok).
-define(rt3(F),    ok).
-endif.


%%----------------------------------------------------------------------
%% SNMP statistics handling functions
%%----------------------------------------------------------------------

%%-----------------------------------------------------------------
%% Func: get_stats/0, get_stats/1, get_stats/2
%% Description: Retreive statistics (counters) for TCP
%%-----------------------------------------------------------------

get_stats() ->
    megaco_stats:get_stats(megaco_stats).

get_stats(ConnHandleOrCounter) ->
    megaco_stats:get_stats(megaco_stats, ConnHandleOrCounter).

get_stats(ConnHandle, Counter) ->
    megaco_stats:get_stats(megaco_stats, ConnHandle, Counter).


%%-----------------------------------------------------------------
%% Func: reset_stats/0, reaet_stats/1
%% Description: Reset statistics (counters)
%%-----------------------------------------------------------------

reset_stats() ->
    megaco_stats:reset_stats(megaco_stats).

reset_stats(ConnHandleOrCounter) ->
    megaco_stats:reset_stats(megaco_stats, ConnHandleOrCounter).



%%----------------------------------------------------------------------
%% cleanup utility functions
%%----------------------------------------------------------------------

cleanup(#megaco_conn_handle{local_mid = LocalMid}, Force) 
  when (Force =:= true) orelse (Force =:= false) ->
    Pat = #reply{trans_id  = '$1', 
		 local_mid = LocalMid, 
		 state     = '$2',
		 _         = '_'},
    do_cleanup(Pat, Force);
cleanup(LocalMid, Force) 
  when (Force =:= true) orelse (Force =:= false) ->
    Pat = #reply{trans_id  = '$1', 
		 local_mid = LocalMid, 
		 state     = '$2',
		 _         = '_'},
    do_cleanup(Pat, Force).
    
do_cleanup(Pat, Force) ->
    Match = megaco_monitor:which_replies(Pat),
    Reps  = [{V1, V2} || [V1, V2] <- Match],
    do_cleanup2(Reps, Force).

do_cleanup2([], _) ->
    ok;
do_cleanup2([{TransId, aborted}|T], Force = false) ->
    megaco_monitor:delete_reply(TransId),
    do_cleanup2(T, Force);
do_cleanup2([_|T], Force = false) ->
    do_cleanup2(T, Force);
do_cleanup2([{TransId, _State}|T], Force = true) ->
    megaco_monitor:delete_reply(TransId),
    do_cleanup2(T, Force).
		  

%%----------------------------------------------------------------------
%% which_requests and which_replies utility functions
%%----------------------------------------------------------------------

which_requests(#megaco_conn_handle{local_mid  = LocalMid, 
				   remote_mid = RemoteMid}) ->
    Pat1 = #trans_id{mid    = LocalMid, 
		     serial = '$1', _ = '_'},
    Pat2 = #request{trans_id   = Pat1, 
		    remote_mid = RemoteMid, 
		    _ = '_'},
    Match = megaco_monitor:which_requests(Pat2),
    [S || [S] <- Match];
which_requests(LocalMid) ->
    Pat1 = #trans_id{mid    = LocalMid, 
		     serial = '$1', _ = '_'},
    Pat2 = #request{trans_id   = Pat1, 
		    remote_mid = '$2', _ = '_'},
    Match0 = megaco_monitor:which_requests(Pat2),
    Match1 = [{mk_ch(LocalMid, V2), V1} || [V1, V2] <- Match0],
    which_requests1(lists:sort(Match1)).

which_requests1([]) ->
    [];
which_requests1([{CH, S}|T]) ->
    which_requests2(T, CH, [S], []).

which_requests2([], CH, Serials, Reqs) ->
    lists:reverse([{CH, Serials}|Reqs]);
which_requests2([{CH, S}|T], CH, Serials, Reqs) ->
    which_requests2(T, CH, [S|Serials], Reqs);
which_requests2([{CH1, S}|T], CH2, Serials, Reqs) ->
    which_requests2(T, CH1, [S], [{CH2, lists:reverse(Serials)}| Reqs]).
    

which_replies(#megaco_conn_handle{local_mid  = LocalMid, 
				  remote_mid = RemoteMid}) ->
    Pat1 = #trans_id{mid    = RemoteMid, 
		     serial = '$1', _ = '_'},
    Pat2 = #reply{trans_id  = Pat1, 
  		  local_mid = LocalMid, 
  		  state     = '$2', 
  		  handler   = '$3', _ = '_'},
    Match = megaco_monitor:which_replies(Pat2),
    [{V1, V2, V3} || [V1, V2, V3] <- Match];
which_replies(LocalMid) ->
    Pat1 = #trans_id{mid    = '$1', 
		     serial = '$2', _ = '_'},
    Pat2 = #reply{trans_id  = Pat1, 
		  local_mid = LocalMid, 
		  state     = '$3', 
		  handler   = '$4', _ = '_'},
    Match0 = megaco_monitor:which_replies(Pat2),
    Match1 = [{mk_ch(LocalMid,V1),{V2,V3,V4}} || [V1, V2, V3, V4] <- Match0],
    which_replies1(lists:sort(Match1)).

which_replies1([]) ->
    [];
which_replies1([{CH, Data}|T]) ->
    which_replies2(T, CH, [Data], []).

which_replies2([], CH, Data, Reps) ->
    lists:reverse([{CH, Data}|Reps]);
which_replies2([{CH, Data}|T], CH, Datas, Reps) ->
    which_replies2(T, CH, [Data|Datas], Reps);
which_replies2([{CH1, Data}|T], CH2, Datas, Reps) ->
    which_replies2(T, CH1, [Data], [{CH2, lists:reverse(Datas)}| Reps]).
    
    
mk_ch(LM, RM) ->
    #megaco_conn_handle{local_mid = LM, remote_mid = RM}.
    

%%----------------------------------------------------------------------
%% Register/unreister connections
%%----------------------------------------------------------------------

%% Returns {ok, ConnHandle} | {error, Reason}
autoconnect(RH, RemoteMid, SendHandle, ControlPid, Extra)
  when is_record(RH, megaco_receive_handle) ->
    ?rt2("autoconnect", [RH, RemoteMid, SendHandle, ControlPid]),
    case megaco_config:autoconnect(RH, RemoteMid, SendHandle, ControlPid) of
        {ok, ConnData} ->
            do_connect(ConnData, Extra);
        {error, Reason} ->
            {error, Reason}
    end;
autoconnect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) ->
    {error, {bad_receive_handle, BadHandle}}.

connect(RH, RemoteMid, SendHandle, ControlPid) ->
    Extra = ?default_user_callback_extra, 
    connect(RH, RemoteMid, SendHandle, ControlPid, Extra).
connect(RH, RemoteMid, SendHandle, ControlPid, Extra)
  when is_record(RH, megaco_receive_handle) ->
    ?rt2("connect", [RH, RemoteMid, SendHandle, ControlPid, Extra]),

    %% The purpose of this is to have a temoporary process, to 
    %% which one can set up a monitor or link and get a 
    %% notification when process exits. The entire connect is 
    %% done in the temporary worker process. 
    %% When it exits, the connect is either successfully done 
    %% or it failed.

    ConnectorFun = 
	fun() ->

		ConnectResult = 
		    case megaco_config:connect(RH, RemoteMid, 
					       SendHandle, ControlPid) of
			{ok, ConnData} ->
			    do_connect(ConnData, Extra);
			{error, Reason} ->
			    {error, Reason}
		    end,
		?rt2("connector: connected", [self(), ConnectResult]),
		exit({result, ConnectResult})
	end,
    Flag      = process_flag(trap_exit, true),
    Connector = erlang:spawn_link(ConnectorFun),
    receive
	{'EXIT', Connector, {result, ConnectResult}} ->
	    ?rt2("connect result: received expected connector exit signal", 
		 [Connector, ConnectResult]),
	    process_flag(trap_exit, Flag),
	    ConnectResult;
	{'EXIT', Connector, OtherReason} ->
	    ?rt2("connect exit: received unexpected connector exit signal", 
		 [Connector, OtherReason]),
	    process_flag(trap_exit, Flag),
	    {error, OtherReason}
    end;
connect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) ->
    {error, {bad_receive_handle, BadHandle}}.

do_connect(CD, Extra) ->
    CH       = CD#conn_data.conn_handle,
    Version  = CD#conn_data.protocol_version,
    UserMod  = CD#conn_data.user_mod,
    UserArgs = CD#conn_data.user_args,
    Args      = 
	case Extra of
	    ?default_user_callback_extra ->
		[CH, Version | UserArgs];
	    _ ->
		[CH, Version, Extra | UserArgs]
	end,
    ?report_trace(CD, "callback: connect", [Args]),
    Res = (catch apply(UserMod, handle_connect, Args)),
    ?report_debug(CD, "return: connect", [{return, Res}]),
    case Res of
        ok ->
	    ?SIM(ok, do_connect), % do_encode),
	    monitor_process(CH, CD#conn_data.control_pid);
        error ->
            megaco_config:disconnect(CH),
            {error, {connection_refused, CD, error}};
        {error, ED} when is_record(ED,'ErrorDescriptor') ->
            megaco_config:disconnect(CH),
            {error, {connection_refused, CD, ED}};
        _Error ->
	    warning_msg("connect callback failed: ~w", [Res]),
            megaco_config:disconnect(CH),
            {error, {connection_refused, CD, Res}}
    end.

finish_connect(#conn_data{control_pid = ControlPid} = CD) 
  when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) ->
    ?rt1(CD, "finish local connect", [ControlPid]),
    do_finish_connect(CD);
finish_connect(#conn_data{conn_handle = CH,
			  control_pid = ControlPid} = CD) 
  when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) ->
    ?rt1(CD, "finish remote connect", [ControlPid]),
    RemoteNode     = node(ControlPid),
    UserMonitorPid = whereis(megaco_monitor),
    Args           = [CH, ControlPid, UserMonitorPid],
    case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of
        {ok, ControlMonitorPid} ->
	    do_finish_connect(CD#conn_data{control_pid = ControlMonitorPid});
        {error, Reason} ->
            disconnect(CH, {connect_remote, Reason}),
            {error, Reason};
        {badrpc, Reason} ->
            Reason2 = {'EXIT', Reason},
            disconnect(CH, {connect_remote, Reason2}),
            {error, Reason2}
    end.

do_finish_connect(#conn_data{conn_handle = CH,
			     send_handle = SendHandle,
			     control_pid = ControlPid} = CD) ->
    M   = ?MODULE,
    F   = disconnect_local,
    A   = [CH],
    MFA = {M, F, A}, 
    case megaco_config:finish_connect(CH, SendHandle, ControlPid, MFA) of
	{ok, Ref} ->
	    {ok, CD#conn_data{monitor_ref = Ref}};
	{error, Reason} ->
            {error, {config_update, Reason}}
    end.


monitor_process(CH, ControlPid) 
  when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) ->
    M = ?MODULE,
    F = disconnect_local,
    A = [CH],
    Ref = megaco_monitor:apply_at_exit(M, F, A, ControlPid),
    case megaco_config:update_conn_info(CH, monitor_ref, Ref) of
        ok ->
            ?SIM({ok, CH}, monitor_process_local);
        {error, Reason} ->
            disconnect(CH, {config_update, Reason}),
            {error, Reason}
    end;
monitor_process(CH, ControlPid) 
  when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) ->
    RemoteNode = node(ControlPid),
    UserMonitorPid = whereis(megaco_monitor),
    Args = [CH, ControlPid, UserMonitorPid],
    case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of
        {ok, ControlMonitorPid} ->
            M = ?MODULE,
            F = disconnect_local,
            A = [CH],
            Ref = megaco_monitor:apply_at_exit(M, F, A, ControlMonitorPid),
            case megaco_config:update_conn_info(CH, monitor_ref, Ref) of
                ok ->
		    ?SIM({ok, CH}, monitor_process_remote);
                {error, Reason} ->
                    disconnect(CH, {config_update, Reason}),
                    {error, Reason}
            end;
        {error, Reason} ->
            disconnect(CH, {connect_remote, Reason}),
            {error, Reason};
        {badrpc, Reason} ->
            Reason2 = {'EXIT', Reason},
            disconnect(CH, {connect_remote, Reason2}),
            {error, Reason2}
    end;
monitor_process(CH, undefined = _ControlPid) ->
    %% We have to do this later (setting up the monitor), 
    %% when the first message arrives. The 'connected' atom is
    %% the indication for the first arriving message to finish
    %% the connect. 
    %% This may be the case when an MGC performs a pre-connect
    %% in order to speed up the handling of an (expected) connecting
    %% MG. 
    case megaco_config:update_conn_info(CH, monitor_ref, connected) of
        ok ->
            ?SIM({ok, CH}, monitor_process_local);
        {error, Reason} ->
            disconnect(CH, {config_update, Reason}),
            {error, Reason}
    end.

connect_remote(CH, ControlPid, UserMonitorPid)
  when node(ControlPid) =:= node() andalso node(UserMonitorPid) =/= node() ->
    case megaco_config:lookup_local_conn(CH) of
        [_ConnData] -> 
            UserNode = node(UserMonitorPid),
            M = ?MODULE,
            F = disconnect_remote,
            A = [CH, UserNode],
            Ref = megaco_monitor:apply_at_exit(M, F, A, UserMonitorPid),
            case megaco_config:connect_remote(CH, UserNode, Ref) of
		ok ->
		    ControlMonitorPid = whereis(megaco_monitor),
		    ?SIM({ok, ControlMonitorPid}, connect_remote);
		{error, Reason} ->
		    {error, Reason}
	    end;
        [] ->
            {error, {no_connection, CH}}
    end.

cancel_apply_at_exit({connecting, _ConnectorPid}) ->
    ok;
cancel_apply_at_exit(connected) ->
    ok;
cancel_apply_at_exit(ControlRef) ->
    megaco_monitor:cancel_apply_at_exit(ControlRef).

node_of_control_pid(Pid) when is_pid(Pid) ->
    node(Pid);
node_of_control_pid(_) ->
    node().

disconnect(ConnHandle, DiscoReason)
  when is_record(ConnHandle, megaco_conn_handle) ->
    case megaco_config:disconnect(ConnHandle) of
        {ok, ConnData, RemoteConnData} ->
            ControlRef = ConnData#conn_data.monitor_ref,
            _ = cancel_apply_at_exit(ControlRef),
            handle_disconnect_callback(ConnData, DiscoReason),
            ControlNode = node_of_control_pid(ConnData#conn_data.control_pid),
            case ControlNode =:= node() of
                true ->
                    %% Propagate to remote users
                    CancelFun =
                        fun(RCD) ->
                                UserRef = RCD#remote_conn_data.monitor_ref,
                                _ = cancel_apply_at_exit(UserRef),
                                RCD#remote_conn_data.user_node
                          end,
                    Nodes = lists:map(CancelFun, RemoteConnData),
		    %% io:format("NODES: ~p~n", [Nodes]),
                    M = ?MODULE,
                    F = disconnect,
                    A = [ConnHandle, DiscoReason],
                    case rpc:multicall(Nodes, M, F, A) of
                        {Res, []} ->
			    Check = fun(ok) -> false;
				       ({error, {no_connection, _CH}}) -> false;
				       (_) -> true
				    end,
                            case lists:filter(Check, Res) of
                                [] ->
                                    ok;
                                Bad ->
                                    {error, {remote_disconnect_error, ConnHandle, Bad}}
                            end;
                        {_Res, Bad} ->
                            {error, {remote_disconnect_crash, ConnHandle, Bad}}
                    end;
                false when (RemoteConnData =:= []) ->
                    %% Propagate to remote control node
                    M = ?MODULE,
                    F = disconnect_remote,
                    A = [DiscoReason, ConnHandle, node()],
                    case rpc:call(ControlNode, M, F, A) of
                        {badrpc, Reason} ->
                            {error, {'EXIT', Reason}};
                        Other ->
                            Other
                    end
            end;
        {error, Reason} ->
            {error, Reason}
    end;
disconnect(BadHandle, Reason) ->
    {error, {bad_conn_handle, BadHandle, Reason}}.

disconnect_local(Reason, ConnHandle) ->
    disconnect(ConnHandle, {no_controlling_process, Reason}).

disconnect_remote(_Reason, ConnHandle, UserNode) ->
    case megaco_config:disconnect_remote(ConnHandle, UserNode) of
        [RCD] ->
            Ref = RCD#remote_conn_data.monitor_ref,
            _ = cancel_apply_at_exit(Ref),
            ok;
        [] ->
            {error, {no_connection, ConnHandle}}
    end.


%%----------------------------------------------------------------------
%% Handle incoming message
%%----------------------------------------------------------------------

receive_message(ReceiveHandle, ControlPid, SendHandle, Bin) ->
    Extra = ?default_user_callback_extra, 
    receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra).

receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) ->
    Opts = [link , {min_heap_size, 5000}],
    _ = spawn_opt(?MODULE,
                  process_received_message,
                  [ReceiveHandle,
                   ControlPid, SendHandle, Bin, self(), Extra], Opts),
    ok.

%% This function is called via the spawn_opt function with the link
%% option, therefor the unlink before the exit.
process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Receiver, 
			 Extra) ->
    process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra),
    unlink(Receiver),
    exit(normal).

process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin) ->
    Extra = ?default_user_callback_extra, 
    process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra).

process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) ->
    Flag = process_flag(trap_exit, true),
    case prepare_message(ReceiveHandle, SendHandle, Bin, ControlPid, Extra) of
        {ok, ConnData, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
	    ?rt1(ConnData, "message prepared", [MegaMsg]),
            Mess = MegaMsg#'MegacoMessage'.mess,
            case Mess#'Message'.messageBody of
                {transactions, Transactions} ->
                    {AckList, ReqList} = 
			prepare_trans(ConnData, Transactions, [], [], Extra),
                    handle_acks(AckList, Extra),
		    case ReqList of
			[] ->
			    ?rt3("no transaction requests"),
			    ignore;
			[Req|Reqs] when (ConnData#conn_data.threaded =:= true) ->
			    ?rt3("handle requests (spawned)"),
 			    lists:foreach(
 			      fun(R) -> 
 				      spawn(?MODULE, handle_request, [R, Extra]) 
 			      end, 
 			      Reqs),
			    handle_request(Req, Extra);
			_ ->
			    ?rt3("handle requests"),
			    case handle_requests(ReqList, [], Extra) of
				[] ->
				    ignore;
				[LongRequest | More] ->
				    lists:foreach(
				      fun(LR) ->
					      spawn(?MODULE, handle_long_request, [LR, Extra])
				      end,
				      More),
				    handle_long_request(LongRequest, Extra)
			    end
		    end;
                {messageError, Error} ->
                    handle_message_error(ConnData, Error, Extra)
            end;
        {silent_fail, ConnData, {_Code, Reason, Error}} ->
            ?report_debug(ConnData, Reason, [no_reply, Error]),
            ignore;
        {verbose_fail, ConnData, {Code, Reason, Error}} ->
            ?report_debug(ConnData, Reason, [Error]),
            send_message_error(ConnData, Code, Reason)
    end,
    process_flag(trap_exit, Flag),
    ok.

prepare_message(RH, SH, Bin, Pid, Extra)
  when is_record(RH, megaco_receive_handle) andalso is_pid(Pid) ->
    ?report_trace(RH, "receive bytes", [{bytes, Bin}]),
    EncodingMod    = RH#megaco_receive_handle.encoding_mod,
    EncodingConfig = RH#megaco_receive_handle.encoding_config,
    ProtVersion    = RH#megaco_receive_handle.protocol_version,
    case (catch EncodingMod:decode_message(EncodingConfig, ProtVersion, Bin)) of
        {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
	    ?report_trace(RH, "receive message", [{message, MegaMsg}]),
            Mess       = MegaMsg#'MegacoMessage'.mess,
            RemoteMid  = Mess#'Message'.mId,
            Version    = Mess#'Message'.version,
            LocalMid   = RH#megaco_receive_handle.local_mid,
            CH         = #megaco_conn_handle{local_mid  = LocalMid,
                                             remote_mid = RemoteMid},
            case megaco_config:lookup_local_conn(CH) of

		%% 
		%% Message is not of the negotiated version
		%% 

                [#conn_data{protocol_version = NegVersion, 
			    strict_version   = true} = ConnData] 
		when NegVersion =/= Version ->
		    %% Use already established connection, 
		    %% but incorrect version
		    ?rt1(ConnData, "not negotiated version", [Version]),
		    Error = {error, {not_negotiated_version, 
				     NegVersion, Version}}, 
		    handle_syntax_error_callback(RH, ConnData, 
						 prepare_error(Error),
						 Extra);


                [ConnData] ->

		    %% 
                    %% Use an already established connection
		    %% 
		    %% This *may* have been set up in the
		    %% "non-official" way, so we may need to 
		    %% create the monitor to the control process
		    %% and store the SendHandle (which is normally 
		    %% done when creating the "temporary" connection). 
		    %% 

		    ?rt1(ConnData, "use already established connection", []),
                    ConnData2 = ConnData#conn_data{send_handle      = SH,
						   control_pid      = Pid, 
                                                   protocol_version = Version},
                    check_message_auth(CH, ConnData2, MegaMsg, Bin);

                [] ->
                    %% Setup a temporary connection
		    ?rt3("setup a temporary connection"),
                    case autoconnect(RH, RemoteMid, SH, Pid, Extra) of
                        {ok, _} ->
			    do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin);
			{error, {already_connected, _ConnHandle}} ->
			    do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin);
			{error, {connection_refused, ConnData, Reason}} ->
			    Error = prepare_error({error, {connection_refused, Reason}}),
                            {verbose_fail, ConnData, Error};
                        {error, Reason} ->
                            ConnData = fake_conn_data(RH, RemoteMid, SH, Pid),
			    ConnData2 = ConnData#conn_data{protocol_version = Version},
			    Error    = prepare_error({error, Reason}),
                            {verbose_fail, ConnData2, Error}
                    end
            end;
        Error ->
	    ?rt2("decode error", [Error]),
	    ConnData = handle_decode_error(Error, 
					   RH, SH, Bin, Pid, 
					   EncodingMod, 
					   EncodingConfig, 
					   ProtVersion),
            handle_syntax_error_callback(RH, ConnData, prepare_error(Error), Extra)
    end;
prepare_message(RH, SendHandle, _Bin, ControlPid, _Extra) ->
    ConnData = fake_conn_data(RH, SendHandle, ControlPid),
    Error    = prepare_error({'EXIT', {bad_receive_handle, RH}}),
    {verbose_fail, ConnData, Error}.


handle_decode_error({error, {unsupported_version, _}},
		    #megaco_receive_handle{local_mid = LocalMid} = RH, SH, 
		    Bin, Pid,
		    EM, EC, V) ->
    case (catch EM:decode_mini_message(EC, V, Bin)) of
	{ok, #'MegacoMessage'{mess = #'Message'{version = _Ver, 
						mId     = RemoteMid}}} ->
	    ?rt2("erroneous message received", [SH, RemoteMid, _Ver]),
            CH = #megaco_conn_handle{local_mid  = LocalMid,
				     remote_mid = RemoteMid},
	    incNumErrors(CH),
	    %% We cannot put the version into conn-data, that will
	    %% make the resulting error message impossible to sent
	    %% (unsupported version)
	    case megaco_config:lookup_local_conn(CH) of
                [ConnData] ->
		    ?rt3("known to us"),
		    ConnData#conn_data{send_handle = SH};
		[] ->
		    ?rt3("unknown to us"),
		    ConnData = fake_conn_data(RH, SH, Pid),
		    ConnData#conn_data{conn_handle = CH}
	    end;

	_ ->
	    ?rt2("erroneous message received", [SH]),
	    incNumErrors(),
	    fake_conn_data(RH, SH, Pid)
    end;

handle_decode_error(_,
		    #megaco_receive_handle{local_mid = LocalMid} = RH, SH, 
		    Bin, Pid,
		    EM, EC, V) ->
    case (catch EM:decode_mini_message(EC, V, Bin)) of
	{ok, #'MegacoMessage'{mess = #'Message'{version = Ver, 
						mId     = RemoteMid}}} ->
	    ?rt2("erroneous message received", [SH, Ver, RemoteMid]),
            CH = #megaco_conn_handle{local_mid  = LocalMid,
				     remote_mid = RemoteMid},
	    incNumErrors(CH),
	    case megaco_config:lookup_local_conn(CH) of
                [ConnData] ->
		    ?rt3("known to us"),
		    ConnData#conn_data{send_handle      = SH,
				       protocol_version = Ver};
		[] ->
		    ?rt3("unknown to us"),
		    ConnData = fake_conn_data(RH, SH, Pid),
		    ConnData#conn_data{conn_handle      = CH,
				       protocol_version = Ver}
	    end;

	_ ->
	    ?rt2("erroneous message received", [SH]),
	    incNumErrors(),
	    fake_conn_data(RH, SH, Pid)
    end.


do_prepare_message(RH, CH, SendHandle, MegaMsg, ControlPid, Bin) ->
    case megaco_config:lookup_local_conn(CH) of
	[ConnData] ->
	    case check_message_auth(CH, ConnData, MegaMsg, Bin) of
		{ok, ConnData2, MegaMsg} ->
		    %% Let the connection be permanent
		    {ok, ConnData2, MegaMsg};
		{ReplyTag, ConnData, Reason} ->
		    %% Remove the temporary connection
		    disconnect(CH, {bad_auth, Reason}),
		    {ReplyTag, ConnData, Reason}
	    end;
	[] ->
	    Reason = no_connection,
	    disconnect(CH, Reason),
	    RemoteMid = CH#megaco_conn_handle.remote_mid,
	    ConnData = fake_conn_data(RH, RemoteMid, SendHandle, ControlPid),
	    Error = prepare_error({error, Reason}),
	    {silent_fail, ConnData, Error}
    end.

check_message_auth(_ConnHandle, ConnData, MegaMsg, Bin) ->
    MsgAuth   = MegaMsg#'MegacoMessage'.authHeader,
    Mess      = MegaMsg#'MegacoMessage'.mess,
    Version   = Mess#'Message'.version,
    ConnData2 = ConnData#conn_data{protocol_version = Version},
    ConnAuth  = ConnData2#conn_data.auth_data,
    ?report_trace(ConnData2, "check message auth", [{bytes, Bin}]),
    if
	(MsgAuth =:= asn1_NOVALUE) andalso (ConnAuth =:= asn1_NOVALUE) ->
            ?SIM({ok, ConnData2, MegaMsg}, check_message_auth);
	true -> 
	    ED = #'ErrorDescriptor'{errorCode = ?megaco_unauthorized,
				    errorText = "Autentication is not supported"},
	    {verbose_fail, ConnData2, prepare_error({error, ED})}
    end.

handle_syntax_error_callback(ReceiveHandle, ConnData, PrepError, Extra) ->
    {Code, Reason, Error} = PrepError,
    ErrorDesc = #'ErrorDescriptor'{errorCode = Code, errorText = Reason},
    Version   = 
	case Error of
	    {error, {unsupported_version, UV}} ->
		UV;
	    _ ->
		ConnData#conn_data.protocol_version
	end,
    UserMod   = ConnData#conn_data.user_mod,
    UserArgs  = ConnData#conn_data.user_args,
    ?report_trace(ReceiveHandle, "callback: syntax error", [ErrorDesc, Error]),
    Args      = 
	case Extra of
	    ?default_user_callback_extra ->
		[ReceiveHandle, Version, ErrorDesc | UserArgs];
	    _ ->
		[ReceiveHandle, Version, ErrorDesc, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_syntax_error, Args)),
    ?report_debug(ReceiveHandle, "return: syntax error", 
		  [{return, Res}, ErrorDesc, Error]),
    case Res of
        reply ->
            {verbose_fail, ConnData, PrepError};
        {reply,#'ErrorDescriptor'{errorCode = Code1, errorText = Reason1}} ->
            {verbose_fail, ConnData, {Code1,Reason1,Error}};
        no_reply ->
            {silent_fail, ConnData, PrepError};
        {no_reply,#'ErrorDescriptor'{errorCode=Code2,errorText=Reason2}} ->
            {silent_fail, ConnData, {Code2,Reason2,Error}}; %%% OTP-????
        _ ->
	    warning_msg("syntax error callback failed: ~w", [Res]),
            {verbose_fail, ConnData, PrepError}
    end.

fake_conn_data(CH) when is_record(CH, megaco_conn_handle) ->
    case (catch megaco_config:conn_info(CH, receive_handle)) of
	RH when is_record(RH, megaco_receive_handle) ->
	    RemoteMid = CH#megaco_conn_handle.remote_mid,
	    ConnData = 
		fake_conn_data(RH, RemoteMid, no_send_handle, no_control_pid),
	    ConnData#conn_data{conn_handle = CH};
	{'EXIT', _} ->
	    UserMid = CH#megaco_conn_handle.local_mid,
	    case catch megaco_config:user_info(UserMid, receive_handle) of
		{'EXIT', _} -> % No such user
		    #conn_data{conn_handle        = CH,
			       serial             = undefined_serial,
			       control_pid        = no_control_pid,
			       monitor_ref        = undefined_monitor_ref,
			       send_mod           = no_send_mod,
			       send_handle        = no_send_handle,
			       encoding_mod       = no_encoding_mod,
			       encoding_config    = no_encoding_config,
			       reply_action       = undefined,
			       sent_pending_limit = infinity,
			       recv_pending_limit = infinity};
		RH ->
		    ConnData = 
			fake_conn_data(RH, no_send_handle, no_control_pid),
		    ConnData#conn_data{conn_handle = CH}
	    end
    end.

fake_conn_data(RH, SendHandle, ControlPid) ->
    fake_conn_data(RH, unknown_remote_mid, SendHandle, ControlPid).

fake_conn_data(RH, RemoteMid, SendHandle, ControlPid) ->
    case catch megaco_config:init_conn_data(RH, RemoteMid, SendHandle, ControlPid) of
	{'EXIT', _} -> % No such user
	    fake_user_data(RH, RemoteMid, SendHandle, ControlPid);
	ConnData ->
	    ConnData
    end.

fake_user_data(RH, RemoteMid, SendHandle, ControlPid) ->
    LocalMid = RH#megaco_receive_handle.local_mid,
    RH2 = RH#megaco_receive_handle{local_mid = default},
    case catch megaco_config:init_conn_data(RH2, RemoteMid, SendHandle, ControlPid) of
	{'EXIT', _} -> % Application stopped?
	    ConnHandle     = #megaco_conn_handle{local_mid  = LocalMid,
						 remote_mid = RemoteMid},
	    EncodingMod    = RH#megaco_receive_handle.encoding_mod,
	    EncodingConfig = RH#megaco_receive_handle.encoding_config,
	    SendMod        = RH#megaco_receive_handle.send_mod,
	    #conn_data{conn_handle        = ConnHandle,
		       serial             = undefined_serial,
		       control_pid        = ControlPid,
		       monitor_ref        = undefined_monitor_ref,
		       send_mod           = SendMod,
		       send_handle        = SendHandle,
		       encoding_mod       = EncodingMod,
		       encoding_config    = EncodingConfig,
		       reply_action       = undefined,
		       sent_pending_limit = infinity,
		       recv_pending_limit = infinity};
	ConnData ->
	    ConnData
    end.

prepare_error(Error) ->
    case Error of
        {error, ED} when is_record(ED, 'ErrorDescriptor') ->
            Code   = ED#'ErrorDescriptor'.errorCode,
            Reason = ED#'ErrorDescriptor'.errorText,
            {Code, Reason, Error};
        {error, [{reason, {bad_token, [BadToken, _Acc]}, Line}]} when is_integer(Line) ->
            Reason = 
		lists:flatten(
		  io_lib:format("Illegal token (~p) on line ~w", [BadToken, Line])),
            Code = ?megaco_bad_request,
            {Code, Reason, Error};
        {error, [{reason, {bad_token, _}, Line}]} when is_integer(Line) ->
            Reason = lists:concat(["Illegal token on line ", Line]),
            Code = ?megaco_bad_request,
            {Code, Reason, Error};
        {error, [{reason, {Line, _ParserMod, RawReasonString}} | _]} when is_integer(Line) andalso is_list(RawReasonString) ->
	    Reason = 
		case RawReasonString of
		    [[$s, $y, $n, $t, $a, $x | _], TokenString] ->
			lists:flatten(
			  io_lib:format("Syntax error on line ~w before token ~s", [Line, TokenString]));
		    _ ->
			lists:flatten(io_lib:format("Syntax error on line ~w", [Line]))
		end,
            Code = ?megaco_bad_request,
            {Code, Reason, Error};
        {error, [{reason, {Line, _, _}} | _]} when is_integer(Line) ->
            Reason = lists:concat(["Syntax error on line ", Line]),
            Code = ?megaco_bad_request,
            {Code, Reason, Error};
        {error, {connection_refused, ED}} when is_record(ED,'ErrorDescriptor') ->
            Code   = ED#'ErrorDescriptor'.errorCode,
            Reason = ED#'ErrorDescriptor'.errorText,
            {Code, Reason, Error};
        {error, {connection_refused, _}} ->
            Reason = "Connection refused by user",
            Code = ?megaco_unauthorized,
            {Code, Reason, Error};
        {error, {unsupported_version, V}} ->
            Reason = 
		lists:flatten(io_lib:format("Unsupported version: ~w",[V])),
            Code = ?megaco_version_not_supported, 
            {Code, Reason, Error};
        {error, {not_negotiated_version, NegV, MsgV}} ->
            Reason = 
		lists:flatten(
		  io_lib:format("Not negotiated version: ~w [negotiated ~w]",
				[MsgV, NegV])),
            Code = ?megaco_version_not_supported, 
            {Code, Reason, Error};
        {error, _} ->
            Reason = "Syntax error",
            Code = ?megaco_bad_request,
            {Code, Reason, Error};
        {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
            Reason = "MID does not match config",
            Code = ?megaco_incorrect_identifier,
            {Code, Reason, Error};
        _ ->
            Reason = "Fatal syntax error",
            Code = ?megaco_internal_gateway_error,
            {Code, Reason, Error}
    end.

prepare_trans(_ConnData, [], AckList, ReqList, _Extra) ->
    ?SIM({AckList, ReqList}, prepare_trans_done);

prepare_trans(ConnData, Trans, AckList, ReqList, Extra) 
  when ConnData#conn_data.monitor_ref =:= undefined_auto_monitor_ref ->

    ?rt3("prepare_trans - autoconnect"),

    %% <BUGBUG>
    %% Do we need something here, if we send more then one 
    %% trans per message?
    %% </BUGBUG>
    
    %% May occur if another process has already setup a
    %% temporary connection, but the handle_connect callback
    %% function has not yet returned before the eager MG
    %% re-sends its initial service change message.

    prepare_autoconnecting_trans(ConnData, Trans, AckList, ReqList, Extra);

prepare_trans(#conn_data{monitor_ref = connected} = ConnData, 
	      Trans, AckList, ReqList, Extra) ->

    ?rt3("prepare_trans - connected"),

    %% 
    %% This will happen when the "MGC" user performs a "pre" connect,
    %% instead of waiting for the auto-connect (which normally
    %% happen when the MGC receives the first message from the
    %% MG). 
    %% 

    %% 
    %% The monitor_ref will have this value when the pre-connect 
    %% is complete, so we finish it here and then continue with the 
    %% normal transaction prepare.
    %% 

    case finish_connect(ConnData) of
	{ok, CD} ->
	    prepare_normal_trans(CD, Trans, AckList, ReqList, Extra);
	{error, Reason} ->
	    disconnect(ConnData#conn_data.conn_handle, Reason),
	    {[], []}
    end;

prepare_trans(#conn_data{monitor_ref = {connecting, _}} = _ConnData, 
	      _Trans, _AckList, _ReqList, _Extra) ->
    
    ?rt3("prepare_trans - connecting"),

    %% 
    %% This will happen when the "MGC" user performs a "pre" connect,
    %% instead of waiting for the auto-connect (which normally
    %% happen when the MGC receives the first message from the
    %% MG). 
    %% 

    %% 
    %% The monitor_ref will have this value when the pre-connect 
    %% is in progress. We drop (ignore) this message and hope the
    %% other side (MG) will resend. 
    %% 

    %% prepare_connecting_trans(ConnData, Trans, AckList, ReqList, Extra);
    {[], []};

prepare_trans(ConnData, Trans, AckList, ReqList, Extra) ->

    ?rt3("prepare_trans - normal"),

    %% Handle transaction in the normal case

    prepare_normal_trans(ConnData, Trans, AckList, ReqList, Extra).


prepare_autoconnecting_trans(_ConnData, [], AckList, ReqList, _Extra) ->
    ?SIM({AckList, ReqList}, prepare_autoconnecting_trans_done);

prepare_autoconnecting_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) ->
    ?rt1(ConnData, "[autoconnecting] prepare trans", [Trans]),
    case Trans of
        {transactionRequest, T} when is_record(T, 'TransactionRequest') ->
	    
            Serial = T#'TransactionRequest'.transactionId,
	    ConnData2 = ConnData#conn_data{serial = Serial},
	    ?report_trace(ConnData2, "Pending handle_connect", [T]),

	    %% ------------------------------------------
	    %% 
	    %%   Check pending limit
	    %% 
	    %% ------------------------------------------

	    Limit = ConnData#conn_data.sent_pending_limit,
	    TransId = to_remote_trans_id(ConnData2),
	    _ = case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
                    ok ->
                        send_pending(ConnData2);
                    error ->
                        %% Pending limit:
                        %% In this (granted, highly hypothetical case)
                        %% we would make the user very confused if we 
                        %% called the abort callback function, since 
                        %% the request callback function has not yet
                        %% been called. Alas, we skip this call here.
                        send_pending_limit_error(ConnData);
                    aborted ->
                        ignore
                end,
	    prepare_autoconnecting_trans(ConnData2, Rest, AckList, ReqList, 
					 Extra);
	_ ->
	    prepare_autoconnecting_trans(ConnData, Rest, AckList, ReqList, 
					 Extra)
    end.


%% =================================================================
%% 
%% Note that the TransactionReply record was changed i v3 (two 
%% new fields where added), and since we don't know which version,
%% we cannot use the record definition of TransactionReply.
%% Instead we transform the record into our own internal format
%% #megaco_transaction_reply{}
%%
%% =================================================================

prepare_normal_trans(_ConnData, [], AckList, ReqList, _Extra) ->
    ?SIM({AckList, ReqList}, prepare_normal_trans_done);

prepare_normal_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) ->
    ?rt1(ConnData, "prepare [normal] trans", [Trans]),
    case Trans of
        {transactionRequest, #'TransactionRequest'{transactionId = asn1_NOVALUE}} ->
            ConnData2 = ConnData#conn_data{serial = 0},
	    Code   = ?megaco_bad_request,
            Reason = "Syntax error in message: transaction id missing",
	    send_trans_error(ConnData2, Code, Reason),
            prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
        {transactionRequest, T} when is_record(T, 'TransactionRequest') ->
            Serial = T#'TransactionRequest'.transactionId,
            ConnData2 = ConnData#conn_data{serial = Serial},
            prepare_request(ConnData2, T, Rest, AckList, ReqList, Extra);
        {transactionPending, T} when is_record(T, 'TransactionPending') ->
            Serial = T#'TransactionPending'.transactionId,
            ConnData2 = ConnData#conn_data{serial = Serial},
            handle_pending(ConnData2, T, Extra),
            prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
        {transactionReply, T} when is_tuple(T) andalso  
				   (element(1, T) == 'TransactionReply') ->
	    T2        = transform_transaction_reply_dec(T),
            Serial    = T2#megaco_transaction_reply.transactionId, 
            ConnData2 = ConnData#conn_data{serial = Serial},
            handle_reply(ConnData2, T2, Extra),
	    prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);
        {transactionResponseAck, List} when is_list(List) ->
            prepare_ack(ConnData, List, Rest, AckList, ReqList, Extra);
        {segmentReply, SR} when is_record(SR, 'SegmentReply') ->
	    handle_segment_reply(ConnData, SR, Extra), 
            prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra)

    end.


prepare_request(ConnData, T, Rest, AckList, ReqList, Extra) ->
    ?rt2("prepare request", [T]),
    ConnHandle = ConnData#conn_data.conn_handle, 
    LocalMid   = ConnHandle#megaco_conn_handle.local_mid,
    TransId    = to_remote_trans_id(ConnData),
    ?rt2("prepare request", [LocalMid, TransId]),
    case lookup_reply(ConnData, TransId) of
        [] ->
	    ?rt3("brand new request"),

            %% Brand new request

	    %% Check pending limit:
	    %% 
	    %% We should actually check the pending limit here
	    %% but since we have to do it later in the 
	    %% handle_request function (just before we call
	    %% the handle_trans_request callback function) we
	    %% can just as well wait (this is after all a very
	    %% unlikely case: see function prepare_trans when 
	    %% monitor_ref == undefined_auto_monitor_ref).
	    %% 

	    #conn_data{send_handle      = SendHandle,
		       pending_timer    = InitTimer,
		       protocol_version = Version,
		       user_mod         = UserMod,
		       user_args        = UserArgs} = ConnData,
	    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
	    M = ?MODULE,
	    F = pending_timeout,
	    A = [ConnHandle, TransId, CurrTimer],     
	    PendingRef = megaco_monitor:apply_after(M, F, A, WaitFor),
            Rep = #reply{send_handle       = SendHandle,
			 trans_id          = TransId,
			 local_mid         = LocalMid,
			 pending_timer_ref = PendingRef,
			 handler           = self(),
			 version           = Version,
                         user_mod          = UserMod,
                         user_args         = UserArgs},
            case megaco_monitor:insert_reply_new(Rep) of
		true ->
		    prepare_normal_trans(ConnData, Rest, AckList, 
					 [{ConnData, TransId, T} | ReqList], 
					 Extra);
		false ->
		    %% Oups - someone got there before we did...
		    ?report_debug(ConnData, 
				  "prepare request: conflicting requests", 
				  [TransId]),
		    _ = send_pending(ConnData),
		    _ = megaco_monitor:cancel_apply_after(PendingRef),
		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
					 Extra)
	    end;

	%% We can ignore the Converted value here as we *know*
	%% conn-data to be correct (not faked), so even if 
	%% the record was converted, it will now have correct
	%% values for user_mod and user_args.
        {_Converted, 
	 #reply{state             = State, 
		handler           = Pid,
		pending_timer_ref = Ref} = Rep}
	when (State =:= prepare) orelse (State =:= eval_request) ->

	    ?rt2("request resend", [State, Pid, Ref]),

            %% Pending limit:
	    %% We are still preparing/evaluating the request
            %% Check if the pending limit has been exceeded...
	    %% If the pending limit is _not_ exceeded then
	    %% we shall send a pending (and actually restart 
	    %% the pending timer, but that we cannot do).
	    %% Don't care about Msg and Rep version diff

	    #conn_data{sent_pending_limit = Limit} = ConnData,
	    
	    case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
		ok ->

		    %% ------------------------------------------
		    %% 
		    %%   Pending limit not exceeded
		    %% 
		    %%   1) Increment number of pendings sent
		    %%      (done in the check function above)
		    %%   2) Send pending message
		    %%      (We should really restart the pending 
		    %%      timer, but we have no way of doing that).
		    %% 
		    %% ------------------------------------------

		    _ = send_pending(ConnData),
		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
					 Extra);


		error ->

		    %% -------------------------------------------
		    %% 
		    %%   Pending limit exceeded
		    %% 
		    %%   1) Cancel pending timer
		    %%   2) Send 506 error message to other side
		    %%   3) Inform user (depends on state)
		    %%   4) Set reply in aborted state
		    %% 
		    %% -------------------------------------------

		    %% 
		    %% State == eval_request:
		    %%   This means that the request is currently beeing 
		    %%   evaluated by the user, and the reply timer has 
		    %%   not yet been started. 
		    %%   Either:
		    %%   a) The "other side" will resend (which will 
		    %%      trigger a pending message send) until we pass the 
		    %%      pending limit 
		    %%   b) We will send pending messages (when the pending 
		    %%      timer expire) until we pass the pending limit.
		    %%   In any event, we cannot delete the reply record
		    %%   or the pending counter in this case. Is there
		    %%   a risk we accumulate aborted reply records?
		    %% 
		    %% State == prepare:
		    %%   The user does not know about this request
		    %%   so we can safely perform cleanup.
		    %%
		    _ = megaco_monitor:cancel_apply_after(Ref),
		    send_pending_limit_error(ConnData),
		    if 
			State == eval_request ->
			    %% 
			    %% What if the user never replies?
			    %% In that case we will have a record
			    %% (and counters) that is never cleaned up...
			    NewFields = 
				[{#reply.state,             aborted}, 
				 {#reply.pending_timer_ref, undefined}], 
			    megaco_monitor:update_reply_fields(TransId, 
							       NewFields),
			    handle_request_abort_callback(ConnData, 
							  TransId, Pid, Extra);
			true ->
			    %% Since the user does not know about
			    %% this call yet, it is safe to cleanup.
			    %% Should we inform?
			    Rep2 = Rep#reply{state = aborted},
			    cancel_reply(ConnData, Rep2, aborted),
			    ok
		    end,
		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
					 Extra);


		aborted ->

		    %% -------------------------------------------
		    %% 
		    %%   Pending limit already exceeded
		    %% 
		    %%   Cleanup, just to make sure:
		    %%     reply record & pending counter
		    %% 
		    %% -------------------------------------------

		    Rep2 = Rep#reply{state = aborted},
		    cancel_reply(ConnData, Rep2, aborted),
		    prepare_normal_trans(ConnData, Rest, AckList, ReqList, 
					 Extra)

	    end;

	%% We can ignore the Converted value here as we *know*
	%% conn-data to be correct (not faked), so even if 
	%% the record was converted, it will now have correct
	%% values for user_mod and user_args.
        {_Converted, 
	 #reply{state   = waiting_for_ack, 
		bytes   = Bin, 
		version = Version} = Rep} ->
	    ?rt3("request resend when waiting for ack"),

            %% We have already sent a reply, but the receiver
            %% has obviously not got it. Resend the reply but
            %% don't restart the reply_timer.
            ConnData2 = ConnData#conn_data{protocol_version = Version},
            ?report_trace(ConnData2, 
			  "re-send trans reply", [T, {bytes, Bin}]),
            case megaco_messenger_misc:send_message(ConnData2, true, Bin) of
		{ok, _} ->
		    ok;
		{error, Reason} ->
		    %% Pass it on to the user (via handle_ack)
		    cancel_reply(ConnData2, Rep, Reason)
	    end,
	    prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra);


	%% We can ignore the Converted value here as we *know*
	%% conn-data to be correct (not faked), so even if 
	%% the record was converted, it will now have correct
	%% values for user_mod and user_args.
	{_Converted, 
	 #reply{state = aborted} = Rep} ->
	    ?rt3("request resend when already in aborted state"),

	    %% OTP-4956:
	    %% Already aborted so ignore.
	    %% This furthermore means that the abnoxious user at the
	    %% other end has already been informed (pending-limit
	    %% passed => error descriptor sent), but keeps sending...
	    %% 
	    %% Shall we perform a cleanup?
	    cancel_reply(ConnData, Rep, aborted),
	    prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra)
        end.

prepare_ack(ConnData, [TA | T], Rest, AckList, ReqList, Extra) 
  when is_record(TA, 'TransactionAck') ->
    First     = TA#'TransactionAck'.firstAck,
    Last      = TA#'TransactionAck'.lastAck,
    TA2       = TA#'TransactionAck'{lastAck = asn1_NOVALUE},
    ConnData2 = ConnData#conn_data{serial = First},
    AckList2  = do_prepare_ack(ConnData2, TA2, AckList),
    if
        Last =:= asn1_NOVALUE ->
            prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra);
        First < Last ->
            TA3 = TA#'TransactionAck'{firstAck = First + 1},
            prepare_ack(ConnData, [TA3 | T], Rest, AckList2, ReqList, Extra);
        First =:= Last ->
            prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra);
        First > Last ->
            %% Protocol violation from the sender of this ack
            ?report_important(ConnData, "<ERROR> discard trans",
			      [TA, {error, "firstAck > lastAck"}]),
	    prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra)
    end;
prepare_ack(ConnData, [], Rest, AckList, ReqList, Extra) ->
    prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra).

do_prepare_ack(ConnData, T, AckList) ->
    TransId = to_remote_trans_id(ConnData),
    case lookup_reply(ConnData, TransId) of
        [] ->
            %% The reply has already been garbage collected. Ignore.
            ?report_trace(ConnData, "discard ack (no receiver)", [T]),
            AckList;
        {_Converted, Rep} when Rep#reply.state =:= waiting_for_ack ->
            %% Don't care about Msg and Rep version diff
            [{ConnData, Rep, T} | AckList];
        {_Converted, _Rep} ->
            %% Protocol violation from the sender of this ack
            ?report_important(ConnData, "<ERROR> discard trans",
			      [T, {error, "got ack before reply was sent"}]),
            AckList
    end.


increment_request_keep_alive_counter(#conn_data{conn_handle = CH}, TransId) ->
    ?rt1(CH, "increment request keep alive counter", [TransId]),
    megaco_config:incr_reply_counter(CH, TransId).

create_or_maybe_increment_request_keep_alive_counter(
  #conn_data{conn_handle = CH}, TransId) ->
    ?rt1(CH, "create or maybe increment request keep alive counter", 
	 [TransId]),
    try
	begin
	    megaco_config:cre_reply_counter(CH, TransId)
	end
    catch 
	_:_ ->
	    megaco_config:incr_reply_counter(CH, TransId)
    end.
	

check_and_maybe_create_pending_limit(infinity, _, _) ->
    ok;
check_and_maybe_create_pending_limit(Limit, Direction, TransId) ->
    ?rt2("check and maybe create pending limit counter", 
	 [Limit, Direction, TransId]),
    try megaco_config:get_pending_counter(Direction, TransId) of
	Val when Val =< Limit ->
	    %% Since we have no intention to increment here, it
	    %% is ok to be _at_ the limit
	    ok;
	_ ->
	    aborted
    catch 
	_:_ ->
	    %% Has not been created yet (connect).
	    megaco_config:cre_pending_counter(Direction, TransId, 0),
	    ok
    end.

%% check_and_maybe_create_pending_limit(infinity, _, _) ->
%%     ok;
%% check_and_maybe_create_pending_limit(Limit, Direction, TransId) ->
%%     case (catch megaco_config:get_pending_counter(Direction, TransId)) of
%% 	{'EXIT', _} ->
%% 	    %% Has not been created yet (connect).
%% 	    megaco_config:cre_pending_counter(Direction, TransId, 0),
%% 	    ok;
%% 	Val when Val =< Limit ->
%% 	    %% Since we have no intention to increment here, it
%% 	    %% is ok to be _at_ the limit
%% 	    ok;
%% 	_ ->
%% 	    aborted
%%     end.


check_pending_limit(infinity, _, _) ->
    {ok, 0};
check_pending_limit(Limit, Direction, TransId) ->
    ?rt2("check pending limit", [Direction, Limit, TransId]),
    try megaco_config:get_pending_counter(Direction, TransId) of
	Val when Val =< Limit ->
	    %% Since we have no intention to increment here, it
	    %% is ok to be _at_ the limit
	    ?rt2("check pending limit - ok", [Val]),
	    {ok, Val};
	_Val ->
	    ?rt2("check pending limit - aborted", [_Val]),
	    aborted
    catch 
	_:_ ->
	    %% This function is only called when we "know" the 
	    %% counter to exist. So, the only reason that this 
	    %% would happen is of the counter has been removed.
	    %% This only happen if the pending limit has been 
	    %% reached. In any case, this is basically the same 
	    %% as aborted!
	    ?rt2("check pending limit - exit", []),
	    aborted
    end.


check_and_maybe_incr_pending_limit(infinity, _, _) ->
    ok;
check_and_maybe_incr_pending_limit(Limit, Direction, TransId) ->
    %% 
    %% We need this kind of test to detect when we _pass_ the limit
    %% 
    ?rt2("check and maybe incr pending limit", [{direction,      Direction}, 
						{transaction_id, TransId}, 
						{counter_limit,  Limit}]),
    try megaco_config:get_pending_counter(Direction, TransId) of
	Val when Val > Limit ->
	    ?rt2("check and maybe incr - aborted", [{counter_value, Val}]),
	    aborted;      % Already passed the limit
	Val ->
	    ?rt2("check and maybe incr - incr", [{counter_value, Val}]),
	    megaco_config:incr_pending_counter(Direction, TransId),
	    if 
		Val < Limit ->
		    ok;   % Still within the limit
		true ->
		    ?rt2("check and maybe incr - error", 
			 [{counter_value, Val}]),
		    error % Passed the limit
	    end
    catch 
	_:_ ->
	    %% Has not been created yet (connect).
	    %% Try create it, but bevare of possible raise condition
	    try
		begin
		    megaco_config:cre_pending_counter(Direction, TransId, 1),
		    ok
		end
	    catch
		_:_ ->
		    %% Ouch, raise condition, increment instead...
		    megaco_config:incr_pending_counter(Direction, TransId),
		    ok
	    end
    end.


%% BUGBUG BUGBUG BUGBUG
%% 
%% Do we know that the Rep is still valid? A previous transaction
%% could have taken a lot of time.
%% 
handle_request({ConnData, TransId, T}, Extra) ->
    case handle_request(ConnData, TransId, T, Extra) of
	{pending, _RequestData} ->
	    handle_long_request(ConnData, TransId, T, Extra);
	Else ->
	    Else
    end.

handle_request(ConnData, TransId, T, Extra) ->
    ?report_trace(ConnData, "handle request", [TransId, T]),

    %% Pending limit:
    %% Ok, before we begin, lets check that this request 
    %% has not been aborted. I.e. exceeded the pending 
    %% limit, so go check it...

    #conn_data{sent_pending_limit = Limit} = ConnData,

    case check_and_maybe_create_pending_limit(Limit, sent, TransId) of
	ok ->
	    %% Ok so far, now update state
	    case megaco_monitor:update_reply_field(TransId, 
						   #reply.state, 
						   eval_request) of
		true ->
		    Actions = T#'TransactionRequest'.actions,
		    {AckAction, SendReply} = 
			handle_request_callback(ConnData, TransId, Actions, 
						T, Extra),

		    %% Next step, while we where in the callback function,
		    %% the pending limit could have been exceeded, so check
		    %% it again...
		    do_handle_request(AckAction, SendReply, 
				      ConnData, TransId);

		false ->
		    %% Ugh?
		    ignore
	    end;

	aborted ->
	    %% Pending limit
	    %% Already exceeded the limit
	    %% The user does not yet know about this request, so
	    %% don't bother telling that it has been aborted...
	    %% Furthermore, the reply timer has not been started,
	    %% so do the cleanup now
	    ?rt1(ConnData, "pending limit already passed", [TransId]),
	    case lookup_reply(ConnData, TransId) of
		{_Converted, Rep} ->
		    cancel_reply(ConnData, Rep, aborted);
		_ ->
		    ok
	    end,
	    ignore
    end.

do_handle_request(_, ignore, _ConnData, _TransId) ->
    ?rt1(_ConnData, "ignore: don't reply", [_TransId]),
    ignore;
do_handle_request(_, ignore_trans_request, ConnData, TransId) ->
    ?rt1(ConnData, "ignore trans request: don't reply", [TransId]),
    case lookup_reply(ConnData, TransId) of
	{_Converted, #reply{} = Rep} ->
	    cancel_reply(ConnData, Rep, ignore);
	_ ->
	    ignore
    end;
do_handle_request({pending, _RequestData}, {aborted, ignore}, _, _) ->
    ?rt2("handle request: pending - aborted - ignore => don't reply", []),
    ignore;
do_handle_request({pending, _RequestData}, {aborted, _SendReply}, _, _) ->
    ?rt2("handle request: pending - aborted => don't reply", []),
    ignore;
do_handle_request({pending, RequestData}, _SendReply, _ConnData, _) ->
    ?rt2("handle request: pending", [RequestData]),
    {pending, RequestData};
do_handle_request(AckAction, {ok, Bin}, ConnData, TransId) 
  when is_binary(Bin) ->
    ?rt1(ConnData, "handle request - ok", [AckAction, TransId]),
    case lookup_reply(ConnData, TransId) of
	{_Converted, #reply{pending_timer_ref = PendingRef} = Rep} ->

	    #conn_data{reply_timer = InitTimer,
		       conn_handle = ConnHandle} = ConnData,

	    %% Pending limit update:
	    %%   - Cancel the pending timer, if running
	    %%   - Delete the pending counter
	    %% 

	    _ = megaco_monitor:cancel_apply_after(PendingRef),
	    megaco_config:del_pending_counter(sent, TransId),

	    Method = timer_method(AckAction),
	    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
	    OptBin = opt_garb_binary(CurrTimer, Bin),
	    M = ?MODULE,
	    F = reply_timeout,
	    A = [ConnHandle, TransId, CurrTimer],
	    Ref = megaco_monitor:apply_after(Method, M, F, A, 
					     WaitFor),
	    Rep2 = Rep#reply{pending_timer_ref = undefined,
			     handler           = undefined,
			     bytes             = OptBin,
			     state             = waiting_for_ack,
			     timer_ref         = Ref,
			     ack_action        = AckAction},
	    megaco_monitor:insert_reply(Rep2), % Timing problem?
	    ignore;

	_ ->
	    %% Been removed already?
	    ignore
    end;
do_handle_request(AckAction, {ok, {Sent, NotSent}}, ConnData, TransId) 
  when is_list(Sent) andalso is_list(NotSent) ->
    ?rt1(ConnData, "handle request - ok [segmented reply]", 
	 [AckAction, TransId]),

    case lookup_reply(ConnData, TransId) of
	{_Converted, #reply{pending_timer_ref = PendingRef} = Rep} ->

	    %% d("do_handle_request -> found reply record:"
	    %%   "~n   Rep: ~p", [Rep]),
	    
	    #conn_data{reply_timer = InitTimer,
		       conn_handle = ConnHandle} = ConnData,

	    %% Pending limit update:
	    %%   - Cancel the pending timer, if running
	    %%   - Delete the pending counter
	    %% 

	    _ = megaco_monitor:cancel_apply_after(PendingRef),
	    megaco_config:del_pending_counter(sent, TransId),

	    Method = timer_method(AckAction),
	    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
	    Garb = fun(Bin) -> opt_garb_binary(CurrTimer, Bin) end,
	    OptBins = [{SN, Garb(Bin), undefined} || {SN, Bin} <- Sent],

	    M   = ?MODULE,
	    F   = reply_timeout,
	    A   = [ConnHandle, TransId, CurrTimer],
	    Ref = megaco_monitor:apply_after(Method, M, F, A, WaitFor),

	    Rep2 = Rep#reply{pending_timer_ref = undefined,
			     handler           = undefined,
			     bytes             = OptBins,
			     state             = waiting_for_ack,
			     timer_ref         = Ref,
			     ack_action        = AckAction,
			     segments          = NotSent},
	    megaco_monitor:insert_reply(Rep2), % Timing problem?

	    ignore;
	_ ->
	    %% Been removed already?
	    ignore
    end;
do_handle_request(_, {error, aborted}, ConnData, TransId) ->
    ?report_trace(ConnData, "aborted during our absence", [TransId]),
    case lookup_reply(ConnData, TransId) of
	{_Converted, Rep} ->
	    cancel_reply(ConnData, Rep, aborted);
	_ ->
	    ok
    end,
    ignore;
do_handle_request(AckAction, {error, Reason}, ConnData, TransId) ->
    ?report_trace(ConnData, "error", [TransId, Reason]),
    case lookup_reply(ConnData, TransId) of
	{_Converted, Rep} ->
	    Rep2 = Rep#reply{state      = waiting_for_ack,
			     ack_action = AckAction},
	    cancel_reply(ConnData, Rep2, Reason);
	_ ->
	    ok
    end,
    ignore;
do_handle_request(AckAction, SendReply, ConnData, TransId) ->
    ?report_trace(ConnData, "unknown send trans reply result", 
		  [TransId, AckAction, SendReply]),
    ignore.
    

handle_requests([{ConnData, TransId, T} | Rest], Pending, Extra) ->
    ?rt2("handle requests", [TransId]),
    case handle_request(ConnData, TransId, T, Extra) of
	{pending, RequestData} ->
	    handle_requests(Rest, [{ConnData,TransId,RequestData} | Pending], Extra);
	_ ->
	    handle_requests(Rest, Pending, Extra)
    end;
handle_requests([], Pending, _Extra) ->
    ?rt2("handle requests - done", [Pending]),
    Pending.

%% opt_garb_binary(timeout, _Bin) -> garb_binary; % Need msg at restart of timer
opt_garb_binary(_Timer,   Bin) -> Bin.

timer_method(discard_ack) ->
    apply_method;
timer_method(_) ->
    spawn_method.


handle_long_request({ConnData, TransId, RequestData}, Extra) ->

    ?rt2("handle long request", [TransId, RequestData]),

    %% Pending limit:
    %% We need to check the pending limit, in case it was
    %% exceeded before we got this far...
    %% We dont need to be able to create the counter here,
    %% since that was done in the handle_request function.

    #conn_data{sent_pending_limit = Limit} = ConnData,

    case check_pending_limit(Limit, sent, TransId) of
	{ok, _} ->
	    handle_long_request(ConnData, TransId, RequestData, Extra);
	_ ->
	    %% Already exceeded the limit
	    ignore
    end.

handle_long_request(ConnData, TransId, RequestData, Extra) ->
    ?report_trace(ConnData, "callback: trans long request",
		  [TransId, {request_data, RequestData}]),
    
    %% Attempt to update the handler field for this reply record
    %% (if there is one).
    case megaco_monitor:update_reply_field(TransId, #reply.handler, self()) of
	true ->
	    {AckAction, Res} = 
		handle_long_request_callback(ConnData, TransId, 
					     RequestData, Extra),
	    do_handle_long_request(AckAction, Res, ConnData, TransId);
	 false ->
	    %% Been removed already?
	    ignore
    end.


do_handle_long_request(AckAction, {ok, Bin}, ConnData, TransId) ->
    case megaco_monitor:lookup_reply_field(TransId, #reply.trans_id) of
	{ok, _} ->
	    Method = timer_method(AckAction),
	    InitTimer = ConnData#conn_data.reply_timer,
	    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
	    OptBin = opt_garb_binary(CurrTimer, Bin),
	    ConnHandle = ConnData#conn_data.conn_handle,
	    M = ?MODULE,
	    F = reply_timeout,
	    A = [ConnHandle, TransId, CurrTimer],
	    Ref = megaco_monitor:apply_after(Method, M, F, A, WaitFor),
	    NewFields = 
		[{#reply.bytes,      OptBin},
		 {#reply.state,      waiting_for_ack},
		 {#reply.timer_ref,  Ref},
		 {#reply.ack_action, AckAction}],
	    megaco_monitor:update_reply_fields(TransId, NewFields); % Timing problem?
	_ ->
	    %% Been removed already?
	    ignore
    end;
do_handle_long_request(_, {error, Reason}, ConnData, TransId) ->
    ?report_trace(ConnData, "send trans reply", [TransId, {error, Reason}]),
    ignore.
    
handle_request_abort_callback(ConnData, TransId, Pid) ->
    Extra = ?default_user_callback_extra, 
    handle_request_abort_callback(ConnData, TransId, Pid, Extra).

handle_request_abort_callback(ConnData, TransId, Pid, Extra) ->
    ?report_trace(ConnData, "callback: trans request aborted", [TransId, Pid]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Serial     = TransId#trans_id.serial,
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, Serial, Pid | UserArgs];
	    _ ->
		[ConnHandle, Version, Serial, Pid, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_trans_request_abort, Args)),
    ?report_debug(ConnData, "return: trans request aborted", 
		  [TransId, {return, Res}]),
    case Res of
	ok ->
	    ok;
	_ ->
	    warning_msg("transaction request abort callback failed: ~w", 
			[Res]),
	    ok
    end.

handle_request_callback(ConnData, TransId, Actions, T, Extra) ->
    ?report_trace(ConnData, "callback: trans request", [T]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, Actions | UserArgs];
	    _ ->
		[ConnHandle, Version, Actions, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_trans_request, Args)),
    ?report_debug(ConnData, "return: trans request", [T, {return, Res}]),
    case Res of
	ignore ->  %% NOTE: Only used for testing!!
	    {discard_ack, ignore};

	ignore_trans_request -> 
	    {discard_ack, ignore_trans_request};

	{discard_ack, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {discard_ack, SendReply};
	{discard_ack, Error} when is_record(Error, 'ErrorDescriptor') ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {discard_ack, SendReply};
	{discard_ack, Replies, SendOpts} when is_list(Replies) andalso 
					      is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, asn1_NOVALUE),
	    {discard_ack, SendReply};
	{discard_ack, Error, SendOpts} 
	when is_record(Error, 'ErrorDescriptor') andalso 
	     is_list(SendOpts) ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, asn1_NOVALUE),
	    {discard_ack, SendReply};

	{{handle_pending_ack, AckData}, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], when_pending_sent),
	    {{handle_ack, AckData}, SendReply};
	{{handle_pending_ack, AckData}, Error} 
	when is_record(Error, 'ErrorDescriptor') ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], when_pending_sent),
	    {{handle_ack, AckData}, SendReply};
	{{handle_pending_ack, AckData}, Replies, SendOpts} 
	when is_list(Replies) andalso 
	     is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, when_pending_sent),
	    {{handle_ack, AckData}, SendReply};
	{{handle_pending_ack, AckData}, Error, SendOpts} 
	when is_record(Error, 'ErrorDescriptor') andalso 
	     is_list(SendOpts) ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, when_pending_sent),
	    {{handle_ack, AckData}, SendReply};

	{{handle_ack, AckData}, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], 'NULL'),
	    {{handle_ack, AckData}, SendReply};
	{{handle_ack, AckData}, Error} 
	when is_record(Error, 'ErrorDescriptor') ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], 'NULL'),
	    {{handle_ack, AckData}, SendReply};
	{{handle_ack, AckData}, Replies, SendOpts} 
	when is_list(Replies) andalso 
	     is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, 'NULL'),
	    {{handle_ack, AckData}, SendReply};
	{{handle_ack, AckData}, Error, SendOpts} 
	when is_record(Error, 'ErrorDescriptor') andalso 
	     is_list(SendOpts) ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, 'NULL'),
	    {{handle_ack, AckData}, SendReply};

	{{handle_sloppy_ack, AckData}, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {{handle_ack, AckData}, SendReply};
	{{handle_sloppy_ack, AckData}, Error} 
	when is_record(Error, 'ErrorDescriptor') ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {{handle_ack, AckData}, SendReply};
	{{handle_sloppy_ack, AckData}, Replies, SendOpts} 
	when is_list(Replies) andalso 
	     is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, asn1_NOVALUE),
	    {{handle_ack, AckData}, SendReply};
	{{handle_sloppy_ack, AckData}, Error, SendOpts} 
	when is_record(Error, 'ErrorDescriptor') andalso 
	     is_list(SendOpts) ->
	    Reply     = {transactionError, Error},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, asn1_NOVALUE),
	    {{handle_ack, AckData}, SendReply};
	
	{pending, RequestData} ->
	    %% The user thinks that this request will take
	    %% quite a while to evaluate. Maybe respond with 
	    %% a pending trans (depends on the pending limit)
	    SendReply = maybe_send_pending(ConnData, TransId),
	    {{pending, RequestData}, SendReply};
	
	Error ->
	    ErrorText = atom_to_list(UserMod),
	    ED = #'ErrorDescriptor'{
	      errorCode = ?megaco_internal_gateway_error,
	      errorText = ErrorText},
	    ?report_important(ConnData, 
			      "callback: <ERROR> trans request",
			      [ED, {error, Error}]),
	    error_msg("transaction request callback failed: ~w", [Error]),
	    Reply = {transactionError, ED},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {discard_ack, SendReply}
    end.

handle_long_request_callback(ConnData, TransId, RequestData, Extra) ->
    ?report_trace(ConnData, "callback: trans long request", [RequestData]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, RequestData | UserArgs];
	    _ ->
		[ConnHandle, Version, RequestData, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_trans_long_request, Args)),
    ?report_debug(ConnData, "return: trans long request",
		  [{request_data, RequestData}, {return, Res}]),
    case Res of
	ignore ->
	    {discard_ack, ignore};
	
	{discard_ack, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {discard_ack, SendReply};
	{discard_ack, Replies, SendOpts} when is_list(Replies) andalso 
					      is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, asn1_NOVALUE),
	    {discard_ack, SendReply};
	
	{{handle_ack, AckData}, Replies} when is_list(Replies) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], 'NULL'),
	    {{handle_ack, AckData}, SendReply};
	{{handle_ack, AckData}, Replies, SendOpts} when is_list(Replies) 
							andalso 
							is_list(SendOpts) ->
	    Reply     = {actionReplies, Replies},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 SendOpts, 'NULL'),
	    {{handle_ack, AckData}, SendReply};
	
	Error ->
	    ErrorText = atom_to_list(UserMod),
	    ED = #'ErrorDescriptor'{errorCode = ?megaco_internal_gateway_error,
				    errorText = ErrorText},
	    ?report_important(ConnData, "callback: <ERROR> trans long request",
			      [ED, {error, Error}]),
	    error_msg("long transaction request callback failed: ~w", [Error]),
	    Reply     = {transactionError, ED},
	    SendReply = maybe_send_reply(ConnData, TransId, Reply, 
					 [], asn1_NOVALUE),
	    {discard_ack, SendReply}
    end.

handle_pending(ConnData, T, Extra) ->
    TransId = to_local_trans_id(ConnData),
    ?rt2("handle pending", [T, TransId]),
    case megaco_monitor:lookup_request(TransId) of
	[Req] ->

	    %% ------------------------------------------
	    %% 
	    %%   Check received pending limit
	    %% 
	    %% ------------------------------------------

	    Limit = ConnData#conn_data.recv_pending_limit,
	    case check_and_maybe_incr_pending_limit(Limit, 
						    recv, TransId) of
		
		ok ->
		    %% ----------------------------------------------------
		    %% 
		    %%      Received pending limit not exceeded     
		    %% 
		    %% ----------------------------------------------------

		    handle_recv_pending(ConnData, TransId, Req, T);
		
		error ->
		    %% ----------------------------------------------------
		    %% 
		    %%      Received pending limit exceeded     
		    %% 
		    %%      Time to give up on this transaction
		    %%      1) Delete request record
		    %%      2) Cancel timers
		    %%      3) Delete the (receive) pending counter
		    %%      4) Inform the user (handle_trans_reply)
		    %% 
		    %% ----------------------------------------------------

		    handle_recv_pending_error(ConnData, TransId, Req, T, Extra);

		
		aborted ->
		    %% ----------------------------------------------------
		    %% 
		    %%      Received pending limit already exceeded     
		    %% 
		    %% BMK BMK BMK -- can this really happen?
		    %% 
		    %%      The user has already been notified about this
		    %%      (see error above)
		    %% 
		    %% ----------------------------------------------------

		    ok

	    end;

	[] ->
	    ?report_trace(ConnData, "remote pending (no receiver)", [T]),
	    return_unexpected_trans(ConnData, T, Extra)
    end.

handle_recv_pending(#conn_data{long_request_resend = LRR,
			       conn_handle         = ConnHandle} = ConnData, 
		    TransId,
		    #request{timer_ref       = {short, Ref},
			     init_long_timer = InitTimer}, T) ->

    ?rt2("handle pending - long request", [LRR, InitTimer]),

    %% The request seems to take a while,
    %% let's reset our transmission timer.
    %% We now know the other side has got
    %% the request and is working on it,
    %% so there is no need to keep the binary
    %% message for re-transmission.
    
    %% Start using the long timer. 
    %% We can now drop the "bytes", since we will
    %% not resend from now on.
    
    _ = megaco_monitor:cancel_apply_after(Ref),
    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
    ConnHandle = ConnData#conn_data.conn_handle,
    M = ?MODULE,
    F = request_timeout,
    A = [ConnHandle, TransId],
    Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor),
    NewFields = 
	case LRR of
	    true ->
		[{#request.timer_ref,  {long, Ref2}},
		 {#request.curr_timer, CurrTimer}];
	    false ->
		[{#request.bytes,      {no_send, garb_binary}}, 
		 {#request.timer_ref,  {long, Ref2}},
		 {#request.curr_timer, CurrTimer}]
	end,
    ?report_trace(ConnData, "trans pending (timer restarted)", [T]),
    megaco_monitor:update_request_fields(TransId, NewFields); % Timing problem?
    
handle_recv_pending(_ConnData, _TransId,
		    #request{timer_ref  = {long, _Ref},
			     curr_timer = timeout}, _T) ->

    ?rt3("handle pending - timeout"),

    %% The request seems to take a while,
    %% let's reset our transmission timer.
    %% We now know the other side has got
    %% the request and is working on it,
    %% so there is no need to keep the binary
    %% message for re-transmission.
    
    %% This can happen if the timer is running for the last 
    %% time. I.e. next time it expires, will be the last.
    %% Therefor we really do not need to do anything here.
    %% The cleanup will be done in request_timeout.
    
    ok;

handle_recv_pending(#conn_data{conn_handle = ConnHandle} = ConnData, TransId,
		    #request{timer_ref  = {long, Ref},
			     curr_timer = CurrTimer}, T) ->
    
    ?rt2("handle pending - still waiting", [CurrTimer]),

    %% The request seems to take a while,
    %% let's reset our transmission timer.
    %% We now know the other side has got
    %% the request and is working on it,
    %% so there is no need to keep the binary
    %% message for re-transmission.
    
    %% We just need to recalculate the timer, i.e. 
    %% increment the timer (one "slot" has been consumed).
    
    _ = megaco_monitor:cancel_apply_after(Ref),
    {WaitFor, Timer2} = megaco_timer:restart(CurrTimer),
    ConnHandle = ConnData#conn_data.conn_handle,
    M = ?MODULE,
    F = request_timeout,
    A = [ConnHandle, TransId],
    Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor),
    NewFields = 
	[{#request.timer_ref,  {long, Ref2}},
	 {#request.curr_timer, Timer2}],
    ?report_trace(ConnData, 
		  "long trans pending"
		  " (timer restarted)", [T]),
    %% Timing problem?
    megaco_monitor:update_request_fields(TransId, NewFields).
    

handle_recv_pending_error(ConnData, TransId, Req, T, Extra) ->
    %% 1) Delete the request record
    megaco_monitor:delete_request(TransId),

    %% 2) Possibly cancel the timer
    _ = case Req#request.timer_ref of
            {_, Ref} ->
                megaco_monitor:cancel_apply_after(Ref);
            _ ->
                ok
        end,
    
    %% 3) Delete the (receive) pending counter
    megaco_config:del_pending_counter(recv, TransId),
    
    %% 4) Inform the user that his/her request reached 
    %%    the receive pending limit
    UserMod   = Req#request.user_mod,
    UserArgs  = Req#request.user_args,
    Action    = Req#request.reply_action,
    UserData  = Req#request.reply_data,
    UserReply = {error, exceeded_recv_pending_limit},
    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
				   user_args    = UserArgs,
				   reply_action = Action,
				   reply_data   = UserData},

    ?report_trace(ConnData, "receive pending limit reached", [T]),
    return_reply(ConnData2, TransId, UserReply, Extra).


%% 
%% This _is_ a segmented message.
%% 
%% Since this is not the last segment, we shall not send any ack.
%% (even if three-way-handshake has been configured).
%% 
handle_reply(
  ConnData, 
  #megaco_transaction_reply{segmentNumber        = SN,
			    segmentationComplete = asn1_NOVALUE} = T, Extra) 
  when is_integer(SN) ->
    TransId = to_local_trans_id(ConnData),
    ?rt2("handle segmented reply", [T, TransId, SN]),
    case megaco_monitor:lookup_request(TransId) of

	%% ---------------------------------------------------------
	%% The first segment, so stop the request timer. No longer
	%% needed when the segment(s) start to arrive.

	[#request{timer_ref     = {_Type, Ref},
		  seg_recv      = [],
		  seg_timer_ref = undefined} = Req] -> 

            %% Don't care about Req and Rep version diff
            ?report_trace(ConnData, "[segmented] trans reply - first seg", 
			  [T]),

	    %% Stop the request timer
            _ = megaco_monitor:cancel_apply_after(Ref), %% OTP-4843

	    %% Acknowledge the segment
	    _ = send_segment_reply(ConnData, SN),

	    %% First segment for this reply
	    NewFields = 
		[{#request.timer_ref, undefined},
		 {#request.seg_recv,  [SN]}],
	    megaco_monitor:update_request_fields(TransId, NewFields),
	    
	    %% Handle the reply
	    UserMod   = Req#request.user_mod,
	    UserArgs  = Req#request.user_args,
	    Action    = Req#request.reply_action,
	    UserData  = Req#request.reply_data,
	    UserReply =
		case T#megaco_transaction_reply.transactionResult of
		    {transactionError, Reason} ->
			{error, {SN, false, Reason}};
		    {actionReplies, Replies} ->
			{ok, {SN, false, Replies}}
		end,
	    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
					   user_args    = UserArgs,
					   reply_action = Action,
					   reply_data   = UserData},
	    return_reply(ConnData2, TransId, UserReply, Extra);


	%% ---------------------------------------------------------
	%% This is not the first segment.
	%% The segment timer has not been started, so the last
	%% segment have been received.
	%% We must check that this is not a re-transmission!

	[#request{seg_recv      = Segs,
		  seg_timer_ref = undefined} = Req] -> 
            %% Don't care about Req and Rep version diff
            ?report_trace(ConnData, "[segmented] trans reply - no seg acc", 
			  [T]),

	    %% Acknowledge the segment
	    _ = send_segment_reply(ConnData, SN),

	    %% Updated/handle received segment
	    case lists:member(SN, Segs) of
		true ->
		    %% This is a re-transmission, so we shall not pass
		    %% it on to the user (or update the request record).
		    ok;
		false ->
		    %% First time for this segment
		    megaco_monitor:update_request_field(TransId,
							#request.seg_recv,
							[ SN | Segs ]),

		    %% Handle the reply
		    UserMod   = Req#request.user_mod,
		    UserArgs  = Req#request.user_args,
		    Action    = Req#request.reply_action,
		    UserData  = Req#request.reply_data,
		    UserReply =
			case T#megaco_transaction_reply.transactionResult of
			    {transactionError, Reason} ->
				{error, {SN, false, Reason}};
			    {actionReplies, Replies} ->
				{ok, {SN, false, Replies}}
			end,
		    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
						   user_args    = UserArgs,
						   reply_action = Action,
						   reply_data   = UserData},
		    return_reply(ConnData2, TransId, UserReply, Extra)
		
		end;


	%% ---------------------------------------------------------
	%% The segment timer is running! 
	%% This could be the last (out-of-order) segment!
	%% We must check that this is not a re-transmission!

	[#request{seg_recv      = Segs,
		  seg_timer_ref = SegRef} = Req] -> 
            %% Don't care about Req and Rep version diff
            ?report_trace(ConnData, "[segmented] trans reply - no seg acc", 
			  [T]),

	    %% Acknowledge the segment
	    _ = send_segment_reply(ConnData, SN),

	    %% Updated received segments
	    case lists:member(SN, Segs) of 
		true ->
		    %% This is a re-transmission
		    ok;
		false ->
		    %% First time for this segment, 
		    %% we may now have a complete set
		    Last = 
			case is_all_segments([SN | Segs]) of
			    {true, _Sorted} ->
				_ = megaco_monitor:cancel_apply_after(SegRef),
				megaco_monitor:delete_request(TransId),
				_ = send_ack(ConnData),
				true;
			    {false, Sorted} ->
				megaco_monitor:update_request_field(TransId,
								    #request.seg_recv,
								    Sorted),
				false
			end,

		    %% Handle the reply
		    UserMod   = Req#request.user_mod,
		    UserArgs  = Req#request.user_args,
		    Action    = Req#request.reply_action,
		    UserData  = Req#request.reply_data,
		    UserReply =
			case T#megaco_transaction_reply.transactionResult of
			    {transactionError, Reason} ->
				{error, {SN, Last, Reason}};
			    {actionReplies, Replies} ->
				{ok, {SN, Last, Replies}}
			end,
		    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
						   user_args    = UserArgs,
						   reply_action = Action,
						   reply_data   = UserData},
		    return_reply(ConnData2, TransId, UserReply, Extra)
		
	    end;
	    

	[] ->
	    ?report_trace(ConnData, "trans reply (no receiver)", [T]),
	    return_unexpected_trans(ConnData, T, Extra)
    end;


%% 
%% This _is_ a segmented message and it's the last segment of the
%% message.
%% 
handle_reply(
  ConnData, 
  #megaco_transaction_reply{segmentNumber        = SN,
			    segmentationComplete = 'NULL'} = T, Extra) 
  when is_integer(SN) ->
    TransId = to_local_trans_id(ConnData),
    ?rt2("handle (last) segmented reply", [T, TransId, SN]),
    case megaco_monitor:lookup_request(TransId) of

	%% ---------------------------------------------------------
	%% The first segment, so stop the request timer. No longer
	%% needed when the segment(s) start to arrive.

	[#request{timer_ref     = {_Type, Ref},
		  seg_recv      = [],
		  seg_timer_ref = undefined} = Req] -> 

            %% Don't care about Req and Rep version diff
            ?report_trace(ConnData, "[segmented] trans reply - "
			  "first/complete seg", [T]),

	    %% Stop the request timer
            _ = megaco_monitor:cancel_apply_after(Ref), %% OTP-4843

	    %% Acknowledge the ("last") segment
	    _ = send_segment_reply_complete(ConnData, SN),

	    %% It is ofcourse pointless to split
	    %% a transaction into just one segment,
	    %% but just to be sure, we handle that 
	    %% case also 
	    Last = 
		if 
		    SN > 1 ->
			%% More then one segment
			%% First time for this segment
			ConnHandle = ConnData#conn_data.conn_handle,
			InitSegTmr = Req#request.init_seg_timer,
			{WaitFor, CurrTimer} = megaco_timer:init(InitSegTmr),
			M = ?MODULE,
			F = segment_timeout,
			A = [ConnHandle, TransId, CurrTimer],
			SegRef = 
			    megaco_monitor:apply_after(M, F, A, WaitFor),
			NewFields = 
			    [{#request.timer_ref,     undefined},
			     {#request.seg_recv,      [SN]},
			     {#request.seg_timer_ref, SegRef}],
			megaco_monitor:update_request_fields(TransId, NewFields),
			false;
		    true ->
			%% Just one segment!
			megaco_monitor:delete_request(TransId),
			_ = send_ack(ConnData),
			true
		end,
	    
	    %% Handle the reply
	    UserMod   = Req#request.user_mod,
	    UserArgs  = Req#request.user_args,
	    Action    = Req#request.reply_action,
	    UserData  = Req#request.reply_data,
	    UserReply =
		case T#megaco_transaction_reply.transactionResult of
		    {transactionError, Reason} ->
			{error, {SN, Last, Reason}};
		    {actionReplies, Replies} ->
			{ok, {SN, Last, Replies}}
		end,
	    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
					   user_args    = UserArgs,
					   reply_action = Action,
					   reply_data   = UserData},
	    return_reply(ConnData2, TransId, UserReply, Extra);


	[#request{seg_recv  = Segs} = Req] -> 
            %% Don't care about Req and Rep version diff
            ?report_trace(ConnData, "[segmented] trans reply - no seg acc", 
			  [T]),

	    %% Acknowledge the ("last") segment
	    _ = send_segment_reply_complete(ConnData, SN),

	    %% Updated received segments
	    %% This is _probably_ the last segment, but some of
	    %% the previous segments may have been lost, so we 
	    %% may not have a complete set!
	    case lists:member(SN, Segs) of 
		true ->
		    %% This is a re-transmission
		    ok;
		false ->
		    Last = 
			case is_all_segments([SN | Segs]) of
			    {true, _Sorted} ->
				?report_trace(ConnData, 
					      "[segmented] trans reply - "
					      "complete set", [T]),
				megaco_monitor:delete_request(TransId),
				_ = send_ack(ConnData),
				true;
			    {false, Sorted} ->
				ConnHandle = ConnData#conn_data.conn_handle,
				InitSegTmr = Req#request.init_seg_timer,
				{WaitFor, CurrTimer} = 
				    megaco_timer:init(InitSegTmr),
				M = ?MODULE,
				F = segment_timeout,
				A = [ConnHandle, TransId, CurrTimer],
				SegRef = 
				    megaco_monitor:apply_after(M, F, A, 
							       WaitFor),
				NewFields = 
				    [{#request.seg_recv,      Sorted},
				     {#request.seg_timer_ref, SegRef}],
				megaco_monitor:update_request_fields(TransId, NewFields),
				false
			end,
		    
		    %% Handle the reply
		    UserMod   = Req#request.user_mod,
		    UserArgs  = Req#request.user_args,
		    Action    = Req#request.reply_action,
		    UserData  = Req#request.reply_data,
		    UserReply =
			case T#megaco_transaction_reply.transactionResult of
			    {transactionError, Reason} ->
				{error, {SN, Last, Reason}};
			    {actionReplies, Replies} ->
				{ok, {SN, Last, Replies}}
			end,
		    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
						   user_args    = UserArgs,
						   reply_action = Action,
						   reply_data   = UserData},
		    return_reply(ConnData2, TransId, UserReply, Extra)

	    end;

	[] ->
	    ?report_trace(ConnData, "trans reply (no receiver)", [T]),
	    return_unexpected_trans(ConnData, T, Extra)
    end;


%% 
%% This is _not_ a segmented message, 
%% i.e. it's an ordinary transaction reply
%% 
handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) ->
    TransId = to_local_trans_id(CD),
    ?rt2("handle reply", [T, TransId]),
    case {megaco_monitor:request_lockcnt_inc(TransId),
	  megaco_monitor:lookup_request(TransId)} of
	{_Cnt, [Req]} when (is_record(Req, request) andalso 
			    (CD#conn_data.cancel =:= true)) -> 
	    ?TC_AWAIT_REPLY_EVENT(true),
	    ?report_trace(CD, "trans reply - cancel(1)", [T]),
	    do_handle_reply_cancel(CD, Req, T);

	{Cnt, [#request{remote_mid = RMid} = Req]} when 
	((Cnt =:= 1) andalso 
	 ((RMid =:= preliminary_mid) orelse 
	  (RMid =:= CH#megaco_conn_handle.remote_mid))) -> 
	    ?TC_AWAIT_REPLY_EVENT(false),
	    %% Just in case conn_data got update after our lookup
	    %% but before we looked up the request record, we
	    %% check the cancel field again.
	    case megaco_config:conn_info(CD, cancel) of
		true ->
		    ?report_trace(CD, "trans reply - cancel(2)", [T]),
		    megaco_monitor:request_lockcnt_del(TransId), 
		    do_handle_reply_cancel(CD, Req, T);
		false ->
		    ?report_trace(CD, "trans reply", [T]),
		    do_handle_reply(CD, Req, TransId, T, Extra)
	    end;

	{Cnt, [#request{remote_mid = RMid} = _Req]} when 
	(is_integer(Cnt) andalso 
	 ((RMid =:= preliminary_mid) orelse 
	  (RMid =:= CH#megaco_conn_handle.remote_mid))) -> 
	    ?TC_AWAIT_REPLY_EVENT(false),
	    %% Ok, someone got there before me, now what?
	    %% This is a plain old raise condition
	    ?report_important(CD, "trans reply - raise condition", 
			      [T, {request_lockcnt, Cnt}]),
	    megaco_monitor:request_lockcnt_dec(TransId);

	%% no counter
	{_Cnt, [#request{remote_mid = RMid} = Req]} when 
	 ((RMid =:= preliminary_mid) orelse 
	  (RMid =:= CH#megaco_conn_handle.remote_mid)) -> 
	    ?TC_AWAIT_REPLY_EVENT(false),
	    %% The counter does not exist. 
	    %% This can only mean a code upgrade raise condition.
	    %% That is, this request record was created before 
	    %% this feature (the counters) was instroduced.
	    %% The simples solution is this is to behave exactly as
	    %% before, that is pass it along, and leave it to the 
	    %% user to figure out.

	    %% Just in case conn_data got update after our lookup
	    %% but before we looked up the request record, we
	    %% check the cancel field again.
	    ?report_verbose(CD, "trans reply - old style", [T]),
	    case megaco_config:conn_info(CD, cancel) of
		true ->
		    megaco_monitor:request_lockcnt_del(TransId), 
		    do_handle_reply_cancel(CD, Req, T);
		false ->
		    do_handle_reply(CD, Req, TransId, T, Extra)
	    end;

	{Cnt, [#request{user_mod     = UserMod,
			user_args    = UserArgs, 
			reply_action = Action, 
			reply_data   = UserData,
			remote_mid   = RMid}]} ->
	    ?report_trace(CD, 
			  "received trans reply with invalid remote mid", 
			  [{transaction,     T}, 
			   {remote_mid,      RMid}, 
			   {request_lockcnt, Cnt}]),
	    if
		is_integer(Cnt) ->
		    megaco_monitor:request_lockcnt_dec(TransId);
		true ->
		    ok
	    end,
	    WrongMid = CH#megaco_conn_handle.remote_mid,
	    T2 = transform_transaction_reply_enc(CD#conn_data.protocol_version,
						 T),
	    UserReply = {error, {wrong_mid, WrongMid, RMid, T2}}, 
	    CD2 = CD#conn_data{user_mod     = UserMod,
			       user_args    = UserArgs,
			       reply_action = Action,
			       reply_data   = UserData},
	    return_reply(CD2, TransId, UserReply, Extra);

	{Cnt, []} when is_integer(Cnt) ->
	    ?TC_AWAIT_REPLY_EVENT(undefined),
	    ?report_trace(CD, "trans reply (no receiver)", 
			  [T, {request_lockcnt, Cnt}]),
	    megaco_monitor:request_lockcnt_dec(TransId),
	    return_unexpected_trans(CD, T, Extra);

	%% No counter
	{_Cnt, []} -> 
	    ?TC_AWAIT_REPLY_EVENT(undefined),
	    ?report_trace(CD, "trans reply (no receiver)", [T]),
	    return_unexpected_trans(CD, T, Extra)
    end.

do_handle_reply_cancel(CD, #request{user_mod     = UserMod,
				    user_args    = UserArgs, 
				    reply_action = Action, 
				    reply_data   = UserData}, T) ->
    CD2 = CD#conn_data{user_mod     = UserMod,
		       user_args    = UserArgs,
		       reply_action = Action,
		       reply_data   = UserData},
    return_unexpected_trans(CD2, T).

%% Plain old handling of incomming replies
do_handle_reply(CD, 
		#request{timer_ref        = {_Type, Ref}, % OTP-4843
			 user_mod         = UserMod,
			 user_args        = UserArgs, 
			 reply_action     = Action, 
			 reply_data       = UserData,
			 keep_alive_timer = RKAT}, 
		TransId, T, Extra) 
  when ((RKAT =:= plain) orelse (Action =:= call)) ->
    %% Don't care about Req and Rep version diff
    ?report_trace(CD, "trans reply", [T]),

    %% This is the first reply (maybe of many)
    megaco_monitor:delete_request(TransId),
    megaco_monitor:request_lockcnt_del(TransId), 
    _ = megaco_monitor:cancel_apply_after(Ref),           % OTP-4843
    megaco_config:del_pending_counter(recv, TransId), % OTP-7189

    %% Send acknowledgement
    _ = maybe_send_ack(T#megaco_transaction_reply.immAckRequired, CD),

    UserReply =
	case T#megaco_transaction_reply.transactionResult of
	    {transactionError, Reason} ->
		{error, Reason};
	    {actionReplies, Replies} ->
		{ok, Replies}
	end,
    CD2 = CD#conn_data{user_mod     = UserMod,
		       user_args    = UserArgs,
		       reply_action = Action,
		       reply_data   = UserData},
    return_reply(CD2, TransId, UserReply, Extra);

%% This may be the first reply (of maybe many) 
do_handle_reply(CD, 
		#request{user_mod       = UserMod,
			 user_args      = UserArgs, 
			 reply_action   = Action, 
			 reply_data     = UserData,
			 keep_alive_ref = undefined} = Req, 
		TransId, T, Extra) ->
    %% Don't care about Req and Rep version diff
    ?report_trace(CD, "trans reply", [T]),

    %% Could be the first reply, in which case we shall start the 
    %% Request Keep Alive timer...
    %% This could happen for more than one (1) reply though, so
    %% we need to check if the counter value actually equals one (1)!
    
    ReplyNo = 
	create_or_maybe_increment_request_keep_alive_counter(CD, TransId), 
    if 
	(ReplyNo =:= 1) ->
	    %% This *is* the first reply!!
	    %% 1) Stop resend timer
	    {_Type, Ref} = Req#request.timer_ref,             % OTP-4843
	    _ = megaco_monitor:cancel_apply_after(Ref),           % OTP-4843
	
	    %% 2) Delete pending counter
	    megaco_config:del_pending_counter(recv, TransId), % OTP-7189
	    
	    %% 3) Start request keep alive timer
	    ConnHandle = CD#conn_data.conn_handle, 
	    RKATimer   = Req#request.keep_alive_timer, 
	    {RKAWaitFor, _} = megaco_timer:init(RKATimer),
	    RKARef = megaco_monitor:apply_after(?MODULE, 
						request_keep_alive_timeout, 
						[ConnHandle, TransId], 
						RKAWaitFor), 

	    %% 4) Maybe send acknowledgement (three-way-handshake)
	    _ = maybe_send_ack(T#megaco_transaction_reply.immAckRequired, CD),

	    %% 5) And finally store the updated request record
	    Req2 = Req#request{keep_alive_ref = RKARef},
	    megaco_monitor:insert_request(Req2);

	true ->
	    ok
    end,

    UserReply =
	case T#megaco_transaction_reply.transactionResult of
	    {transactionError, Reason} ->
		{error, ReplyNo, Reason};
	    {actionReplies, Replies} ->
		{ok, ReplyNo, Replies}
	end,
    CD2 = CD#conn_data{user_mod     = UserMod,
		       user_args    = UserArgs,
		       reply_action = Action,
		       reply_data   = UserData},
    return_reply(CD2, TransId, UserReply, Extra);

%% This is *not* the first reply (of many) 
do_handle_reply(CD, #request{user_mod     = UserMod,
			     user_args    = UserArgs, 
			     reply_action = Action, 
			     reply_data   = UserData}, TransId, T, Extra) ->
    %% Don't care about Req and Rep version diff
    ?report_trace(CD, "trans reply (first reply already delivered)", [T]),

    ReplyNo = increment_request_keep_alive_counter(CD, TransId), 

    UserReply =
	case T#megaco_transaction_reply.transactionResult of
	    {transactionError, Reason} ->
		{error, ReplyNo, Reason};
	    {actionReplies, Replies} ->
		{ok, ReplyNo, Replies}
	end,
    CD2 = CD#conn_data{user_mod     = UserMod,
		       user_args    = UserArgs,
		       reply_action = Action,
		       reply_data   = UserData},
    return_reply(CD2, TransId, UserReply, Extra).

is_all_segments(Segs) ->
    Sorted = lists:sort(Segs),
    {is_all_segments(Sorted, 1, lists:last(Sorted)), Sorted}.

is_all_segments([Last], Last, Last) ->
    true;
is_all_segments([_], _, _) ->
    false;
is_all_segments([SN|Segs], SN, Last) when (SN < Last) ->
    is_all_segments(Segs, SN+1, Last);
is_all_segments([SN1|_], SN2, _Last) when SN1 =/= SN2 ->
    false.


handle_segment_reply(CD, 
		     #'SegmentReply'{transactionId        = TransId,
				     segmentNumber        = SN, 
				     segmentationComplete = SC}, Extra) ->
    ?rt2("handle segment reply", [{trans_id, TransId}, 
				  {segment_no, SN}, 
				  {segmentation_complete, SC}]),
    TransId2 = to_remote_trans_id(CD#conn_data{serial = TransId}),
    case lookup_reply(CD, TransId2) of
	{_Converted, 
	 #reply{bytes    = Sent,
		segments = []} = Rep} when is_list(Sent) ->
	    ?rt2("no unsent segments", [Sent]),
	    handle_segment_reply_callback(CD, TransId, SN, SC, Extra),
	    case lists:keysearch(SN, 1, Sent) of
		{value, {SN, _Bin, SegTmr}} ->
		    _ = megaco_monitor:cancel_apply_after(SegTmr), %% BMK BMK
		    case lists:keydelete(SN, 1, Sent) of
			[] ->  %% We are done
			    Ref = Rep#reply.timer_ref, 
			    _ = megaco_monitor:cancel_apply_after(Ref),
			    megaco_monitor:update_reply_field(TransId2, 
							      #reply.bytes,
							      []),
			    ok;
			NewSent ->
			    megaco_monitor:update_reply_field(TransId2, 
							      #reply.bytes,
							      NewSent),
			    ok
		    end;
		_ ->
		    ok
	    end;
		
	{_Converted,
	 #reply{bytes    = Sent,
		segments = NotSent}} when is_list(Sent) andalso 
					  is_list(NotSent) ->
	    ?rt2("unsent segments", [Sent, NotSent]),
	    handle_segment_reply_callback(CD, TransId, SN, SC, Extra),
	    case lists:keysearch(SN, 1, Sent) of
		{value, {SN, _Bin, SegTmr}} ->
		    _ = megaco_monitor:cancel_apply_after(SegTmr), %% BMK BMK
		    NewSent = lists:keydelete(SN, 1, Sent),
		    [{SN2, Bin2}|NewNotSent] = NotSent,
		    case send_reply_segment(CD, "send trans reply segment", 
					    SN2, Bin2) of
			{ok, Bin3} ->
			    ?rt2("another segment sent", [Bin3]),
			    NewSent2 = [{SN2, Bin3, undefined}|NewSent],
			    NewFields = 
				[{#reply.bytes,    NewSent2},
				 {#reply.segments, NewNotSent}],
			    megaco_monitor:update_reply_fields(TransId2, 
							       NewFields),
			    ok;
			Error ->
			    incNumErrors(CD#conn_data.conn_handle),
			    ?report_important(CD, "failed sending segment", 
					      [{segment_no, SN2}, 
					       {error,      Error}]),
			    error_msg("failed sending transaction reply [~w] "
				      "segment [~w]: ~w", 
				      [TransId, SN2, Error]),
			    megaco_monitor:update_reply_field(TransId2, 
							      #reply.bytes,
							      NewSent),
			    ok
		    end;
		_ ->
		    ok
	    end;

	{_Converted,
	 #reply{state = State}} ->
	    %% We received a segment reply for a segmented reply we have 
	    %% not yet sent? This is either some sort of race condition 
	    %% or the "the other side" is really confused.
	    %% Ignore the message but issue a warning just in case...
	    warning_msg("received unexpected segment reply: "
			"~n   Transaction Id:        ~p"
			"~n   Segment Number:        ~p"
			"~n   Segmentation Complete: ~p"
			"~n   Reply state:           ~p", 
			[TransId2, SN, SC, State]),
	    ignore;

	[] ->
	    ignore

    end.
    

%% 
%% This should be passed on to the user only if the user wish it
%% (sri = segment reply indication)
%% 
handle_segment_reply_callback(#conn_data{segment_reply_ind = true,
					 conn_handle       = ConnHandle,
					 protocol_version  = Version,
					 user_mod          = UserMod,
					 user_args         = UserArgs},
			      TransId, SN, SC, Extra) ->
    Args = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, TransId, SN, SC | UserArgs];
	    _ ->
		[ConnHandle, Version, TransId, SN, SC, Extra | UserArgs]
	end,
    (catch apply(UserMod, handle_segment_reply, Args));
handle_segment_reply_callback(_CD, _TransId, _SN, _SC, _Extra) ->
    ok.


handle_acks([{ConnData, Rep, T} | Rest], Extra)
  when Rep#reply.state == waiting_for_ack ->
    handle_ack(ConnData, ok, Rep, T, Extra),
    handle_acks(Rest, Extra);
handle_acks([], _Extra) ->
    ok.

%% If the reply to which this is the ack was segmented,
%% then we also need to check that we have received all 
%% the segment-replies. If not, an error callback call 
%% shall be made instead.
handle_ack(ConnData, AckStatus, 
	   #reply{trans_id          = TransId,
		  bytes             = Bytes, 
		  timer_ref         = ReplyRef,
		  pending_timer_ref = PendingRef,  %% BMK Still running?
		  ack_action        = AckAction}, T, Extra) 
  when is_binary(Bytes) orelse (Bytes =:= undefined) ->
    handle_ack_cleanup(TransId, ReplyRef, PendingRef),
    handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra);

handle_ack(ConnData, AckStatus, 
	   #reply{trans_id          = TransId,
		  bytes             = [], 
		  segments          = [], 
		  timer_ref         = ReplyRef,
		  pending_timer_ref = PendingRef,  %% BMK Still running?
		  ack_action        = AckAction}, T, Extra) ->
    handle_ack_cleanup(TransId, ReplyRef, PendingRef),
    handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra);

handle_ack(ConnData, OrigAckStatus, 
	   #reply{trans_id          = TransId,
		  bytes             = SegSent, 
		  segments          = NotSent, 
		  timer_ref         = ReplyRef,
		  pending_timer_ref = PendingRef,  %% BMK Still running?
		  ack_action        = OrigAckAction}, T, Extra) 
  when is_list(SegSent) andalso is_list(NotSent) ->
    SN_NotAcked = [SN || {SN, _, _} <- SegSent],
    SN_NotSent  = [SN || {SN, _}    <- NotSent],
    AckStatus   = {error, {segment_failure, 
			   [{original_ack_status, OrigAckStatus},
			    {segments_not_acked,  SN_NotAcked},
			    {segments_not_sent,   SN_NotSent}]}},
    AckAction   = 
	case OrigAckAction of
	    {handle_ack, _} ->
		OrigAckAction;
	    _ ->
		{handle_ack, segmented_reply} 
	end,
    cancel_segment_timers(SegSent),
    handle_ack_cleanup(TransId, ReplyRef, PendingRef),
    handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra).

handle_ack_cleanup(TransId, ReplyRef, PendingRef) ->
    _ = megaco_monitor:cancel_apply_after(ReplyRef),
    _ = megaco_monitor:cancel_apply_after(PendingRef),
    megaco_monitor:delete_reply(TransId),
    megaco_config:del_pending_counter(sent, TransId). %% BMK: Still existing?
    
cancel_segment_timers(SegSent) when is_list(SegSent) ->
    Cancel = fun({_, _, Ref}) ->
                     megaco_monitor:cancel_apply_after(Ref)
	     end,
    lists:foreach(Cancel, SegSent);
cancel_segment_timers(_) ->
    ok.

handle_ack_callback(_CD, ok = _AS, discard_ack = _AA, _T, _Extra) ->
    ok;
handle_ack_callback(ConnData, {error, Reason}, discard_ack = AckAction, T, Extra) ->
    ?report_trace(ConnData, "handle ack (no callback)",
		  [T, AckAction, {error, Reason}, Extra]);
handle_ack_callback(ConnData, AckStatus, {handle_ack, AckData}, T, Extra) ->
    ?report_trace(ConnData, "callback: trans ack", [{ack_data, AckData}]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, AckStatus, AckData | UserArgs];
	    _ ->
		[ConnHandle, Version, AckStatus, AckData, Extra | UserArgs]
	end,
    Res = (catch handle_callback(ConnData, UserMod, handle_trans_ack, Args)),
    ?report_debug(ConnData, "return: trans ack", [T, AckData, {return, Res}]),
    case Res of
	ok ->
	    ok;
	_ ->
	    warning_msg("transaction ack callback failed: ~w", [Res]),
	    ok
    end,
    Res.


handle_callback(ConnData, undefined = _UserMod, Func, Args) ->
    ?report_important(ConnData, "callback: unknown callback module", 
		      [{func, Func}, {args, Args}]),
    ok;
handle_callback(_ConnData, UserMod, Func, Args) ->
    (catch apply(UserMod, Func, Args)).


handle_message_error(ConnData, _Error, _Extra) 
  when ConnData#conn_data.monitor_ref == undefined_monitor_ref ->
    %% May occur if another process already has setup a
    %% temporary connection, but the handle_connect callback
    %% function has not yet returned before the eager MG
    %% re-sends its initial service change message.
    ignore;
handle_message_error(ConnData, Error, Extra) ->
    ?report_trace(ConnData, "callback: message error", [Error]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, Error | UserArgs];
	    _ ->
		[ConnHandle, Version, Error, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_message_error, Args)),
    ?report_debug(ConnData, "return: message error", [Error, {return, Res}]),
    case Res of
	ok ->
	    ok;
	_ ->
	    warning_msg("message error callback failed: ~w", [Res]),
	    ok
    end,
    Res.

handle_disconnect_callback(ConnData, UserReason)
  when is_record(ConnData, conn_data) ->
    ?report_trace(ConnData, "callback: disconnect", [{reason, UserReason}]),
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    Args       = [ConnHandle, Version, UserReason | UserArgs], 
    Res = (catch apply(UserMod, handle_disconnect, Args)),
    ?report_debug(ConnData, "return: disconnect", [{reason, UserReason}, {return, Res}]),
    case Res of
	ok ->
	    ok;
	_ ->
	    warning_msg("disconnect callback failed: ~w", [Res]),
	    ok
    end,
    Res.


%%----------------------------------------------------------------------
%% Test "outgoing" messages
%%----------------------------------------------------------------------

%% test_request/5 -> {MegacoMessage, EncodingRes}
%% 
%% This function is only intended for testing 
%% (e.g. answer the question: have I constructed a valid action request?)
%% 
%% It's not exactly the same code as a call to 'call'
%% or 'cast' but close enough.
%% 
test_request(ConnHandle, Actions, 
	     Version, EncodingMod, EncodingConfig)
  when is_record(ConnHandle, megaco_conn_handle) and
       is_integer(Version) andalso is_atom(EncodingMod) ->
    %% Create a fake conn_data structure
    ConnData = #conn_data{serial           = 1, 
			  protocol_version = Version,
			  conn_handle      = ConnHandle,
			  auth_data        = asn1_NOVALUE,
			  encoding_mod     = EncodingMod,
			  encoding_config  = EncodingConfig},

    TRs = test_req_compose_transactions(ConnData, Actions),
    Body = {transactions, TRs},
    MegaMsg = megaco_messenger_misc:compose_message(ConnData, Version, Body),
    EncodeRes = megaco_messenger_misc:encode_message(ConnData, MegaMsg),
    {MegaMsg, EncodeRes}.


test_req_compose_transactions(ConnData, [A|_] = ActionsList) when is_list(A) ->
    LastSerial = ConnData#conn_data.serial,
    test_req_compose_transactions(LastSerial, lists:reverse(ActionsList), []);
test_req_compose_transactions(#conn_data{serial = Serial}, Actions) ->
    TR   = #'TransactionRequest'{transactionId = Serial,
				 actions       = Actions},
    [{transactionRequest, TR}].

test_req_compose_transactions(_Serial, [], Acc) ->
    lists:reverse(Acc);
test_req_compose_transactions(Serial, [A|As], Acc) ->
    TR = #'TransactionRequest'{transactionId = Serial,
                               actions       = A},
    test_req_compose_transactions(Serial, As, [{transactionRequest, TR}|Acc]).


test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Error) 
  when is_record(Error, 'ErrorDescriptor') ->
    Reply = {transactionError, Error},
    test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply);
test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Replies) 
  when is_list(Replies) ->
    Reply = {actionReplies, Replies},
    test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply).

test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) ->
    ImmAck    = asn1_NOVALUE,
    Serial    = 1,
    %% Create a fake conn_data structure
    CD        = #conn_data{serial           = Serial, 
			   protocol_version = Version,
			   conn_handle      = ConnHandle,
			   auth_data        = asn1_NOVALUE,
			   encoding_mod     = EncodingMod,
			   encoding_config  = EncodingConfig},
    TR0       = #megaco_transaction_reply{transactionId     = Serial, 
					  immAckRequired    = ImmAck, 
					  transactionResult = Reply}, 
    TR        = megaco_messenger_misc:transform_transaction_reply(CD, TR0),
    Body      = {transactions, [{transactionReply, TR}]},
    MegaMsg   = megaco_messenger_misc:compose_message(CD, Version, Body),
    EncodeRes = megaco_messenger_misc:encode_message(CD, MegaMsg),
    {MegaMsg, EncodeRes}.
    

%%----------------------------------------------------------------------
%% Send (or prepare) outgoing messages
%%----------------------------------------------------------------------

%% Description:
%% Encode a list of actions or a list of list of actions for
%% later sending (using call or cast).
%% 
%% encode_actions(CH, Acts, Opts) -> {ok, encoded_actions()} | {error, Reason}
%% CH -> connection_handle()
%% Acts -> action_reqs() | [action_reqs()]
%% action_reqs() -> [action_req()]
%% action_req() -> #'ActionRequest'{}
%% Opts -> [option()]
%% option() -> {Tab, Val}
%% Tag -> atom()
%% Val -> term()
%% encoded_actions() -> binary() | [binary()]
%% Reason -> term()
encode_actions(CH, [A|_] = ActionsList, Opts) 
  when is_record(CH, megaco_conn_handle) andalso is_list(A) ->
    (catch encode_multi_actions(CH, ActionsList, Opts));

encode_actions(CH, [A|_] = Actions, Opts) 
  when is_record(CH, megaco_conn_handle) andalso is_tuple(A) ->
    do_encode_actions(CH, Actions, Opts).
    
encode_multi_actions(CH, ActionsList, Opts) ->
    case prepare_req_send_options(CH, Opts) of
	{ok, CD} ->
	    ActsList = [encode_multi_actions(CD, Acts) || Acts <- ActionsList],
	    {ok, ActsList};
	Error ->
	    Error
    end.

encode_multi_actions(CD, Actions) ->
    case megaco_messenger_misc:encode_actions(CD, 
					      "encode multi actions", 
					      Actions) of
	{ok, Bin} ->
	    Bin;
	Error ->
	    throw(Error)
    end.

do_encode_actions(CH, Actions, Opts) 
  when is_record(CH, megaco_conn_handle) ->
    case prepare_req_send_options(CH, Opts) of
        {ok, CD} ->
	    megaco_messenger_misc:encode_actions(CD, 
						 "encode actions", 
						 Actions);
	Error ->
	    Error
    end.

prepare_req_send_options(CH, Opts) ->
    case megaco_config:lookup_local_conn(CH) of
        [CD] ->
            override_req_send_options(any, CD, Opts);
        [] ->
            {error, {not_found, conn_data}}
    end.
    

call(ConnHandle, Actions, Options) ->
    case lists:keymember(reply_data, 1, Options) of
	true ->
	    {error, {bad_option, reply_data}};
	false ->
	    Self          = self(), 
	    ProxyFun      = fun() -> call_proxy(Self) end,
	    {Proxy, MRef} = erlang:spawn_monitor(ProxyFun),
	    Options2      = [{reply_data, Proxy} | Options],
	    call_or_cast(call, ConnHandle, Actions, Options2, MRef)
    end.

cast(ConnHandle, Actions, Options) ->
    call_or_cast(cast, ConnHandle, Actions, Options, undefined).

%% In a transaction there can be several actions, so if the 
%% First element of the Actions list is an ''ActionRequest''
%% record this a list of ActionRequest's for one Transaction
%% request. If on the other hand this is not the case, then
%% the Actions list is assumed to be a list of list of
%% ActionRequest. That is, action requests for several transactions.
%% It could also be a binary or a list of binaries (if 
%% the actions has already been encoded).
call_or_cast(CallOrCast, ConnHandle, [A|_] = Actions, Options, ProxyMon) 
  when is_tuple(A) ->
    %% Just one transaction
    case call_or_cast(CallOrCast, ConnHandle, [Actions], Options, ProxyMon) of
	ok ->
	    ok;
	{error, Reason} ->
	    {error, Reason};
	{Version, [Reply]} when is_integer(Version) ->
	    {Version, Reply};
	{Version, Error} when is_integer(Version) ->
	    {Version, Error}
    end;

call_or_cast(CallOrCast, ConnHandle, Actions, Options, ProxyMon) 
  when is_binary(Actions) ->
    %% Just one transaction (although the actions has already been encoded)
    case call_or_cast(CallOrCast, ConnHandle, [Actions], Options, ProxyMon) of
	ok ->
	    ok;
	{error, Reason} ->
	    {error, Reason};
	{Version, [Reply]} when is_integer(Version) ->
	    {Version, Reply};
	{Version, Error} when is_integer(Version) ->
	    {Version, Error}
    end;

call_or_cast(CallOrCast, ConnHandle, ActionsList, Options, ProxyMon)
  when is_record(ConnHandle, megaco_conn_handle) ->
    case prepare_req_send_options(CallOrCast, 
				  ConnHandle, Options, ActionsList) of
        {ok, ConnData} ->
	    ?report_trace(ConnData, "call_or_cast - options prepared", []),
            case encode_requests(ConnData, ActionsList) of
                {ok, TRs, BinOrBins} -> 
		    ?report_trace(ConnData, 
				  "call_or_cast - request encoded", []),
                    send_request(ConnData, ConnHandle, 
				 TRs, CallOrCast, BinOrBins),
		    case CallOrCast of
			call -> 
			    TransIds = to_local_trans_id(ConnData, TRs),
			    wait_for_reply(ConnData, TransIds, ProxyMon);
			cast -> 
			    ok
		    end;
                {error, Reason} ->
		    call_proxy_cleanup(ConnData, ProxyMon),
		    Version = ConnData#conn_data.protocol_version,
                    return_error(CallOrCast, Version, {error, Reason})
            end;
        {error, Reason} ->
	    call_proxy_cleanup(Options, ProxyMon),
            return_error(CallOrCast, 1, {error, Reason})
    end;
call_or_cast(CallOrCast, ConnHandle, _Actions, Options, ProxyMon) ->
    call_proxy_cleanup(Options, ProxyMon),
    return_error(CallOrCast, 1, {error, {bad_megaco_conn_handle, ConnHandle}}).


return_error(Action, Version, Error) ->
    case Action of
	call -> {Version, Error};
	cast -> Error
    end.

wait_for_reply(CD, TransIds, ProxyMon) ->
    ProxyPid = CD#conn_data.reply_data,
    ProxyPid ! {go, self(), CD, TransIds},
    receive
	{reply, ProxyPid, Reply} ->
	    erlang:demonitor(ProxyMon, [flush]),
	    Reply;
	{'DOWN', ProxyMon, process, ProxyPid, Info} ->
	    UserReply = {error, {call_proxy_crash, Info}}, 
	    {CD#conn_data.protocol_version, UserReply}
    end.
    

call_proxy_cleanup(#conn_data{reply_data = ProxyPid}, ProxyMon) ->
    do_call_proxy_cleanup(ProxyPid, ProxyMon);
call_proxy_cleanup(Options, ProxyMon) when is_list(Options) ->
    ProxyPid = 
	case lists:keysearch(reply_data, 1, Options) of
	    {value, {reply_data, Data}} ->
		Data;
	    _ ->
		undefined
	end,
    do_call_proxy_cleanup(ProxyPid, ProxyMon);
call_proxy_cleanup(ProxyPid, ProxyMon) ->
    do_call_proxy_cleanup(ProxyPid, ProxyMon).

do_call_proxy_cleanup(ProxyPid, ProxyMon) ->
    maybe_demonitor(ProxyMon),
    maybe_stop_proxy(ProxyPid),
    ok.
   
maybe_demonitor(undefined) ->
    ok;
maybe_demonitor(Mon) ->
    (catch erlang:demonitor(Mon, [flush])),
    ok.

maybe_stop_proxy(Pid) when is_pid(Pid) ->
    Pid ! {stop, self()},
    ok;
maybe_stop_proxy(_) ->
    ok.


call_proxy(Parent) ->
    receive
	{go, Parent, CD, TransIds} ->
	    call_proxy(Parent, CD, TransIds);
	{stop, Parent} ->
	    exit(normal)
    end.

call_proxy(Parent, CD, TransIds) ->
    Reply = proxy_wait_for_reply(CD, TransIds, []),
    Parent ! {reply, self(), Reply},
    call_proxy_gc(CD, CD#conn_data.call_proxy_gc_timeout).

call_proxy_gc(CD, Timeout) when (Timeout > 0) ->
    T = t(),
    receive
	{?MODULE, TransId, Version, Result} -> % Old format
	    CD2 = CD#conn_data{protocol_version = Version}, 
	    Extra = ?default_user_callback_extra, 
	    return_unexpected_trans_reply(CD2, TransId, Result, Extra),
	    call_proxy_gc(CD, Timeout - (t() - T));

	{?MODULE, TransId, Version, Result, Extra} ->
	    CD2 = CD#conn_data{protocol_version = Version}, 
	    return_unexpected_trans_reply(CD2, TransId, Result, Extra),
	    call_proxy_gc(CD, Timeout - (t() - T))

    after Timeout ->
	    exit(normal)
    end;
call_proxy_gc(_CD, _Timeout) ->
    exit(normal).

proxy_wait_for_reply(_CD, [], Replies0) ->
    % Make sure they come in the same order as the requests where sent
    Replies1 = lists:keysort(2, Replies0), 
    %% Must all be the same version
    [{Version, _, _}|_] = Replies1,
    Replies2 = [Result || {_Version, _TransId, Result} <- Replies1], 
    {Version, Replies2};
proxy_wait_for_reply(CD, TransIds, Replies) -> 
    receive
	{?MODULE, TransId, Version, Reply} -> % Old format
	    {TransIds2, Replies2} = 
		wfr_handle_reply(CD, 
				 TransIds, TransId, 
				 Version, Replies, Reply),
	    proxy_wait_for_reply(CD, TransIds2, Replies2);
	    
	{?MODULE, TransId, Version, Reply, Extra} -> 
	    {TransIds2, Replies2} = 
		wfr_handle_reply(CD, 
				 TransIds, TransId, 
				 Version, Replies, Reply, Extra),
	    proxy_wait_for_reply(CD, TransIds2, Replies2)
    end.

wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply) ->
    Extra = ?default_user_callback_extra,
    wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply, Extra).

wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply, Extra) ->
    %% Is this meant for us?
    case lists:member(TransId, TransIds) of
	true -> % Yep
	    wfr_update(TransIds, TransId, Version, Replies, Reply, Extra);
	false -> % Nop
	    CD2 = CD#conn_data{protocol_version = Version}, 
	    return_unexpected_trans_reply(CD2, TransId, Reply, Extra),
	    {TransIds, Replies}
    end.
    
wfr_mk_reply(Version, TransId, Result, ?default_user_callback_extra = _Extra) ->
    {Version, TransId, Result};
wfr_mk_reply(Version, TransId, Result0, Extra) ->
    Result = list_to_tuple(lists:append(tuple_to_list(Result0), [Extra])),
    {Version, TransId, Result}.

%% Last segment of a reply
%% transactionResult "=" actionReplies
wfr_update(TransIds, TransId, Version, Results, {ok, {SegNo, Last, ARs}}, Extra) 
  when is_integer(SegNo) andalso (Last == true) ->
    TransIds2 = lists:delete(TransId, TransIds), 
    case lists:keysearch(TransId, 2, Results) of
	
	%% All segments ok (actionReplies)
	{value, {V, TransId, {ok, SegReps}}} ->
	    SegReps2 = lists:keysort(1, [{SegNo, ARs}|SegReps]),
	    Rep      = wfr_mk_reply(V, TransId, {ok, SegReps2}, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};
	
	%% Atleast one segment error (transactionError)
	{value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} ->
	    OkSegs2  = lists:keysort(1, [{SegNo, ARs}|OkSegs]),
	    ErrSegs2 = lists:keysort(1, ErrSegs),
	    Error    = {error, {segment, OkSegs2, ErrSegs2}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};
	
	false ->
	    %% First and only segment
	    Rep = wfr_mk_reply(Version, TransId, {ok, [{SegNo, ARs}]}, Extra),
	    {TransIds2, [Rep | Results]}
    
    end;

%% Last segment of a reply
%% transactionResult "=" transactionError
wfr_update(TransIds, TransId, Version, Results, {error, {SegNo, Last, ED}}, Extra) 
  when is_integer(SegNo) andalso (Last == true) ->
    TransIds2 = lists:delete(TransId, TransIds), 
    case lists:keysearch(TransId, 2, Results) of
	
	%% First segment with error (transactionError)
	{value, {V, TransId, {ok, SegReps}}} ->
	    OkSegs   = lists:keysort(1, [{SegNo, ED}|SegReps]),
	    ErrSegs  = [{SegNo, ED}], 
	    Error    = {error, {segment, OkSegs, ErrSegs}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};

	%% Another segment with error (transactionError)
	{value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} ->
	    OkSegs2  = lists:keysort(1, OkSegs),
	    ErrSegs2 = lists:keysort(1, [{SegNo, ED}|ErrSegs]),
	    Error    = {error, {segment, OkSegs2, ErrSegs2}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};

	false ->
	    %% First and only segment
	    OkSegs  = [],
	    ErrSegs = [{SegNo, ED}], 
	    Error   = {error, {segment, OkSegs, ErrSegs}}, 
	    Rep     = wfr_mk_reply(Version, TransId, Error, Extra),
	    {TransIds2, [Rep]}

    end;

%% One segment of a reply
%% transactionResult "=" actionReplies
wfr_update(TransIds, TransId, Version, Results, {ok, {SegNo, _Last, ARs}}, Extra) 
  when is_integer(SegNo) ->
    case lists:keysearch(TransId, 2, Results) of
	
	%% All segments ok (actionReplies)
	{value, {V, TransId, {ok, SegReps}}} ->
	    SegReps2 = [{SegNo, ARs}|SegReps],
	    Rep      = wfr_mk_reply(V, TransId, {ok, SegReps2}, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds, Results2};
	
	%% Atleast one segment error (transactionError)
	{value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} ->
	    OkSegs2  = [{SegNo, ARs}|OkSegs],
	    Error    = {error, {segment, OkSegs2, ErrSegs}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds, Results2};
	
	false ->
	    %% First and only segment
	    Rep = wfr_mk_reply(Version, TransId, {ok, [{SegNo, ARs}]}, Extra),
	    {TransIds, [Rep | Results]}
    
    end;

%% One segment of a reply
%% transactionResult "=" transactionError
wfr_update(TransIds, TransId, Version, Results, {error, {SegNo, _Last, ED}}, Extra) 
  when is_integer(SegNo) ->
    case lists:keysearch(TransId, 2, Results) of
	
	%% First segment with error (transactionError)
	{value, {V, TransId, {ok, OkSegs}}} ->
	    ErrSegs  = [{SegNo, ED}], 
	    Error    = {error, {segment, OkSegs, ErrSegs}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds, Results2};

	%% Another segment with error (transactionError)
	{value, {V, TransId, {error, {OkSegs, ErrSegs}}}} ->
	    ErrSegs2 = [{SegNo, ED}|ErrSegs],
	    Error    = {error, {segment, OkSegs, ErrSegs2}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds, Results2};

	false ->
	    %% First segment
	    OkSegs  = [],
	    ErrSegs = [{SegNo, ED}], 
	    Error   = {error, {segment, OkSegs, ErrSegs}}, 
	    Rep     = wfr_mk_reply(Version, TransId, Error, Extra),
	    {TransIds, [Rep]}

    end;

%% This means that some segments did not make it in time
wfr_update(TransIds, TransId, Version, Results, 
	   {error, {segment_timeout, Missing}}, Extra) ->
    TransIds2 = lists:delete(TransId, TransIds), 
    case lists:keysearch(TransId, 2, Results) of
	
	%% First segment with error (transactionError)
	{value, {V, TransId, {ok, OkSegs}}} ->
	    Error    = {error, {segment_timeout, Missing, OkSegs, []}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};
       
	%% Another segment with error (transactionError)
	{value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} ->
	    Error    = {error, {segment_timeout, Missing, OkSegs, ErrSegs}}, 
	    Rep      = wfr_mk_reply(V, TransId, Error, Extra),
	    Results2 = lists:keyreplace(TransId, 2, Results, Rep),
	    {TransIds2, Results2};

	false ->
	    %% First segment
	    Error   = {error, {segment_timeout, Missing, [], []}}, 
	    Rep     = wfr_mk_reply(Version, TransId, Error, Extra), 
	    {TransIds2, [Rep]}

    end;
    
%% And all other results (presumably results without segments).
wfr_update(TransIds, TransId, Version, Results, Result, Extra) ->
    TransIds2 = lists:delete(TransId, TransIds), 
    Results2  = [wfr_mk_reply(Version, TransId, Result, Extra)|Results],
    {TransIds2, Results2}.
    
    
%% TransInfo is either [trans_id()] or a [trans_req()]

%% This is the normal case where we have just one
%% transaction to be sent (using call or cast) using
%% the transaction sender process.
send_request(#conn_data{control_pid  = CP, 
			trans_req    = true, 
			trans_sender = Pid} = CD, 
	     CH, [Serial], Action, [Bin])
  when is_pid(Pid)        andalso 
       is_integer(Serial) andalso 
       (node(CP) =:= node()) ->

    ?report_trace(CD, 
		  "send_request - one transaction via trans-sender", 
		  [Serial]),

    #conn_data{request_timer       = InitTimer,
	       long_request_timer  = LongTimer} = CD,
    TransId = to_local_trans_id(CH, Serial),
    insert_request(CD, CH, TransId, Action, {Serial, Bin},
		   InitTimer, LongTimer),
    megaco_trans_sender:send_req(Pid, Serial, Bin);

%% This is the general case where we have several transactions
%% beeing sent (using call or cast) at once using
%% the transaction sender process.
send_request(#conn_data{control_pid  = CP, 
			trans_req    = true, 
			trans_sender = Pid} = CD, 
	     CH, TransInfo, Action, Bins)
  when is_pid(Pid)   andalso 
       is_list(Bins) andalso 
       (node(CP) =:= node()) ->

    ?report_trace(CD, 
		  "send_request - multi transactions via trans_sender", 
		  [TransInfo, Pid]),

    #conn_data{request_timer       = InitTimer,
	       long_request_timer  = LongTimer} = CD,
    insert_requests(CD, CH, TransInfo, Action, Bins,
		    InitTimer, LongTimer),
    megaco_trans_sender:send_reqs(Pid, TransInfo, Bins);

%% This is the case when one or more transactions is
%% beeing sent in one message immediatelly (not using
%% the transaction sender process. E.g. the binary is 
%% this encoded message.
send_request(#conn_data{control_pid = CP} = CD, 
	     CH, TRs, Action, Bin)
  when is_list(TRs)   andalso 
       is_binary(Bin) andalso 
       (node(CP) =:= node()) ->

    %%     d("send_request -> entry with"
    %%       "~n   TRs: ~p", [TRs]),

    ?report_trace(CD, "send_request - multi transaction", [TRs]),

    #conn_data{request_timer       = InitTimer,
	       long_request_timer  = LongTimer} = CD,
    insert_requests(CD, CH, TRs, Action, Bin,
		    InitTimer, LongTimer),
    case megaco_messenger_misc:send_message(CD, false, Bin) of
	{error, Reason} ->
	    cancel_requests(CD, TRs, Reason);
	{ok, _} ->
	    ignore
    end;

%% This is the case where we are not on the node where the
%% transport process run.
send_request(#conn_data{control_pid = CP} = CD, 
	     CH, TransInfo, Action, Bin) 
  when node(CP) =/= node() ->

    ?report_trace(CD, "send_request - remote", [TransInfo]),

    InitTimer = infinity,
    LongTimer = infinity,
    insert_requests(CD, CH, TransInfo, Action, Bin,
		    InitTimer, LongTimer),
    Node = node(CP),
    Args = [node(), CD, TransInfo, Bin],
    rpc:cast(Node, ?MODULE, send_request_remote, Args).


insert_requests(_, _, [], _, _, _, _) ->
    ok;

insert_requests(ConnData, ConnHandle, [Serial|Serials], 
		Action, [Bin|Bins], InitTimer, LongTimer) 
  when is_integer(Serial) andalso is_binary(Bin) ->
    TransId = to_local_trans_id(ConnHandle, Serial),
    insert_request(ConnData, ConnHandle, 
		   TransId, Action, Bin, InitTimer, LongTimer),

    insert_requests(ConnData, ConnHandle, Serials, Action, Bins, 
		    InitTimer, LongTimer);

insert_requests(ConnData, ConnHandle, 
		[{transactionRequest, TR}|TRs], 
		Action, Bin, InitTimer, LongTimer) 
  when is_record(TR, 'TransactionRequest') andalso is_binary(Bin) ->
    #'TransactionRequest'{transactionId = Serial} = TR,
    TransId = to_local_trans_id(ConnHandle, Serial),
    insert_request(ConnData, ConnHandle, 
		   TransId, Action, TR, InitTimer, LongTimer),

    insert_requests(ConnData, ConnHandle, TRs, Action, Bin, 
		    InitTimer, LongTimer).


insert_request(ConnData, ConnHandle, TransId, 
	       Action, Data, InitTimer, LongTimer) ->
    %% We dont check the result of the lock-counter creation because
    %% the only way it could already exist is if the transaction-id
    %% range has wrapped and an old counter was not deleted.
    megaco_monitor:request_lockcnt_cre(TransId),

    #megaco_conn_handle{remote_mid = RemoteMid} = ConnHandle,
    #conn_data{protocol_version           = Version,
	       user_mod                   = UserMod,
	       user_args                  = UserArgs,
	       send_handle                = SendHandle,
	       reply_data                 = ReplyData,
	       segment_recv_timer         = InitSegTimer,
	       request_keep_alive_timeout = RKATimer} = ConnData,
    {WaitFor, CurrTimer} = megaco_timer:init(InitTimer),
    M   = ?MODULE,
    F   = request_timeout,
    A   = [ConnHandle, TransId],
    Ref = megaco_monitor:apply_after(M, F, A, WaitFor),
    Req = #request{trans_id         = TransId, 
		   remote_mid       = RemoteMid,
                   timer_ref        = ?SIM({short, Ref}, init_request_timer),
                   init_timer       = InitTimer,
                   init_long_timer  = LongTimer,
                   curr_timer       = CurrTimer,
                   version          = Version,
                   bytes            = {send, Data},
		   send_handle      = SendHandle,
                   user_mod         = UserMod,
                   user_args        = UserArgs,
                   reply_action     = Action,
                   reply_data       = ReplyData,
		   init_seg_timer   = InitSegTimer,
		   keep_alive_timer = RKATimer},
    megaco_monitor:insert_request(Req). % Timing problem?

    
send_request_remote(ReplyNode, ConnData, TransInfo, Bin) ->
    Action = remote,
    ConnHandle = ConnData#conn_data.conn_handle,
    ConnData2  = ConnData#conn_data{reply_data = ReplyNode},
    send_request(ConnData2, ConnHandle, TransInfo, Action, Bin).

prepare_req_send_options(CallOrCast, ConnHandle, Options, Actions) ->
    %% Ensures that two processes cannot get same transaction id.
    %% Bad send options may cause spurious transaction id to be consumed.
    Incr = number_of_transactions(Actions),
    case megaco_config:incr_trans_id_counter(ConnHandle, Incr) of
        {ok, ConnData} ->
            override_req_send_options(CallOrCast, ConnData, Options);
        {error, Reason} ->
            {error, Reason}
    end.

number_of_transactions([Action|_]) when is_tuple(Action) ->
    1;
number_of_transactions(ActionsList) ->
    length(ActionsList).

override_req_send_options(ReplyAction, ConnData, [{Key, Val} | Tail]) ->
    case Key of
	protocol_version ->
            ConnData2 = ConnData#conn_data{protocol_version = Val},
            override_req_send_options(ReplyAction, ConnData2, Tail);	    
        send_handle ->
            ConnData2 = ConnData#conn_data{send_handle = Val},
            override_req_send_options(ReplyAction, ConnData2, Tail);
        request_timer ->
            case megaco_config:verify_val(Key, Val) of
                true ->
                    ConnData2 = ConnData#conn_data{request_timer = Val},
                    override_req_send_options(ReplyAction, ConnData2, Tail);
                false ->
                    {error, {bad_send_option, {Key, Val}}}
            end;
        long_request_timer ->
            case megaco_config:verify_val(Key, Val) of
                true ->
                    ConnData2 = ConnData#conn_data{long_request_timer = Val},
                    override_req_send_options(ReplyAction, ConnData2, Tail);
                false ->
                    {error, {bad_send_option, {Key, Val}}}
            end;
	call_proxy_gc_timeout when (ReplyAction =:= call) orelse 
				   (ReplyAction =:= any) ->
            case megaco_config:verify_val(Key, Val) of
                true ->
                    ConnData2 = 
			ConnData#conn_data{call_proxy_gc_timeout = Val},
                    override_req_send_options(ReplyAction, ConnData2, Tail);
                false ->
                    {error, {bad_send_option, {Key, Val}}}
            end;
	request_keep_alive_timeout when (ReplyAction =:= cast) orelse 
					(ReplyAction =:= any) ->
            case megaco_config:verify_val(Key, Val) of
                true ->
                    ConnData2 = 
			ConnData#conn_data{request_keep_alive_timeout = Val},
                    override_req_send_options(ReplyAction, ConnData2, Tail);
                false ->
                    {error, {bad_send_option, {Key, Val}}}
            end;
        reply_data ->
            ConnData2 = ConnData#conn_data{reply_data = Val},
            override_req_send_options(ReplyAction, ConnData2, Tail);
        user_mod when is_atom(Val) ->
            ConnData2 = ConnData#conn_data{user_mod = Val},
            override_req_send_options(ReplyAction, ConnData2, Tail);
        user_args when is_list(Val) ->
            ConnData2 = ConnData#conn_data{user_args = Val},
            override_req_send_options(ReplyAction, ConnData2, Tail);
	trans_req when Val =:= false -> 
	    %% We only allow turning the transaction-sender off, since
	    %% the opposite (turning it on) would causing to much headake...
	    %% This will allow not using the transaction sender for
	    %% occasional messages
	    ConnData2 = ConnData#conn_data{trans_req    = Val, 
					   trans_sender = undefined},
	    override_req_send_options(ReplyAction, ConnData2, Tail);
        _Bad ->
            {error, {bad_send_option, {Key, Val}}}
    end;
override_req_send_options(_ReplyAction, ConnData, []) ->
    {ok, ConnData}.

override_rep_send_options(ConnData, [{Key, Val} | Tail]) ->
    case Key of
	protocol_version ->
            ConnData2 = ConnData#conn_data{protocol_version = Val},
            override_rep_send_options(ConnData2, Tail);	    
        send_handle ->
            ConnData2 = ConnData#conn_data{send_handle = Val},
            override_rep_send_options(ConnData2, Tail);
        reply_timer ->
            case megaco_config:verify_val(Key, Val) of
                true ->
                    ConnData2 = ConnData#conn_data{reply_timer = Val},
                    override_rep_send_options(ConnData2, Tail);
                false ->
                    {error, {bad_send_option, {Key, Val}}}
            end;
	trans_req when Val =:= false -> 
	    %% We only allow turning the transaction-sender off, since
	    %% the opposite (turning it on) would causing to much headake...
	    %% This will allow not using the transaction sender for
	    %% occasional messages
	    ConnData2 = ConnData#conn_data{trans_req    = Val, 
					   trans_sender = undefined},
	    override_rep_send_options(ConnData2, Tail);
        _Bad ->
            {error, {bad_send_option, {Key, Val}}}
    end;
override_rep_send_options(ConnData, []) ->
    {ok, ConnData}.


%% ----
%% This list is allways atleast one (list of actions) long.
%% ----
%% The proper number of transaction id numbers has already
%% been "allocated", and the connection data record is
%% updated accordingly.
encode_requests(#conn_data{trans_req    = true,
			   trans_sender = Pid,
			   serial       = LastSerial} = CD, ActionsList) 
  when is_pid(Pid) ->
    (catch encode_requests(CD, LastSerial, 
			   lists:reverse(ActionsList), [], []));
encode_requests(#conn_data{serial    = LastSerial} = CD, ActionsList) ->
    %% We shall not accumulate transactions. 
    %% This means that we shall not encode 
    %% the transactions individually (and send
    %% them to the sender process, which
    %% accumulate transactions for later sending),
    %% Instead we encode the entire message directly.
    %% => We shall return one binary, containing, 
    %%    possibly, many transactions
    encode_requests_in_msg(CD, LastSerial, lists:reverse(ActionsList)).


%% This means that we shall compose and encode one complete 
%% megaco message, containing one or more transactions.
encode_requests_in_msg(CD, LastSerial, ActionsList) ->
    TRs  = compose_requests_in_msg(LastSerial, ActionsList, []),
    Body = {transactions, TRs},
    Res  = megaco_messenger_misc:encode_body(CD, 
					     "encode trans request(s) msg", 
					     Body),
    case Res of
	{ok, Bin} ->
	    {ok, TRs, Bin};
	Error ->
	    Error
    end.

compose_requests_in_msg(_S, [], TRs) ->
    TRs;
compose_requests_in_msg(Serial, [A|As], Acc) ->
    TR = #'TransactionRequest'{transactionId = Serial,
                               actions       = A},
    compose_requests_in_msg(Serial - 1, As, [{transactionRequest, TR}|Acc]).


%% We have done the encoding in reverse order, so there
%% is no need to reverse now.
encode_requests(_, _, [], Serials, EncodedTRs) ->
    {ok, Serials, EncodedTRs}; 
encode_requests(CD, Serial, [Actions|ActionsList], Serials, EncodedTRs) ->
    case do_encode_request(CD, Serial, Actions) of
	{ok, Bin} ->
	    encode_requests(CD, Serial - 1, ActionsList, 
			    [Serial|Serials], [Bin|EncodedTRs]);
	Error ->
	    throw(Error)
    end.


do_encode_request(CD, Serial, Actions) ->
    TR = #'TransactionRequest'{transactionId = Serial,
			       actions       = Actions},
    megaco_messenger_misc:encode_trans_request(CD, TR).
    

imm_ack_req(Counter,  when_pending_sent) when (Counter > 0) -> 'NULL';
imm_ack_req(_Counter, when_pending_sent)                    -> asn1_NOVALUE;
imm_ack_req(_Counter, ImmAck)                               -> ImmAck.

maybe_send_reply(#conn_data{sent_pending_limit = Limit} = ConnData, 
		 TransId, Result, SendOpts, ImmAck) ->

    %% d("maybe_send_reply -> entry with"
    %%   "~n   Limit:    ~p"
    %%   "~n   TransId:  ~p"
    %%   "~n   Result:   ~p"
    %%   "~n   SendOpts: ~p"
    %%   "~n   ImmAck:   ~p", [Limit, TransId, Result, SendOpts, ImmAck]),

    %% Pending limit
    %% Before we can send the reply we must check that we have 
    %% not passed the pending limit (and sent an error message).
    case check_pending_limit(Limit, sent, TransId) of
	{ok, Counter} ->
	    case override_rep_send_options(ConnData, SendOpts) of
		{ok, ConnData2} ->
		    send_reply(ConnData2, Result, 
			       imm_ack_req(Counter, ImmAck));
		Error ->
		    Error
	    end;
	aborted ->
	    {error, aborted}
    end.

encode_reply(CD, TR) ->
    megaco_messenger_misc:encode_trans_reply(CD, TR).

send_reply(#conn_data{serial       = Serial,
		      trans_req    = TransReq,
		      trans_sender = TransSnd} = CD, TransRes, ImmAck) ->

    %% Encapsule the transaction result into a reply message

    %% d("send_reply -> entry with"
    %%   "~n   Serial:   ~p"
    %%   "~n   TransRes: ~p"
    %%   "~n   ImmAck:   ~p", [Serial, TransRes, ImmAck]),

    TR = #megaco_transaction_reply{transactionId        = Serial,
				   immAckRequired       = ImmAck,
				   transactionResult    = TransRes},
    case encode_reply(CD, TR) of
	{ok, Bin} when is_binary(Bin) andalso (TransReq =:= true) ->
	    ?rt2("send_reply - pass it on to the transaction sender", 
		 [size(Bin)]),
	    _ = megaco_trans_sender:send_reply(TransSnd, Bin),
	    {ok, Bin};

        {ok, Bin} when is_binary(Bin) ->
	    ?rt2("send_reply - encoded", [size(Bin)]),
	    TraceLabel = "send trans reply", 
	    Body = {transactions, [Bin]},
            megaco_messenger_misc:send_body(CD, TraceLabel, Body);

        {ok, Bins} when is_list(Bins) ->
	    ?rt2("send_reply - encoded (segmented)", [length(Bins)]),
	    Res = send_reply_segments(CD, Bins),
	    {ok, Res};

	{error, not_implemented} ->
	    %% Oups, we cannot segment regardless the config,
	    %% so pack it all into one message and hope for
	    %% the best...
	    ?rt2("send_reply - cannot encode separate transactions", []),
	    TR2  = megaco_messenger_misc:transform_transaction_reply(CD, TR), 
	    Body = {transactions, [{transactionReply, TR2}]},
	    megaco_messenger_misc:send_body(CD, "encode trans reply", Body);

        {error, Reason} = Error ->
	    Code = ?megaco_internal_gateway_error,
	    Text = "encode transaction reply", 
	    ED   = #'ErrorDescriptor'{errorCode = Code, 
				      errorText = Text},
            Res = {transactionError, ED},
	    TR2 =  #megaco_transaction_reply{transactionId     = Serial, 
					     transactionResult = Res},
	    TR3 = megaco_messenger_misc:transform_transaction_reply(CD, TR2), 
            TraceLabel = "<ERROR> encode trans reply body failed",
            ?report_important(CD, TraceLabel, [TR, TR3, ED, Error]),
	    error_msg("failed encoding transaction reply body: ~s", 
		      [format_encode_error_reason(Reason)]),
            Body = {transactions, [{transactionReply, TR3}]},
            _ = megaco_messenger_misc:send_body(CD, TraceLabel, Body),
	    Error
    end.

send_reply_segments(CD, Bins) ->
    TraceLabelPre = "send segmented trans reply", 
    (catch send_reply_segments(CD, TraceLabelPre, Bins)).

send_reply_segments(#conn_data{segment_send = infinity} = CD, Label, Bins) ->
    send_reply_segments(CD, Label, length(Bins), Bins);

send_reply_segments(#conn_data{segment_send = K} = CD, Label, Bins) 
  when is_integer(K) andalso (K =< length(Bins)) ->
    send_reply_segments(CD, Label, K, Bins);

send_reply_segments(#conn_data{segment_send = K} = CD, Label, Bins) 
  when is_integer(K) ->
    send_reply_segments(CD, Label, length(Bins), Bins).

send_reply_segments(CD, Label, K, Bins) ->
    send_reply_segments(CD, Label, K, Bins, []).

send_reply_segments(_CD, _Label, 0, Bins, Sent) ->
    ?rt2("send_reply_segments - done", [Sent, Bins]),
    {Sent, Bins};
send_reply_segments(CD, TraceLabelPre, K, [{SN, Bin}|Bins], Sent) ->
    case send_reply_segment(CD, TraceLabelPre, SN, Bin) of
	{ok, Bin2} ->
	    ?rt2("send_reply_segments - send", [K, SN]),
	    send_reply_segments(CD, TraceLabelPre, K-1, 
				Bins, [{SN, Bin2}|Sent]);
	Error ->
	    throw(Error)
    end.

send_reply_segment(CD, TraceLabelPre, SN, Bin) ->
    Label = lists:flatten(io_lib:format("~s[~w]", [TraceLabelPre, SN])),
    Body  = {transactions, [Bin]},
    megaco_messenger_misc:send_body(CD, Label, Body).
    

format_encode_error_reason(Reason) ->
    FS = 
	case Reason of
	    {Mod, Func, [EC, Msg], {AE, CS}} when is_atom(Mod)  andalso 
						  is_atom(Func) andalso
						  is_list(EC)   and
						  is_tuple(Msg) and
						  is_list(CS) ->
		io_lib:format("~n   Encode module: ~w"
			      "~n   Func:          ~w"
			      "~n   Encode config: ~w"
			      "~n   Message part:  ~p"
			      "~n   Actual error:  ~p" 
			      "~n   Call stack:    ~w", 
			      [Mod, Func, EC, Msg, AE, CS]);

	    {Mod, Func, [EC, Msg], AE} when is_atom(Mod)  andalso 
					    is_atom(Func) andalso
					    is_list(EC)   andalso
					    is_tuple(Msg) ->
		io_lib:format("~n   Encode module: ~w"
			      "~n   Func:          ~w"
			      "~n   Encode config: ~w"
			      "~n   Message part:  ~p"
			      "~n   Actual error:  ~p", 
			      [Mod, Func, EC, Msg, AE]);

	    {Mod, [EC, Msg], {AE, CS}} when is_atom(Mod)  andalso 
					    is_list(EC)   andalso
					    is_tuple(Msg) andalso
					    is_list(CS) ->
		io_lib:format("~n   Encode module: ~w"
			      "~n   Encode config: ~w"
			      "~n   Message part:  ~p"
			      "~n   Actual error:  ~p" 
			      "~n   Call stack:    ~w", 
			      [Mod, EC, Msg, AE, CS]);

	    {Mod, [EC, Msg], AE} when is_atom(Mod)  andalso 
				      is_list(EC)   andalso
				      is_tuple(Msg) ->
		io_lib:format("~n   Encode module: ~w"
			      "~n   Encode config: ~w"
			      "~n   Message part:  ~p"
			      "~n   Actual error:  ~p", 
			      [Mod, EC, Msg, AE]);

	    Error ->
		io_lib:format("~n   ~w", [Error])
	end,
    lists:flatten(FS).

			  
%% Presumably the user would return immediately (with {pending, Data}) if it 
%% knows or suspects a request to take a long time to process. 
%% For this reason we assume that handling a resent request 
%% could not have caused an update of the pending limit counter.
maybe_send_pending(#conn_data{sent_pending_limit = Limit} = ConnData, 
		   TransId) ->	    
    case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
	ok ->
	    send_pending(ConnData);
	error ->
	    SendReply = send_pending_limit_error(ConnData),
	    {aborted, SendReply};
	aborted ->
	    {aborted, ignore}
    end.


send_pending(#conn_data{serial       = Serial,
			trans_req    = true,
			trans_sender = Pid}) ->
    megaco_trans_sender:send_pending(Pid, Serial);
send_pending(#conn_data{serial = Serial} = CD) ->
    %% Encapsule the transaction result into a pending message
    TP = #'TransactionPending'{transactionId = Serial},
    Body = {transactions, [{transactionPending, TP}]},
    megaco_messenger_misc:send_body(CD, "send trans pending", Body).
	    

maybe_send_ack('NULL', #conn_data{serial       = Serial,
				  trans_ack    = true,
				  trans_sender = Pid}) ->
    megaco_trans_sender:send_ack_now(Pid, Serial);
maybe_send_ack('NULL', CD) ->
    send_ack(CD);
maybe_send_ack(_, #conn_data{auto_ack = false}) ->
    ignore;
maybe_send_ack(_, #conn_data{serial       = Serial,
			     trans_ack    = true,
			     trans_sender = Pid}) 
  when is_pid(Pid) ->
    %% Send (later) via the transaction sender
    megaco_trans_sender:send_ack(Pid, Serial),
    ok;
maybe_send_ack(_, CD) ->
    %% Send now
    send_ack(CD).


send_ack(#conn_data{serial = Serial} = CD) ->
    %% Encapsule the transaction result into a ack message
    TRA = #'TransactionAck'{firstAck = Serial},
    Body = {transactions, [{transactionResponseAck, [TRA]}]},
    megaco_messenger_misc:send_body(CD, "send trans ack", Body).


send_segment_reply(#conn_data{serial = Serial} = CD, SegNo) ->
    SR = #'SegmentReply'{transactionId = Serial,
			 segmentNumber = SegNo},
    Body = {transactions, [{segmentReply, SR}]},
    megaco_messenger_misc:send_body(CD, "send segment reply", Body).

send_segment_reply(#conn_data{serial = Serial} = CD, SegNo, Complete) ->
    SR = #'SegmentReply'{transactionId        = Serial,
			 segmentNumber        = SegNo,
			 segmentationComplete = Complete},
    Body = {transactions, [{segmentReply, SR}]},
    megaco_messenger_misc:send_body(CD, "send segment reply", Body).

send_segment_reply_complete(CD, SegNo) ->
    send_segment_reply(CD, SegNo, 'NULL').

    
send_pending_limit_error(ConnData) ->
    ?report_pending_limit_exceeded(ConnData),
    Code   = ?megaco_number_of_transactionpending_exceeded,
    Reason = "Pending limit exceeded",
    send_trans_error(ConnData, Code, Reason).
    
send_trans_error(ConnData, Code, Reason) ->
    %% Encapsulate the transaction error into a reply message
    ED       = #'ErrorDescriptor'{errorCode = Code, errorText = Reason},
    Serial   = ConnData#conn_data.serial,
    %% Version  = ConnData#conn_data.protocol_version,
    TransRes = {transactionError, ED}, 
    TR       = #megaco_transaction_reply{transactionId     = Serial, 
					 transactionResult = TransRes},
    TR2      = megaco_messenger_misc:transform_transaction_reply(ConnData, TR),
    Body     = {transactions, [{transactionReply, TR2}]},
    case megaco_messenger_misc:send_body(ConnData, "send trans error", Body) of
	{error, Reason2} ->
	    ?report_important(ConnData, 
			      "<ERROR> failed sending transaction error",
			      [Body, {error, Reason2}]),
	    error;
	_ ->
	    ok
    end.


send_message_error(ConnData, Code, Reason) ->
    ED = #'ErrorDescriptor'{errorCode = Code, errorText = Reason},
    Body = {messageError, ED},
    case megaco_messenger_misc:send_body(ConnData, "send trans error", Body) of
	{error, Reason2} ->
	    ?report_important(ConnData, 
			      "<ERROR> failed sending message error",
			      [Body, {error, Reason2}]),
	    error;
	_ ->
	    ok
    end.
	    

cancel(ConnHandle, Reason) when is_record(ConnHandle, megaco_conn_handle) ->
    case megaco_config:lookup_local_conn(ConnHandle) of
        [CD] ->
	    megaco_config:update_conn_info(CD, cancel, true),
	    do_cancel(ConnHandle, Reason, CD#conn_data{cancel = true}),
	    megaco_config:update_conn_info(CD, cancel, false),
	    ok;
        [] ->
	    ConnData = fake_conn_data(ConnHandle),
	    do_cancel(ConnHandle, Reason, ConnData)
    end.

do_cancel(ConnHandle, Reason, ConnData) ->
    ?report_trace(ConnData, "cancel", [ConnHandle, Reason]),
    LocalMid      = ConnHandle#megaco_conn_handle.local_mid,
    RemoteMid     = ConnHandle#megaco_conn_handle.remote_mid,
    ReqTransIdPat = #trans_id{mid = LocalMid, _ = '_'}, 
    ReqPat = #request{trans_id          = ReqTransIdPat,
		      remote_mid        = RemoteMid,
		      _                 = '_'},
    CancelReq = fun(Req) ->
			cancel_request(ConnData, Req, Reason),
			{_Type, Ref} = Req#request.timer_ref,  %% OTP-4843
			megaco_monitor:cancel_apply_after(Ref)
		end,
    Requests  = megaco_monitor:match_requests(ReqPat),
    lists:foreach(CancelReq, Requests),
    RemoteMid = ConnHandle#megaco_conn_handle.remote_mid,
    RepTransIdPat = #trans_id{mid = RemoteMid, _ = '_'}, % BUGBUG List here?
    RepPat = #reply{trans_id  = RepTransIdPat,
		    local_mid = LocalMid,
		    _         = '_'},
    CancelRep = fun(Rep) -> 
			cancel_reply(ConnData, Rep, Reason) 
		end,
    Replies   = megaco_monitor:match_replies(RepPat),
    lists:foreach(CancelRep, Replies),
    ok.

cancel_requests(_ConnData, [], _Reason)  ->
    ok;
cancel_requests(ConnData, [{transactionRequest,TR}|TRs], Reason) ->
    #'TransactionRequest'{transactionId = TransId0} = TR,
    TransId = to_local_trans_id(ConnData#conn_data.conn_handle, TransId0),
    case megaco_monitor:lookup_request(TransId) of
	[] ->
	    ignore;
	[Req] when is_record(Req, request) ->
	    cancel_request(ConnData, Req, Reason)
    end,
    cancel_requests(ConnData, TRs, Reason).

cancel_request(ConnData, Req, Reason)  ->
    ?report_trace(ignore, "cancel request", [Req]),
    ?TC_AWAIT_CANCEL_EVENT(),
    TransId   = Req#request.trans_id,
    Version   = Req#request.version,
    UserMod   = Req#request.user_mod,
    UserArgs  = Req#request.user_args,
    Action    = Req#request.reply_action,
    UserData  = Req#request.reply_data,
    UserReply = {error, Reason},
    ConnData2 = ConnData#conn_data{protocol_version = Version,
				   user_mod         = UserMod,
				   user_args        = UserArgs,
				   reply_action     = Action,
				   reply_data       = UserData},
    cancel_request2(ConnData2, TransId, UserReply).

cancel_request2(ConnData, TransId, UserReply) ->
    megaco_monitor:delete_request(TransId),
    megaco_monitor:request_lockcnt_del(TransId), 
    megaco_config:del_pending_counter(recv, TransId), % OTP-7189
    Serial    = TransId#trans_id.serial,
    ConnData2 = ConnData#conn_data{serial = Serial},
    return_reply(ConnData2, TransId, UserReply).
    

return_reply(ConnData, TransId, UserReply) ->
    Extra = ?default_user_callback_extra, 
    return_reply(ConnData, TransId, UserReply, Extra).

return_reply(ConnData, TransId, UserReply, Extra) ->
    ?report_trace(ConnData, "callback: trans reply", [UserReply]),
    Version  = ConnData#conn_data.protocol_version,
    UserData = ConnData#conn_data.reply_data,
    case ConnData#conn_data.reply_action of
        call when is_pid(UserData) ->
	    ?report_trace(ConnData, "callback: (call) trans reply", 
			  [UserReply]),
            Pid = UserData,
            Pid ! {?MODULE, TransId, Version, UserReply, Extra}; 
        cast ->
	    ?report_trace(ConnData, "callback: (cast) trans reply", [UserReply]),
	    UserMod    = ConnData#conn_data.user_mod,
	    UserArgs   = ConnData#conn_data.user_args,
            ConnHandle = ConnData#conn_data.conn_handle,
	    Args       = 
		case Extra of
		    ?default_user_callback_extra ->
			[ConnHandle, Version, UserReply, UserData | UserArgs];
		    _ ->
			[ConnHandle, Version, UserReply, UserData, Extra | UserArgs]
		end,
            Res = (catch apply(UserMod, handle_trans_reply, Args)),
	    ?report_debug(ConnData, "return: (cast) trans reply",
			  [UserReply, {return, Res}]),
	    case Res of
		ok ->
		    ok;
		_ ->
		    warning_msg("transaction reply callback failed: ~w", 
				[Res]),
		    ok
	    end,
	    Res;
        remote ->
	    ?report_trace(ConnData, "callback: (remote) trans reply", [UserReply]),
            Node = UserData,
            Args = [ConnData, UserReply, Extra],
            rpc:cast(Node, ?MODULE, receive_reply_remote, Args)
    end.

receive_reply_remote(ConnData, UserReply) ->
    Extra = ?default_user_callback_extra, 
    receive_reply_remote(ConnData, UserReply, Extra).

receive_reply_remote(ConnData, UserReply, Extra) ->
    TransId = to_local_trans_id(ConnData),
    case {megaco_monitor:request_lockcnt_inc(TransId),
	  (catch megaco_monitor:lookup_request(TransId))} of
        {Cnt, [Req]} when (Cnt =:= 1) andalso is_record(Req, request) -> 
            %% Don't care about Req and Rep version diff
	    do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra);

        {Cnt, [Req]} when is_integer(Cnt) andalso is_record(Req, request) -> 
            %% Another process is accessing, handle as unexpected 
	    %% (so it has a possibillity to get logged).
	    ?report_important(ConnData, "trans reply (no receiver)", 
			      [{user_reply,      UserReply}, 
			       {request_lockcnt, Cnt}]),
	    megaco_monitor:request_lockcnt_dec(TransId),
	    return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra);

	%% no counter
        {_Cnt, [Req]} when is_record(Req, request) -> 
            %% The counter does not exist. 
	    %% This can only mean a code upgrade raise condition.
	    %% That is, this request record was created before 
	    %% this feature (the counters) was instroduced.
	    %% The simples solution to this is to behave exactly as
	    %% before, that is, pass it along, and leave it to the 
	    %% user to figure out.
	    ?report_trace(ConnData, 
			  "remote reply - "
			  "code upgrade raise condition", 
			  [{user_reply, UserReply}]),
	    do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra);

	{Cnt, _} when is_integer(Cnt) ->
	    ?report_trace(ConnData, "trans reply (no receiver)", 
			  [{user_reply, UserReply}, {request_lockcnt, Cnt}]),
	    megaco_monitor:request_lockcnt_dec(TransId), 
	    return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra);

	_ ->
	    ?report_trace(ConnData, "remote reply (no receiver)", 
			  [{user_reply, UserReply}]),
	    return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra)
    end.

do_receive_reply_remote(ConnData, TransId, 
			#request{timer_ref    = {_Type, Ref},
				 user_mod     = UserMod, 
				 user_args    = UserArgs,
				 reply_action = Action,
				 reply_data   = UserData} = _Req, 
			UserReply, Extra) ->
    megaco_monitor:delete_request(TransId),
    megaco_monitor:request_lockcnt_del(TransId), 
    _ = megaco_monitor:cancel_apply_after(Ref),           % OTP-4843
    megaco_config:del_pending_counter(recv, TransId), % OTP-7189

    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
				   user_args    = UserArgs,
				   reply_action = Action,
				   reply_data   = UserData},
    return_reply(ConnData2, TransId, UserReply, Extra).


cancel_reply(ConnData, #reply{state     = waiting_for_ack,
			      user_mod  = UserMod,
			      user_args = UserArgs} = Rep, Reason) ->
    ?report_trace(ignore, "cancel reply [waiting_for_ack]", [Rep]),
    _ = megaco_monitor:cancel_apply_after(Rep#reply.pending_timer_ref),
    Serial = (Rep#reply.trans_id)#trans_id.serial,
    ConnData2 = ConnData#conn_data{serial    = Serial,
				   user_mod  = UserMod,
				   user_args = UserArgs},
    T = #'TransactionAck'{firstAck = Serial},
    Extra = ?default_user_callback_extra, 
    handle_ack(ConnData2, {error, Reason}, Rep, T, Extra);

cancel_reply(_ConnData, #reply{state = aborted} = Rep, _Reason) ->
    ?report_trace(ignore, "cancel reply [aborted]", [Rep]),
    #reply{trans_id          = TransId,
	   timer_ref         = ReplyRef,
	   pending_timer_ref = PendingRef} = Rep,
    megaco_monitor:delete_reply(TransId),
    _ = megaco_monitor:cancel_apply_after(ReplyRef),
    _ = megaco_monitor:cancel_apply_after(PendingRef),     % Still running?
    megaco_config:del_pending_counter(sent, TransId),  % Still existing?
    ok;

cancel_reply(_ConnData, Rep, ignore) ->
    ?report_trace(ignore, "cancel reply [ignore]", [Rep]),
    #reply{trans_id          = TransId,
	   timer_ref         = ReplyRef,
	   pending_timer_ref = PendingRef} = Rep,
    megaco_monitor:delete_reply(TransId),
    _ = megaco_monitor:cancel_apply_after(ReplyRef),
    _ = megaco_monitor:cancel_apply_after(PendingRef),     % Still running?
    megaco_config:del_pending_counter(sent, TransId),  % Still existing?
    ok;

cancel_reply(_CD, _Rep, _Reason) ->
    ok.


request_keep_alive_timeout(ConnHandle, TransId) ->
    megaco_config:del_pending_counter(ConnHandle, TransId), 
    _ = megaco_monitor:lookup_request(TransId),
    ok.

    
request_timeout(ConnHandle, TransId) ->
    ?rt1(ConnHandle, "request timeout", [TransId]),
    case megaco_monitor:lookup_request(TransId) of
	[] ->
	    request_not_found_ignore;
	[Req] when is_record(Req, request) ->
	    case megaco_config:lookup_local_conn(ConnHandle) of
		[CD] when (CD#conn_data.cancel =:= true) ->
		    cancel_in_progress_ignore;
		[CD] ->
 		    incNumTimerRecovery(ConnHandle),
 		    do_request_timeout(ConnHandle, TransId, CD, Req);
		[] when ConnHandle#megaco_conn_handle.remote_mid =:= preliminary_mid ->
 		    %% There are two possibillities:
		    %% 1) The connection has just been upgraded from a 
 		    %%    preliminary to a real connection. So this timeout
 		    %%    is just a glitch. E.g. between the removel of this
 		    %%    ConnHandle and the timer.
		    %% 2) The first message sent, the service-change, got no 
		    %%    reply (UDP without three-way-handshake).
		    %%    And then the other side (MGC) sends a request,
		    %%    which causes an auto-upgrade
 		    request_timeout_upgraded(ConnHandle, Req);
		[] ->
 		    incNumTimerRecovery(ConnHandle),
 		    ConnData = fake_conn_data(ConnHandle),
 		    do_request_timeout(ConnHandle, TransId, ConnData, Req)
	    end
    end.

request_timeout_upgraded(ConnHandle, Req) ->
    CD = fake_conn_data(ConnHandle),
    cancel_request(CD, Req, timeout).
    
do_request_timeout(ConnHandle, TransId, ConnData, 
		   #request{curr_timer = CurrTimer} = Req) ->

    ?rt1(ConnHandle, "process request timeout", [TransId, CurrTimer]),

    SendHandle = Req#request.send_handle,
    Version    = Req#request.version,
    ConnData2  = ConnData#conn_data{send_handle      = SendHandle,
				    protocol_version = Version},
    case CurrTimer of
	timeout ->  %%%%%%%
	    cancel_request(ConnData2, Req, timeout),
	    timeout1;

	%% Restartable timer 
	%% (max_retries = infinity_restartable)
	{_, timeout} ->  
	    cancel_request(ConnData2, Req, timeout),
	    timeout2;

	Timer ->
	    {SendOrNoSend, Data} = Req#request.bytes,
	    case SendOrNoSend of
		send ->
		    case maybe_encode(ConnData2, Data) of
			{ok, Bin} ->
			    ?report_trace(ConnData2, "re-send trans request", 
					  [{bytes, Bin}]),
			    case maybe_send_message(ConnData2, true, Bin) of
				ok ->
				    sent1_ignore;
				{ok, _} ->
				    sent2_ignore;
				{error, Reason} ->
				    ?report_important(ConnData2, 
						      "<ERROR> "
						      "re-send trans "
						      "request failed",
						      [{bytes, Bin}, 
						       {error, Reason}])
			    end;

			{error, Reason} ->
			    %% Since it was possible to encode the original
			    %% message this should really never happen...
			    ?report_important(ConnData2, 
					      "<ERROR> "
					      "re-send trans request failed",
					      [{transaction,
						Req#request.bytes}, 
					       {error, Reason}])
		    end;
		no_send ->
		    not_sent_ok
	    end,
	    {WaitFor, Timer2} = megaco_timer:restart(Timer),
	    OptBin            = opt_garb_binary(Timer2, Data),
	    {Type, _}         = Req#request.timer_ref,
	    M = ?MODULE,
	    F = request_timeout,
	    A = [ConnHandle, TransId],
	    Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor),
	    NewFields = 
		[{#request.bytes,      {SendOrNoSend, OptBin}},
		 {#request.timer_ref,  {Type, Ref2}},
		 {#request.curr_timer, Timer2}],
	    megaco_monitor:update_request_fields(TransId, NewFields), % Timing problem
	    {restarted, WaitFor, Timer2}

    end.

maybe_encode(#conn_data{trans_req = false} = CD, {_Serial, Bin}) 
  when is_binary(Bin) ->
    Body = {transactions, [{transactionRequest, Bin}]},
    megaco_messenger_misc:encode_body(CD, "encode trans request msg", Body);
maybe_encode(_CD, {_Serial, Bin} = D) when is_binary(Bin) ->
    {ok, D};
maybe_encode(#conn_data{trans_req    = true,
			trans_sender = Pid} = CD, 
	     #'TransactionRequest'{transactionId = Serial} = TR) 
  when is_pid(Pid) ->
    case megaco_messenger_misc:encode_trans_request(CD, TR) of
	{ok, Bin} ->
	    {ok, {Serial, Bin}};
	Error ->
	    Error
    end;
maybe_encode(CD, TR) 
  when is_record(TR, 'TransactionRequest') ->
    Body = {transactions, [{transactionRequest, TR}]},
    megaco_messenger_misc:encode_body(CD, "encode trans request msg", Body);
maybe_encode(_CD, Trash) ->
    {error, {invalid_bin, Trash}}.

maybe_send_message(CD, Resend, Bin) when is_binary(Bin) ->
    megaco_messenger_misc:send_message(CD, Resend, Bin);
maybe_send_message(#conn_data{trans_sender = Pid}, _Resend, {Serial, Bin}) 
  when is_pid(Pid) andalso is_integer(Serial) andalso is_binary(Bin) ->
    megaco_trans_sender:send_req(Pid, Serial, Bin).

    
reply_timeout(ConnHandle, TransId, timeout) ->
    handle_reply_timer_timeout(ConnHandle, TransId);

%% This means that infinity_restartable was used for max_retries.
%% There is currently no reason to use this for the reply_timeout,
%% since there is no external event to restart the timer!
reply_timeout(ConnHandle, TransId, {_, timeout}) ->
    handle_reply_timer_timeout(ConnHandle, TransId);

reply_timeout(ConnHandle, TransId, Timer) ->
    ?report_trace(ConnHandle, "reply timeout", [Timer, TransId]),

    case lookup_reply(undefined, TransId) of
	[] ->
	    reply_not_found_ignore; 

	{Converted, 
	 #reply{state      = waiting_for_ack, 
		ack_action = {handle_ack, _}} = Rep} ->
	    case megaco_config:lookup_local_conn(ConnHandle) of
		[CD] when (CD#conn_data.cancel =:= true) ->
 		    cancel_in_progress_ignore;
		[CD] when (Converted =:= true) ->
 		    incNumTimerRecovery(ConnHandle),
		    %% When we did the reply record lookup, we had no
		    %% conn_data record, and the reply record was 
		    %% converted. This means that the reply record
		    %% has no valid info about user_mod or user_args.
		    %% Therefor, the user_mod and user_args of the 
		    %% conn_data record is better then nothing.
		    #conn_data{user_mod  = UserMod, 
			       user_args = UserArgs} = CD,
		    Rep2 = Rep#reply{user_mod  = UserMod, 
				     user_args = UserArgs}, 
 		    do_reply_timeout(ConnHandle, TransId, CD, Timer, Rep2);
		[CD] when (Converted =:= false) ->
 		    incNumTimerRecovery(ConnHandle),
 		    do_reply_timeout(ConnHandle, TransId, CD, Timer, Rep);
		[] ->
 		    incNumTimerRecovery(ConnHandle),
 		    CD = fake_conn_data(ConnHandle),
 		    do_reply_timeout(ConnHandle, TransId, CD, Timer, Rep)
	    end;

	{Converted, 
	 #reply{state      = waiting_for_ack, 
		bytes      = Sent} = Rep} when is_list(Sent) ->
	    case megaco_config:lookup_local_conn(ConnHandle) of
		[ConnData] when (Converted =:= true) ->
 		    incNumTimerRecovery(ConnHandle),
		    %% When we did the reply record lookup, we had no
		    %% conn_data record, and the reply record was 
		    %% converted. This means that the reply record
		    %% has no valid info about user_mod or user_args.
		    %% Therefor, the user_mod and user_args of the 
		    %% conn_data record is better then nothing.
		    #conn_data{user_mod  = UserMod, 
			       user_args = UserArgs} = ConnData,
		    Rep2 = Rep#reply{user_mod  = UserMod, 
				     user_args = UserArgs}, 
 		    do_reply_timeout(ConnHandle, TransId, ConnData, 
				     Timer, Rep2);
		[ConnData] when (Converted =:= false) ->
 		    incNumTimerRecovery(ConnHandle),
 		    do_reply_timeout(ConnHandle, TransId, ConnData, 
				     Timer, Rep);
		[] ->
 		    incNumTimerRecovery(ConnHandle),
 		    ConnData = fake_conn_data(ConnHandle),
 		    do_reply_timeout(ConnHandle, TransId, ConnData, 
				     Timer, Rep)
	    end;

	{_Converted, 
	 #reply{state = waiting_for_ack} = Rep} ->
	    do_reply_timeout(ConnHandle, TransId, Timer, Rep);
		    
	{_Converted, 
	 #reply{state = aborted} = Rep} ->
	    do_reply_timeout(ConnHandle, TransId, Timer, Rep);
		    
	_ ->
	    ignore
		
    end.

do_reply_timeout(ConnHandle, TransId, ConnData, Timer, 
		 #reply{send_handle = SH,
			version     = V,
			bytes       = Bytes} = Rep) when is_binary(Bytes) ->

%%     d("do_reply_timeout -> entry with"
%%       "~n   ConnHandle: ~p"
%%       "~n   TransId:    ~p"
%%       "~n   Timer:      ~p"
%%       "~n   Rep:        ~p"
%%       "~n", [ConnHandle, TransId, Timer, Rep]),

    CD = ConnData#conn_data{send_handle      = SH,
			    protocol_version = V},
    
    ?rt1(CD, "re-send trans reply", [{bytes, Bytes}]),
    case megaco_messenger_misc:send_message(CD, true, Bytes) of
	{ok, _} ->
	    ignore;
	{error, Reason} ->
	    ?report_important(CD, "<ERROR> re-send trans reply failed",
			      [{bytes, Bytes}, {error, Reason}])
    end,
    do_reply_timeout(ConnHandle, TransId, Timer, Rep);

do_reply_timeout(ConnHandle, TransId, ConnData, Timer, 
		 #reply{send_handle = SH,
			version     = V,
			bytes       = Sent} = Rep) when is_list(Sent) ->

%%     d("do_reply_timeout -> entry with"
%%       "~n   ConnHandle: ~p"
%%       "~n   TransId:    ~p"
%%       "~n   Timer:      ~p"
%%       "~n   Rep:        ~p"
%%       "~n", [ConnHandle, TransId, Timer, Rep]),

    CD = ConnData#conn_data{send_handle      = SH,
			    protocol_version = V},
    
    ReSend = 
	fun({SN, Bytes}) ->
		?rt1(CD, "re-send segmented trans reply", 
		     [{segment_no, SN}, {bytes, Bytes}]),
		case megaco_messenger_misc:send_message(CD, true, Bytes) of
%% 		    ok ->
%% 			ignore;
		    {ok, _} ->
			ignore;
		    {error, Reason} ->
			?report_important(CD, 
					  "<ERROR> re-send segmented "
					  "trans reply failed",
					  [{segment_no, SN}, 
					   {bytes,      Bytes}, 
					   {error,      Reason}])
		end
	end,
    lists:foreach(ReSend, Sent),
    do_reply_timeout(ConnHandle, TransId, Timer, Rep).

do_reply_timeout(ConnHandle, TransId, Timer, #reply{bytes = Bytes}) ->
    {WaitFor, Timer2} = megaco_timer:restart(Timer),
    OptBin = case Bytes of
		 Bin when is_binary(Bin) ->
		     opt_garb_binary(Timer2, Bin);
		 Sent when is_list(Sent) ->
		     Garb = fun(Bin) -> opt_garb_binary(Timer2, Bin) end,
		     [{SN, Garb(Bin)} || {SN, Bin} <- Sent]
	     end,
    M = ?MODULE,
    F = reply_timeout,
    A = [ConnHandle, TransId, Timer2],
    Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor),
    NewFields = 
	[{#reply.bytes,     OptBin},
	 {#reply.timer_ref, Ref2}],
    megaco_monitor:update_reply_fields(TransId, NewFields), % Timing problem?
    {restarted, WaitFor, Timer2}.

    
handle_reply_timer_timeout(ConnHandle, TransId) ->
    ?report_trace(ConnHandle, "handle reply timeout", [timeout, TransId]),
    incNumTimerRecovery(ConnHandle),
    %% OTP-4378
    case lookup_reply(undefined, TransId) of
	{Converted, 
	 #reply{state = waiting_for_ack} = Rep} ->
	    Serial = (Rep#reply.trans_id)#trans_id.serial,
	    {Rep2, ConnData} = 
		case megaco_config:lookup_local_conn(ConnHandle) of
		    [ConnData0] when (Converted =:= false) ->
			#reply{user_mod  = UserMod,
			       user_args = UserArgs} = Rep, 
			{Rep, 
			 ConnData0#conn_data{user_mod  = UserMod,
					     user_args = UserArgs}};
		    [ConnData0] when (Converted =:= true) ->
			{Rep#reply{user_mod  = ConnData0#conn_data.user_mod,
				   user_args = ConnData0#conn_data.user_args},
			 ConnData0};
		    [] when (Converted =:= false) ->
			ConnData0 = fake_conn_data(ConnHandle),
			#reply{user_mod  = UserMod,
			       user_args = UserArgs} = Rep, 
			{Rep, 
			 ConnData0#conn_data{user_mod  = UserMod,
					     user_args = UserArgs}};
		    [] when (Converted =:= true) ->
			%% We have no valid info about user_mod and user_args 
			{Rep, fake_conn_data(ConnHandle)}
		end,
	    ConnData2 = ConnData#conn_data{serial = Serial},
	    T = #'TransactionAck'{firstAck = Serial},
	    Extra = ?default_user_callback_extra, 
	    handle_ack(ConnData2, {error, timeout}, Rep2, T, Extra);

	{_Converted, 
	 #reply{pending_timer_ref = Ref,              % aborted?
		bytes             = SegSent}} ->      % may be a binary
	    _ = megaco_monitor:cancel_apply_after(Ref),
	    cancel_segment_timers(SegSent), 
	    megaco_monitor:delete_reply(TransId),
	    megaco_config:del_pending_counter(sent, TransId);
	[] ->
	    ignore_reply_removed
    end.
    
%% segment_reply_timeout(ConnHandle, TransId, SN, timeout) ->
%%     ?report_trace(ConnHandle, "segment reply timeout", [timeout, SN, TransId]),
%%     D = fun({_, _, SegRef}) ->
%% 		megaco_monitor:cancel_apply_after(SegRef)
%% 	end,
%%     incNumTimerRecovery(ConnHandle),
%%     %% OTP-4378
%%     case megaco_monitor:lookup_reply(TransId) of
%% 	[#reply{state = waiting_for_ack,
%% 		bytes = Sent} = Rep] ->
%% 	    Serial = (Rep#reply.trans_id)#trans_id.serial,
%% 	    ConnData = 
%% 		case megaco_config:lookup_local_conn(ConnHandle) of
%% 		    [ConnData0] ->
%% 			ConnData0;
%% 		    [] ->
%% 			fake_conn_data(ConnHandle)
%% 		end,
%% 	    ConnData2 = ConnData#conn_data{serial = Serial},
%% 	    T = #'TransactionAck'{firstAck = Serial},
%% 	    lists:foreach(D, Sent),
%%          Extra = ?default_user_callback_extra, 
%% 	    handle_ack(ConnData2, {error, timeout}, Rep, T, Extra);
%% 	[#reply{pending_timer_ref = Ref,
%% 		bytes             = Sent}] -> % aborted?
%% 	    lists:foreach(D, Sent),
%% 	    megaco_monitor:cancel_apply_after(Ref),
%% 	    megaco_monitor:delete_reply(TransId),
%% 	    megaco_config:del_pending_counter(sent, TransId);
	
%% 	[] ->
%% 	    ignore

%%     end.

%% segment_reply_timeout(ConnHandle, TransId, SN, Timer) ->
%%     ?report_trace(ConnHandle, "reply timeout", [Timer, SN, TransId]),

%% %%     d("reply_timeout -> entry with"
%% %%       "~n   ConnHandle: ~p"
%% %%       "~n   TransId:    ~p"
%% %%       "~n   Timer:      ~p", [ConnHandle, TransId, Timer]),

%%     case megaco_monitor:lookup_reply(TransId) of
%% 	[] ->
%% 	    ignore; % Trace ??

%% 	[#reply{state      = waiting_for_ack, 
%% 		bytes      = ack_action = {handle_ack, _}} = Rep] ->
%% 	    case megaco_config:lookup_local_conn(ConnHandle) of
%% 		[ConnData] ->
%%  		    incNumTimerRecovery(ConnHandle),
%%  		    do_reply_timeout(ConnHandle, TransId, ConnData, 
%% 				     Timer, Rep);
%% 		[] ->
%%  		    incNumTimerRecovery(ConnHandle),
%%  		    ConnData = fake_conn_data(ConnHandle),
%%  		    do_reply_timeout(ConnHandle, TransId, ConnData, 
%% 				     Timer, Rep)
%% 	    end;

%% 	[#reply{state = waiting_for_ack} = Rep] ->
%% 	    do_reply_timeout(ConnHandle, TransId, Timer, Rep);
		    
%% 	[#reply{state = aborted} = Rep] ->
%% 	    do_reply_timeout(ConnHandle, TransId, Timer, Rep);
		    
%% 	_ ->
%% 	    ignore
		
%%     end.


%% This clause is to catch the timers started prior to the code-upgrade
pending_timeout(#conn_data{conn_handle = CH}, TransId, Timer) ->
    ?report_trace(CH, "pending timeout(1)", [Timer, TransId]),
    pending_timeout(CH, TransId, Timer);

pending_timeout(ConnHandle, TransId, Timer) ->
    ?report_trace(ConnHandle, "pending timeout(2)", [Timer, TransId]),
    case megaco_config:lookup_local_conn(ConnHandle) of
	[CD] when (CD#conn_data.cancel == true) ->
	    cancel_in_progress_ignore;
	[CD] ->
	    Serial = TransId#trans_id.serial, 
	    handle_pending_timeout(CD#conn_data{serial = Serial}, 
				   TransId, Timer);
	[] ->
	    no_such_connection_ignore
    end.

handle_pending_timeout(CD, TransId, Timer) ->
    ?report_trace(CD, "handle pending timeout", []),
    case lookup_reply(CD, TransId) of
	{_Converted, 
	 #reply{state   = State,
		handler = Pid} = Rep} when (State =:= prepare) orelse  
					   (State =:= eval_request) ->

	    #conn_data{sent_pending_limit = Limit,
		       conn_handle        = ConnHandle} = CD,

	    %% ------------------------------------------
	    %% 
	    %%   Check pending limit
	    %% 
	    %% ------------------------------------------

	    case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of
		ok ->

		    %% ---------------------------------------------
		    %% 
		    %%   1) Send pending message
		    %%   2) Possibly restart the pending timer
		    %% 
		    %% ---------------------------------------------

		    _ = send_pending(CD),
		    case Timer of
			timeout ->
			    %% We are done
			    incNumTimerRecovery(ConnHandle),
			    timeout1;
			{_, timeout} ->
			    %% We are done
			    incNumTimerRecovery(ConnHandle),
			    timeout2;
			_ ->
			    {WaitFor, Timer2} = megaco_timer:restart(Timer),
			    M = ?MODULE,
			    F = pending_timeout,
			    A = [ConnHandle, TransId, Timer2],
			    PendingRef = 
				megaco_monitor:apply_after(M, F, A, WaitFor),
			    %% Timing problem?
			    megaco_monitor:update_reply_field(TransId, 
							      #reply.pending_timer_ref,
							      PendingRef),
			    {restarted, WaitFor, Timer2}
		    end;


		error ->

		    %% ------------------------------------------
		    %% 
		    %%   1) Send 506 error message to other side
		    %%   2) Notify user
		    %%   3) Set reply data in aborted state
		    %% 
		    %% -------------------------------------------

		    send_pending_limit_error(CD),
		    handle_request_abort_callback(CD, TransId, Pid),
		    %% Timing problem?
		    Rep2 = Rep#reply{state = aborted},
		    cancel_reply(CD, Rep2, aborted),
		    pending_limit_error;


		aborted ->

		    %% ------------------------------------------
		    %% 
		    %%   Pending limit already passed 
		    %% 
		    %% -------------------------------------------
		    Rep2 = Rep#reply{state = aborted},
		    cancel_reply(CD, Rep2, aborted),
		    pending_limit_aborted

	    end;
	[] ->
	    reply_not_found; % Trace ??

	{_Converted, 
	 #reply{state = waiting_for_ack}} ->
	    %% The reply has already been sent
	    %% No need for any pending trans reply
	    reply_has_been_sent;

	{_Converted, 
	 #reply{state = aborted} = Rep} ->
	    %% glitch, but cleanup just the same
	    cancel_reply(CD, Rep, aborted),
	    reply_aborted_state

    end.


segment_timeout(ConnHandle, TransId, timeout = Timer) ->
    ?report_trace(ConnHandle, "segment timeout", [TransId, Timer]),
    incNumTimerRecovery(ConnHandle),
    case megaco_monitor:lookup_request(TransId) of
	[] ->
	    timeout_not_found_ignore;
	
	[#request{seg_recv = Segs} = Req] ->
	    ConnData = 
		case megaco_config:lookup_local_conn(ConnHandle) of
		    [ConnData0] ->
			ConnData0;
		    [] ->
			fake_conn_data(ConnHandle)
		end,
	    Last = lists:last(lists:sort(Segs)),
	    All  = lists:seq(1,Last),
	    case All -- Segs of
		[] ->
		    %% The last segment has just arrived, ignore
		    ok;
		Missing ->
		    %% Send the error message
		    Code   = ?megaco_segments_not_received, 
		    Reason = missing_to_str(Missing),
		    send_message_error(ConnData, Code, Reason),

		    %% Report to the user
		    UserMod   = Req#request.user_mod,
		    UserArgs  = Req#request.user_args,
		    Action    = Req#request.reply_action,
		    UserData  = Req#request.reply_data,
		    UserReply = {error, {segment_timeout, Missing}},
		    ConnData2 = ConnData#conn_data{user_mod     = UserMod,
						   user_args    = UserArgs,
						   reply_action = Action,
						   reply_data   = UserData},
		    return_reply(ConnData2, TransId, UserReply)
	    end
    end;

segment_timeout(ConnHandle, TransId, Timer) ->
    ?report_trace(ConnHandle, "segment timeout", [TransId, Timer]),
    case megaco_monitor:lookup_request_field(TransId, #request.trans_id) of
	{ok, _} ->
	    {WaitFor, Timer2} = megaco_timer:restart(Timer),
	    M = ?MODULE,
	    F = segment_timeout,
	    A = [ConnHandle, TransId, Timer2],
	    Ref = megaco_monitor:apply_after(M, F, A, WaitFor),
	    %% Timing problem?
	    megaco_monitor:update_request_field(TransId, 
						#request.seg_timer_ref,
						Ref),
	    {restarted, WaitFor, Timer2};
	_ ->
	    not_found_ignore
    end.

%% segment_reply_timeout() ->
%%     ok.

missing_to_str(Missing) ->
    lists:flatten(missing_to_str2(Missing)).

missing_to_str2([X]) ->
    [integer_to_list(X)];
missing_to_str2([H|T]) ->
    [integer_to_list(H) , "," | missing_to_str2(T)].

return_unexpected_trans_reply(ConnData, TransId, 
			      {actionReplies, _} = UserReply, Extra) ->
    Trans = make_transaction_reply(ConnData, TransId, UserReply),
    return_unexpected_trans(ConnData, Trans, Extra);
return_unexpected_trans_reply(ConnData, TransId, 
			      {transactionError, _} = UserReply, Extra) ->
    Trans = make_transaction_reply(ConnData, TransId, UserReply),
    return_unexpected_trans(ConnData, Trans, Extra);
return_unexpected_trans_reply(CD, TransId, {error, Reason}, Extra) ->
    ?report_important(CD, "unexpected trans reply with error", 
		      [TransId, Reason, Extra]),
    ok;
return_unexpected_trans_reply(CD, TransId, Crap, Extra) ->
    ?report_important(CD, "unexpected trans reply with crap", 
		      [TransId, Crap, Extra]),
    ok.

return_unexpected_trans(ConnData, Trans) ->
    Extra = ?default_user_callback_extra, 
    return_unexpected_trans(ConnData, Trans, Extra).

return_unexpected_trans(ConnData, Trans0, Extra) ->
    UserMod    = ConnData#conn_data.user_mod,
    UserArgs   = ConnData#conn_data.user_args,
    ConnHandle = ConnData#conn_data.conn_handle,
    Version    = ConnData#conn_data.protocol_version,
    Trans      = transform_transaction_reply_enc(Version, Trans0),
    Args       = 
	case Extra of
	    ?default_user_callback_extra ->
		[ConnHandle, Version, Trans | UserArgs];
	    _ ->
		[ConnHandle, Version, Trans, Extra | UserArgs]
	end,
    Res = (catch apply(UserMod, handle_unexpected_trans, Args)),
    ?report_debug(ConnData, "return: unexpected trans", 
		  [Trans, {return, Res}]),
    case Res of
	ok ->
	    ok;
	_ ->
	    warning_msg("unexpected transaction callback failed: ~w", [Res]),
	    ok
    end,
    Res.
    

%%-----------------------------------------------------------------

to_remote_trans_id(#conn_data{conn_handle = CH, serial = Serial}) ->
    Mid = CH#megaco_conn_handle.remote_mid,
    #trans_id{mid = Mid, serial = Serial}.

to_local_trans_id(#conn_data{conn_handle = CH, serial = Serial}) ->
    Mid = CH#megaco_conn_handle.local_mid,
    #trans_id{mid = Mid, serial = Serial}.

to_local_trans_id(#conn_data{conn_handle = CH}, [S|_] = Serials) 
  when is_integer(S) ->
    Mid = CH#megaco_conn_handle.local_mid, 
    [#trans_id{mid = Mid, serial = Serial} || Serial <- Serials];
to_local_trans_id(#conn_data{conn_handle = CH}, 
		  [{transactionRequest, TR}|_] = TRs) 
  when is_record(TR, 'TransactionRequest') ->
    Mid = CH#megaco_conn_handle.local_mid, 
    [#trans_id{mid = Mid, serial = Serial} || 
	{transactionRequest, 
	 #'TransactionRequest'{transactionId = Serial}} <- TRs];

to_local_trans_id(#megaco_conn_handle{local_mid = Mid}, Serial) 
  when is_integer(Serial) ->
    #trans_id{mid = Mid, serial = Serial};
to_local_trans_id(#conn_data{conn_handle = CH}, Serial) 
  when is_integer(Serial) ->
    Mid = CH#megaco_conn_handle.local_mid, 
    #trans_id{mid = Mid, serial = Serial}.    
    

%%-----------------------------------------------------------------

transform_transaction_reply_dec({'TransactionReply', 
				 TransId, IAR, TransRes}) ->
    #megaco_transaction_reply{transactionId     = TransId, 
			      immAckRequired    = IAR, 
			      transactionResult = TransRes};
transform_transaction_reply_dec({'TransactionReply', 
				 TransId, IAR, TransRes,
				 SegNo, SegComplete}) ->
    #megaco_transaction_reply{transactionId        = TransId, 
			      immAckRequired       = IAR, 
			      transactionResult    = TransRes,
			      segmentNumber        = SegNo,
			      segmentationComplete = SegComplete}.

transform_transaction_reply_enc(
  3,
  #megaco_transaction_reply{transactionId        = TransId, 
			    immAckRequired       = IAR, 
			    transactionResult    = TransRes,
			    segmentNumber        = SegNo,
			    segmentationComplete = SegComplete}) ->
    {'TransactionReply', TransId, IAR, TransRes, SegNo, SegComplete};
transform_transaction_reply_enc(
  Version,
  #megaco_transaction_reply{transactionId        = TransId, 
			    immAckRequired       = IAR, 
			    transactionResult    = TransRes}) 
  when (Version < 3) ->
    {'TransactionReply', TransId, IAR, TransRes};
transform_transaction_reply_enc(_, TR) ->
    TR.
  
make_transaction_reply(#conn_data{protocol_version = Version}, 
		       TransId, TransRes) ->
    make_transaction_reply(Version, TransId, asn1_NOVALUE, TransRes).

%% make_transaction_reply(#conn_data{protocol_version = Version}, 
%% 		       TransId, IAR, TransRes) ->
%%     make_transaction_reply(Version, TransId, IAR, TransRes);

make_transaction_reply(3, TransId, IAR, TransRes) ->
    {'TransactionReply', TransId, IAR, TransRes, asn1_NOVALUE, asn1_NOVALUE};
make_transaction_reply(_, TransId, IAR, TransRes) ->
    {'TransactionReply', TransId, IAR, TransRes}.

  
%%-----------------------------------------------------------------

%% This function is used as a wrapper for reply-record lookups.
%% The intention is that during upgrade, this function
%% can perform on-the-fly conversions of reply-records.
lookup_reply(CD, TransId) ->
    case megaco_monitor:lookup_reply(TransId) of
	[#reply{} = Rep] ->
	    {false, Rep};

	%% Old (pre-3.13.1) version of the record => Convert to new version
	[{reply, TransId, 
	  LocalMid, State, PendingTmrRef, Handler, TimerRef, 
	  Version, Bytes, AckAction, SendHandle, Segments}] 
	when is_record(CD, conn_data) ->
	    #conn_data{user_mod  = UserMod, 
		       user_args = UserArgs} = CD,
	    Rep = #reply{trans_id          = TransId,
			 local_mid         = LocalMid,
			 state             = State, 
			 pending_timer_ref = PendingTmrRef,
			 handler           = Handler, 
			 timer_ref         = TimerRef,
			 version           = Version,
			 bytes             = Bytes, 
			 ack_action        = AckAction, 
			 send_handle       = SendHandle,
			 segments          = Segments, 
			 user_mod          = UserMod,
			 user_args         = UserArgs},
	    {true, Rep};

	%% Old (pre-3.13.1) version of the record => Convert to new version
	[{reply, TransId, 
	  LocalMid, State, PendingTmrRef, Handler, TimerRef, 
	  Version, Bytes, AckAction, SendHandle, Segments}] ->
	    %% ConnData is not known here, so ignore for now
	    Rep = #reply{trans_id          = TransId,
			 local_mid         = LocalMid,
			 state             = State, 
			 pending_timer_ref = PendingTmrRef,
			 handler           = Handler, 
			 timer_ref         = TimerRef,
			 version           = Version,
			 bytes             = Bytes, 
			 ack_action        = AckAction, 
			 send_handle       = SendHandle,
			 segments          = Segments},
	    {true, Rep};

	Else ->
	    Else
    end.


%%-----------------------------------------------------------------

%%-----------------------------------------------------------------
%% info_msg(F, A) ->
%%     ?megaco_info(F, A).

warning_msg(F, A) ->
    ?megaco_warning(F, A).

error_msg(F, A) ->
    ?megaco_error(F, A).


%%-----------------------------------------------------------------

%% d(F) ->
%%     d(F,[]).
%% 
%% d(F,A) ->
%%     d(true,F,A).
%%     %% d(get(dbg),F,A).
%% 
%% d(true,F,A) ->
%%     io:format("*** [~s] ~p:~p ***"
%% 	      "~n   " ++ F ++ "~n", 
%% 	      [format_timestamp(now()), self(),?MODULE|A]);
%% d(_, _, _) ->
%%     ok.
%% 
%% format_timestamp({_N1, _N2, N3} = Now) ->
%%     {Date, Time}   = calendar:now_to_datetime(Now),
%%     {YYYY,MM,DD}   = Date,
%%     {Hour,Min,Sec} = Time,
%%     FormatDate = 
%%         io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
%%                       [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),  
%%     lists:flatten(FormatDate).

%% Time in milli seconds
t() ->
    {A,B,C} = os:timestamp(),
    A*1000000000+B*1000+(C div 1000).

	      
%%-----------------------------------------------------------------
%% Func: incNumErrors/0, incNumErrors/1, incNumTimerRecovery/1
%% Description: SNMP counter increment functions
%%-----------------------------------------------------------------
incNumErrors() ->
    incNum(medGwyGatewayNumErrors).

incNumErrors(CH) ->
    incNum({CH, medGwyGatewayNumErrors}).

incNumTimerRecovery(CH) ->
    incNum({CH, medGwyGatewayNumTimerRecovery}).

incNum(Cnt) ->
    case (catch ets:update_counter(megaco_stats, Cnt, 1)) of
	{'EXIT', {badarg, _Reason}} ->
	    ets:insert(megaco_stats, {Cnt, 1});
	Old ->
	    Old
    end.
	    

