%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%----------------------------------------------------------------------
%% Purpose: Handle configuration of Megaco/H.248
%%----------------------------------------------------------------------
-module(megaco_config).
-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(CH, Item)
when is_record(CH, megaco_conn_handle) andalso (Item /= cancel) ->
case Item of
conn_handle ->
CH;
mid ->
CH#megaco_conn_handle.local_mid;
local_mid ->
CH#megaco_conn_handle.local_mid;
remote_mid ->
CH#megaco_conn_handle.remote_mid;
conn_data ->
case lookup_local_conn(CH) of
[] ->
exit({no_such_connection, CH});
[ConnData] ->
ConnData
end;
_ ->
case lookup_local_conn(CH) of
[] ->
exit({no_such_connection, CH});
[ConnData] ->
conn_info(ConnData, Item)
end
end;
conn_info(#conn_data{conn_handle = CH}, cancel) ->
%% 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);
conn_info(CD, Item) when is_record(CD, conn_data) ->
case Item of
all ->
Tags0 = record_info(fields, conn_data),
Tags1 = replace(serial, trans_id, Tags0),
Tags = [mid, local_mid, remote_mid] ++
replace(max_serial, max_trans_id, Tags1),
[{Tag, conn_info(CD,Tag)} || Tag <- Tags,
Tag /= conn_data,
Tag /= trans_sender,
Tag /= cancel];
conn_data -> CD;
conn_handle -> CD#conn_data.conn_handle;
mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid;
local_mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid;
remote_mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.remote_mid;
trans_id -> CH = CD#conn_data.conn_handle,
LocalMid = CH#megaco_conn_handle.local_mid,
Item2 = {LocalMid, trans_id_counter},
case (catch ets:lookup(megaco_config, Item2)) of
{'EXIT', _} ->
undefined_serial;
[] ->
user_info(LocalMid, min_trans_id);
[{_, Serial}] ->
Max = CD#conn_data.max_serial,
if
((Max =:= infinity) andalso
is_integer(Serial) andalso
(Serial < 4294967295)) ->
Serial + 1;
(Max =:= infinity) andalso
is_integer(Serial) andalso
(Serial =:= 4294967295) ->
user_info(LocalMid,
min_trans_id);
Serial < Max ->
Serial + 1;
Serial =:= Max ->
user_info(LocalMid,
min_trans_id);
Serial =:= 4294967295 ->
user_info(LocalMid,
min_trans_id);
true ->
undefined_serial
end
end;
max_trans_id -> CD#conn_data.max_serial;
request_timer -> CD#conn_data.request_timer;
long_request_timer -> CD#conn_data.long_request_timer;
auto_ack -> CD#conn_data.auto_ack;
trans_ack -> CD#conn_data.trans_ack;
trans_ack_maxcount -> CD#conn_data.trans_ack_maxcount;
trans_req -> CD#conn_data.trans_req;
trans_req_maxcount -> CD#conn_data.trans_req_maxcount;
trans_req_maxsize -> CD#conn_data.trans_req_maxsize;
trans_timer -> CD#conn_data.trans_timer;
pending_timer -> CD#conn_data.pending_timer;
orig_pending_limit -> CD#conn_data.sent_pending_limit;
sent_pending_limit -> CD#conn_data.sent_pending_limit;
recv_pending_limit -> CD#conn_data.recv_pending_limit;
reply_timer -> CD#conn_data.reply_timer;
control_pid -> CD#conn_data.control_pid;
monitor_ref -> CD#conn_data.monitor_ref;
send_mod -> CD#conn_data.send_mod;
send_handle -> CD#conn_data.send_handle;
encoding_mod -> CD#conn_data.encoding_mod;
encoding_config -> CD#conn_data.encoding_config;
protocol_version -> CD#conn_data.protocol_version;
auth_data -> CD#conn_data.auth_data;
user_mod -> CD#conn_data.user_mod;
user_args -> CD#conn_data.user_args;
reply_action -> CD#conn_data.reply_action;
reply_data -> CD#conn_data.reply_data;
threaded -> CD#conn_data.threaded;
strict_version -> CD#conn_data.strict_version;
long_request_resend -> CD#conn_data.long_request_resend;
call_proxy_gc_timeout -> CD#conn_data.call_proxy_gc_timeout;
cancel -> CD#conn_data.cancel;
resend_indication -> CD#conn_data.resend_indication;
segment_reply_ind -> CD#conn_data.segment_reply_ind;
segment_recv_acc -> CD#conn_data.segment_recv_acc;
segment_recv_timer -> CD#conn_data.segment_recv_timer;
segment_send -> CD#conn_data.segment_send;
segment_send_timer -> CD#conn_data.segment_send_timer;
max_pdu_size -> CD#conn_data.max_pdu_size;
request_keep_alive_timeout -> CD#conn_data.request_keep_alive_timeout;
receive_handle ->
LocalMid = (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid,
#megaco_receive_handle{local_mid = LocalMid,
encoding_mod = CD#conn_data.encoding_mod,
encoding_config = CD#conn_data.encoding_config,
send_mod = CD#conn_data.send_mod};
_ ->
exit({no_such_item, Item})
end;
conn_info(BadHandle, _Item) ->
{error, {no_such_connection, BadHandle}}.
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:_ ->
try
begin
cre_counter(Item, Incr)
end
catch
exit:_ ->
%% Ok, some other process got there before us,
%% so try again
ets:update_counter(megaco_config, Item, Incr)
end
end.
%% incr_counter(Item, Incr) ->
%% case (catch ets:update_counter(megaco_config, Item, Incr)) of
%% {'EXIT', _} ->
%% case (catch cre_counter(Item, Incr)) of
%% {'EXIT', _} ->
%% %% Ok, some other process got there before us,
%% %% so try again
%% ets:update_counter(megaco_config, Item, Incr);
%% NewVal ->
%% NewVal
%% end;
%% NewVal ->
%% NewVal
%% end.
cre_counter(Item, Initial) ->
case whereis(?SERVER) =:= self() of
false ->
case call({cre_counter, Item, Initial}) of
{ok, Value} ->
Value;
Error ->
exit(Error)
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};
[_] ->
%% Ouch, now what?
{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 -> verify_strict_uint(Val, 4294967295); % uint32
max_trans_id -> verify_uint(Val, 4294967295); % uint32
request_timer -> verify_timer(Val);
long_request_timer -> verify_timer(Val);
auto_ack -> verify_bool(Val);
trans_ack -> verify_bool(Val);
trans_ack_maxcount -> verify_uint(Val);
trans_req -> verify_bool(Val);
trans_req_maxcount -> verify_uint(Val);
trans_req_maxsize -> 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 -> verify_uint(Val) andalso
(Val > 0);
recv_pending_limit -> 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) -> true;
protocol_version -> 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 -> verify_bool(Val);
strict_version -> verify_bool(Val);
long_request_resend -> verify_bool(Val);
call_proxy_gc_timeout -> verify_strict_uint(Val);
cancel -> verify_bool(Val);
resend_indication -> verify_resend_indication(Val);
segment_reply_ind -> verify_bool(Val);
segment_recv_acc -> verify_bool(Val);
segment_recv_timer -> verify_timer(Val);
segment_send -> verify_segmentation_window(Val);
segment_send_timer -> verify_timer(Val);
max_pdu_size -> verify_int(Val) andalso (Val > 0);
request_keep_alive_timeout ->
(verify_uint(Val) orelse (Val =:= plain));
_ -> false
end.
verify_bool(true) -> true;
verify_bool(false) -> true;
verify_bool(_) -> false.
verify_resend_indication(flag) -> true;
verify_resend_indication(Val) -> verify_bool(Val).
-spec verify_strict_int(Int :: integer()) -> boolean().
verify_strict_int(Int) when is_integer(Int) -> true;
verify_strict_int(_) -> false.
-spec verify_strict_int(Int :: integer(),
Max :: integer() | 'infinity') -> boolean().
verify_strict_int(Int, infinity) ->
verify_strict_int(Int);
verify_strict_int(Int, Max) ->
verify_strict_int(Int) andalso verify_strict_int(Max) andalso (Int =< Max).
-spec verify_strict_uint(Int :: non_neg_integer()) -> boolean().
verify_strict_uint(Int) when is_integer(Int) andalso (Int >= 0) -> true;
verify_strict_uint(_) -> false.
-spec verify_strict_uint(Int :: non_neg_integer(),
Max :: non_neg_integer() | 'infinity') -> boolean().
verify_strict_uint(Int, infinity) ->
verify_strict_uint(Int);
verify_strict_uint(Int, Max) ->
verify_strict_int(Int, 0, Max).
-spec verify_uint(Val :: non_neg_integer() | 'infinity') -> boolean().
verify_uint(infinity) -> true;
verify_uint(Val) -> verify_strict_uint(Val).
-spec verify_int(Val :: integer() | 'infinity') -> boolean().
verify_int(infinity) -> true;
verify_int(Val) -> verify_strict_int(Val).
-spec verify_int(Int :: integer() | 'infinity',
Max :: integer() | 'infinity') -> boolean().
verify_int(Int, infinity) ->
verify_int(Int);
verify_int(infinity, _Max) ->
true;
verify_int(Int, Max) ->
verify_strict_int(Int) andalso verify_strict_int(Max) andalso (Int =< Max).
-spec verify_uint(Int :: non_neg_integer() | 'infinity',
Max :: non_neg_integer() | 'infinity') -> boolean().
verify_uint(Int, infinity) ->
verify_uint(Int);
verify_uint(infinity, _Max) ->
true;
verify_uint(Int, Max) ->
verify_strict_int(Int, 0, Max).
-spec verify_strict_int(Int :: integer(),
Min :: integer(),
Max :: integer()) -> boolean().
verify_strict_int(Val, Min, Max)
when (is_integer(Val) andalso
is_integer(Min) andalso
is_integer(Max) andalso
(Val >= Min) andalso
(Val =< Max)) ->
true;
verify_strict_int(_Val, _Min, _Max) ->
false.
-spec verify_int(Val :: integer() | 'infinity',
Min :: integer(),
Max :: integer() | 'infinity') -> boolean().
verify_int(infinity, Min, infinity) ->
verify_strict_int(Min);
verify_int(Val, Min, infinity) ->
verify_strict_int(Val) andalso
verify_strict_int(Min) andalso (Val >= Min);
verify_int(Int, Min, Max) ->
verify_strict_int(Int, Min, Max).
verify_timer(Timer) ->
megaco_timer:verify(Timer).
verify_segmentation_window(none) ->
true;
verify_segmentation_window(K) ->
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, A) ->
?megaco_warning("Config server: " ++ F, A).
error_msg(F, A) ->
?megaco_error("Config server: " ++ F, A).