%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2024. 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: Handle configuration of Megaco/H.248
%%----------------------------------------------------------------------

-module(megaco_config).
-moduledoc false.

-behaviour(gen_server).

%% Application internal exports
-export([
         start_link/0,
         stop/0,

         start_user/2,
         stop_user/1,

         user_info/2,
         update_user_info/3,
         conn_info/2,
         update_conn_info/3,
         system_info/1,

         %% incr_counter/2,
         incr_trans_id_counter/1,
         incr_trans_id_counter/2,

	 %% Verification functions
         verify_val/2,
%% 	 verify_strict_uint/1,
%% 	 verify_strict_int/1, verify_strict_int/2, 
%% 	 verify_uint/1,
%% 	 verify_int/1, verify_int/2,
         

	 %% Reply limit counter
	 cre_reply_counter/2,
	 get_reply_counter/2,
	 incr_reply_counter/2,
	 del_reply_counter/2,

	 %% Pending limit counter
	 cre_pending_counter/3,
	 get_pending_counter/2,
	 incr_pending_counter/2,
	 del_pending_counter/2,
	 %% Backward compatibillity functions (to be removed in later versions)
	 cre_pending_counter/1,  
	 get_pending_counter/1,  
	 incr_pending_counter/1, 
	 del_pending_counter/1,  

         lookup_local_conn/1,
         connect/4, finish_connect/4,
         autoconnect/4,
         disconnect/1,
	 connect_remote/3,
	 disconnect_remote/2,
	 init_conn_data/4, 

	 trans_sender_exit/2

        ]).


%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-define(SERVER, ?MODULE).
-record(state, {parent_pid}).

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


-ifdef(MEGACO_TEST_CODE).
-define(megaco_test_init(),
	(catch ets:new(megaco_test_data, [set, public, named_table]))).
-else.
-define(megaco_test_init(),
	ok).
-endif.

-define(TID_CNT(LMID), {LMID, trans_id_counter}).


%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

start_link() ->
    ?d("start_link -> entry", []),
    gen_server:start_link({local, ?SERVER}, ?MODULE, [self()], []).

stop() ->
    ?d("stop -> entry", []),
    call({stop, self()}).

start_user(UserMid, Config) ->
    call({start_user, UserMid, Config}).

stop_user(UserMid) ->
    call({stop_user, UserMid}).

user_info(UserMid, all) ->
    All0 = ets:match_object(megaco_config, {{UserMid, '_'}, '_'}),
    All1 = [{Item, Val} || {{_, Item}, Val} <- All0, Item /= trans_sender],
    case lists:keysearch(trans_id_counter, 1, All1) of
	{value, {_, Val}} ->
	    lists:keyreplace(trans_id_counter, 1, All1, {trans_id, Val});
	false when UserMid /= default ->
	    [{trans_id, undefined_serial}|All1];
	false ->
	    All1
    end;
user_info(UserMid, receive_handle) ->
    case call({receive_handle, UserMid}) of
	{ok, RH} ->
	    RH;
	{error, Reason} ->
	    exit(Reason)
    end;
user_info(UserMid, conn_data) ->
    HandlePat = #megaco_conn_handle{local_mid = UserMid, remote_mid = '_'},
    Pat = #conn_data{conn_handle      	  = HandlePat,
                     serial           	  = '_',
                     max_serial       	  = '_',
                     request_timer    	  = '_',
                     long_request_timer   = '_',

                     auto_ack         	  = '_',

                     trans_ack   	  = '_',
                     trans_ack_maxcount	  = '_',

                     trans_req   	  = '_',
                     trans_req_maxcount	  = '_',
                     trans_req_maxsize	  = '_',

                     trans_timer   	  = '_',
                     trans_sender         = '_',

                     pending_timer     	  = '_',
                     sent_pending_limit   = '_',
                     recv_pending_limit   = '_',
                     reply_timer      	  = '_',
                     control_pid      	  = '_',
                     monitor_ref      	  = '_',
                     send_mod         	  = '_',
                     send_handle      	  = '_',
                     encoding_mod     	  = '_',
                     encoding_config  	  = '_',
                     protocol_version  	  = '_',
                     auth_data         	  = '_',
                     user_mod         	  = '_',
                     user_args         	  = '_',
                     reply_action     	  = '_',
                     reply_data       	  = '_',
		     threaded       	  = '_',
		     strict_version   	  = '_',
		     long_request_resend  = '_',
		     call_proxy_gc_timeout = '_',
		     cancel               = '_',
		     resend_indication    = '_',
		     segment_reply_ind 	  = '_',
		     segment_recv_acc 	  = '_',
		     segment_recv_timer	  = '_',
		     segment_send   	  = '_',
		     segment_send_timer	  = '_',
		     max_pdu_size   	  = '_',
		     request_keep_alive_timeout = '_'
		    },
    %% ok = io:format("PATTERN: ~p~n", [Pat]),
    ets:match_object(megaco_local_conn, Pat);
user_info(UserMid, connections) ->
    [C#conn_data.conn_handle || C <- user_info(UserMid, conn_data)];
user_info(UserMid, mid) ->
    ets:lookup_element(megaco_config, {UserMid, mid}, 2);
user_info(UserMid, orig_pending_limit) ->
    user_info(UserMid, sent_pending_limit);
user_info(UserMid, trans_id) ->
    case (catch user_info(UserMid, trans_id_counter)) of
	{'EXIT', _} ->
	    %% There is only two cases where this can occure:
	    %% 1) The user does not exist
	    %% 2) Called before the first message is sent, use 
	    %%    undefined_serial, since there is no 
	    %%    "current transaction id"
	    case (catch user_info(UserMid, mid)) of
		{'EXIT', _} ->
		    %% case 1:
		    exit({no_such_user, UserMid});
		_ ->
		    undefined_serial
	    end;
	Else ->
	    Else
    end;
user_info(UserMid, Item) ->
    ets:lookup_element(megaco_config, {UserMid, Item}, 2).

update_user_info(UserMid, orig_pending_limit, Val) ->
    update_user_info(UserMid, sent_pending_limit, Val);
update_user_info(UserMid, Item, Val) ->
    call({update_user_info, UserMid, Item, Val}).


conn_info(Data, Item) ->
    %% The purpose of this is a compiler optimization...
    %% Args are processed from left to right.
    do_conn_info(Item, Data).

do_conn_info(mid = _Item, #megaco_conn_handle{local_mid = Mid}) ->
    Mid;
do_conn_info(local_mid = _Item, #megaco_conn_handle{local_mid = LMid}) ->
    LMid;
do_conn_info(remote_mid = _Item, #megaco_conn_handle{remote_mid = RMid}) ->
    RMid;
do_conn_info(conn_handle = _Item, CH) when is_record(CH, megaco_conn_handle) ->
    CH;
do_conn_info(conn_data = _Item, CH) when is_record(CH, megaco_conn_handle) ->
    case lookup_local_conn(CH) of
	[] ->
	    exit({no_such_connection, CH});
	[ConnData] ->
	    ConnData
    end;
do_conn_info(Item, CH) when is_record(CH, megaco_conn_handle) ->
    case lookup_local_conn(CH) of
	[] ->
	    exit({no_such_connection, CH});
	[ConnData] ->
	    do_conn_info(Item, ConnData)
    end;

do_conn_info(cancel = _Item, #conn_data{conn_handle = CH}) ->
    %% To minimise raise-condition propabillity,
    %% we always look in the table instead of
    %% in the record for this one
    ets:lookup_element(megaco_local_conn, CH, #conn_data.cancel);
do_conn_info(cancel = _Item, CH) when is_record(CH, megaco_conn_handle) ->
    %% To minimise raise-condition propabillity,
    %% we always look in the table instead of
    %% in the record for this one
    ets:lookup_element(megaco_local_conn, CH, #conn_data.cancel);

do_conn_info(all = _Item, 
	     #conn_data{conn_handle                = CH,
			serial                     = TransId,
			max_serial                 = MaxTransId,
			request_timer              = ReqTmr,
			long_request_timer         = LongReqTmr,
			auto_ack                   = AutoAck,
			trans_ack                  = TransAck,
			trans_ack_maxcount         = TransAckMaxCount,
			trans_req                  = TransReq, 
			trans_req_maxcount         = TransReqMaxCount, 
			trans_req_maxsize          = TransReqMaxSz, 
			trans_timer                = TransTmr, 
			%% trans_sender,   
			pending_timer              = PendingTmr,
			sent_pending_limit         = SentPendingLimit, 
			recv_pending_limit         = RecvPendingLimit, 
			reply_timer                = ReplyTmr,
			control_pid                = CtrlPid,
			monitor_ref                = MonRef,
			send_mod                   = SendMod,
			send_handle                = SendHandle,
			encoding_mod               = EncodingMod,
			encoding_config            = EncodingConf,
			protocol_version           = ProtoVersion,
			auth_data                  = AuthData,
			user_mod                   = UserMod,
			user_args                  = UserArgs,
			reply_action               = ReplyAction, 
			reply_data                 = ReplyData, 
			threaded                   = Threaded, 
			strict_version             = StrictVersion, 
			long_request_resend        = LongReqResend, 
			call_proxy_gc_timeout      = CallProxyGCTimeout, 
			%% cancel, 
			resend_indication          = ResendInd, 
			segment_reply_ind          = SegReplyInd, 
			segment_recv_acc           = SegRecvAcc, 
			segment_recv_timer         = SegRecvTmr, 
			segment_send               = SegSend, 
			segment_send_timer         = SegSendTmr, 
			max_pdu_size               = MaxPduSz, 
			request_keep_alive_timeout = RequestKeepAliveTmr}) ->
    [{conn_handle,                CH}, 
     {trans_id,                   TransId}, 
     {max_trans_id,               MaxTransId},
     {request_timer,              ReqTmr},
     {long_request_timer,         LongReqTmr},
     {mid,                        CH#megaco_conn_handle.local_mid},
     {local_mid,                  CH#megaco_conn_handle.local_mid},
     {remote_mid,                 CH#megaco_conn_handle.remote_mid},
     {auto_ack,                   AutoAck},
     {trans_ack,                  TransAck},
     {trans_ack_maxcount,         TransAckMaxCount},
     {trans_req,                  TransReq}, 
     {trans_req_maxcount,         TransReqMaxCount}, 
     {trans_req_maxsize,          TransReqMaxSz}, 
     {trans_timer,                TransTmr}, 
     {pending_timer,              PendingTmr},
     {sent_pending_limit,         SentPendingLimit}, 
     {recv_pending_limit,         RecvPendingLimit}, 
     {reply_timer,                ReplyTmr},
     {control_pid,                CtrlPid},
     {monitor_ref,                MonRef},
     {send_mod,                   SendMod},
     {send_handle,                SendHandle},
     {encoding_mod,               EncodingMod},
     {encoding_config,            EncodingConf},
     {protocol_version,           ProtoVersion},
     {auth_data,                  AuthData},
     {user_mod,                   UserMod},
     {user_args,                  UserArgs},
     {reply_action,               ReplyAction}, 
     {reply_data,                 ReplyData}, 
     {threaded,                   Threaded}, 
     {strict_version,             StrictVersion}, 
     {long_request_resend,        LongReqResend}, 
     {call_proxy_gc_timeout,      CallProxyGCTimeout}, 
     {resend_indication,          ResendInd}, 
     {segment_reply_ind,          SegReplyInd}, 
     {segment_recv_acc,           SegRecvAcc}, 
     {segment_recv_timer,         SegRecvTmr}, 
     {segment_send,               SegSend}, 
     {segment_send_timer,         SegSendTmr}, 
     {max_pdu_size,               MaxPduSz}, 
     {request_keep_alive_timeout, RequestKeepAliveTmr}];

do_conn_info(conn_data = _Item, CD) ->
    CD;
do_conn_info(conn_handle = _Item, #conn_data{conn_handle = Val}) ->
    Val;
do_conn_info(mid = _Item, 
	     #conn_data{conn_handle = #megaco_conn_handle{local_mid = Val}}) ->
    Val;
do_conn_info(local_mid = _Item, 
	     #conn_data{conn_handle = #megaco_conn_handle{local_mid = Val}}) ->
    Val;
do_conn_info(remote_mid = _Item, 
	     #conn_data{conn_handle = #megaco_conn_handle{remote_mid = Val}}) ->
    Val;
do_conn_info(trans_id = _Item, 
	     #conn_data{conn_handle = #megaco_conn_handle{local_mid = LMid},
			max_serial  = Max}) ->
    Item2 = {LMid, trans_id_counter},
    case (catch ets:lookup(megaco_config, Item2)) of
	{'EXIT', _} ->
	    undefined_serial;
	[] ->
	    user_info(LMid, min_trans_id);
	[{_, Serial}] ->
	    if
		((Max =:= infinity) andalso 
		 is_integer(Serial) andalso 
		 (Serial < 4294967295)) ->
		    Serial + 1;
		((Max =:= infinity) andalso  
		 is_integer(Serial) andalso 
		 (Serial =:= 4294967295)) ->
		    user_info(LMid, min_trans_id);
		Serial < Max ->
		    Serial  + 1;
		Serial =:= Max ->
		    user_info(LMid, min_trans_id);
		Serial =:= 4294967295 ->
		    user_info(LMid, min_trans_id);
		true ->
		    undefined_serial
	    end
    end;
do_conn_info(max_trans_id = _Item, #conn_data{max_serial = Val}) ->
    Val;
do_conn_info(request_timer = _Item, #conn_data{request_timer = Val}) ->
    Val;
do_conn_info(long_request_timer = _Item, #conn_data{long_request_timer = Val}) ->
    Val;
do_conn_info(auto_ack = _Item, #conn_data{auto_ack = Val}) ->
    Val;
do_conn_info(trans_ack = _Item, #conn_data{trans_ack = Val}) ->
    Val;
do_conn_info(trans_ack_maxcount = _Item, #conn_data{trans_ack_maxcount = Val}) ->
    Val;
do_conn_info(trans_req = _Item, #conn_data{trans_req = Val}) ->
    Val;
do_conn_info(trans_req_maxcount = _Item, #conn_data{trans_req_maxcount = Val}) ->
    Val;
do_conn_info(trans_req_maxsize = _Item, #conn_data{trans_req_maxsize = Val}) ->
    Val;
do_conn_info(trans_timer = _Item, #conn_data{trans_timer = Val}) ->
    Val;
do_conn_info(pending_timer = _Item, #conn_data{pending_timer = Val}) ->
    Val;
do_conn_info(orig_pending_limit = _Item, #conn_data{sent_pending_limit = Val}) ->
    Val;
do_conn_info(sent_pending_limit = _Item, #conn_data{sent_pending_limit = Val}) ->
    Val;
do_conn_info(recv_pending_limit = _Item, #conn_data{recv_pending_limit = Val}) ->
    Val;
do_conn_info(reply_timer = _Item, #conn_data{reply_timer = Val}) ->
    Val;
do_conn_info(control_pid = _Item, #conn_data{control_pid = Val}) ->
    Val;
do_conn_info(send_mod = _Item, #conn_data{send_mod = Val}) ->
    Val;
do_conn_info(send_handle = _Item, #conn_data{send_handle = Val}) ->
    Val;
do_conn_info(encoding_mod = _Item, #conn_data{encoding_mod = Val}) ->
    Val;
do_conn_info(encoding_config = _Item, #conn_data{encoding_config = Val}) ->
    Val;
do_conn_info(protocol_version = _Item, #conn_data{protocol_version = Val}) ->
    Val;
do_conn_info(auth_data = _Item, #conn_data{auth_data = Val}) ->
    Val;
do_conn_info(user_mod = _Item, #conn_data{user_mod = Val}) ->
    Val;
do_conn_info(user_args = _Item, #conn_data{user_args = Val}) ->
    Val;
do_conn_info(reply_action = _Item, #conn_data{reply_action = Val}) ->
    Val;
do_conn_info(reply_data = _Item, #conn_data{reply_data = Val}) ->
    Val;
do_conn_info(threaded = _Item, #conn_data{threaded = Val}) ->
    Val;
do_conn_info(strict_version = _Item, #conn_data{strict_version = Val}) ->
    Val;
do_conn_info(long_request_resend = _Item, 
	     #conn_data{long_request_resend = Val}) ->
    Val;
do_conn_info(call_proxy_gc_timeout = _Item, 
	     #conn_data{call_proxy_gc_timeout = Val}) ->
    Val;
do_conn_info(resend_indication = _Item, #conn_data{resend_indication = Val}) ->
    Val;
do_conn_info(segment_reply_ind = _Item, #conn_data{segment_reply_ind = Val}) ->
    Val;
do_conn_info(segment_recv_acc = _Item, #conn_data{segment_recv_acc = Val}) ->
    Val;
do_conn_info(segment_recv_timer = _Item, 
	     #conn_data{segment_recv_timer = Val}) ->
    Val;
do_conn_info(segment_send = _Item, #conn_data{segment_send = Val}) ->
    Val;
do_conn_info(segment_send_timer = _Item, 
	     #conn_data{segment_send_timer = Val}) ->
    Val;
do_conn_info(max_pdu_size = _Item, #conn_data{max_pdu_size = Val}) ->
    Val;
do_conn_info(request_keep_alive_timeout = _Item, 
	     #conn_data{request_keep_alive_timeout = Val}) ->
    Val;
do_conn_info(receive_handle = _Item, 
	     #conn_data{conn_handle = #megaco_conn_handle{local_mid = LMid},
			encoding_mod    = EM,
			encoding_config = EC,
			send_mod        = SM}) ->
    #megaco_receive_handle{local_mid       = LMid,
			   encoding_mod    = EM,
			   encoding_config = EC,
			   send_mod        = SM};
do_conn_info(Item, Data) 
  when is_record(Data, conn_data) orelse is_record(Data, megaco_conn_handle) ->
    exit({no_such_item, Item});
do_conn_info(_Item, BadData) ->
    {error, {no_such_connection, BadData}}.


%% replace(_, _, []) ->
%%     [];
%% replace(Item, WithItem, [Item|List]) ->
%%     [WithItem|List];
%% replace(Item, WithItem, [OtherItem|List]) ->
%%     [OtherItem | replace(Item, WithItem, List)].


update_conn_info(#conn_data{conn_handle = CH}, Item, Val) ->
    do_update_conn_info(CH, Item, Val);
update_conn_info(CH, Item, Val) 
  when is_record(CH, megaco_conn_handle) andalso (Item /= cancel) ->
    do_update_conn_info(CH, Item, Val);
update_conn_info(BadHandle, _Item, _Val) ->
    {error, {no_such_connection, BadHandle}}.

do_update_conn_info(CH, orig_pending_limit, Val) ->
    do_update_conn_info(CH, sent_pending_limit, Val);
do_update_conn_info(CH, Item, Val) ->
    call({update_conn_data, CH, Item, Val}).


system_info(all) ->
    AllItems = [n_active_requests,
		n_active_replies,
		n_active_connections,
		users,
		connections,
		text_config,
		reply_counters,
		pending_counters], 
    [{Item, system_info(Item)} || Item <- AllItems];
system_info(Item) ->
    case Item of
        n_active_requests ->
            ets:info(megaco_requests, size);
        n_active_replies  ->
            ets:info(megaco_replies, size);
        n_active_connections  ->
            ets:info(megaco_local_conn, size);
        users ->
            Pat = {{'_', mid}, '_'},
            [Mid || {_, Mid} <- ets:match_object(megaco_config, Pat)];
        connections ->
            [C#conn_data.conn_handle || C <- ets:tab2list(megaco_local_conn)];
	text_config ->
	    case ets:lookup(megaco_config, text_config) of
		[] ->
		    [];
		[{text_config, Conf}] ->
		    [Conf]
	    end;
	
	reply_counters ->
	    reply_counters();

	pending_counters ->
	    pending_counters();

	recv_pending_counters ->
	    pending_counters(recv);

	sent_pending_counters ->
	    pending_counters(sent);

	BadItem ->
	    exit({no_such_item, BadItem})

    end.


get_env(Env, Default) ->
    case application:get_env(megaco, Env) of
        {ok, Val} -> Val;
        undefined -> Default
    end.

lookup_local_conn(Handle) ->
    ets:lookup(megaco_local_conn, Handle).


autoconnect(RH, RemoteMid, SendHandle, ControlPid) ->
    ?d("autoconnect -> entry with "
	"~n   RH:         ~p"
	"~n   RemoteMid:  ~p"
	"~n   SendHandle: ~p"
	"~n   ControlPid: ~p", [RH, RemoteMid, SendHandle, ControlPid]),
    case RemoteMid of
	{MidType, _MidValue} when is_atom(MidType) ->
	    call({connect, RH, RemoteMid, SendHandle, ControlPid, auto});
	preliminary_mid ->
	    call({connect, RH, RemoteMid, SendHandle, ControlPid, auto});
	BadMid ->
	    {error, {bad_remote_mid, BadMid}}
    end.

connect(RH, RemoteMid, SendHandle, ControlPid) ->
    ?d("connect -> entry with "
	"~n   RH:         ~p"
	"~n   RemoteMid:  ~p"
	"~n   SendHandle: ~p"
	"~n   ControlPid: ~p", [RH, RemoteMid, SendHandle, ControlPid]),
    case RemoteMid of
	{MidType, _MidValue} when is_atom(MidType) ->
	    call({connect, RH, RemoteMid, SendHandle, ControlPid, 
		  {plain, self()}});
	preliminary_mid ->
	    call({connect, RH, RemoteMid, SendHandle, ControlPid, 
		  {plain, self()}});
	BadMid ->
	    {error, {bad_remote_mid, BadMid}}
    end.

finish_connect(ConnHandle, SendHandle, ControlPid, MFA) ->
    ?d("finish_connect -> entry with "
	"~n   ConnHandle: ~p"
	"~n   SendHandle: ~p"
	"~n   ControlPid: ~p"
	"~n   MFA:        ~p", [ConnHandle, SendHandle, ControlPid, MFA]),
    call({finish_connect, ConnHandle, SendHandle, ControlPid, MFA}).

connect_remote(ConnHandle, UserNode, Ref) ->
    call({connect_remote, ConnHandle, UserNode, Ref}).

disconnect(ConnHandle) ->
    call({disconnect, ConnHandle}).

disconnect_remote(ConnHandle, UserNode) ->
    call({disconnect_remote, ConnHandle, UserNode}).


incr_counter(Item, Incr) ->
    try
	begin
	    ets:update_counter(megaco_config, Item, Incr)
	end
    catch
	error:_ ->
	    %% Counter does not exist, so try creat it
	    try
		begin
		    cre_counter(Item, Incr)
		end
	    catch
		exit:_ ->
		    %% This is a raise condition. 
		    %% When we tried to update the counter above, it
		    %% did not exist, but now it does...
		    ets:update_counter(megaco_config, Item, Incr)
	    end
    end.

cre_counter(Item, Initial) ->
    case whereis(?SERVER) =:= self() of
	false ->
	    case call({cre_counter, Item, Initial}) of
		{ok, Value} ->
		    Value;
		{error, Reason} ->
		    exit({failed_creating_counter, Item, Initial, Reason})
	    end;
	true ->
	    %% Check that the counter does not already exists
	    %% so we don't overwrite an already existing counter
	    case ets:lookup(megaco_config, Item) of
		[] ->
		    ets:insert(megaco_config, {Item, Initial}),
		    {ok, Initial};
		[_] ->
		    %% Possibly a raise condition
		    {error, already_exists}
		
		end
    end.
    

cre_reply_counter(ConnHandle, TransId) ->
    Counter = {reply_counter, ConnHandle, TransId},
    Initial = 1, 
    cre_counter(Counter, Initial).

incr_reply_counter(ConnHandle, TransId) ->
    Counter = {reply_counter, ConnHandle, TransId},
    incr_counter(Counter, 1).

get_reply_counter(ConnHandle, TransId) ->
    Counter = {reply_counter, ConnHandle, TransId},
    [{Counter, Val}] = ets:lookup(megaco_config, Counter),
    Val.

del_reply_counter(ConnHandle, TransId) ->
    Counter = {reply_counter, ConnHandle, TransId},
    ets:delete(megaco_config, Counter).

reply_counters() ->
    Pattern   = {{reply_counter, '_', '_'}, '_'}, 
    Counters1 = ets:match_object(megaco_config, Pattern),
    [{ConnHandle, TransId, CounterVal} || 
	{{reply_counter, ConnHandle, TransId}, CounterVal} <- Counters1].


cre_pending_counter(TransId) ->
    cre_pending_counter(sent, TransId, 0).

cre_pending_counter(Direction, TransId, Initial) ->
    Counter = {pending_counter, Direction, TransId},
    cre_counter(Counter, Initial).

incr_pending_counter(TransId) ->
    incr_pending_counter(sent, TransId).

incr_pending_counter(Direction, TransId) ->
    Counter = {pending_counter, Direction, TransId},
    incr_counter(Counter, 1).

get_pending_counter(TransId) ->
    get_pending_counter(sent, TransId).

get_pending_counter(Direction, TransId) ->
    Counter = {pending_counter, Direction, TransId},
    [{Counter, Val}] = ets:lookup(megaco_config, Counter),
    Val.

del_pending_counter(TransId) ->
    del_pending_counter(sent, TransId).

del_pending_counter(Direction, TransId) ->
    Counter = {pending_counter, Direction, TransId},
    ets:delete(megaco_config, Counter).


pending_counters() ->
    Pattern   = {{pending_counter, '_', '_'}, '_'}, 
    Counters1 = ets:match_object(megaco_config, Pattern),
    Counters2 = [{Direction, TransId, CounterVal} || 
		    {{pending_counter, Direction, TransId}, CounterVal} <- 
			Counters1],
    RecvCounters = [{TransId, CounterVal} || 
		       {recv, TransId, CounterVal} <- Counters2],
    SentCounters = [{TransId, CounterVal} || 
		       {sent, TransId, CounterVal} <- Counters2],
    [{recv, RecvCounters}, {sent, SentCounters}].
    

pending_counters(Direction) 
  when ((Direction =:= sent) orelse (Direction =:= recv)) ->
    Pattern  = {{pending_counter, Direction, '_'}, '_'}, 
    Counters = ets:match_object(megaco_config, Pattern),
    [{TransId, CounterVal} || 
	{{pending_counter, D, TransId}, CounterVal} <- 
	    Counters, (Direction == D)].

%% A wrapping transaction id counter
incr_trans_id_counter(ConnHandle) ->
    incr_trans_id_counter(ConnHandle, 1).
incr_trans_id_counter(ConnHandle, Incr) 
  when is_integer(Incr) andalso (Incr > 0) ->
    case megaco_config:lookup_local_conn(ConnHandle) of
        [] ->
            {error, {no_such_connection, ConnHandle}};
        [ConnData] ->
            LocalMid = ConnHandle#megaco_conn_handle.local_mid,
	    Min      = user_info(LocalMid, min_trans_id), 
	    Max      = 
		case ConnData#conn_data.max_serial of
		    infinity ->
			4294967295;
		    MS ->
			MS
		end,
	    Item     = ?TID_CNT(LocalMid),
	    do_incr_trans_id_counter(ConnData, Item, Min, Max, Incr, -1)
    end.

do_incr_trans_id_counter(ConnData, _Item, _Min, _Max, 0, Serial) ->
    ConnData2 = ConnData#conn_data{serial = Serial},
    {ok, ConnData2};
do_incr_trans_id_counter(ConnData, Item, Min, Max, N, _) ->
    case (catch ets:update_counter(megaco_config, Item, {2, 1, Max, Min})) of
	{'EXIT', _} ->
	    %% This can only happen for the first ever increment,
	    %% in which case N is equal to (the initial) Incr
	    ConnHandle = ConnData#conn_data.conn_handle, 
	    init_trans_id_counter(ConnHandle, Item, N);
	Serial ->
	    do_incr_trans_id_counter(ConnData, Item, Min, Max, N-1, Serial)
    end.

init_trans_id_counter(ConnHandle, Item, Incr) ->
    case whereis(?SERVER) == self() of
        false ->
	    call({init_trans_id_counter, ConnHandle, Item, Incr});
	true ->
	    do_init_trans_id_counter(ConnHandle, Item, Incr)
    end.

do_init_trans_id_counter(ConnHandle, Item, Incr) ->
    case megaco_config:lookup_local_conn(ConnHandle) of
	[] ->
	    {error, {no_such_connection, ConnHandle}};
	[ConnData] ->
	    %% Make sure that the counter still does not exist
	    LocalMid = ConnHandle#megaco_conn_handle.local_mid,
	    Min      = user_info(LocalMid, min_trans_id), 
	    Max      = 
		case ConnData#conn_data.max_serial of
		    infinity ->
			4294967295;
		    MS ->
			MS
		end,
	    Item     = ?TID_CNT(LocalMid),
	    Incr2    = {2, Incr, Max, Min}, 
	    case (catch ets:update_counter(megaco_config, Item, Incr2)) of
		{'EXIT', _} ->
		    %% Yep, we are the first one here 
		    Serial1 = Min + (Incr-1),
		    ets:insert(megaco_config, {Item, Serial1}),
		    ConnData2 = ConnData#conn_data{serial = Serial1},
		    {ok, ConnData2};
		Serial2 ->
		    %% No, someone got there before we did
		    ConnData2 = ConnData#conn_data{serial = Serial2},
		    {ok, ConnData2}
	    end
    end.

%% For backward compatibillity (during code upgrade)
reset_trans_id_counter(ConnHandle, Item, Serial) ->
    LocalMid = ConnHandle#megaco_conn_handle.local_mid,
    case megaco_config:lookup_local_conn(ConnHandle) of
        [] ->
            {error, {no_such_connection, ConnHandle}};
        [ConnData] ->
	    do_reset_trans_id_counter(ConnData, LocalMid, 
				      Item, Serial)
    end.

do_reset_trans_id_counter(ConnData, LocalMid, Item, Serial) 
  when is_integer(Serial) ->
    Max = ConnData#conn_data.max_serial,
    Overflow = 
	if 
	    (Max == infinity) ->
		Serial - 4294967295;

	    is_integer(Max) ->
		Serial - Max
	end,
    NewSerial = user_info(LocalMid, min_trans_id) + (Overflow-1),
    ConnData2 = ConnData#conn_data{serial = NewSerial},
    ets:insert(megaco_config, {Item, NewSerial}),
    {ok, ConnData2}.


trans_sender_exit(Reason, CH) ->
    ?d("trans_sender_exit -> entry with"
	"~n   Reason: ~p"
	"~n   CH: ~p", [Reason, CH]),
    cast({trans_sender_exit, Reason, CH}).


call(Request) ->
    case (catch gen_server:call(?SERVER, Request, infinity)) of
	{'EXIT', _} ->
	    {error, megaco_not_started};
	Res ->
	    Res
    end.


cast(Msg) ->
    case (catch gen_server:cast(?SERVER, Msg)) of
	{'EXIT', _} ->
	    {error, megaco_not_started};
	Res ->
	    Res
    end.


%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------

%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%%----------------------------------------------------------------------

init([Parent]) ->
    ?d("init -> entry with "
	"~n   Parent: ~p", [Parent]),
    process_flag(trap_exit, true),
    case (catch do_init()) of
	ok ->
	    ?d("init -> init ok", []),
	    {ok, #state{parent_pid = Parent}};
	Else ->
	    ?d("init -> init error: "
	       "~n   ~p", [Else]),
	    {stop, Else}
    end.

do_init() ->
    ?megaco_test_init(),
    _ = ets:new(megaco_config,      [public, named_table, {keypos, 1}]),
    _ = ets:new(megaco_local_conn,  [public, named_table, {keypos, 2}]),
    _ = ets:new(megaco_remote_conn, [public, named_table, {keypos, 2}, bag]),
    megaco_stats:init(megaco_stats, global_snmp_counters()),
    init_scanner(),
    init_user_defaults(),
    init_users().
    


init_scanner() ->
    case get_env(scanner, undefined) of
	undefined ->
	    Key  = text_config,
	    Data = [],
	    ets:insert(megaco_config, {Key, Data});
	flex ->
	    start_scanner(megaco_flex_scanner_handler, 
			  start_link, [], [gen_server]);
	{flex, Opts} when is_list(Opts) -> % For future use
	    start_scanner(megaco_flex_scanner_handler, 
			  start_link, [Opts], [gen_server]);
	{M, F, A, Mods} when is_atom(M) andalso 
			     is_atom(F) andalso 
			     is_list(A) andalso 
			     is_list(Mods) ->
	    start_scanner(M, F, A, Mods)
    end.

start_scanner(M, F, A, Mods) ->
    case megaco_misc_sup:start_permanent_worker(M, F, A, Mods) of
	{ok, Pid, Conf} when is_pid(Pid) ->
	    Key  = text_config,
	    Data = [Conf],
	    ets:insert(megaco_config, {Key, Data});
	Else ->
	    throw({scanner_start_failed, Else})
    end.

init_user_defaults() ->
    init_user_default(min_trans_id,         1),
    init_user_default(max_trans_id,         infinity), 
    init_user_default(request_timer,        #megaco_incr_timer{}),
    init_user_default(long_request_timer,   timer:seconds(60)),

    init_user_default(auto_ack,             false),

    init_user_default(trans_ack,            false),
    init_user_default(trans_ack_maxcount,   10),

    init_user_default(trans_req,            false),
    init_user_default(trans_req_maxcount,   10),
    init_user_default(trans_req_maxsize,    2048),

    init_user_default(trans_timer,          0),
    init_user_default(trans_sender,         undefined),

    init_user_default(pending_timer,        timer:seconds(30)),
    init_user_default(sent_pending_limit,   infinity),
    init_user_default(recv_pending_limit,   infinity),
    init_user_default(reply_timer,          timer:seconds(30)),
    init_user_default(send_mod,             megaco_tcp),
    init_user_default(encoding_mod,         megaco_pretty_text_encoder),
    init_user_default(protocol_version,     1),
    init_user_default(auth_data,            asn1_NOVALUE),
    init_user_default(encoding_config,      []),
    init_user_default(user_mod,             megaco_user_default),
    init_user_default(user_args,            []),
    init_user_default(reply_data,           undefined),
    init_user_default(threaded,             false),
    init_user_default(strict_version,       true),
    init_user_default(long_request_resend,  false),
    init_user_default(call_proxy_gc_timeout, timer:seconds(5)),
    init_user_default(cancel,               false),
    init_user_default(resend_indication,    false),
    init_user_default(segment_reply_ind,    false),
    init_user_default(segment_recv_acc,     false),
    init_user_default(segment_recv_timer,   timer:seconds(10)),
    init_user_default(segment_send,         none),
    init_user_default(segment_send_timer,   timer:seconds(5)),
    init_user_default(max_pdu_size,         infinity),
    init_user_default(request_keep_alive_timeout, plain).

init_user_default(Item, Default) when Item /= mid ->
    Val = get_env(Item, Default),
    case do_update_user(default, Item, Val) of
	ok ->
	    ok;
	{error, Reason} ->
	    throw(Reason)
    end.

init_users() ->
    Users = get_env(users, []),
    init_users(Users).

init_users([]) ->
    ok;
init_users([{UserMid, Config} | Rest]) ->
    case handle_start_user(UserMid, Config) of
        ok ->
            init_users(Rest);
        Else ->
            throw({bad_user, UserMid, Else})
    end;
init_users(BadConfig) ->
    throw({bad_config, users, BadConfig}).

%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------

handle_call({cre_counter, Item, Incr}, _From, S) ->
    Reply = cre_counter(Item, Incr),
    {reply, Reply, S};

handle_call({del_counter, Item, Incr}, _From, S) ->
    Reply = cre_counter(Item, Incr),
    {reply, Reply, S};

%% For backward compatibillity (code upgrade)
handle_call({incr_trans_id_counter, ConnHandle}, _From, S) ->
    Reply = incr_trans_id_counter(ConnHandle),
    {reply, Reply, S};

handle_call({init_trans_id_counter, ConnHandle, Item, Incr}, _From, S) ->
    Reply = do_init_trans_id_counter(ConnHandle, Item, Incr),
    {reply, Reply, S};

%% For backward compatibillity (code upgrade)
handle_call({reset_trans_id_counter, ConnHandle, Item, Serial}, _From, S) ->
    Reply = reset_trans_id_counter(ConnHandle, Item, Serial),
    {reply, Reply, S};

handle_call({receive_handle, UserMid}, _From, S) ->
    case catch make_receive_handle(UserMid) of
	{'EXIT', _} ->
	    {reply, {error, {no_receive_handle, UserMid}}, S};
	RH ->
	    {reply, {ok, RH}, S}
    end;
handle_call({connect, RH, RemoteMid, SendHandle, ControlPid}, _From, S) ->
    Reply = handle_connect(RH, RemoteMid, SendHandle, ControlPid, auto),
    {reply, Reply, S};
handle_call({connect, RH, RemoteMid, SendHandle, ControlPid, Auto}, _From, S) ->
    Reply = handle_connect(RH, RemoteMid, SendHandle, ControlPid, Auto),
    {reply, Reply, S};

handle_call({finish_connect, ConnHandle, SendHandle, ControlPid, MFA}, 
	    _From, S) ->
    Reply = handle_finish_connect(ConnHandle, SendHandle, ControlPid, MFA),
    {reply, Reply, S};

handle_call({connect_remote, CH, UserNode, Ref}, _From, S) ->
    Reply = handle_connect_remote(CH, UserNode, Ref),
    {reply, Reply, S};

handle_call({disconnect, ConnHandle}, _From, S) ->
    Reply = handle_disconnect(ConnHandle),
    {reply, Reply, S};
handle_call({disconnect_remote, CH, UserNode}, _From, S) ->
    Reply = handle_disconnect_remote(CH, UserNode),
    {reply, Reply, S};

handle_call({start_user, UserMid, Config}, _From, S) ->
    Reply = handle_start_user(UserMid, Config),
    {reply, Reply, S};
handle_call({stop_user, UserMid}, _From, S) ->
    Reply = handle_stop_user(UserMid),
    {reply, Reply, S};
handle_call({update_conn_data, CH, Item, Val}, _From, S) ->
    case lookup_local_conn(CH) of
        [] ->
            {reply, {error, {no_such_connection, CH}}, S};
        [CD] ->
            Reply = handle_update_conn_data(CD, Item, Val),
            {reply, Reply, S}
    end;
handle_call({update_user_info, UserMid, Item, Val}, _From, S) ->
    case catch user_info(UserMid, mid) of
        {'EXIT', _} ->
            {reply, {error, {no_such_user, UserMid}}, S};
        _ ->
            Reply = do_update_user(UserMid, Item, Val),
            {reply, Reply, S}
    end;

handle_call({stop, ParentPid}, _From, #state{parent_pid = ParentPid} = S) ->
    Reason = normal, 
    Reply  = ok,
    {stop, Reason, Reply, S};

handle_call(Req, From, S) ->
    warning_msg("received unexpected request from ~p: "
		"~n~w", [From, Req]),
    {reply, {error, {bad_request, Req}}, S}.


%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------

handle_cast({trans_sender_exit, Reason, CH}, S) ->
    warning_msg("transaction sender [~p] restarting: "
		"~n~p", [CH, Reason]),
    case lookup_local_conn(CH) of
	[] ->
	    error_msg("connection data not found for ~p~n"
		      "when restarting transaction sender", [CH]);
	[CD] ->
	    CD2 = trans_sender_start(CD#conn_data{trans_sender = undefined}),
	    ets:insert(megaco_local_conn, CD2)
    end,
    {noreply, S};

handle_cast(Msg, S) ->
    warning_msg("received unexpected message: "
		"~n~w", [Msg]),
    {noreply, S}.



%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%----------------------------------------------------------------------

handle_info({'EXIT', Pid, Reason}, S) when Pid =:= S#state.parent_pid ->
    {stop, Reason, S};

handle_info(Info, S) ->
    warning_msg("received unknown info: "
		"~n~w", [Info]),
    {noreply, S}.



%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------

terminate(_Reason, _State) ->
    ok.


%%----------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%----------------------------------------------------------------------

code_change(_Vsn, S, upgrade_from_pre_3_12) ->
    upgrade_user_info_from_pre_3_12(),
    upgrade_conn_data_from_pre_3_12(),
    {ok, S};

code_change(_Vsn, S, downgrade_to_pre_3_12) ->
    downgrade_user_info_to_pre_3_12(),
    downgrade_conn_data_to_pre_3_12(),
    {ok, S};

code_change(_Vsn, S, _Extra) ->
    {ok, S}.


%% -- Upgrade user info --

upgrade_user_info_from_pre_3_12() ->
    NewValues = [{request_keep_alive_timeout, plain}],
    upgrade_user_info(NewValues).

%% upgrade_user_info_from_pre_3_7() ->
%%     NewValues = [{segment_reply_ind,  false},
%% 		 {segment_recv_acc,   false},
%% 		 {segment_recv_timer, #megaco_incr_timer{}},
%% 		 {segment_send,       none}, 
%% 		 {segment_send_timer, infinity}, 
%% 		 {max_pdu_size,       infinity}],
%%     upgrade_user_info(NewValues).

upgrade_user_info(NewValues) ->
    Users = [default|system_info(users)],
    F = fun({Item, Val}) ->
		upgrade_user_info(Users, Item, Val)
	end,
    lists:foreach(F, NewValues),
    ok.

upgrade_user_info(Users, Item, Val) ->
    F = fun(User) -> do_update_user(User, Item, Val) end,
    lists:foreach(F, Users),
    ok.


%% %% -- Downgrade user info --

downgrade_user_info_to_pre_3_12() ->
    NewItems = [
		request_keep_alive_timeout
	       ],
    downgrade_user_info(NewItems).

%% downgrade_user_info_to_pre_3_7() ->
%%     NewItems = [
%% 		segment_reply_ind, 
%% 		segment_recv_acc, 
%% 		segment_recv_timer, 
%% 		segment_send,     
%% 		segment_send_timer, 
%% 		max_pdu_size
%% 	       ],
%%     downgrade_user_info(NewItems).

downgrade_user_info(NewItems) ->
    Users = [default|system_info(users)],
    F = fun(Item) ->
		downgrade_user_info(Users, Item)
	end,
    lists:foreach(F, NewItems),
    ok.
    
downgrade_user_info(Users, Item) ->
    F = fun(User) -> do_downgrade_user_info(User, Item) end,
    lists:foreach(F, Users),
    ok.

do_downgrade_user_info(User, Item) ->
    ets:delete(megaco_config, {User, Item}).
		

%% %% -- Upgrade conn data --

upgrade_conn_data_from_pre_3_12() ->
    Conns    = system_info(connections),
    Defaults = [{request_keep_alive_timeout, plain}], 
    upgrade_conn_data(Conns, Defaults).

%% upgrade_conn_data_from_pre_3_7() ->
%%     Conns    = system_info(connections),
%%     Defaults = [{segment_reply_ind,  false}, 
%% 		{segment_recv_acc,   false}, 
%% 		{segment_recv_timer, #megaco_incr_timer{}}, 
%% 		{segment_send,       false}, 
%% 		{segment_send_timer, #megaco_incr_timer{}}, 
%% 		{max_pdu_size,       infinity}], 
%%     upgrade_conn_data(Conns, Defaults).

upgrade_conn_data(Conns, Defaults) ->
    F = fun(CH) ->
		case lookup_local_conn(CH) of
		    [] ->
			ok;
		    [CD] ->
			do_upgrade_conn_data(CD, Defaults)
		end
	end,
    lists:foreach(F, Conns),
    ok.

do_upgrade_conn_data(OldStyleCD, Defaults) ->
    NewStyleCD = new_conn_data(OldStyleCD, Defaults),
    ets:insert(megaco_local_conn, NewStyleCD).
    
%% Pre 3.12
new_conn_data({conn_data, CH, Serial, MaxSerial, ReqTmr, LongReqTmr, 
	       AutoAck, 
	       TransAck, TransAckMaxCnt, 
	       TransReq, TransReqMaxCnt, TransReqMaxSz, 
	       TransTmr, TransSndr, 
	       
	       PendingTmr, 
	       SentPendingLimit, 
	       RecvPendingLimit,
	       ReplyTmr, CtrPid, MonRef, 
	       Sendmod, SendHandle, 
	       EncodeMod, EncodeConf, 
	       ProtV, AuthData, 
	       UserMod, UserArgs, ReplyAction, ReplyData,
	       Threaded,
	       StrictVersion,
	       LongReqResend,
	       Cancel,
	       ResendIndication, 
	       SegmentReplyInd, 
	       SegmentRecvAcc, 
	       SegmentRecvTimer, 
	       SegmentSend, 
	       SegmentSendTimer, 
	       MaxPDUSize 
	       %% RequestKeepAliveTimerDefault - New values
	      }, 
	      Defaults) ->
    #conn_data{conn_handle          = CH, 
	       serial               = Serial,
	       max_serial           = MaxSerial,
	       request_timer        = ReqTmr,
	       long_request_timer   = LongReqTmr,
	       
	       auto_ack             = AutoAck,
	       
	       trans_ack            = TransAck,
	       trans_ack_maxcount   = TransAckMaxCnt,
	       
	       trans_req            = TransReq,
	       trans_req_maxcount   = TransReqMaxCnt,
	       trans_req_maxsize    = TransReqMaxSz,
	       
	       trans_timer          = TransTmr,
	       trans_sender         = TransSndr,
	       
	       pending_timer        = PendingTmr,
	       sent_pending_limit   = SentPendingLimit,
	       recv_pending_limit   = RecvPendingLimit, 

	       reply_timer          = ReplyTmr,
	       control_pid          = CtrPid,
	       monitor_ref          = MonRef,
	       send_mod             = Sendmod,
	       send_handle          = SendHandle,
	       encoding_mod         = EncodeMod,
	       encoding_config      = EncodeConf,
	       protocol_version     = ProtV,
	       auth_data            = AuthData,
	       user_mod             = UserMod,
	       user_args            = UserArgs,
	       reply_action         = ReplyAction,
	       reply_data           = ReplyData,
	       threaded             = Threaded,
	       strict_version       = StrictVersion,
	       long_request_resend  = LongReqResend, 
	       cancel               = Cancel,
	       resend_indication    = ResendIndication,
	       segment_reply_ind    = SegmentReplyInd,
	       segment_recv_acc     = SegmentRecvAcc,
	       segment_recv_timer   = SegmentRecvTimer,
	       segment_send         = SegmentSend,
	       segment_send_timer   = SegmentSendTimer,
	       max_pdu_size         = MaxPDUSize,
	       request_keep_alive_timeout = get_default(request_keep_alive_timeout, Defaults)
	      }.


get_default(Key, Defaults) ->
    {value, {Key, Default}} = lists:keysearch(Key, 1, Defaults),
    Default.


%% %% -- Downgrade conn data --

downgrade_conn_data_to_pre_3_12() ->
    Conns = system_info(connections),
    Downgrade = fun(NewCD) -> old_conn_data_to_pre_3_12(NewCD) end,
    downgrade_conn_data(Downgrade, Conns).

downgrade_conn_data(Downgrade, Conns) ->
    F = fun(CH) ->
		case lookup_local_conn(CH) of
		    [] ->
			ok;
		    [CD] ->
			do_downgrade_conn_data(Downgrade, CD)
		end
	end,
    lists:foreach(F, Conns).

do_downgrade_conn_data(Downgrade, NewStyleCD) ->
    OldStyleCD = Downgrade(NewStyleCD),
    ets:insert(megaco_local_conn, OldStyleCD).

old_conn_data_to_pre_3_12(
  #conn_data{conn_handle          = CH, 
	     serial               = Serial,
	     max_serial           = MaxSerial,
	     request_timer        = ReqTmr,
	     long_request_timer   = LongReqTmr,
	     
	     auto_ack             = AutoAck,
	     
	     trans_ack            = TransAck,
	     trans_ack_maxcount   = TransAckMaxCnt,
	     
	     trans_req            = TransReq,
	     trans_req_maxcount   = TransReqMaxCnt,
	     trans_req_maxsize    = TransReqMaxSz,
	     
	     trans_timer          = TransTmr,
	     trans_sender         = TransSndr,
	     
	     pending_timer        = PendingTmr,
	     sent_pending_limit   = SentPendingLimit,
	     recv_pending_limit   = RecvPendingLimit, 
	     
	     reply_timer          = ReplyTmr,
	     control_pid          = CtrPid,
	     monitor_ref          = MonRef,
	     send_mod             = Sendmod,
	     send_handle          = SendHandle,
	     encoding_mod         = EncodeMod,
	     encoding_config      = EncodeConf,
	     protocol_version     = ProtV,
	     auth_data            = AuthData,
	     user_mod             = UserMod,
	     user_args            = UserArgs,
	     reply_action         = ReplyAction,
	     reply_data           = ReplyData,
	     threaded             = Threaded,
	     strict_version       = StrictVersion,
	     long_request_resend  = LongReqResend,
	     cancel               = Cancel,
	     resend_indication    = ResendIndication, 
	     segment_reply_ind    = SegmentRecvAcc,
	     segment_recv_acc     = SegmentRecvAcc,
	     segment_recv_timer   = SegmentRecvTimer,
	     segment_send         = SegmentSend,
	     segment_send_timer   = SegmentSendTimer,
	     max_pdu_size         = MaxPDUSize
	     %% request_keep_alive_timeout = RequestKeepAliveTimeout
	    }) ->
    {conn_data, CH, Serial, MaxSerial, ReqTmr, LongReqTmr, 
     AutoAck, 
     TransAck, TransAckMaxCnt, 
     TransReq, TransReqMaxCnt, TransReqMaxSz, 
     TransTmr, TransSndr, 
     PendingTmr, 
     SentPendingLimit, 
     RecvPendingLimit, 
     ReplyTmr, CtrPid, MonRef, 
     Sendmod, SendHandle, 
     EncodeMod, EncodeConf, 
     ProtV, AuthData, 
     UserMod, UserArgs, ReplyAction, ReplyData,
     Threaded,
     StrictVersion,
     LongReqResend,
     Cancel,
     ResendIndication,
     SegmentRecvAcc,
     SegmentRecvAcc,
     SegmentRecvTimer,
     SegmentSend,
     SegmentSendTimer,
     MaxPDUSize}.


		
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

handle_start_user(default, _Config) ->
    {error, bad_user_mid};
handle_start_user(Mid, Config) ->
    case catch user_info(Mid, mid) of
        {'EXIT', _} ->
	    DefaultConfig = user_info(default, all),
            _ = do_handle_start_user(Mid, DefaultConfig),
	    do_handle_start_user(Mid, Config);
        _LocalMid ->
            {error, {user_already_exists, Mid}}
    end.

do_handle_start_user(UserMid, [{Item, Val} | Rest]) ->
    case do_update_user(UserMid, Item, Val) of
        ok ->
            do_handle_start_user(UserMid, Rest);
        {error, Reason} ->
            ets:match_delete(megaco_config, {{UserMid, '_'}, '_'}),
            {error, Reason}
    end;
do_handle_start_user(UserMid, []) ->
    _ = do_update_user(UserMid, mid, UserMid),
    ok;
do_handle_start_user(UserMid, BadConfig) ->
    ets:match_delete(megaco_config, {{UserMid, '_'}, '_'}),
    {error, {bad_user_config, UserMid, BadConfig}}.

do_update_user(UserMid, Item, Val) ->
    case verify_val(Item, Val) of
        true  ->
            ets:insert(megaco_config, {{UserMid, Item}, Val}),
            ok;
        false ->
            {error, {bad_user_val, UserMid, Item, Val}}
    end.

verify_val(Item, Val) ->
    case Item of
        mid                    -> true;
        local_mid              -> true;
        remote_mid             -> true;
        min_trans_id           -> 
	    megaco_config_misc:verify_strict_uint(Val, 4294967295); % uint32
        max_trans_id           -> 
	    megaco_config_misc:verify_uint(Val, 4294967295);        % uint32
        request_timer          -> verify_timer(Val);
        long_request_timer     -> verify_timer(Val);

        auto_ack               -> 
	    megaco_config_misc:verify_bool(Val);

	trans_ack              -> 
	    megaco_config_misc:verify_bool(Val);
        trans_ack_maxcount     -> 
	    megaco_config_misc:verify_uint(Val);

	trans_req              -> 
	    megaco_config_misc:verify_bool(Val);
        trans_req_maxcount     -> 
	    megaco_config_misc:verify_uint(Val);
        trans_req_maxsize      -> 
	    megaco_config_misc:verify_uint(Val);

        trans_timer            -> 
	    verify_timer(Val) and (Val >= 0);
	trans_sender when Val =:= undefined -> true;

        pending_timer                      -> verify_timer(Val);
        sent_pending_limit                 -> 
	    megaco_config_misc:verify_uint(Val) andalso (Val > 0);
        recv_pending_limit                 -> 
	    megaco_config_misc:verify_uint(Val) andalso (Val > 0);
        reply_timer                        -> verify_timer(Val);
        control_pid      when is_pid(Val)  -> true;
        monitor_ref                        -> true; % Internal usage only
        send_mod         when is_atom(Val) -> true;
        send_handle                        -> true;
        encoding_mod     when is_atom(Val) -> true;
        encoding_config  when is_list(Val) ->
            case Val of
                [{version3, V3}|_] when (V3 =/= v3) ->
                    error_msg("Encoding Config version3 ~p is "
                              "no longer supported!~n"),
                    ok;
                _ ->
                    ok
            end,
            true;
        protocol_version                   -> 
	    megaco_config_misc:verify_strict_uint(Val);
        auth_data                          -> true;
        user_mod         when is_atom(Val) -> true;
        user_args        when is_list(Val) -> true;
        reply_data                         -> true;
        threaded                           -> 
	    megaco_config_misc:verify_bool(Val);
        strict_version                     -> 
	    megaco_config_misc:verify_bool(Val);
	long_request_resend                -> 
	    megaco_config_misc:verify_bool(Val);
	call_proxy_gc_timeout              -> 
	    megaco_config_misc:verify_strict_uint(Val);
	cancel                             -> 
	    megaco_config_misc:verify_bool(Val);
	resend_indication                  -> verify_resend_indication(Val);

	segment_reply_ind               -> 
	    megaco_config_misc:verify_bool(Val);
	segment_recv_acc                -> 
	    megaco_config_misc:verify_bool(Val);
	segment_recv_timer              -> verify_timer(Val);
	segment_send                    -> verify_segmentation_window(Val);
	segment_send_timer              -> verify_timer(Val);
	max_pdu_size                    -> 
	    megaco_config_misc:verify_int(Val) andalso (Val > 0);
	request_keep_alive_timeout      -> 
	    (megaco_config_misc:verify_uint(Val) orelse (Val =:= plain));

        _                               -> false
    end.



verify_resend_indication(flag) -> true;
verify_resend_indication(Val)  -> megaco_config_misc:verify_bool(Val).

verify_timer(Timer) ->
    megaco_timer:verify(Timer).

verify_segmentation_window(none) ->
    true;
verify_segmentation_window(K) ->
    megaco_config_misc:verify_int(K, 1, infinity).

handle_stop_user(UserMid) ->
    case catch user_info(UserMid, mid) of
        {'EXIT', _} ->
	    {error, {no_such_user, UserMid}};
	_ ->
	    case catch user_info(UserMid, connections) of
		[] ->
		    ets:match_delete(megaco_config, {{UserMid, '_'}, '_'}),
		    ok;
		{'EXIT', _} ->
		    {error, {no_such_user, UserMid}};
		_Else ->
		    {error, {active_connections, UserMid}}
	    end
    end.

handle_update_conn_data(CD, Item = receive_handle, RH) ->
    UserMid = (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid,
    if
        is_record(RH, megaco_receive_handle) andalso 
        is_atom(RH#megaco_receive_handle.encoding_mod) andalso
        is_list(RH#megaco_receive_handle.encoding_config) andalso
        is_atom(RH#megaco_receive_handle.send_mod) andalso
        (RH#megaco_receive_handle.local_mid /= UserMid) ->
            CD2 = CD#conn_data{
		    encoding_mod    = RH#megaco_receive_handle.encoding_mod,
		    encoding_config = RH#megaco_receive_handle.encoding_config,
		    send_mod        = RH#megaco_receive_handle.send_mod},
            ets:insert(megaco_local_conn, CD2),
            ok;
        true ->
            {error, {bad_user_val, UserMid, Item, RH}}
    end;
handle_update_conn_data(CD, Item, Val) ->
    case verify_val(Item, Val) of
        true ->
            CD2 = replace_conn_data(CD, Item, Val),
            ets:insert(megaco_local_conn, CD2),
            ok;
        false ->
            UserMid = (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid,
            {error, {bad_user_val, UserMid, Item, Val}}
    end.

replace_conn_data(CD, Item, Val) ->
    case Item of
        trans_id             -> CD#conn_data{serial             = Val};
        max_trans_id         -> CD#conn_data{max_serial         = Val};
        request_timer        -> CD#conn_data{request_timer      = Val};
        long_request_timer   -> CD#conn_data{long_request_timer = Val};

	auto_ack             -> update_auto_ack(CD, Val);

	%% Accumulate trans ack before sending
	trans_ack            -> update_trans_ack(CD, Val); 
	trans_ack_maxcount   -> update_trans_ack_maxcount(CD, Val);

	%% Accumulate trans req before sending
	trans_req            -> update_trans_req(CD, Val); 
	trans_req_maxcount   -> update_trans_req_maxcount(CD, Val);
	trans_req_maxsize    -> update_trans_req_maxsize(CD, Val);

	trans_timer          -> update_trans_timer(CD, Val); 
	%% trans_sender      - Automagically updated by 
	%%                     update_auto_ack & update_trans_timer & 
	%%                     update_trans_ack & update_trans_req

        pending_timer        -> CD#conn_data{pending_timer        = Val};
        sent_pending_limit   -> CD#conn_data{sent_pending_limit   = Val};
        recv_pending_limit   -> CD#conn_data{recv_pending_limit   = Val};
        reply_timer          -> CD#conn_data{reply_timer          = Val};
        control_pid          -> CD#conn_data{control_pid          = Val};
        monitor_ref          -> CD#conn_data{monitor_ref          = Val};
        send_mod             -> CD#conn_data{send_mod             = Val};
        send_handle          -> CD#conn_data{send_handle          = Val};
        encoding_mod         -> CD#conn_data{encoding_mod         = Val};
        encoding_config      -> CD#conn_data{encoding_config      = Val};
        protocol_version     -> CD#conn_data{protocol_version     = Val};
        auth_data            -> CD#conn_data{auth_data            = Val};
        user_mod             -> CD#conn_data{user_mod             = Val};
        user_args            -> CD#conn_data{user_args            = Val};
        reply_action         -> CD#conn_data{reply_action         = Val};
        reply_data           -> CD#conn_data{reply_data           = Val};
        threaded             -> CD#conn_data{threaded             = Val};
        strict_version       -> CD#conn_data{strict_version       = Val};
	long_request_resend  -> CD#conn_data{long_request_resend  = Val};
	call_proxy_gc_timeout -> CD#conn_data{call_proxy_gc_timeout = Val};
	cancel               -> CD#conn_data{cancel               = Val};
	resend_indication    -> CD#conn_data{resend_indication    = Val};
        segment_reply_ind    -> CD#conn_data{segment_reply_ind    = Val};
        segment_recv_acc     -> CD#conn_data{segment_recv_acc     = Val};
        segment_recv_timer   -> CD#conn_data{segment_recv_timer   = Val};
        segment_send         -> CD#conn_data{segment_send         = Val};
        segment_send_timer   -> CD#conn_data{segment_send_timer   = Val};
        max_pdu_size         -> CD#conn_data{max_pdu_size         = Val};
        request_keep_alive_timeout -> CD#conn_data{request_keep_alive_timeout = Val}
    end.

%% update auto_ack
update_auto_ack(#conn_data{trans_sender = Pid,
			   trans_req    = false} = CD, 
		false) when is_pid(Pid) ->
    megaco_trans_sender:stop(Pid),
    CD#conn_data{auto_ack = false, trans_sender = undefined};

update_auto_ack(#conn_data{trans_timer  = To, 
			   trans_ack    = true,
			   trans_sender = undefined} = CD, 
		true) when To > 0 ->
    #conn_data{conn_handle        = CH, 
	       trans_ack_maxcount = AcksMax, 
	       trans_req_maxcount = ReqsMax, 
	       trans_req_maxsize  = ReqsMaxSz} = CD,
    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{auto_ack = true, trans_sender = Pid};

update_auto_ack(CD, Val) ->
    ?d("update_auto_ack -> entry with ~p", [Val]),
    CD#conn_data{auto_ack = Val}.

%% update trans_ack
update_trans_ack(#conn_data{auto_ack     = true,
			    trans_req    = false,
			    trans_sender = Pid} = CD, 
		      false) when is_pid(Pid) ->
    megaco_trans_sender:stop(Pid),
    CD#conn_data{trans_ack = false, trans_sender = undefined};

update_trans_ack(#conn_data{trans_timer  = To,
			    auto_ack     = true, 
			    trans_sender = undefined} = CD, 
		      true) when To > 0 ->
    #conn_data{conn_handle        = CH, 
	       trans_ack_maxcount = AcksMax, 
	       trans_req_maxcount = ReqsMax, 
	       trans_req_maxsize  = ReqsMaxSz} = CD,
    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_ack = true, trans_sender = Pid};

update_trans_ack(CD, Val) ->
    ?d("update_trans_ack -> entry with ~p", [Val]),
    CD#conn_data{trans_ack = Val}.

%% update trans_req
update_trans_req(#conn_data{trans_ack    = false,
			    trans_sender = Pid} = CD, 
		      false) when is_pid(Pid) ->
    megaco_trans_sender:stop(Pid),
    CD#conn_data{trans_req = false, trans_sender = undefined};

update_trans_req(#conn_data{trans_timer  = To, 
			    trans_sender = undefined} = CD, 
		      true) when To > 0 ->
    #conn_data{conn_handle        = CH, 
	       trans_ack_maxcount = AcksMax, 
	       trans_req_maxcount = ReqsMax, 
	       trans_req_maxsize  = ReqsMaxSz} = CD,
    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_req = true, trans_sender = Pid};

update_trans_req(CD, Val) ->
    ?d("update_trans_req -> entry with ~p", [Val]),
    CD#conn_data{trans_req = Val}.

%% update trans_timer
update_trans_timer(#conn_data{auto_ack     = true, 
			      trans_ack    = true,
			      trans_sender = undefined} = CD, 
		   To) when To > 0 ->
    #conn_data{conn_handle        = CH, 
	       trans_ack_maxcount = AcksMax, 
	       trans_req_maxcount = ReqsMax, 
	       trans_req_maxsize  = ReqsMaxSz} = CD,
    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_timer = To, trans_sender = Pid};

update_trans_timer(#conn_data{trans_req    = true, 
			      trans_sender = undefined} = CD, 
		   To) when To > 0 ->
    #conn_data{conn_handle        = CH, 
	       trans_ack_maxcount = AcksMax, 
	       trans_req_maxcount = ReqsMax, 
	       trans_req_maxsize  = ReqsMaxSz} = CD,
    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_timer = To, trans_sender = Pid};

update_trans_timer(#conn_data{trans_sender = Pid} = CD, 0) when is_pid(Pid) ->
    megaco_trans_sender:stop(Pid),
    CD#conn_data{trans_timer = 0, trans_sender = undefined};

update_trans_timer(#conn_data{trans_sender = Pid} = CD, To) 
  when is_pid(Pid) and (To > 0) ->
    megaco_trans_sender:timeout(Pid, To),
    CD#conn_data{trans_timer = To};

update_trans_timer(CD, To) when To > 0 ->
    CD#conn_data{trans_timer = To}.

%% update trans_ack_maxcount
update_trans_ack_maxcount(#conn_data{trans_sender = Pid} = CD, Max) 
  when is_pid(Pid) and (Max > 0) ->
    megaco_trans_sender:ack_maxcount(Pid, Max),
    CD#conn_data{trans_ack_maxcount = Max};

update_trans_ack_maxcount(CD, Max) 
  when Max > 0 ->
    ?d("update_trans_ack_maxcount -> entry with ~p", [Max]),
    CD#conn_data{trans_ack_maxcount = Max}.

%% update trans_req_maxcount
update_trans_req_maxcount(#conn_data{trans_sender = Pid} = CD, Max) 
  when is_pid(Pid) and (Max > 0) ->
    megaco_trans_sender:req_maxcount(Pid, Max),
    CD#conn_data{trans_req_maxcount = Max};

update_trans_req_maxcount(CD, Max) 
  when Max > 0 ->
    ?d("update_trans_req_maxcount -> entry with ~p", [Max]),
    CD#conn_data{trans_req_maxcount = Max}.

%% update trans_req_maxsize
update_trans_req_maxsize(#conn_data{trans_sender = Pid} = CD, Max) 
  when is_pid(Pid) and (Max > 0) ->
    megaco_trans_sender:req_maxsize(Pid, Max),
    CD#conn_data{trans_req_maxsize = Max};

update_trans_req_maxsize(CD, Max) 
  when Max > 0 ->
    ?d("update_trans_req_maxsize -> entry with ~p", [Max]),
    CD#conn_data{trans_req_maxsize = Max}.

    

handle_connect(RH, RemoteMid, SendHandle, ControlPid, Auto) ->
    LocalMid   = RH#megaco_receive_handle.local_mid,
    ConnHandle = #megaco_conn_handle{local_mid  = LocalMid,
				     remote_mid = RemoteMid},
    ?d("handle_connect -> entry with"
	"~n   ConnHandle: ~p", [ConnHandle]),
    case ets:lookup(megaco_local_conn, ConnHandle) of
        [] ->
	    PrelMid = preliminary_mid,
	    PrelHandle = ConnHandle#megaco_conn_handle{remote_mid = PrelMid},
	    case ets:lookup(megaco_local_conn, PrelHandle) of
		[] ->
		    case (catch init_conn_data(RH, 
					       RemoteMid, SendHandle, 
					       ControlPid, Auto)) of
			{'EXIT', _Reason} ->
			    ?d("handle_connect -> init conn data failed: "
				"~n   ~p",[_Reason]),
			    {error, {no_such_user, LocalMid}};
			ConnData ->
			    ?d("handle_connect -> new connection"
				"~n   ConnData: ~p", [ConnData]),
			    %% Brand new connection, use 
			    %% When is the preliminary_mid used?
			    create_snmp_counters(ConnHandle),
			    %% Maybe start transaction sender
			    ConnData2 = trans_sender_start(ConnData),
			    ets:insert(megaco_local_conn, ConnData2),
			    {ok, ConnData2}
		    end;
		[PrelData] ->
		    ?d("handle_connect -> connection upgrade"
			"~n   PrelData: ~p", [PrelData]),
		    %% OK, we need to fix the snmp counters. Used 
		    %% with the temporary (preliminary_mid) conn_handle.
		    create_snmp_counters(ConnHandle),
		    ConnData = PrelData#conn_data{conn_handle = ConnHandle},
		    trans_sender_upgrade(ConnData),
		    ets:insert(megaco_local_conn, ConnData),
		    ets:delete(megaco_local_conn, PrelHandle),
		    update_snmp_counters(ConnHandle, PrelHandle),
		    TH = ConnHandle#megaco_conn_handle{local_mid  = PrelMid,
						       remote_mid = RemoteMid},
		    TD = ConnData#conn_data{conn_handle = TH},
 		    ?report_debug(TD, 
				  "Upgrade preliminary_mid to "
				  "actual remote_mid",
				  [{preliminary_mid, preliminary_mid},
				   {local_mid,       LocalMid},
				   {remote_mid,      RemoteMid}]),
		    {ok, ConnData}
	    end;
        [_ConnData] ->
            {error, {already_connected, ConnHandle}}
    end.

handle_finish_connect(ConnHandle, SendHandle, ControlPid, MFA) ->
    case (catch ets:lookup(megaco_local_conn, ConnHandle)) of
	[#conn_data{monitor_ref = connected} = CD] ->
	    {M, F, A} = MFA,
	    Ref = megaco_monitor:apply_at_exit(M, F, A, ControlPid),
	    ConnData2 = CD#conn_data{monitor_ref = Ref,
				     control_pid = ControlPid,
				     send_handle = SendHandle},
	    ets:insert(megaco_local_conn, ConnData2),
	    {ok, Ref};
	[#conn_data{monitor_ref = Ref}] ->
	    {ok, Ref};
	[] ->
	    {error, {no_such_connection, ConnHandle}}
    end.
    

%% also trans_req == true
trans_sender_start(#conn_data{conn_handle        = CH,
			      auto_ack           = true, 
			      trans_ack          = true, 
			      trans_ack_maxcount = AcksMax, 
			      trans_req_maxcount = ReqsMax, 
			      trans_req_maxsize  = ReqsMaxSz,
			      trans_timer        = To,
			      trans_sender       = undefined} = CD)
  when To > 0 ->

    ?d("trans_sender_start(ack) -> entry when"
	"~n   CH:        ~p"
	"~n   To:        ~p"
	"~n   AcksMax:   ~p"
	"~n   ReqsMax:   ~p"
	"~n   ReqsMaxSz: ~p", [CH, To, ReqsMaxSz, ReqsMax, AcksMax]),

    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    ?d("trans_sender_start(ack) -> Pid: ~p", [Pid]),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_sender = Pid};

trans_sender_start(#conn_data{conn_handle        = CH,
			      trans_req          = true, 
			      trans_ack_maxcount = AcksMax, 
			      trans_req_maxcount = ReqsMax, 
			      trans_req_maxsize  = ReqsMaxSz,
			      trans_timer        = To,
			      trans_sender       = undefined} = CD)
  when To > 0 ->

    ?d("trans_sender_start(req) -> entry when"
	"~n   CH:        ~p"
	"~n   To:        ~p"
	"~n   AcksMax:   ~p"
	"~n   ReqsMax:   ~p"
	"~n   ReqsMaxSz: ~p", [CH, To, ReqsMaxSz, ReqsMax, AcksMax]),

    {ok, Pid} = megaco_trans_sup:start_trans_sender(CH, To, ReqsMaxSz, 
						    ReqsMax, AcksMax),

    ?d("trans_sender_start(req) -> Pid: ~p", [Pid]),

    %% Make sure we are notified when/if the transaction 
    %% sender goes down. 
    %% Do we need to store the ref? Will we ever need to 
    %% cancel this (apply_at_exit)?
    _ = megaco_monitor:apply_at_exit(?MODULE, trans_sender_exit, [CH], Pid),

    CD#conn_data{trans_sender = Pid};

trans_sender_start(CD) ->
    ?d("trans_sender_start -> undefined", []),
    CD#conn_data{trans_sender = undefined}.

trans_sender_upgrade(#conn_data{conn_handle  = CH,
				trans_sender = Pid})
  when is_pid(Pid) ->
    ?d("trans_sende_upgrade -> entry when"
	"~n   CH:  ~p"
	"~n   Pid: ~p", [CH, Pid]),
    megaco_trans_sender:upgrade(Pid, CH);
trans_sender_upgrade(_CD) ->
    ok.


handle_connect_remote(ConnHandle, UserNode, Ref) ->
    Pat = #remote_conn_data{conn_handle = ConnHandle,
			    user_node   = UserNode,
			    monitor_ref = '_'},
    case ets:match_object(megaco_remote_conn, Pat) of
        [] ->
	    RCD = #remote_conn_data{conn_handle = ConnHandle,
				    user_node   = UserNode,
				    monitor_ref = Ref},
            ets:insert(megaco_remote_conn, RCD),
            ok;
        _ ->
            {error, {already_connected, ConnHandle, UserNode}}
    end.

init_conn_data(RH, RemoteMid, SendHandle, ControlPid) ->
    init_conn_data(RH, RemoteMid, SendHandle, ControlPid, auto).
init_conn_data(RH, RemoteMid, SendHandle, ControlPid, Auto) ->
    Mid            = RH#megaco_receive_handle.local_mid,
    ConnHandle     = #megaco_conn_handle{local_mid  = Mid,
					 remote_mid = RemoteMid},
    EncodingMod    = RH#megaco_receive_handle.encoding_mod,
    EncodingConfig = RH#megaco_receive_handle.encoding_config,
    SendMod        = RH#megaco_receive_handle.send_mod,
    MonitorRef     = 
	case Auto of
	    auto ->
		undefined_auto_monitor_ref;
	    {plain, ConnectorPid} ->
		{connecting, ConnectorPid}
	end,
    #conn_data{conn_handle          = ConnHandle,
               serial               = undefined_serial,
               max_serial           = user_info(Mid, max_trans_id),
               request_timer        = user_info(Mid, request_timer),
               long_request_timer   = user_info(Mid, long_request_timer),

               auto_ack             = user_info(Mid, auto_ack),
               trans_ack            = user_info(Mid, trans_req),
               trans_req            = user_info(Mid, trans_req),

	       trans_timer          = user_info(Mid, trans_timer),
	       trans_req_maxsize    = user_info(Mid, trans_req_maxsize),
	       trans_req_maxcount   = user_info(Mid, trans_req_maxcount),
	       trans_ack_maxcount   = user_info(Mid, trans_ack_maxcount),

               pending_timer        = user_info(Mid, pending_timer),
               sent_pending_limit   = user_info(Mid, sent_pending_limit),
               recv_pending_limit   = user_info(Mid, recv_pending_limit),
               reply_timer          = user_info(Mid, reply_timer),
               control_pid          = ControlPid,
               monitor_ref          = MonitorRef,
               send_mod             = SendMod,
               send_handle          = SendHandle,
               encoding_mod         = EncodingMod,
               encoding_config      = EncodingConfig,
               protocol_version     = user_info(Mid, protocol_version),
               auth_data            = user_info(Mid, auth_data),
               user_mod             = user_info(Mid, user_mod),
               user_args            = user_info(Mid, user_args),
               reply_action         = undefined,
               reply_data           = user_info(Mid, reply_data),
	       threaded             = user_info(Mid, threaded),
	       strict_version       = user_info(Mid, strict_version),
	       long_request_resend  = user_info(Mid, long_request_resend),
	       call_proxy_gc_timeout = user_info(Mid, call_proxy_gc_timeout),
	       cancel               = false,
	       resend_indication    = user_info(Mid, resend_indication),
	       segment_reply_ind    = user_info(Mid, segment_reply_ind),
	       segment_recv_acc     = user_info(Mid, segment_recv_acc),
	       segment_recv_timer   = user_info(Mid, segment_recv_timer),
	       segment_send         = user_info(Mid, segment_send),
	       segment_send_timer   = user_info(Mid, segment_send_timer),
	       max_pdu_size         = user_info(Mid, max_pdu_size),
	       request_keep_alive_timeout = user_info(Mid, request_keep_alive_timeout)
	      }.

handle_disconnect(ConnHandle) when is_record(ConnHandle, megaco_conn_handle) ->
    case ets:lookup(megaco_local_conn, ConnHandle) of
        [ConnData] ->
	    ets:delete(megaco_local_conn, ConnHandle),
	    RemoteConnData = handle_disconnect_remote(ConnHandle, '_'),
            {ok, ConnData, RemoteConnData};
        [] ->
            {error, {already_disconnected, ConnHandle}}
    end.

handle_disconnect_remote(ConnHandle, UserNode) ->
    Pat = #remote_conn_data{conn_handle = ConnHandle,
			    user_node   = UserNode,
			    monitor_ref = '_'},
    RemoteConnData = ets:match_object(megaco_remote_conn, Pat),
    ets:match_delete(megaco_remote_conn, Pat),
    RemoteConnData.

make_receive_handle(UserMid) ->
    #megaco_receive_handle{local_mid       = UserMid,
			   encoding_mod    = user_info(UserMid, encoding_mod),
			   encoding_config = user_info(UserMid, encoding_config),
			   send_mod        = user_info(UserMid, send_mod)}.


%%-----------------------------------------------------------------
%% Func: create_snmp_counters/1, update_snmp_counters/2
%% Description: create/update all the SNMP statistic counters
%%-----------------------------------------------------------------

create_snmp_counters(CH) ->
    create_snmp_counters(CH, snmp_counters()).

% create_snmp_counters(CH, []) ->
%     ok;
% create_snmp_counters(CH, [Counter|Counters]) ->
%     Key = {CH, Counter},
%     ets:insert(megaco_stats, {Key, 0}),
%     create_snmp_counters(CH, Counters).

create_snmp_counters(CH, Counters) ->
    F = fun(Counter) -> 
		Key = {CH, Counter},
		ets:insert(megaco_stats, {Key, 0}) 
	end,
    lists:foreach(F, Counters).


update_snmp_counters(CH, PrelCH) ->
    update_snmp_counters(CH, PrelCH, snmp_counters()).

update_snmp_counters(_CH, _PrelCH, []) ->
    ok;
update_snmp_counters(CH, PrelCH, [Counter|Counters]) ->
    PrelKey = {PrelCH, Counter},
    Key     = {CH, Counter},
    [{PrelKey,PrelVal}] = ets:lookup(megaco_stats, PrelKey),
    _ = ets:update_counter(megaco_stats, Key, PrelVal),
    ets:delete(megaco_stats, PrelKey),
    update_snmp_counters(CH, PrelCH, Counters).


global_snmp_counters() ->
    [medGwyGatewayNumErrors].

snmp_counters() ->
    [medGwyGatewayNumTimerRecovery,
     medGwyGatewayNumErrors].



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

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


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

%% warning_msg(F) ->
%%     warning_msg(F, []).
warning_msg(F, A) ->
    ?megaco_warning("Config server: " ++ F, A).

error_msg(F) ->
    error_msg(F, []).
error_msg(F, A) ->
    ?megaco_error("Config server: " ++ F, A).

