diff options
Diffstat (limited to 'lib/megaco/src/engine')
23 files changed, 15050 insertions, 0 deletions
diff --git a/lib/megaco/src/engine/Makefile b/lib/megaco/src/engine/Makefile new file mode 100644 index 0000000000..3943f4b957 --- /dev/null +++ b/lib/megaco/src/engine/Makefile @@ -0,0 +1,107 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-2009. 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% + +include $(ERL_TOP)/make/target.mk + +EBIN = ../../ebin +MEGACO_INCLUDEDIR = ../../include + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(MEGACO_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/megaco-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +include modules.mk + +ERL_FILES = $(MODULES:%=%.erl) + +TARGET_FILES = \ + $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ifeq ($(TYPE),debug) +ERL_COMPILE_FLAGS += -Ddebug +endif + +include ../app/megaco.mk + +ERL_COMPILE_FLAGS += \ + $(MEGACO_ERL_COMPILE_FLAGS) \ + -I../../include + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +debug: + @${MAKE} TYPE=debug opt + +opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f errs core *~ + +docs: + +info: + @echo "MODULES = $(MODULES)" + @echo "" + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/engine + $(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src/engine + $(INSTALL_DIR) $(RELSYSDIR)/include + + +release_docs_spec: + + +# ---------------------------------------------------- +# Include dependencies +# ---------------------------------------------------- + +include depend.mk + diff --git a/lib/megaco/src/engine/depend.mk b/lib/megaco/src/engine/depend.mk new file mode 100644 index 0000000000..8d8c83e923 --- /dev/null +++ b/lib/megaco/src/engine/depend.mk @@ -0,0 +1,81 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2003-2009. 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% + +$(EBIN)/megaco_config.$(EMULATOR): megaco_config.erl \ + ../../include/megaco.hrl \ + ../app/megaco_internal.hrl + +$(EBIN)/megaco_digit_map.$(EMULATOR): megaco_digit_map.erl \ + megaco_message_internal.hrl \ + ../text/megaco_text_tokens.hrl + +$(EBIN)/megaco_encoder.$(EMULATOR): megaco_encoder.erl + +$(EBIN)/megaco_edist_compress.$(EMULATOR): megaco_edist_compress.erl + +$(EBIN)/megaco_erl_dist_encoder.$(EMULATOR): megaco_erl_dist_encoder.erl \ + megaco_message_internal.hrl + +$(EBIN)/megaco_erl_dist_encoder_mc.$(EMULATOR): megaco_erl_dist_encoder_mc.erl \ + megaco_message_internal.hrl \ + ../app/megaco_internal.hrl + +$(EBIN)/megaco_filter.$(EMULATOR): megaco_filter.erl \ + ../../include/megaco.hrl \ + ../../include/megaco_message_v1.hrl \ + ../app/megaco_internal.hrl + +$(EBIN)/megaco_messenger.$(EMULATOR): megaco_messenger.erl \ + ../../include/megaco.hrl \ + ../app/megaco_internal.hrl \ + megaco_message_internal.hrl + +$(EBIN)/megaco_messenger_misc.$(EMULATOR): megaco_messenger_misc.erl \ + ../../include/megaco.hrl \ + ../app/megaco_internal.hrl \ + megaco_message_internal.hrl + +$(EBIN)/megaco_misc_sup.$(EMULATOR): megaco_misc_sup.erl + +$(EBIN)/megaco_monitor.$(EMULATOR): megaco_monitor.erl + +$(EBIN)/megaco_sdp.$(EMULATOR): \ + megaco_sdp.erl \ + ../../include/megaco_sdp.hrl + +$(EBIN)/megaco_stats.$(EMULATOR): megaco_stats.erl + +$(EBIN)/megaco_sup.$(EMULATOR): megaco_sup.erl + +$(EBIN)/megaco_timer.$(EMULATOR): \ + megaco_timer.erl \ + ../../include/megaco.hrl + +$(EBIN)/megaco_trans_sender.$(EMULATOR): megaco_trans_sender.erl + +$(EBIN)/megaco_trans_sup.$(EMULATOR): megaco_trans_sup.erl + +$(EBIN)/megaco_transport.$(EMULATOR): megaco_transport.erl + +$(EBIN)/megaco_user.$(EMULATOR): megaco_user.erl + +$(EBIN)/megaco_user_default.$(EMULATOR): megaco_user_default.erl \ + ../../include/megaco.hrl \ + ../../include/megaco_message_v1.hrl + diff --git a/lib/megaco/src/engine/megaco_config.erl b/lib/megaco/src/engine/megaco_config.erl new file mode 100644 index 0000000000..2058c53973 --- /dev/null +++ b/lib/megaco/src/engine/megaco_config.erl @@ -0,0 +1,2113 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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_int(Val) andalso (Val >= 0)) 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). + diff --git a/lib/megaco/src/engine/megaco_digit_map.erl b/lib/megaco/src/engine/megaco_digit_map.erl new file mode 100644 index 0000000000..de28686d6d --- /dev/null +++ b/lib/megaco/src/engine/megaco_digit_map.erl @@ -0,0 +1,856 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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: Parse and evaluate digit maps +%%---------------------------------------------------------------------- +%% +%% digitMap = digitString +%% / LWSP "(" LWSP digitStringList LWSP ")" LWSP +%% digitStringList = digitString *( LWSP "|" LWSP digitString ) +%% digitString = 1*(digitStringElement) +%% digitStringElement = digitPosition [DOT] +%% digitPosition = digitMapLetter / digitMapRange +%% digitMapRange = ("x" / LWSP "[" LWSP digitLetter LWSP "]" LWSP) +%% digitLetter = *((DIGIT "-" DIGIT ) / digitMapLetter) +%% digitMapLetter = DIGIT ; Basic event symbols +%% / %x41-4B ; a-k +%% / %x61-6B ; A-K +%% / "T" ; Start inter-event timers +%% / "S" ; Short inter-event timers +%% / "L" ; Long inter-event timers, e.g. 16 sec +%% / "Z" ; Long duration modifier +%% DIGIT = %x30-39 ; 0-9 +%% +%%---------------------------------------------------------------------- +%% Example of a digit map: +%% +%% (0| 00|[1-7]xxx|8xxxxxxx|Fxxxxxxx|Exx|91xxxxxxxxxx|9011x.) +%% +%% DM = "(0| 00|[1-7]xxx|8xxxxxxx|Fxxxxxxx|Exx|91xxxxxxxxxx|9011x.)". +%% DM = "xxx | xxL3 | xxS4". +%% megaco:parse_digit_map(DM). +%% megaco:test_digit_event(DM, "1234"). +%% megaco:test_digit_event(DM, "12ssss3"). +%% megaco:test_digit_event(DM, "12ssss4"). +%% megaco:test_digit_event(DM, "12ssss5"). +%% +%%---------------------------------------------------------------------- + +-module(megaco_digit_map). + +-export([parse/1, eval/1, eval/2, report/2, test/2]). % Public +-export([test_eval/2]). % Internal + +-include_lib("megaco/src/app/megaco_internal.hrl"). +-include("megaco_message_internal.hrl"). +-include_lib("megaco/src/text/megaco_text_tokens.hrl"). + +-record(state_transition, {mode, next, cont}). + +-record(timers, {mode = state_dependent, + start = 0, + short = timer_to_millis(3), + long = timer_to_millis(9), + duration = 100, % (not used) 100 ms <-> 9.9 sec + unexpected = reject}). % ignore | reject + + +%%---------------------------------------------------------------------- +%% Parses a digit map body, represented as a list of chars, +%% into a list of state transitions. +%% +%% Returns {ok, StateTransitionList} | {error, Reason} +%% +%%---------------------------------------------------------------------- + +parse(DigitMapBody) when is_list(DigitMapBody) -> + ?d("parse -> entry with" + "~n DigitMapBody: ~p", [DigitMapBody]), + case parse_digit_map(DigitMapBody) of + {ok, STL} -> + {ok, duration_cleanup(STL, [])}; + {error, Reason} -> + {error, Reason} + end; +parse(_DigitMapBody) -> + {error, not_a_digit_map_body}. + +duration_cleanup([], Acc) -> + Acc; +duration_cleanup([STL|T], Acc) -> + #state_transition{cont = Events} = STL, + Events2 = duration_events_cleanup(Events, []), + duration_cleanup(T, [STL#state_transition{cont = Events2}|Acc]). + +duration_events_cleanup([], Acc) -> + lists:reverse(Acc); +duration_events_cleanup([duration_event, Event|Events], Acc) -> + duration_events_cleanup(Events, [{duration_event, Event}|Acc]); +duration_events_cleanup([Event|Events], Acc) -> + duration_events_cleanup(Events, [Event|Acc]). + +parse_digit_map(Chars) -> + parse_digit_map(Chars, 1, [], []). + +parse_digit_map(Chars, Line, DS, STL) -> + ?d("parse_digit_map -> entry with" + "~n Chars: ~p" + "~n DS: ~p", [Chars, DS]), + case megaco_text_scanner:skip_sep_chars(Chars, Line) of + {[], _Line2} when (DS =/= []) -> + case parse_digit_string(DS) of + {ok, DS2} -> + ST = #state_transition{mode = state_dependent, + next = start, + cont = DS2}, + STL2 = lists:reverse([ST | STL]), + {ok, STL2}; + {error, Reason} -> + {error, Reason} + end; + {[Char | Chars2], Line2} -> + case Char of + $( when (DS =:= []) andalso (STL =:= []) -> + parse_digit_map(Chars2, Line2, DS, STL); + $) when (DS =/= []) -> + case megaco_text_scanner:skip_sep_chars(Chars2, Line2) of + {[], _Line3} -> + case parse_digit_string(DS) of + {ok, DS2} -> + ST = #state_transition{mode = state_dependent, + next = start, + cont = DS2}, + STL2 = lists:reverse([ST | STL]), + {ok, STL2}; + {error, Reason} -> + {error, Reason} + end; + {Chars3, Line3} -> + Trash = lists:reverse(Chars3), + {error, {round_bracket_mismatch, Trash, Line3}} + end; + $| when (DS =/= []) -> + case parse_digit_string(DS) of + {ok, DS2} -> + ST = #state_transition{mode = state_dependent, + next = start, + cont = DS2}, + parse_digit_map(Chars2, Line2, [], [ST | STL]); + {error, Reason} -> + {error, Reason} + end; + _ when ( Char =/= $( ) andalso + ( Char =/= $| ) andalso + ( Char =/= $) ) -> + parse_digit_map(Chars2, Line2, [Char | DS], STL); + _ -> + {error, {round_bracket_mismatch, Line2}} + end; + {[], Line2} -> + {error, {digit_string_expected, Line2}} + end. + +parse_digit_string(Chars) -> + ?d("parse_digit_string -> entry with" + "~n Chars: ~p", [Chars]), + parse_digit_string(Chars, []). + +parse_digit_string([Char | Chars], DS) -> + ?d("parse_digit_string -> entry with" + "~n Char: ~p" + "~n Chars: ~p" + "~n DS: ~p", [[Char], Chars, DS]), + case Char of + $] -> + parse_digit_letter(Chars, [], DS); + $[ -> + {error, square_bracket_mismatch}; + $x -> + parse_digit_string(Chars, [{range, $0, $9} | DS]); + $. -> + parse_digit_string(Chars, [zero_or_more | DS]); + + I when (I >= $0) andalso (I =< $9) -> + parse_digit_string(Chars, [{single, I} | DS]); + + A when (A >= $a) andalso (A =< $k) -> + parse_digit_string(Chars, [{single, A} | DS]); + A when (A >= $A) andalso (A =< $K) -> + parse_digit_string(Chars, [{single, A} | DS]); + + $S -> + parse_digit_string(Chars, [use_short_timer | DS]); + $s -> + parse_digit_string(Chars, [use_short_timer | DS]); + + $L -> + parse_digit_string(Chars, [use_long_timer | DS]); + $l -> + parse_digit_string(Chars, [use_long_timer | DS]); + + $Z when length(Chars) > 0 -> + parse_digit_string(Chars, [duration_event | DS]); + $z when length(Chars) > 0 -> + parse_digit_string(Chars, [duration_event | DS]); + + $Z -> + {error, duration_not_allowed_as_last_char}; + $z -> + {error, duration_not_allowed_as_last_char}; + + BadChar -> + {error, {illegal_char_in_digit_string, BadChar}} + end; +parse_digit_string([], DM) -> + ?d("parse_digit_string -> entry when done with" + "~n DM: ~p", [DM]), + {ok, DM}. + + +parse_digit_letter([Char | Chars], DL, DS) -> + ?d("parse_digit_letter -> entry with" + "~n Char: ~p" + "~n Chars: ~p" + "~n DL: ~p" + "~n DS: ~p", [[Char], Chars, DL, DS]), + case Char of + $[ -> + parse_digit_string(Chars, [{letter, DL} | DS]); + $] -> + {error, square_bracket_mismatch}; + To when (To >= $0) andalso (To =< $9) -> + case Chars of + [$-, From | Chars2] when (From >= $0) andalso (From =< $9) -> + parse_digit_letter(Chars2, [{range, From, To} | DL], DS); + _ -> + parse_digit_letter(Chars, [{single, To} | DL], DS) + end; + + A when (A >= $a) andalso (A =< $k) -> + parse_digit_letter(Chars, [{single, A} | DL], DS); + A when (A >= $A) andalso (A =< $K) -> + parse_digit_letter(Chars, [{single, A} | DL], DS); + + $S -> + parse_digit_letter(Chars, [use_short_timer | DL], DS); + $s -> + parse_digit_letter(Chars, [use_short_timer | DL], DS); + + $L -> + parse_digit_letter(Chars, [use_long_timer | DL], DS); + $l -> + parse_digit_letter(Chars, [use_long_timer | DL], DS); + + $Z -> + parse_digit_letter(Chars, [duration_event | DL], DS); + $z -> + parse_digit_letter(Chars, [duration_event | DL], DS); + + BadChar -> + {error, {illegal_char_between_square_brackets, BadChar}} + end; +parse_digit_letter([], _DL, _DS) -> + {error, square_bracket_mismatch}. + + +%%---------------------------------------------------------------------- +%% Collect digit map letters according to digit map +%% Returns {ok, Letters} | {error, Reason} +%%---------------------------------------------------------------------- + +eval(DMV) when is_record(DMV, 'DigitMapValue') -> + case parse(DMV#'DigitMapValue'.digitMapBody) of + {ok, DigitMapBody} -> + eval(DigitMapBody, DMV); + {error, Reason} -> + {error, Reason} + end; +eval(STL) when is_list(STL) -> + eval(STL, #timers{}). + +eval(STL, #'DigitMapValue'{startTimer = Start, + shortTimer = Short, + longTimer = Long, + durationTimer = Duration}) -> + Timers = #timers{start = timer_to_millis(Start), + short = timer_to_millis(Short), + long = timer_to_millis(Long), + duration = duration_to_millis(Duration)}, + eval(STL, Timers); +eval(STL, {ignore, #'DigitMapValue'{startTimer = Start, + shortTimer = Short, + longTimer = Long, + durationTimer = Duration}}) -> + Timers = #timers{start = timer_to_millis(Start), + short = timer_to_millis(Short), + long = timer_to_millis(Long), + duration = duration_to_millis(Duration), + unexpected = ignore}, + eval(STL, Timers); +eval(STL, {reject, #'DigitMapValue'{startTimer = Start, + shortTimer = Short, + longTimer = Long, + durationTimer = Duration}}) -> + Timers = #timers{start = timer_to_millis(Start), + short = timer_to_millis(Short), + long = timer_to_millis(Long), + duration = duration_to_millis(Duration), + unexpected = reject}, + eval(STL, Timers); +eval(STL, Timers) when is_list(STL) andalso + is_record(hd(STL), state_transition) andalso + is_record(Timers, timers) -> + ?d("eval -> entry with" + "~n STL: ~p" + "~n Timers: ~p", [STL, Timers]), + case collect(start, mandatory_event, Timers, lists:reverse(STL), []) of + {error, _} = Error -> + ?d("eval -> error:" + "~n Error: ~p", [Error]), + Error; + OK -> + ?d("eval -> ok:" + "~n OK: ~p", [OK]), + OK + end; +eval(DigitMapBody, ignore) -> + eval(DigitMapBody, #timers{unexpected = ignore}); +eval(DigitMapBody, reject) -> + eval(DigitMapBody, #timers{unexpected = reject}); +eval(DigitMapBody, Timers) -> + case parse(DigitMapBody) of + {ok, STL} -> + eval(STL, Timers); + {error, Reason} -> + {error, Reason} + end. + +%% full | unambiguous + +collect(Event, State, Timers, STL, Letters) -> + ?d("collect -> entry with" + "~n Event: ~p" + "~n State: ~p" + "~n Timers: ~p" + "~n STL: ~p", [Event, State, Timers, STL]), + case handle_event(Event, State, Timers, STL, Letters) of + {completed_full, _Timers2, _STL2, Letters2} -> + completed(full, Letters2); + {completed, _Timers2, _STL2, Letters2} -> + completed(unambiguous, Letters2); + {State2, Timers2, STL2, Letters2} -> + ?d("collect -> " + "~n State2: ~p" + "~n Timers2: ~p" + "~n Letters2: ~p", [State2, Timers2, Letters2]), + MaxWait = choose_timer(State2, Event, Timers2), + ?d("collect -> Timer choosen: " + "~n MaxWait: ~p", [MaxWait]), + receive + {?MODULE, _FromPid, Event2} -> + ?d("collect -> Got event: " + "~n ~p", [Event2]), + collect(Event2, State2, Timers2, STL2, Letters2) + after MaxWait -> + ?d("collect -> timeout after ~w", [MaxWait]), + collect(inter_event_timeout, + State2, Timers2, STL2, Letters2) + end; + + {error, Reason} -> + ?d("collect -> error: " + "~n Reason: ~p", [Reason]), + {error, Reason} + end. + +choose_timer(_State, start, #timers{start = 0}) -> + ?d("choose_timer(start) -> entry", []), + infinity; +choose_timer(_State, start, #timers{start = T}) -> + ?d("choose_timer(start) -> entry with" + "~n T: ~p", [T]), + T; +choose_timer(State, _Event, T) -> + ?d("choose_timer(~p) -> entry with" + "~n State: ~p" + "~n T: ~p", [_Event, State, T]), + do_choose_timer(State, T). + +do_choose_timer(mandatory_event, #timers{mode = state_dependent, long = T}) -> + T; +do_choose_timer(optional_event, #timers{mode = state_dependent, short = T}) -> + T; +do_choose_timer(_State, #timers{mode = use_short_timer, short = T}) -> + T; +do_choose_timer(_State, #timers{mode = use_long_timer, long = T}) -> + T. + +timer_to_millis(asn1_NOVALUE) -> infinity; +timer_to_millis(infinity) -> infinity; +timer_to_millis(Seconds) -> timer:seconds(Seconds). + +%% Time for duration is in hundreds of milliseconds +duration_to_millis(asn1_NOVALUE) -> 100; +duration_to_millis(Time) when is_integer(Time) -> Time*100. + +completed(Kind, {Letters, Event}) when is_list(Letters) -> + ?d("completed -> entry with" + "~n Kind: ~p" + "~n Event: ~s", [Kind, [Event]]), + {ok, {Kind, duration_letter_cleanup(Letters, []), Event}}; +completed(Kind, Letters) when is_list(Letters) -> + ?d("completed -> entry with" + "~n Kind: ~p", [Kind]), + {ok, {Kind, duration_letter_cleanup(Letters, [])}}. + +duration_letter_cleanup([], Acc) -> + Acc; +duration_letter_cleanup([{long, Letter}|Letters], Acc) -> + duration_letter_cleanup(Letters, [$Z,Letter|Acc]); +duration_letter_cleanup([Letter|Letters], Acc) -> + duration_letter_cleanup(Letters, [Letter|Acc]). + +unexpected_event(Event, STL, Letters) -> + Expected = [Next || #state_transition{next = Next} <- STL], + SoFar = lists:reverse(Letters), + Reason = {unexpected_event, Event, SoFar, Expected}, + {error, Reason}. + + +%%---------------------------------------------------------------------- +%% Handles a received event according to digit map +%% State ::= optional_event | mandatory_event +%% +%% Returns {State, NewSTL, Letters} | {error, Reason} +%%---------------------------------------------------------------------- +handle_event(inter_event_timeout, optional_event, Timers, STL, Letters) -> + {completed_full, Timers, STL, Letters}; % 7.1.14.5 2 +handle_event(cancel, _State, _Timers, STL, Letters) -> + unexpected_event(cancel, STL, Letters); +handle_event(start, _State, Timers, STL, Letters) -> + {State2, Timers2, STL2} = compute(Timers, STL), + {State2, Timers2, STL2, Letters}; +handle_event(Event, State, Timers, STL, Letters) -> + ?d("handle_event -> entry when" + "~n Event: ~p" + "~n State: ~p" + "~n Timers: ~p" + "~n Letters: ~p", [Event, State, Timers, Letters]), + {STL2, Collect, KeepDur} = match_event(Event, STL), + ?d("handle_event -> match event result: " + "~n Collect: ~p" + "~n KeepDur: ~p" + "~n STL2: ~p", [Collect, KeepDur, STL2]), + case STL2 of + [] when (State =:= optional_event) -> % 7.1.14.5 5 + ?d("handle_event -> complete-full with event - 7.1.14.5 5", []), + {completed_full, Timers, [], {Letters, Event}}; + [] when (Timers#timers.unexpected =:= ignore) -> + ok = io:format("<WARNING> Ignoring unexpected event: ~p~n" + "Expected: ~p~n", + [Event, STL]), + {State, Timers, STL, Letters}; + [] when (Timers#timers.unexpected =:= reject) -> + ?d("handle_event -> unexpected (reject)", []), + unexpected_event(Event, STL, Letters); + _ -> + {State3, Timers2, STL3} = compute(Timers, STL2), + ?d("handle_event -> computed: " + "~n State3: ~p" + "~n Timers2: ~p" + "~n STL3: ~p", [State3, Timers2, STL3]), + case Collect of + true when (KeepDur =:= true) -> + {State3, Timers2, STL3, [Event | Letters]}; + true -> + case Event of + {long, ActualEvent} -> + {State3, Timers2, STL3, [ActualEvent | Letters]}; + _ -> + {State3, Timers2, STL3, [Event | Letters]} + end; + false -> + {State3, Timers2, STL3, Letters} + end + end. + +match_event(Event, STL) -> + MatchingDuration = matching_duration_event(Event, STL), + match_event(Event, STL, [], false, false, MatchingDuration). + +match_event(Event, [ST | OldSTL], NewSTL, Collect, KeepDur, MatchingDuration) + when is_record(ST, state_transition) -> + ?d("match_event -> entry with" + "~n Event: ~p" + "~n ST: ~p" + "~n NewSTL: ~p" + "~n Collect: ~p" + "~n KeepDur: ~p" + "~n MatchingDuration: ~p", + [Event, ST, NewSTL, Collect, KeepDur, MatchingDuration]), + case ST#state_transition.next of + {single, Event} -> + ?d("match_event -> keep ST (1)", []), + match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur, + MatchingDuration); + + {single, Single} when (Event =:= {long, Single}) andalso + (MatchingDuration =:= false) -> + %% Chap 7.1.14.5 point 4 + ?d("match_event -> keep ST - change to ordinary event (2)", []), + match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur, + MatchingDuration); + + {range, From, To} when (Event >= From) andalso (Event =< To) -> + ?d("match_event -> keep ST (3)", []), + ST2 = ST#state_transition{next = {single, Event}}, + match_event(Event, OldSTL, [ST2 | NewSTL], true, KeepDur, + MatchingDuration); + + {range, From, To} -> + case Event of + {long, R} when (R >= From) andalso + (R =< To) andalso + (MatchingDuration =:= false) -> + ?d("match_event -> keep ST (4)", []), + ST2 = ST#state_transition{next = {single, R}}, + match_event(Event, OldSTL, [ST2 | NewSTL], true, true, + MatchingDuration); + _ -> + ?d("match_event -> drop ST - " + "change to ordinary event (5)", []), + match_event(Event, OldSTL, NewSTL, Collect, KeepDur, + MatchingDuration) + end; + + {duration_event, {single, Single}} when (Event =:= {long, Single}) -> + ?d("match_event -> keep ST (5)", []), + match_event(Event, OldSTL, [ST | NewSTL], true, true, + MatchingDuration); + + {duration_event, {range, From, To}} -> + case Event of + {long, R} when (R >= From) andalso (R =< To) -> + ?d("match_event -> keep ST (6)", []), + match_event(Event, OldSTL, [ST | NewSTL], true, true, + MatchingDuration); + _ -> + ?d("match_event -> drop ST (7)", []), + match_event(Event, OldSTL, NewSTL, Collect, KeepDur, + MatchingDuration) + end; + + Event -> + ?d("match_event -> keep ST (8)", []), + match_event(Event, OldSTL, [ST | NewSTL], Collect, KeepDur, + MatchingDuration); + + {letter, Letters} -> + case match_letter(Event, Letters, MatchingDuration) of + {true, ChangedEvent} -> + ?d("match_event -> keep ST (9)", []), + ST2 = ST#state_transition{next = ChangedEvent}, + match_event(Event, OldSTL, [ST2 | NewSTL], true, KeepDur, + MatchingDuration); + true -> + ?d("match_event -> keep ST (10)", []), + match_event(Event, OldSTL, [ST | NewSTL], true, KeepDur, + MatchingDuration); + false -> + ?d("match_event -> drop ST (11)", []), + match_event(Event, OldSTL, NewSTL, Collect, KeepDur, + MatchingDuration) + end; + + _ -> + ?d("match_event -> drop ST (12)", []), + match_event(Event, OldSTL, NewSTL, Collect, KeepDur, + MatchingDuration) + end; +match_event(Event, [H | T], NewSTL, Collect, KeepDur0, MatchingDuration) + when is_list(H) -> + ?d("match_event -> entry with" + "~n Event: ~p" + "~n H: ~p" + "~n NewSTL: ~p" + "~n Collect: ~p" + "~n KeepDur0: ~p" + "~n MatchingDuration: ~p", + [Event, H, NewSTL, Collect, KeepDur0, MatchingDuration]), + {NewSTL2, _Letters, KeepDur} = + match_event(Event, H, NewSTL, Collect, KeepDur0, MatchingDuration), + ?d("match_event -> " + "~n NewSTLs: ~p", [NewSTL2]), + match_event(Event, T, NewSTL2, Collect, KeepDur, + MatchingDuration); +match_event(_Event, [], NewSTL, Collect, KeepDur, _MatchingDuration) -> + ?d("match_event -> entry with" + "~n NewSTL: ~p" + "~n Collect: ~p" + "~n KeepDur: ~p", [NewSTL, Collect, KeepDur]), + {lists:reverse(NewSTL), Collect, KeepDur}. + + +match_letter(_Event, [], _MatchingDuration) -> + false; +match_letter(Event, [Letter | Letters], MatchingDuration) -> + ?d("match_letter -> entry with" + "~n Event: ~p" + "~n Letter: ~p", + [Event, Letter]), + case Letter of + {single, Event} -> + ?d("match_letter -> keep ST (1)", []), + true; + + {single, Single} when (Event =:= {long, Single}) andalso + (MatchingDuration =:= false) -> + %% Chap 7.1.14.5 point 4 + ?d("match_letter -> keep ST - change to ordinary event (2)", []), + true; + + {range, From, To} when (Event >= From) andalso (Event =< To) -> + ?d("match_letter -> keep ST (3)", []), + {true, {single, Event}}; + + {range, From, To} -> + case Event of + {long, R} when (R >= From) andalso + (R =< To) andalso + (MatchingDuration =:= false) -> + ?d("match_letter -> keep ST (4)", []), + {true, {single, R}}; + _ -> + ?d("match_letter -> drop ST - " + "change to ordinary event (5)", []), + match_letter(Event, Letters, MatchingDuration) + end; + + {duration_event, {single, Single}} when (Event =:= {long, Single}) -> + ?d("match_letter -> keep ST (5)", []), + true; + + {duration_event, {range, From, To}} -> + case Event of + {long, R} when (R >= From) andalso (R =< To) -> + ?d("match_letter -> keep ST (6)", []), + true; + _ -> + ?d("match_letter -> drop ST (7)", []), + match_letter(Event, Letters, MatchingDuration) + end; + + _ -> + ?d("match_letter -> drop ST (8)", []), + match_letter(Event, Letters, MatchingDuration) + + end. + + + + +matching_duration_event({long, Event}, STL) -> + Nexts = [Next || #state_transition{next = Next} <- STL], + mde(Event, Nexts); +matching_duration_event(_Event, _STL) -> + false. + + +mde(_, []) -> + false; +mde(Event, [{duration_event, {single, Event}}|_]) -> + true; +mde(Event, [{duration_event, {range, From, To}}|_]) + when Event >= From, Event =< To -> + true; +mde(Event, [_|Nexts]) -> + mde(Event, Nexts). + + +%%---------------------------------------------------------------------- +%% Compute new state transitions +%% Returns {State, Timers, NewSTL} +%%---------------------------------------------------------------------- +compute(Timers, OldSTL) -> + ?d("compute -> entry with" + "~n Timers: ~p" + "~n OldSTL: ~p", [Timers, OldSTL]), + {State, GlobalMode, NewSTL} = + compute(mandatory_event, state_dependent, OldSTL, []), + ?d("compute -> " + "~n State: ~p" + "~n GlobalMode: ~p" + "~n NewSTL: ~p", [State, GlobalMode, NewSTL]), + Timers2 = Timers#timers{mode = GlobalMode}, + ?d("compute -> " + "~n Timers2: ~p", [Timers2]), + {State, Timers2, NewSTL}. + +compute(State, GlobalMode, [ST | OldSTL], NewSTL) + when is_record(ST, state_transition) -> + ?d("compute(~w) -> entry with" + "~n GlobalMode: ~p" + "~n ST: ~p" + "~n NewSTL: ~p", [State, GlobalMode, ST, NewSTL]), + Cont = ST#state_transition.cont, + Mode = ST#state_transition.mode, + {State2, GlobalMode2, NewSTL2} = + compute_cont(Cont, Mode, GlobalMode, State, NewSTL), + compute(State2, GlobalMode2, OldSTL, NewSTL2); +compute(State, GlobalMode, [H | T], NewSTL) when is_list(H) -> + ?d("compute(~w) -> entry with" + "~n GlobalMode: ~p" + "~n H: ~p" + "~n NewSTL: ~p", [State, GlobalMode, H, NewSTL]), + {State2, GlobalMode2, NewSTL2} = compute(State, GlobalMode, H, NewSTL), + compute(State2, GlobalMode2, T, NewSTL2); +compute(State, GlobalMode, [], NewSTL) -> + ?d("compute(~w) -> entry with" + "~n GlobalMode: ~p" + "~n NewSTL: ~p", [State, GlobalMode, NewSTL]), + case NewSTL of + [] -> {completed, GlobalMode, NewSTL}; + _ -> {State, GlobalMode, NewSTL} + end. + +compute_cont([Next | Cont] = All, Mode, GlobalMode, State, STL) -> + ?d("compute_cont -> entry with" + "~n Next: ~p" + "~n Mode: ~p" + "~n GlobalMode: ~p", [Next, Mode, GlobalMode]), + case Next of + %% Retain long timer if that has already been choosen + use_short_timer when GlobalMode =:= use_long_timer -> + compute_cont(Cont, Mode, GlobalMode, State, STL); + use_short_timer -> + Mode2 = use_short_timer, + compute_cont(Cont, Mode2, GlobalMode, State, STL); + use_long_timer -> + Mode2 = use_long_timer, + compute_cont(Cont, Mode2, GlobalMode, State, STL); + [] -> + %% Skip empty list + case Cont of + [zero_or_more | Cont2] -> + compute_cont(Cont2, Mode, GlobalMode, State, STL); + _ -> + compute_cont(Cont, Mode, GlobalMode, State, STL) + end; + _ -> + GlobalMode2 = + case Mode of + state_dependent -> GlobalMode; + _ -> Mode + end, + case Cont of + [zero_or_more | Cont2] -> + ST = make_cont(Mode, Next, All), + compute_cont(Cont2, Mode, GlobalMode2, State, [ST | STL]); + _ -> + ST = make_cont(Mode, Next, Cont), + {State, GlobalMode2, [ST | STL]} + end + end; +compute_cont([], GlobalMode, _Mode, _State, STL) -> + {optional_event, GlobalMode, STL}. + +make_cont(Mode, [Next | Cont2], Cont) -> + #state_transition{mode = Mode, next = Next, cont = [Cont2 | Cont]}; +make_cont(Mode, Next, Cont) -> + #state_transition{mode = Mode, next = Next, cont = Cont}. + + +%%---------------------------------------------------------------------- +%% Send one or more events to event collector process +%% +%% Events ::= Event* | Event +%% Event ::= $0-$9 | $a-$k | $A-$K | $S | $L | $Z +%% $S means sleep one second +%% $L means sleep ten seconds +%% $Z means cancel +%% Returns ok | {error, Reason} +%%---------------------------------------------------------------------- + +report(Pid, [H | T])-> + case report(Pid, H) of + ok -> + report(Pid, T); + {error, Reason} -> + {error, Reason} + end; +report(_Pid, [])-> + ok; +report(Pid, Event) when is_pid(Pid) -> + case Event of + I when I >= $0, I =< $9 -> cast(Pid, Event); + A when A >= $a, A =< $k -> cast(Pid, Event); + A when A >= $A, A =< $K -> cast(Pid, Event); + cancel -> cast(Pid, Event); + $Z -> cast(Pid, cancel); + $z -> cast(Pid, cancel); + $R -> timer:sleep(100); % 100 ms + $r -> timer:sleep(100); % 100 ms + $S -> sleep(1); % 1 sec (1000 ms) + $s -> sleep(1); % 1 sec (1000 ms) + $L -> sleep(10); % 10 sec (10000 ms) + $l -> sleep(10); % 10 sec (10000 ms) + {long, I} when (I >= $0) and (I =< $9) -> cast(Pid, {long, I}); + {long, A} when (A >= $a) and (A =< $k) -> cast(Pid, {long, A}); + {long, A} when (A >= $A) and (A =< $K) -> cast(Pid, {long, A}); +%% {long, I} when (I >= $0) and (I =< $9) -> long(Pid, I); +%% {long, A} when (A >= $a) and (A =< $k) -> long(Pid, A); +%% {long, A} when (A >= $A) and (A =< $K) -> long(Pid, A); + _ -> {error, {illegal_event, Event}} + end. + +%% long(Pid, Event) -> +%% cast(Pid, long), +%% cast(Pid, Event). +%% +sleep(Sec) -> + timer:sleep(timer:seconds(Sec)), + ok. + +cast(Pid, Event) -> + Pid ! {?MODULE, self(), Event}, + ok. + +%%---------------------------------------------------------------------- +%% Feed digit map collector with events +%% Returns: {ok, Letters} | {error, Reason} +%%---------------------------------------------------------------------- + +test(DigitMap, Events) -> + Self = self(), + Pid = spawn_link(?MODULE, test_eval, [DigitMap, Self]), + report(Pid, Events), + receive + {Self, Pid, Res} -> + Res; + {'EXIT', Pid, Reason} -> + {error, {'EXIT', Reason}} + end. + +test_eval(DigitMap, Parent) -> + Res = eval(DigitMap), + unlink(Parent), + Parent ! {Parent, self(), Res}, + exit(normal). diff --git a/lib/megaco/src/engine/megaco_edist_compress.erl b/lib/megaco/src/engine/megaco_edist_compress.erl new file mode 100644 index 0000000000..544e3b8ff0 --- /dev/null +++ b/lib/megaco/src/engine/megaco_edist_compress.erl @@ -0,0 +1,33 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. 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: Megaco erlang dist compress behaviour module +%%---------------------------------------------------------------------- + +-module(megaco_edist_compress). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{encode,2}, + {decode,2}]; +behaviour_info(_) -> + undefined. diff --git a/lib/megaco/src/engine/megaco_encoder.erl b/lib/megaco/src/engine/megaco_encoder.erl new file mode 100644 index 0000000000..a07ee1a6bc --- /dev/null +++ b/lib/megaco/src/engine/megaco_encoder.erl @@ -0,0 +1,37 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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: Megaco encoder behaviour module +%%---------------------------------------------------------------------- + +-module(megaco_encoder). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{encode_message, 3}, + {decode_message, 3}, + {decode_mini_message, 3}, + {encode_transaction, 3}, + {encode_action_requests, 3}, + {encode_action_reply, 3}]; +behaviour_info(_) -> + undefined. diff --git a/lib/megaco/src/engine/megaco_erl_dist_encoder.erl b/lib/megaco/src/engine/megaco_erl_dist_encoder.erl new file mode 100644 index 0000000000..e8ade615df --- /dev/null +++ b/lib/megaco/src/engine/megaco_erl_dist_encoder.erl @@ -0,0 +1,275 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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: Externalize/internalize Megaco/H.248 messages +%%---------------------------------------------------------------------- + +-module(megaco_erl_dist_encoder). + +-behaviour(megaco_encoder). + +-export([encode_message/3, decode_message/3, + decode_mini_message/3, + + encode_transaction/3, + encode_action_requests/3, + encode_action_request/3, + encode_command_request/3, + encode_action_reply/3 + ]). +-export([version_of/2]). + +%% Backward compatible funcs: +-export([encode_message/2, decode_message/2]). + + +-include("megaco_message_internal.hrl"). + +-define(MC_MOD, megaco_erl_dist_encoder_mc). + + +%%---------------------------------------------------------------------- +%% Convert a 'MegacoMessage' record into a binary +%% Return {ok, DeepIoList} | {error, Reason} +%%---------------------------------------------------------------------- + +encode_message(Config, + #'MegacoMessage'{mess = #'Message'{version = V}} = MegaMsg) -> + encode_message(Config, V, MegaMsg). + +encode_message([{version3, _}|EC], Vsn, MegaMsg) -> + encode_message(EC, Vsn, MegaMsg); +encode_message([megaco_compressed|Config], Vsn, MegaMsg) + when is_record(MegaMsg, 'MegacoMessage') -> + {ok, erlang:term_to_binary(?MC_MOD:encode(MegaMsg, Vsn), Config)}; +encode_message([{megaco_compressed, Mod}|Config], Vsn, MegaMsg) + when is_atom(Mod) and is_record(MegaMsg, 'MegacoMessage') -> + {ok, erlang:term_to_binary(Mod:encode(MegaMsg, Vsn), Config)}; +encode_message(Config, _Vsn, MegaMsg) + when is_record(MegaMsg, 'MegacoMessage') -> + {ok, erlang:term_to_binary(MegaMsg, Config)}; +encode_message(_Config, _Vsn, _MegaMsg) -> + {error, not_a_megaco_message}. + + +%%---------------------------------------------------------------------- +%% Convert a transaction record into a binary +%% Return {ok, Bin} | {error, Reason} +%%---------------------------------------------------------------------- + +encode_transaction([{version3, _}|EC], Vsn, Trans) -> + encode_transaction(EC, Vsn, Trans); +encode_transaction([megaco_compressed|Config], _Vsn, Trans) -> + {ok, erlang:term_to_binary(?MC_MOD:encode(Trans), Config)}; +encode_transaction([{megaco_compressed, Mod}|Config], _Vsn, Trans) -> + {ok, erlang:term_to_binary(Mod:encode(Trans), Config)}; +encode_transaction(Config, _Vsn, Trans) -> + {ok, erlang:term_to_binary(Trans, Config)}. + + +%%---------------------------------------------------------------------- +%% Convert a list of ActionRequest record's into a binary +%% Return {ok, Binary} | {error, Reason} +%%---------------------------------------------------------------------- +encode_action_requests([{version3, _}|EC], Vsn, ActReqs) -> + encode_action_requests(EC, Vsn, ActReqs); +encode_action_requests([megaco_compressed|Config], Vsn, ActReqs0) + when is_list(ActReqs0) -> + ActReqs = [?MC_MOD:encode(AR, Vsn) || AR <- ActReqs0], + {ok, erlang:term_to_binary(ActReqs, Config)}; +encode_action_requests([{megaco_compressed, Mod}|Config], Vsn, ActReqs0) + when is_list(ActReqs0) -> + ActReqs = [Mod:encode(AR, Vsn) || AR <- ActReqs0], + {ok, erlang:term_to_binary(ActReqs, Config)}; +encode_action_requests(Config, _Vsn, ActReqs) + when is_list(ActReqs) -> + {ok, erlang:term_to_binary(ActReqs, Config)}. + + + +%%---------------------------------------------------------------------- +%% Convert a ActionRequest record into a binary +%% Return {ok, Binary} | {error, Reason} +%%---------------------------------------------------------------------- +encode_action_request([{version3, _}|EC], Vsn, ActReq) -> + encode_action_request(EC, Vsn, ActReq); +encode_action_request([megaco_compressed|Config], Vsn, ActReq) + when is_tuple(ActReq) -> + {ok, erlang:term_to_binary(?MC_MOD:encode(ActReq, Vsn), Config)}; +encode_action_request([{megaco_compressed, Mod}|Config], Vsn, ActReq) + when is_tuple(ActReq) -> + {ok, erlang:term_to_binary(Mod:encode(ActReq, Vsn), Config)}; +encode_action_request(Config, _Vsn, ActReq) + when is_tuple(ActReq) -> + {ok, erlang:term_to_binary(ActReq, Config)}. + + + +%%---------------------------------------------------------------------- +%% Convert a CommandRequest record into a binary +%% Return {ok, DeepIoList} | {error, Reason} +%%---------------------------------------------------------------------- +encode_command_request([{version3, _}|EC], Vsn, CmdReq) -> + encode_command_request(EC, Vsn, CmdReq); +encode_command_request([megaco_compressed|Config], Vsn, CmdReq) + when is_tuple(CmdReq) -> + {ok, erlang:term_to_binary(?MC_MOD:encode(CmdReq, Vsn), Config)}; +encode_command_request([{megaco_compressed, Mod}|Config], Vsn, CmdReq) + when is_tuple(CmdReq) -> + {ok, erlang:term_to_binary(Mod:encode(CmdReq, Vsn), Config)}; +encode_command_request(Config, _Vsn, CmdReq) + when is_tuple(CmdReq) -> + {ok, erlang:term_to_binary(CmdReq, Config)}. + + +%%---------------------------------------------------------------------- +%% Convert a action reply into a binary +%% Return {ok, DeepIoList} | {error, Reason} +%%---------------------------------------------------------------------- +encode_action_reply([{version3, _}|EC], Vsn, ActRep) -> + encode_action_reply(EC, Vsn, ActRep); +encode_action_reply([megaco_compressed|Config], Vsn, ActRep) + when is_tuple(ActRep) -> + {ok, erlang:term_to_binary(?MC_MOD:encode(ActRep, Vsn), Config)}; +encode_action_reply([{megaco_compressed, Mod}|Config], Vsn, ActRep) + when is_tuple(ActRep) -> + {ok, erlang:term_to_binary(Mod:encode(ActRep, Vsn), Config)}; +encode_action_reply(Config, _Vsn, ActRep) + when is_tuple(ActRep) -> + {ok, erlang:term_to_binary(ActRep, Config)}. + + +%%---------------------------------------------------------------------- +%% Get the megaco version of the message +%% Return {ok, Version} | {error, Reason} +%%---------------------------------------------------------------------- + +version_of(Config, Bin) when is_binary(Bin) -> + case decode_message(Config, 1, Bin) of + {ok, M} -> + V = (M#'MegacoMessage'.mess)#'Message'.version, + {ok, V}; + Error -> + Error + end. + +decode_message(Config, Bin) -> + decode_message(Config, 1, Bin). + +decode_message([{version3, _}|EC], V, Bin) -> + decode_message(EC, V, Bin); +decode_message([megaco_compressed = MC|_Config], Vsn, Bin) -> + case catch erlang:binary_to_term(Bin) of + Msg when is_tuple(Msg) -> + case (?MC_MOD:decode(Msg, Vsn)) of + MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> + {ok, dm(MegaMsg, MC, Vsn)}; + _ -> + {error, {bad_message, Msg}} + end; + {'EXIT', _Reason} -> + {error, bad_binary} + end; +decode_message([{megaco_compressed, Mod} = MC|_Config], Vsn, Bin) + when is_atom(Mod) -> + case catch erlang:binary_to_term(Bin) of + Msg when is_tuple(Msg) -> + case (Mod:decode(Msg, Vsn)) of + MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> + {ok, dm(MegaMsg, MC, Vsn)}; + _ -> + {error, {bad_message, Msg}} + end; + {'EXIT', _Reason} -> + {error, bad_binary} + end; +decode_message(_Config, Vsn, Bin) -> + case catch erlang:binary_to_term(Bin) of + MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> + {ok, dm(MegaMsg, undefined, Vsn)}; + {'EXIT', _Reason} -> + {error, bad_binary} + end. + + +decode_mini_message(EC, Vsn, Bin) when is_binary(Bin) -> + decode_message(EC, Vsn, Bin). + + +%% This crap is because the transactions or the action-requests +%% might have been encoded separetely + +dm(#'MegacoMessage'{mess = Mess} = M, MC, Vsn) -> + #'Message'{messageBody = Body} = Mess, + case Body of + {transactions, Transactions} -> + Body2 = {transactions, dmt(Transactions, [], MC, Vsn)}, + Mess2 = Mess#'Message'{messageBody = Body2}, + M#'MegacoMessage'{mess = Mess2}; + _ -> + M + end. + +dmt([], Acc, _, _Vsn) -> + lists:reverse(Acc); +dmt([Trans0|Transactions], Acc, MC, Vsn) when is_binary(Trans0) -> + Trans1 = erlang:binary_to_term(Trans0), + Trans2 = dmt1(Trans1, MC, Vsn), + dmt(Transactions, [Trans2|Acc], MC, Vsn); +dmt([{Tag, Trans0}|Transactions], Acc, MC, Vsn) when is_binary(Trans0) -> + Trans1 = erlang:binary_to_term(Trans0), + Trans2 = dmt1(Trans1, MC, Vsn), + dmt(Transactions, [{Tag, Trans2}|Acc], MC, Vsn); +dmt([{transactionRequest, + #'TransactionRequest'{actions = Acts0} = TR0}|Transactions], + Acc, MC, Vsn) + when is_binary(Acts0) -> + Acts1 = erlang:binary_to_term(Acts0), + Acts2 = dmt1(Acts1, MC, Vsn), + TR1 = TR0#'TransactionRequest'{actions = Acts2}, + dmt(Transactions, [{transactionRequest, TR1}|Acc], MC, Vsn); +dmt([{transactionRequest, + #'TransactionRequest'{actions = Acts0} = TR0}|Transactions], + Acc, MC, Vsn) -> + Acts2 = [dmt2(AR, MC, Vsn) || AR <- Acts0], + TR1 = TR0#'TransactionRequest'{actions = Acts2}, + dmt(Transactions, [{transactionRequest, TR1}|Acc], MC, Vsn); +dmt([Trans|Transactions], Acc, MC, Vsn) -> + dmt(Transactions, [Trans|Acc], MC, Vsn). + +dmt1(L, megaco_compressed, Vsn) when is_list(L) -> + [?MC_MOD:decode(E, Vsn) || E <- L]; +dmt1(L, {megaco_compressed, Mod}, Vsn) when is_list(L) -> + [Mod:decode(E, Vsn) || E <- L]; +dmt1(T, megaco_compressed, Vsn) when is_tuple(T) -> + ?MC_MOD:decode(T, Vsn); +dmt1(T, {megaco_compressed, Mod}, Vsn) when is_tuple(T) -> + Mod:decode(T, Vsn); +dmt1(Else, _, _Vsn) -> + Else. + +dmt2(Bin, MC, Vsn) when is_binary(Bin) -> + AR = erlang:binary_to_term(Bin), + dmt1(AR, MC, Vsn); +dmt2(AR, _MC, _Vsn) -> + AR. + + diff --git a/lib/megaco/src/engine/megaco_erl_dist_encoder_mc.erl b/lib/megaco/src/engine/megaco_erl_dist_encoder_mc.erl new file mode 100644 index 0000000000..52395ae516 --- /dev/null +++ b/lib/megaco/src/engine/megaco_erl_dist_encoder_mc.erl @@ -0,0 +1,1894 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. 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: Externalize/internalize Megaco/H.248 messages +%%---------------------------------------------------------------------- + +-module(megaco_erl_dist_encoder_mc). + +-behaviour(megaco_edist_compress). + +-export([ + encode/1, encode/2, + decode/1, decode/2 + ]). + + +-include("megaco_message_internal.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + + +%%---------------------------------------------------------------------- +%% Megaco compress a Megaco record into a binary +%% Return {ok, DeepIoList} | {error, Reason} +%%---------------------------------------------------------------------- + +encode(M) -> + e(M, 1). + +encode(M, Vsn) -> + ?d("encode -> entry with" + "~n M: ~p" + "~n Vsn: ~p", [M, Vsn]), + Result = e(M, Vsn), + ?d("encode -> " + "~n Result: ~p", [Result]), + Result. + +decode(M) -> + d(M, 1). + +decode(M, Vsn) -> + ?d("decode -> entry with" + "~n M: ~p" + "~n Vsn: ~p", [M, Vsn]), + Result = d(M, Vsn), + ?d("decode -> " + "~n Result: ~p", [Result]), + Result. + + +el(L, V) when is_list(L) -> [e(T, V) || T <- L]; +el(L, _V) -> L. +dl(L, V) when is_list(L) -> [d(T, V) || T <- L]; +dl(L, _V) -> L. + +ell(L, V) when is_list(L) -> [el(T, V) || T <- L]; +ell(L, _V) -> L. +dll(L, V) when is_list(L) -> [dl(T, V) || T <- L]; +dll(L, _V) -> L. + +e(asn1_NOVALUE, _) -> + {1}; +e('NULL', _V) -> + {2}; +e(sendRecv, _V) -> + {3}; +e(recvOnly, _V) -> + {4}; +e(restart, _V) -> + {5}; +e(mediaToken, _V) -> + {6}; +e(eventsToken, _V) -> + {7}; +e(signalsToken, _V) -> + {8}; +e(digitMapToken, _V) -> + {9}; +e(statsToken, _V) -> + {10}; +e(packagesToken, _V) -> + {11}; +e(h221, _V) -> + {12}; +e(h223, _V) -> + {13}; +e(h226, _V) -> + {14}; +e(v76, _V) -> + {15}; + +e({'MegacoMessage', asn1_NOVALUE, {'Message', 1 = V, Mid, Body}}, _) -> + {20, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', asn1_NOVALUE, {'Message', 2 = V, Mid, Body}}, _) -> + {21, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', asn1_NOVALUE, {'Message', V, Mid, Body}}, _) -> + {22, V, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', AuthHeader, {'Message', 1 = V, Mid, Body}}, _) -> + {23, e(AuthHeader, V), V, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', AuthHeader, {'Message', 2 = V, Mid, Body}}, _) -> + {24, e(AuthHeader, V), V, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', AuthHeader, {'Message', V, Mid, Body}}, _) -> + {25, V, e(AuthHeader, V), V, e(Mid, V), e(Body, V)}; +e({'MegacoMessage', AuthHeader, Mess}, V) -> + {26, e(AuthHeader, V), e(Mess, V)}; +e({'Message', V, Mid, Body}, _) -> + {27, V, e(Mid, V), e(Body, V)}; + +e({domainName, {'DomainName', Name, asn1_NOVALUE}}, _V) -> + {30, Name}; +e({domainName, {'DomainName', Name, PortNumber}}, _V) -> + {31, Name, PortNumber}; +e({domainName, N}, V) -> + {32, e(N, V)}; +e({'DomainName', Name, asn1_NOVALUE}, _V) -> + {33, Name}; +e({'DomainName', Name, PortNumber}, _V) -> + {34, Name, PortNumber}; +e({ip4Address, {'IP4Address', Addr, asn1_NOVALUE}}, _V) -> + {35, Addr}; +e({ip4Address, {'IP4Address', Addr, PortNumber}}, _V) -> + {36, Addr, PortNumber}; +e({ip4Address, A}, V) -> + {37, e(A, V)}; +e({'IP4Address', Addr, asn1_NOVALUE}, _V) -> + {38, Addr}; +e({'IP4Address', Addr, PortNumber}, _V) -> + {39, Addr, PortNumber}; +e({ip6Address, {'IP6Address', Addr, asn1_NOVALUE}}, _V) -> + {40, Addr}; +e({ip6Address, {'IP6Address', Addr, PortNumber}}, _V) -> + {41, Addr, PortNumber}; +e({ip6Address, A}, V) -> + {42, e(A, V)}; +e({'IP6Address', Addr, asn1_NOVALUE}, _V) -> + {43, Addr}; +e({'IP6Address', Addr, PortNumber}, _V) -> + {44, Addr, PortNumber}; + +e({transactions, [Transaction]}, V) -> + {50, e(Transaction, V)}; +e({transactions, Transactions}, V) -> + {51, el(Transactions, V)}; +e({messageError, {'ErrorDescriptor', EC, asn1_NOVALUE}}, _V) -> + {52, EC}; +e({messageError, {'ErrorDescriptor', EC, ET}}, _V) -> + {53, EC, ET}; +e({messageError, Error}, V) -> + {54, e(Error, V)}; +e({transactionRequest, {'TransactionRequest', TransId, Actions}}, V) -> + {55, TransId, el(Actions, V)}; +e({transactionPending, {'TransactionPending', TransId}}, _V) -> + {56, TransId}; +e({transactionReply, {'TransactionReply', TransId, asn1_NOVALUE, TransRes}}, V) -> + {57, TransId, e(TransRes, V)}; +e({transactionReply, {'TransactionReply', TransId, 'NULL', TransRes}}, V) -> + {58, TransId, e(TransRes, V)}; +e({transactionReply, {'TransactionReply', TransId, ImmAckReq, TransRes}}, V) -> + {59, TransId, e(ImmAckReq, V), e(TransRes, V)}; +e({transactionResponseAck, T}, V) -> + {60, el(T, V)}; +e({'TransactionAck', FirstAck, asn1_NOVALUE}, _V) -> + {61, FirstAck}; +e({'TransactionAck', FirstAck, LastAck}, _V) -> + {62, FirstAck, LastAck}; + +e({'ErrorDescriptor', EC, asn1_NOVALUE}, _V) -> + {70, EC}; +e({'ErrorDescriptor', EC, ET}, _V) -> + {71, EC, ET}; + +e({'ActionRequest', Cid, CtxReq, CtxAAR, [CmdReq]}, V) -> + {80, Cid, e(CtxReq, V), e(CtxAAR, V), e(CmdReq, V)}; +e({'ActionRequest', Cid, CtxReq, CtxAAR, CmdReqs}, V) -> + {81, Cid, e(CtxReq, V), e(CtxAAR, V), el(CmdReqs, V)}; + +e({'ContextRequest', P, E, T}, V) when V < 3 -> + {90, e(P, V), e(E, V), el(T, V)}; +e({'ContextRequest', P, E, T, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {91, e(P, V), e(E, V), el(T, V)}; +e({'ContextRequest', P, E, T, IC, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {92, e(P, V), e(E, V), el(T, V), e(IC, V)}; +e({'ContextRequest', P, E, T, IC, CP, asn1_NOVALUE}, V) + when V >= 3 -> + {93, e(P, V), e(E, V), el(T, V), e(IC, V), el(CP, V)}; +e({'ContextRequest', P, E, T, IC, CP, CL}, V) + when V >= 3 -> + {94, e(P, V), e(E, V), el(T, V), e(IC, V), el(CP, V), el(CL, V)}; + +e({'ContextAttrAuditRequest', P, E, T}, V) when V < 3 -> + {100, e(P, V), e(E, V), e(T, V)}; +e({'ContextAttrAuditRequest', P, E, T, + asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {101, e(P, V), e(E, V), e(T, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {102, e(P, V), e(E, V), e(T, V), + e(IC, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, CPA, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {103, e(P, V), e(E, V), e(T, V), + e(IC, V), el(CPA, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, CPA, SP, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {104, e(P, V), e(E, V), e(T, V), + e(IC, V), el(CPA, V), e(SP, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, CPA, SP, SE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {105, e(P, V), e(E, V), e(T, V), + e(IC, V), el(CPA, V), e(SP, V), e(SE, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, CPA, SP, SE, SIC, asn1_NOVALUE}, V) + when V >= 3 -> + {106, e(P, V), e(E, V), e(T, V), + e(IC, V), el(CPA, V), e(SP, V), e(SE, V), e(SIC, V)}; +e({'ContextAttrAuditRequest', P, E, T, + IC, CPA, SP, SE, SIC, SL}, V) + when V >= 3 -> + {107, e(P, V), e(E, V), e(T, V), + e(IC, V), el(CPA, V), e(SP, V), e(SE, V), e(SIC, V), e(SL, V)}; + +e({'CommandRequest', Cmd, asn1_NOVALUE, asn1_NOVALUE}, V) -> + {110, e(Cmd, V)}; +e({'CommandRequest', Cmd, 'NULL', asn1_NOVALUE}, V) -> + {111, e(Cmd, V)}; +e({'CommandRequest', Cmd, asn1_NOVALUE, 'NULL'}, V) -> + {112, e(Cmd, V)}; +e({'CommandRequest', Cmd, 'NULL', 'NULL'}, V) -> + {113, e(Cmd, V)}; +e({'CommandRequest', Cmd, Opt, WR}, V) -> + {114, e(Cmd, V), e(Opt, V), e(WR, V)}; + +e({'TopologyRequest', From, To, Dir}, 1 = V) -> + {120, e(From, V), e(To, V), e(Dir, V)}; +e({'TopologyRequest', From, To, Dir, SID}, 2 = V) -> + {121, e(From, V), e(To, V), e(Dir, V), e(SID, V)}; +e({'TopologyRequest', From, To, Dir, SID, asn1_NOVALUE}, V) when (V >= 3) -> + {122, e(From, V), e(To, V), e(Dir, V), e(SID, V)}; +e({'TopologyRequest', From, To, Dir, SID, TDE}, V) when (V >= 3) -> + {123, e(From, V), e(To, V), e(Dir, V), e(SID, V), e(TDE, V)}; + +e({modReq, {'AmmRequest', TID, []}}, V) -> + {130, el(TID, V)}; +e({modReq, {'AmmRequest', TID, [Desc]}}, V) -> + {131, el(TID, V), e(Desc, V)}; +e({modReq, {'AmmRequest', TID, Descs}}, V) -> + {132, el(TID, V), el(Descs, V)}; +e({addReq, {'AmmRequest', TID, []}}, V) -> + {133, el(TID, V)}; +e({addReq, {'AmmRequest', TID, [Desc]}}, V) -> + {134, el(TID, V), e(Desc, V)}; +e({addReq, {'AmmRequest', TID, Descs}}, V) -> + {135, el(TID, V), el(Descs, V)}; +e({'AmmRequest', TID, Descs}, V) -> + {136, el(TID, V), el(Descs, V)}; + +e({subtractReq, {'SubtractRequest', TID, asn1_NOVALUE}}, V) -> + {140, el(TID, V)}; +e({subtractReq, {'SubtractRequest', TID, AudDesc}}, V) -> + {141, el(TID, V), e(AudDesc, V)}; +e({'SubtractRequest', TID, asn1_NOVALUE}, V) -> + {142, el(TID, V)}; +e({'SubtractRequest', TID, AudDesc}, V) -> + {143, el(TID, V), e(AudDesc, V)}; + +e({auditValueRequest, AR}, V) -> + {150, e(AR, V)}; + +e({'AuditRequest', TID, AudDesc}, V) when V < 3 -> + {160, e(TID, V), e(AudDesc, V)}; +e({'AuditRequest', TID, AudDesc, asn1_NOVALUE}, V) when V >= 3 -> + {161, e(TID, V), e(AudDesc, V)}; +e({'AuditRequest', TID, AudDesc, TIDs}, V) when V >= 3 -> + {162, e(TID, V), e(AudDesc, V), el(TIDs, V)}; + +e({actionReplies, [AR]}, V) -> + {170, e(AR, V)}; +e({actionReplies, ARs}, V) -> + {171, el(ARs, V)}; + +e({'ActionReply', CID, asn1_NOVALUE, asn1_NOVALUE, [CmdRep]}, V) -> + {180, CID, e(CmdRep, V)}; +e({'ActionReply', CID, asn1_NOVALUE, asn1_NOVALUE, CmdRep}, V) -> + {181, CID, el(CmdRep, V)}; +e({'ActionReply', CID, asn1_NOVALUE, CtxRep, [CmdRep]}, V) -> + {182, CID, e(CtxRep, V), e(CmdRep, V)}; +e({'ActionReply', CID, asn1_NOVALUE, CtxRep, CmdRep}, V) -> + {183, CID, e(CtxRep, V), el(CmdRep, V)}; +e({'ActionReply', CID, ED, asn1_NOVALUE, [CmdRep]}, V) -> + {184, CID, e(ED, V), e(CmdRep, V)}; +e({'ActionReply', CID, ED, asn1_NOVALUE, CmdRep}, V) -> + {185, CID, e(ED, V), el(CmdRep, V)}; +e({'ActionReply', CID, ED, CtxRep, [CmdRep]}, V) -> + {186, CID, e(ED, V), e(CtxRep, V), e(CmdRep, V)}; +e({'ActionReply', CID, ED, CtxRep, CmdRep}, V) -> + {187, CID, e(ED, V), e(CtxRep, V), el(CmdRep, V)}; + +e({'AuditDescriptor', asn1_NOVALUE}, 1 = _V) -> + {190}; +e({'AuditDescriptor', AT}, 1 = V) -> + {191, el(AT, V)}; +e({'AuditDescriptor', asn1_NOVALUE, asn1_NOVALUE}, V) when V >= 2 -> + {192}; +e({'AuditDescriptor', AT, APT}, V) + when is_list(AT) andalso is_list(APT) andalso (V >= 2) -> + {193, el(AT, V), el(APT, V)}; +e({'AuditDescriptor', AT, APT}, V) + when is_list(APT) andalso (V >= 2) -> + {194, e(AT, V), el(APT, V)}; +e({'AuditDescriptor', AT, APT}, V) + when is_list(AT) andalso (V >= 2) -> + {195, el(AT, V), e(APT, V)}; +e({'AuditDescriptor', AT, APT}, V) when (V >= 2) -> + {196, e(AT, V), e(APT, V)}; + +e({notifyReq, {'NotifyRequest', TID, OED, asn1_NOVALUE}}, V) -> + {200, el(TID, V), e(OED, V)}; +e({notifyReq, {'NotifyRequest', TID, OED, ED}}, V) -> + {201, el(TID, V), e(OED, V), e(ED, V)}; +e({'NotifyRequest', TID, OED}, V) -> + {202, el(TID, V), e(OED, V)}; +e({'NotifyRequest', TID, OED, ED}, V) -> + {203, el(TID, V), e(OED, V), e(ED, V)}; + +e({'ObservedEventsDescriptor', RID, OEL}, V) -> + {210, RID, el(OEL, V)}; + +e({'ObservedEvent', EN, SID, EPL, TN}, V) -> + {220, EN, e(SID, V), el(EPL, V), e(TN, V)}; + +e({'EventParameter', "type", ["est"], asn1_NOVALUE}, _V) -> + {230}; +e({'EventParameter', "type", [Val], asn1_NOVALUE}, _V) -> + {231, Val}; +e({'EventParameter', "type", Val, asn1_NOVALUE}, _V) -> + {232, Val}; +e({'EventParameter', "Generalcause", ["NR"], asn1_NOVALUE}, _V) -> + {233}; +e({'EventParameter', "Generalcause", ["UR"], asn1_NOVALUE}, _V) -> + {234}; +e({'EventParameter', "Generalcause", ["FT"], asn1_NOVALUE}, _V) -> + {235}; +e({'EventParameter', "Generalcause", ["FP"], asn1_NOVALUE}, _V) -> + {236}; +e({'EventParameter', "Generalcause", ["IW"], asn1_NOVALUE}, _V) -> + {237}; +e({'EventParameter', "Generalcause", ["UN"], asn1_NOVALUE}, _V) -> + {238}; +e({'EventParameter', "Generalcause", [Val], asn1_NOVALUE}, _V) -> + {239, Val}; +e({'EventParameter', "Generalcause", Val, asn1_NOVALUE}, _V) -> + {240, Val}; +e({'EventParameter', "Failurecause", [Val], asn1_NOVALUE}, _V) -> + {241, Val}; +e({'EventParameter', "Failurecause", Val, asn1_NOVALUE}, _V) -> + {242, Val}; +e({'EventParameter', EPN, Val, asn1_NOVALUE}, _V) -> + {243, EPN, Val}; +e({'EventParameter', EPN, Val, EI}, _V) -> + {244, EPN, Val, EI}; + +e({serviceChangeReq, {'ServiceChangeRequest', TID, SCPs}}, V) -> + {260, el(TID, V), e(SCPs, V)}; +e({serviceChangeReq, SCR}, V) -> + {261, e(SCR, V)}; +e({'ServiceChangeRequest', TID, SCPs}, V) -> + {262, el(TID, V), e(SCPs, V)}; + +e({serviceChangeReply, {'ServiceChangeReply', TID, SCR}}, V) -> + {270, el(TID, V), e(SCR, V)}; +e({serviceChangeReply, SCR}, V) -> + {271, e(SCR, V)}; +e({'ServiceChangeReply', TID, SCR}, V) -> %% KOLLA + {272, el(TID, V), e(SCR, V)}; + +e({mediaDescriptor, {'MediaDescriptor', TSD, S}}, V) -> + {280, e(TSD, V), e(S, V)}; +e({mediaDescriptor, MD}, V) -> + {281, e(MD, V)}; +e({'MediaDescriptor', TSD, S}, V) -> + {282, e(TSD, V), e(S, V)}; + +e({oneStream, S}, V) -> + {290, e(S, V)}; +e({multiStream, S}, V) -> + {291, el(S, V)}; +e({'StreamDescriptor', SID, SP}, V) -> + {292, e(SID, V), e(SP, V)}; + +e({'StreamParms', LCD, asn1_NOVALUE, asn1_NOVALUE}, V) when V < 3 -> + {300, e(LCD, V)}; +e({'StreamParms', LCD, LD, asn1_NOVALUE}, V) when V < 3 -> + {301, e(LCD, V), e(LD, V)}; +e({'StreamParms', LCD, LD, RD}, V) when V < 3 -> + {302, e(LCD, V), e(LD, V), e(RD, V)}; + +e({'StreamParms', LCD, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {303, e(LCD, V)}; +e({'StreamParms', LCD, LD, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {304, e(LCD, V), e(LD, V)}; +e({'StreamParms', LCD, LD, RD, asn1_NOVALUE}, V) + when V >= 3 -> + {305, e(LCD, V), e(LD, V), e(RD, V)}; +e({'StreamParms', LCD, LD, RD, SD}, V) + when V >= 3 -> + {306, e(LCD, V), e(LD, V), e(RD, V), el(SD, V)}; + +e({'LocalControlDescriptor', SM, RV, RG, PP}, V) -> + {310, e(SM, V), e(RV, V), e(RG, V), el(PP, V)}; + +e({'PropertyParm', "v", [Val], asn1_NOVALUE}, _V) -> + {320, Val}; +e({'PropertyParm', "v", Val, asn1_NOVALUE}, _V) -> + {321, Val}; +e({'PropertyParm', "o", [Val], asn1_NOVALUE}, _V) -> + {332, Val}; +e({'PropertyParm', "o", Val, asn1_NOVALUE}, _V) -> + {333, Val}; +e({'PropertyParm', "s", [Val], asn1_NOVALUE}, _V) -> + {334, Val}; +e({'PropertyParm', "s", Val, asn1_NOVALUE}, _V) -> + {335, Val}; +e({'PropertyParm', "i", [Val], asn1_NOVALUE}, _V) -> + {336, Val}; +e({'PropertyParm', "i", Val, asn1_NOVALUE}, _V) -> + {337, Val}; +e({'PropertyParm', "u", [Val], asn1_NOVALUE}, _V) -> + {338, Val}; +e({'PropertyParm', "u", Val, asn1_NOVALUE}, _V) -> + {339, Val}; +e({'PropertyParm', "e", [Val], asn1_NOVALUE}, _V) -> + {340, Val}; +e({'PropertyParm', "e", Val, asn1_NOVALUE}, _V) -> + {341, Val}; +e({'PropertyParm', "p", [Val], asn1_NOVALUE}, _V) -> + {342, Val}; +e({'PropertyParm', "p", Val, asn1_NOVALUE}, _V) -> + {343, Val}; +e({'PropertyParm', "c", [Val], asn1_NOVALUE}, _V) -> + {344, Val}; +e({'PropertyParm', "c", Val, asn1_NOVALUE}, _V) -> + {345, Val}; +e({'PropertyParm', "b", [Val], asn1_NOVALUE}, _V) -> + {346, Val}; +e({'PropertyParm', "b", Val, asn1_NOVALUE}, _V) -> + {347, Val}; +e({'PropertyParm', "z", [Val], asn1_NOVALUE}, _V) -> + {348, Val}; +e({'PropertyParm', "z", Val, asn1_NOVALUE}, _V) -> + {349, Val}; +e({'PropertyParm', "k", [Val], asn1_NOVALUE}, _V) -> + {350, Val}; +e({'PropertyParm', "k", Val, asn1_NOVALUE}, _V) -> + {351, Val}; +e({'PropertyParm', "a", [Val], asn1_NOVALUE}, _V) -> + {352, Val}; +e({'PropertyParm', "a", Val, asn1_NOVALUE}, _V) -> + {353, Val}; +e({'PropertyParm', "t", [Val], asn1_NOVALUE}, _V) -> + {354, Val}; +e({'PropertyParm', "t", Val, asn1_NOVALUE}, _V) -> + {355, Val}; +e({'PropertyParm', "r", [Val], asn1_NOVALUE}, _V) -> + {356, Val}; +e({'PropertyParm', "r", Val, asn1_NOVALUE}, _V) -> + {357, Val}; +e({'PropertyParm', "m", [Val], asn1_NOVALUE}, _V) -> + {358, Val}; +e({'PropertyParm', "m", Val, asn1_NOVALUE}, _V) -> + {359, Val}; +e({'PropertyParm', "nt/jit", [Val], asn1_NOVALUE}, _V) -> + {360, Val}; +e({'PropertyParm', "nt/jit", Val, asn1_NOVALUE}, _V) -> + {361, Val}; +e({'PropertyParm', "tdmc/ec", ["on"], asn1_NOVALUE}, _V) -> + {362}; +e({'PropertyParm', "tdmc/ec", ["off"], asn1_NOVALUE}, _V) -> + {363}; +e({'PropertyParm', "tdmc/gain", ["automatic"], asn1_NOVALUE}, _V) -> + {364}; +e({'PropertyParm', "tdmc/gain", [Val], asn1_NOVALUE}, _V) -> + {365, Val}; +e({'PropertyParm', "tdmc/gain", Val, asn1_NOVALUE}, _V) -> + {366, Val}; +e({'PropertyParm', "maxNumberOfContexts", [Val], asn1_NOVALUE}, _V) -> + {367, Val}; +e({'PropertyParm', "maxNumberOfContexts", Val, asn1_NOVALUE}, _V) -> + {368, Val}; +e({'PropertyParm', "maxTerminationsPerContext", [Val], asn1_NOVALUE}, _V) -> + {369, Val}; +e({'PropertyParm', "maxTerminationsPerContext", Val, asn1_NOVALUE}, _V) -> + {370, Val}; +e({'PropertyParm', "normalMGExecutionTime", [Val], asn1_NOVALUE}, _V) -> + {371, Val}; +e({'PropertyParm', "normalMGExecutionTime", Val, asn1_NOVALUE}, _V) -> + {372, Val}; +e({'PropertyParm', "normalMGCExecutionTime", [Val], asn1_NOVALUE}, _V) -> + {373, Val}; +e({'PropertyParm', "normalMGCExecutionTime", Val, asn1_NOVALUE}, _V) -> + {374, Val}; +e({'PropertyParm', "MGProvisionalResponseTimerValue", [Val], asn1_NOVALUE}, _V) -> + {375, Val}; +e({'PropertyParm', "MGProvisionalResponseTimerValue", Val, asn1_NOVALUE}, _V) -> + {376, Val}; +e({'PropertyParm', "MGCProvisionalResponseTimerValue", [Val], asn1_NOVALUE}, _V) -> + {377, Val}; +e({'PropertyParm', "MGCProvisionalResponseTimerValue", Val, asn1_NOVALUE}, _V) -> + {378, Val}; +e({'PropertyParm', N, [Val], asn1_NOVALUE}, _V) -> + {379, N, Val}; +e({'PropertyParm', N, Val, asn1_NOVALUE}, _V) -> + {380, N, Val}; +e({'PropertyParm', N, Val, EI}, _V) -> + {381, N, Val, EI}; + +e({'LocalRemoteDescriptor', [[PG]]}, V) -> + {400, e(PG, V)}; +e({'LocalRemoteDescriptor', [PG]}, V) -> + {401, el(PG, V)}; +e({'LocalRemoteDescriptor', PG}, V) -> + {402, ell(PG, V)}; + +e({'TerminationStateDescriptor', PP, EBC, SS}, V) -> + {410, el(PP, V), e(EBC, V), e(SS, V)}; + +e({eventsDescriptor, {'EventsDescriptor', RID, [E]}}, V) -> + {420, e(RID, V), e(E, V)}; +e({eventsDescriptor, {'EventsDescriptor', RID, EL}}, V) -> + {421, e(RID, V), el(EL, V)}; +e({eventsDescriptor, ED}, V) -> + {422, e(ED, V)}; +e({'EventsDescriptor', RID, [E]}, V) -> + {423, e(RID, V), e(E, V)}; +e({'EventsDescriptor', RID, EL}, V) -> + {424, e(RID, V), el(EL, V)}; + +e({'RequestedEvent', PN, SID, EA, EPL}, V) -> + {425, PN, e(SID, V), e(EA, V), el(EPL, V)}; + +e({'RegulatedEmbeddedDescriptor', SED, SD}, V) -> + {430, e(SED, V), el(SD, V)}; + +e({notifyImmediate, NI}, V) -> + {435, e(NI, V)}; +e({notifyRegulated, NR}, V) -> + {436, e(NR, V)}; +e({neverNotify, NN}, V) -> + {437, e(NN, V)}; + +e({'RequestedActions', KA, EDM, SE, SD}, V) -> + {440, e(KA, V), e(EDM, V), e(SE, V), e(SD, V)}; + +e({'RequestedActions', KA, EDM, SE, SD, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {441, e(KA, V), e(EDM, V), e(SE, V), e(SD, V)}; +e({'RequestedActions', KA, EDM, SE, SD, NB, asn1_NOVALUE}, V) + when V >= 3 -> + {442, e(KA, V), e(EDM, V), e(SE, V), e(SD, V), e(NB, V)}; +e({'RequestedActions', KA, EDM, SE, SD, NB, RED}, V) + when V >= 3 -> + {443, e(KA, V), e(EDM, V), e(SE, V), e(SD, V), e(NB, V), e(RED, V)}; + +e({'SecondEventsDescriptor', RID, [E]}, V) -> + {450, e(RID, V), e(E, V)}; +e({'SecondEventsDescriptor', RID, EL}, V) -> + {451, e(RID, V), el(EL, V)}; + +e({'SecondRequestedEvent', PN, SID, EA, EPL}, V) -> + {460, PN, e(SID, V), e(EA, V), e(EPL, V)}; + +e({'SecondRequestedActions', KA, EDM, SD}, V) -> + {470, e(KA, V), e(EDM, V), e(SD, V)}; + +e({'SecondRequestedActions', KA, EDM, SD, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {471, e(KA, V), e(EDM, V), e(SD, V)}; +e({'SecondRequestedActions', KA, EDM, SD, NB, asn1_NOVALUE}, V) + when V >= 3 -> + {472, e(KA, V), e(EDM, V), e(SD, V), e(NB, V)}; +e({'SecondRequestedActions', KA, EDM, SD, NB, RED}, V) + when V >= 3 -> + {473, e(KA, V), e(EDM, V), e(SD, V), e(NB, V), e(RED, V)}; + +e({'EventSpec', EN, SID, EPL}, V) -> + {480, EN, e(SID, V), el(EPL, V)}; + +e({'SeqSigList', ID, SL}, V) -> + {490, ID, el(SL, V)}; + +e({signalsDescriptor, S}, V) -> + {500, el(S, V)}; +e({signal, S}, V) -> + {510, e(S, V)}; + +e({'Signal', SN, SID, ST, D, NC, KA, SPL}, V) -> + {520, SN, e(SID, V), e(ST, V), e(D, V), e(NC, V), e(KA, V), el(SPL, V)}; + +e({'Signal', SN, SID, ST, D, NC, KA, SPL, + asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {521, SN, e(SID, V), e(ST, V), e(D, V), e(NC, V), e(KA, V), el(SPL, V)}; +e({'Signal', SN, SID, ST, D, NC, KA, SPL, + SD, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {522, SN, e(SID, V), e(ST, V), e(D, V), e(NC, V), e(KA, V), el(SPL, V), + e(SD, V)}; +e({'Signal', SN, SID, ST, D, NC, KA, SPL, + SD, RID, asn1_NOVALUE}, V) + when V >= 3 -> + {523, SN, e(SID, V), e(ST, V), e(D, V), e(NC, V), e(KA, V), el(SPL, V), + e(SD, V), e(RID, V)}; +e({'Signal', SN, SID, ST, D, NC, KA, SPL, + SD, RID, IsD}, V) + when V >= 3 -> + {524, SN, e(SID, V), e(ST, V), e(D, V), e(NC, V), e(KA, V), el(SPL, V), + e(SD, V), e(RID, V), e(IsD, V)}; + +e({'SigParameter', SPN, Val, asn1_NOVALUE}, _V) -> + {530, SPN, Val}; +e({'SigParameter', SPN, Val, EI}, _V) -> + {531, SPN, Val, EI}; + +e({modemDescriptor, MD}, V) -> + {550, e(MD, V)}; +e({'ModemDescriptor', MTL, MPL, asn1_NOVALUE}, _V) -> + {551, MTL, MPL}; +e({'ModemDescriptor', MTL, MPL, NSD}, _V) -> + {552, MTL, MPL, NSD}; + +e({digitMapDescriptor, {'DigitMapDescriptor', DMN, DMV}}, V) -> + {560, DMN, e(DMV, V)}; +e({digitMapDescriptor, DMD}, V) -> + {561, e(DMD, V)}; +e({'DigitMapDescriptor', DMN, DMV}, V) -> + {562, DMN, e(DMV, V)}; + +e({'DigitMapValue', Start, Stop, Long, DMB}, 1 = V) -> + {570, e(Start, V), e(Stop, V), e(Long, V), DMB}; +e({'DigitMapValue', Start, Stop, Long, DMB, Dur}, V) when V >= 2 -> + {571, e(Start, V), e(Stop, V), e(Long, V), DMB, e(Dur, V)}; + +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, asn1_NOVALUE, asn1_NOVALUE}, V) -> + {580, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V)}; +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, asn1_NOVALUE}, V) -> + {581, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V)}; +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD}, V) -> + {582, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), NSD}; + +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD, asn1_NOVALUE}, V) + when V == 2 -> + {583, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), NSD}; +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD, Info}, V) + when V == 2 -> + {584, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), NSD, e(Info, V)}; + +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD, + asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {585, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), NSD}; +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD, Info, + asn1_NOVALUE}, V) + when V >= 3 -> + {586, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), e(TS, V), NSD, e(Info, V)}; +e({'ServiceChangeParm', M, A, Ver, Prof, R, D, Id, TS, NSD, Info, Flag}, V) + when V >= 3 -> + {587, e(M, V), e(A, V), e(Ver, V), e(Prof, V), R, e(D, V), e(Id, V), + e(TS, V), NSD, e(Info, V), e(Flag, V)}; + +e({serviceChangeResParms, {'ServiceChangeResParm', Id, A, Ver, Prof, TS}}, V) -> + {590, Id, e(A, V), Ver, e(Prof, V), TS}; +e({serviceChangeResParms, SCRP}, V) -> + {591, e(SCRP, V)}; +e({'ServiceChangeResParm', Id, A, Ver, Prof, TS}, V) -> + {592, Id, e(A, V), Ver, e(Prof, V), TS}; + +e({portNumber, N}, _V) -> + {600, N}; + +e({'TimeNotation', D, T}, _V) -> + {610, D, T}; + +e({'ServiceChangeProfile', N, Ver}, _V) -> + {620, N, Ver}; + +e({digitMapName, N}, _V) -> + {630, N}; + +e({megaco_term_id, false, Id}, _V) -> + {640, Id}; +e({megaco_term_id, true, [[$*]]}, _V) -> + {641}; +e({megaco_term_id, true, [[$$]]}, _V) -> + {642}; +e({megaco_term_id, true, Id}, _V) -> + {643, Id}; +e({'TerminationID', W, ID}, _V) -> + {644, W, ID}; + +e({modReply, {'AmmsReply', TID, asn1_NOVALUE}}, V) -> + {650, el(TID, V)}; +e({modReply, {'AmmsReply', TID, [TA]}}, V) -> + {651, el(TID, V), e(TA, V)}; +e({modReply, {'AmmsReply', TID, TA}}, V) when is_list(TA) -> + {652, el(TID, V), el(TA, V)}; +e({modReply, R}, V) -> + {653, e(R, V)}; + +e({moveReply, AR}, V) -> + {655, e(AR, V)}; + +e({addReply, {'AmmsReply', TID, asn1_NOVALUE}}, V) -> + {660, el(TID, V)}; +e({addReply, {'AmmsReply', TID, [TA]}}, V) -> + {661, el(TID, V), e(TA, V)}; +e({addReply, {'AmmsReply', TID, TA}}, V) when is_list(TA) -> + {662, el(TID, V), el(TA, V)}; +e({addReply, R}, V) -> + {663, e(R, V)}; + +e({subtractReply, {'AmmsReply', TID, asn1_NOVALUE}}, V) -> + {670, el(TID, V)}; +e({subtractReply, {'AmmsReply', TID, [TA]}}, V) -> + {671, el(TID, V), e(TA, V)}; +e({subtractReply, {'AmmsReply', TID, TA}}, V) when is_list(TA) -> + {672, el(TID, V), el(TA, V)}; +e({subtractReply, R}, V) -> + {673, e(R, V)}; + +e({'AmmsReply', TID, asn1_NOVALUE}, V) -> + {680, el(TID, V)}; +e({'AmmsReply', TID, [TA]}, V) -> + {681, el(TID, V), e(TA, V)}; +e({'AmmsReply', TID, TA}, V) when is_list(TA) -> + {682, el(TID, V), el(TA, V)}; + +e({notifyReply, {'NotifyReply', TID, asn1_NOVALUE}}, V) -> + {690, el(TID, V)}; +e({notifyReply, {'NotifyReply', TID, ED}}, V) -> + {691, el(TID, V), e(ED, V)}; +e({notifyReply, R}, V) -> + {692, e(R, V)}; +e({'NotifyReply', TID, asn1_NOVALUE}, V) -> + {693, el(TID, V)}; +e({'NotifyReply', TID, ED}, V) -> + {694, el(TID, V), e(ED, V)}; + +e({auditValueReply, AVR}, V) -> + {700, e(AVR, V)}; + +e({contextAuditResult, TIDs}, V) -> + {705, el(TIDs, V)}; + +e({auditResult, {'AuditResult', TID, [TAR]}}, V) -> + {710, e(TID, V), e(TAR, V)}; +e({auditResult, {'AuditResult', TID, TAR}}, V) -> + {711, e(TID, V), el(TAR, V)}; +e({auditResult, AR}, V) -> + {712, e(AR, V)}; +e({'AuditResult', TID, [TAR]}, V) -> + {713, e(TID, V), e(TAR, V)}; +e({'AuditResult', TID, TAR}, V) -> + {714, e(TID, V), el(TAR, V)}; + +e({auditResultTermList, {'TermListAuditResult', TIDs, [TAR]}}, V) -> + {715, el(TIDs, V), e(TAR, V)}; +e({auditResultTermList, {'TermListAuditResult', TIDs, TAR}}, V) -> + {716, el(TIDs, V), el(TAR, V)}; + +e({packagesDescriptor, PsD}, V) -> + {720, el(PsD, V)}; + +e({'PackagesItem', "g", 1}, _V) -> + {730}; +e({'PackagesItem', "tonegen", 1}, _V) -> + {731}; +e({'PackagesItem', "tonedet", 1}, _V) -> + {732}; +e({'PackagesItem', "tg", 1}, _V) -> + {733}; +e({'PackagesItem', "dd", 1}, _V) -> + {734}; +e({'PackagesItem', "cg", 1}, _V) -> + {735}; +e({'PackagesItem', "cd", 1}, _V) -> + {736}; +e({'PackagesItem', "al", 1}, _V) -> + {737}; +e({'PackagesItem', "ct", 1}, _V) -> + {738}; +e({'PackagesItem', "nt", 1}, _V) -> + {739}; +e({'PackagesItem', "rtp", 1}, _V) -> + {740}; +e({'PackagesItem', "tdmc", 1}, _V) -> + {741}; +e({'PackagesItem', Name, Ver}, _V) -> + {742, Name, Ver}; + +e({emptyDescriptors, AD}, V) -> + {760, e(AD, V)}; + +e({statisticsDescriptor, [SD]}, V) -> + {770, e(SD, V)}; +e({statisticsDescriptor, SsD}, V) -> + {771, el(SsD, V)}; + +e({'StatisticsParameter', Name, asn1_NOVALUE}, _V) -> + {780, Name}; +e({'StatisticsParameter', Name, Value}, _V) -> + {781, Name, Value}; + +e({'MuxDescriptor', MT, TL, asn1_NOVALUE}, V) -> + {800, e(MT, V), el(TL, V)}; +e({'MuxDescriptor', MT, TL, NSD}, V) -> + {801, e(MT, V), el(TL, V), NSD}; + +e({indAudPackagesDescriptor, {'IndAudPackagesDescriptor', N, Ver}}, V) + when (V >= 2) -> + {900, N, Ver}; +e({indAudPackagesDescriptor, IAPD}, V) + when (V >= 2) -> + {900, e(IAPD, V)}; +e({'IndAudPackagesDescriptor', N, Ver}, V) + when (V >= 2) -> + {901, N, Ver}; + +e({indAudStatisticsDescriptor, {'IndAudStatisticsDescriptor', N}}, V) + when (V >= 2) -> + {910, N}; +e({indAudStatisticsDescriptor, IASD}, V) + when (V >= 2) -> + {911, e(IASD, V)}; +e({'IndAudStatisticsDescriptor', N}, V) + when (V >= 2) -> + {912, N}; + +e({indAudDigitMapDescriptor, {'IndAudDigitMapDescriptor', DMN}}, V) + when (V >= 2) -> + {920, DMN}; +e({indAudDigitMapDescriptor, IADMD}, V) + when (V >= 2) -> + {921, e(IADMD, V)}; +e({'IndAudDigitMapDescriptor', DMN}, V) + when (V >= 2) -> + {922, DMN}; + +e({indAudSignalsDescriptor, {seqSigList, IASD}}, V) + when (V >= 2) -> + {930, e(IASD, V)}; +e({indAudSignalsDescriptor, {signal, IAS}}, V) + when (V >= 2) -> + {931, e(IAS, V)}; + +e({'IndAudSeqSigList', Id, SL}, V) + when (V >= 2) -> + {940, Id, e(SL, V)}; + +e({'IndAudSignal', N, SID}, 2 = V) -> + {950, N, e(SID, V)}; +e({'IndAudSignal', N, SID, asn1_NOVALUE}, V) + when (V >= 3) -> + {951, N, e(SID, V)}; +e({'IndAudSignal', N, SID, RID}, V) + when (V >= 3) -> + {952, N, e(SID, V), e(RID, V)}; + +e({indAudEventBufferDescriptor, {'IndAudEventBufferDescriptor', EN, SID}}, V) + when (V >= 2) -> + {960, EN, e(SID, V)}; +e({indAudEventBufferDescriptor, IAEBD}, V) + when (V >= 2) -> + {961, e(IAEBD, V)}; +e({'IndAudEventBufferDescriptor', EN, SID}, V) + when (V >= 2) -> + {962, EN, e(SID, V)}; + +e({indAudEventsDescriptor, {'IndAudEventsDescriptor', RID, N, SID}}, V) + when (V >= 2) -> + {970, e(RID, V), N, e(SID, V)}; +e({indAudEventsDescriptor, IAED}, V) + when (V >= 2) -> + {971, e(IAED, V)}; +e({'IndAudEventsDescriptor', RID, N, SID}, V) + when (V >= 2) -> + {972, e(RID, V), N, e(SID, V)}; + +e({indAudMediaDescriptor, {'IndAudMediaDescriptor', TSD, S}}, V) when V >= 2 -> + {980, e(TSD, V), e(S, V)}; +e({indAudMediaDescriptor, IAMD}, V) when V >= 2 -> + {981, e(IAMD, V)}; +e({'IndAudMediaDescriptor', TSD, S}, V) when V >= 2 -> + {982, e(TSD, V), e(S, V)}; + +e({'IndAudTerminationStateDescriptor', PP, EBC, SS}, 2 = V) -> + {990, el(PP, V), e(EBC, V), e(SS, V)}; +e({'IndAudTerminationStateDescriptor', PP, EBC, SS, asn1_NOVALUE}, V) + when V >= 3 -> + {991, el(PP, V), e(EBC, V), e(SS, V)}; +e({'IndAudTerminationStateDescriptor', PP, EBC, SS, SSS}, V) + when V >= 3 -> + {992, el(PP, V), e(EBC, V), e(SS, V), e(SSS, V)}; + +e({'IndAudStreamDescriptor', SID, SP}, V) -> + {1000, e(SID, V), e(SP, V)}; + +e({'IndAudStreamParms', LCD, asn1_NOVALUE, asn1_NOVALUE}, 2 = V) -> + {1010, e(LCD, V)}; +e({'IndAudStreamParms', LCD, LD, RD}, 2 = V) -> + {1011, e(LCD, V), e(LD, V), e(RD, V)}; +e({'IndAudStreamParms', LCD, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {1012, e(LCD, V)}; +e({'IndAudStreamParms', LCD, LD, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {1013, e(LCD, V), e(LD, V)}; +e({'IndAudStreamParms', LCD, LD, RD, asn1_NOVALUE}, V) + when V >= 3 -> + {1014, e(LCD, V), e(LD, V), e(RD, V)}; +e({'IndAudStreamParms', LCD, LD, RD, SD}, V) + when V >= 3 -> + {1015, e(LCD, V), e(LD, V), e(RD, V), e(SD, V)}; + +e({'IndAudLocalControlDescriptor', SM, RV, RG, asn1_NOVALUE}, 2 = V) -> + {1020, e(SM, V), e(RV, V), e(RG, V)}; +e({'IndAudLocalControlDescriptor', SM, RV, RG, PP}, 2 = V) when is_list(PP) -> + {1021, e(SM, V), e(RV, V), e(RG, V), el(PP, V)}; +e({'IndAudLocalControlDescriptor', SM, RV, RG, asn1_NOVALUE, asn1_NOVALUE}, V) + when V >= 3 -> + {1022, e(SM, V), e(RV, V), e(RG, V)}; +e({'IndAudLocalControlDescriptor', SM, RV, RG, PP, asn1_NOVALUE}, V) + when is_list(PP) andalso (V >= 3) -> + {1023, e(SM, V), e(RV, V), e(RG, V), el(PP, V)}; +e({'IndAudLocalControlDescriptor', SM, RV, RG, PP, SMS}, V) + when is_list(PP) andalso (V >= 3) -> + {1024, e(SM, V), e(RV, V), e(RG, V), el(PP, V), e(SMS, V)}; + +e({'IndAudPropertyParm', N}, 2 = _V) -> + {1030, N}; +e({'IndAudPropertyParm', N, asn1_NOVALUE}, V) when V >= 3 -> + {1031, N}; +e({'IndAudPropertyParm', N, PP}, V) when V >= 3 -> + {1032, N, e(PP, V)}; + +e(oneway, _V) -> + {1100}; +e(bothway, _V) -> + {1101}; +e(isolate, _V) -> + {1102}; +e(onewayexternal, _V) -> + {1103}; +e(onewayboth, _V) -> + {1104}; + +e(T, _V) -> + %% io:format("e(~w) -> ~nT: ~w~n", [_V, T]), + T. + + +d({1}, _) -> + asn1_NOVALUE; +d({2}, _V) -> + 'NULL'; +d({3}, _V) -> + sendRecv; +d({4}, _V) -> + recvOnly; +d({5}, _V) -> + restart; +d({6}, _) -> + mediaToken; +d({7}, _) -> + eventsToken; +d({8}, _) -> + signalsToken; +d({9}, _) -> + digitMapToken; +d({10}, _) -> + statsToken; +d({11}, _) -> + packagesToken; +d({12}, _V) -> + h221; +d({13}, _V) -> + h223; +d({14}, _V) -> + h226; +d({15}, _V) -> + v76; + +d({20, Mid, Body}, _) -> + {'MegacoMessage', asn1_NOVALUE, {'Message', 1, d(Mid, 1), d(Body, 1)}}; +d({21, Mid, Body}, _) -> + {'MegacoMessage', asn1_NOVALUE, {'Message', 2, d(Mid, 2), d(Body, 2)}}; +d({22, V, Mid, Body}, _) -> + {'MegacoMessage', asn1_NOVALUE, {'Message', V, d(Mid, V), d(Body, V)}}; +d({23, AuthHeader, Mid, Body}, _) -> + {'MegacoMessage', d(AuthHeader, 1), {'Message', 1, d(Mid, 1), d(Body, 1)}}; +d({24, AuthHeader, Mid, Body}, _) -> + {'MegacoMessage', d(AuthHeader, 2), {'Message', 2, d(Mid, 2), d(Body, 2)}}; +d({25, V, AuthHeader, Mid, Body}, _) -> + {'MegacoMessage', d(AuthHeader, V), {'Message', V, d(Mid, V), d(Body, V)}}; +d({26, AuthHeader, Mess}, V) -> + {'MegacoMessage', d(AuthHeader, V), d(Mess, V)}; +d({27, V, Mid, Body}, _) -> + {'Message', V, d(Mid, V), d(Body, V)}; + +d({30, Name}, _V) -> + {domainName, {'DomainName', Name, asn1_NOVALUE}}; +d({31, Name, PortNumber}, _V) -> + {domainName, {'DomainName', Name, PortNumber}}; +d({32, N}, V) -> + {domainName, d(N, V)}; +d({33, Name}, _V) -> + {'DomainName', Name, asn1_NOVALUE}; +d({34, Name, PortNumber}, _V) -> + {'DomainName', Name, PortNumber}; +d({35, Addr}, _V) -> + {ip4Address, {'IP4Address', Addr, asn1_NOVALUE}}; +d({36, Addr, PortNumber}, _V) -> + {ip4Address, {'IP4Address', Addr, PortNumber}}; +d({37, A}, V) -> + {ip4Address, d(A, V)}; +d({38, Addr}, _V) -> + {'IP4Address', Addr, asn1_NOVALUE}; +d({39, Addr, PortNumber}, _V) -> + {'IP4Address', Addr, PortNumber}; +d({40, Addr}, _V) -> + {ip6Address, {'IP6Address', Addr, asn1_NOVALUE}}; +d({41, Addr, PortNumber}, _V) -> + {ip6Address, {'IP6Address', Addr, PortNumber}}; +d({42, A}, V) -> + {ip6Address, d(A, V)}; +d({43, Addr}, _V) -> + {'IP6Address', Addr, asn1_NOVALUE}; +d({44, Addr, PortNumber}, _V) -> + {'IP6Address', Addr, PortNumber}; + +d({50, Transaction}, V) -> + {transactions, [d(Transaction, V)]}; +d({51, Transactions}, V) -> + {transactions, dl(Transactions, V)}; +d({52, EC}, _V) -> + {messageError, {'ErrorDescriptor', EC, asn1_NOVALUE}}; +d({53, EC, ET}, _V) -> + {messageError, {'ErrorDescriptor', EC, ET}}; +d({54, Error}, V) -> + {messageError, d(Error, V)}; +d({55, TransId, Actions}, V) -> + {transactionRequest, {'TransactionRequest', TransId, dl(Actions, V)}}; +d({56, TransId}, _V) -> + {transactionPending, {'TransactionPending', TransId}}; +d({57, TransId, TransRes}, V) -> + {transactionReply, {'TransactionReply', TransId, asn1_NOVALUE, d(TransRes, V)}}; +d({58, TransId, TransRes}, V) -> + {transactionReply, {'TransactionReply', TransId, 'NULL', d(TransRes, V)}}; +d({59, TransId, ImmAckReq, TransRes}, V) -> + {transactionReply, {'TransactionReply', TransId, d(ImmAckReq, V), d(TransRes, V)}}; +d({60, T}, V) -> + {transactionResponseAck, dl(T, V)}; +d({61, FirstAck}, _V) -> + {'TransactionAck', FirstAck, asn1_NOVALUE}; +d({62, FirstAck, LastAck}, _V) -> + {'TransactionAck', FirstAck, LastAck}; + +d({70, EC}, _V) -> + {'ErrorDescriptor', EC, asn1_NOVALUE}; +d({71, EC, ET}, _V) -> + {'ErrorDescriptor', EC, ET}; + +d({80, Cid, CtxReq, CtxAAR, CmdReq}, V) -> + {'ActionRequest', Cid, d(CtxReq, V), d(CtxAAR, V), [d(CmdReq, V)]}; +d({81, Cid, CtxReq, CtxAAR, CmdReqs}, V) -> + {'ActionRequest', Cid, d(CtxReq, V), d(CtxAAR, V), dl(CmdReqs, V)}; + +d({90, P, E, T}, V) -> + {'ContextRequest', d(P, V), d(E, V), dl(T, V)}; +d({91, P, E, T}, V) -> + {'ContextRequest', d(P, V), d(E, V), dl(T, V), + asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({92, P, E, T, IC}, V) -> + {'ContextRequest', d(P, V), d(E, V), dl(T, V), + d(IC, V), asn1_NOVALUE, asn1_NOVALUE}; +d({93, P, E, T, IC, CP}, V) -> + {'ContextRequest', d(P, V), d(E, V), dl(T, V), + d(IC, V), dl(CP, V), asn1_NOVALUE}; +d({94, P, E, T, IC, CP, CL}, V) -> + {'ContextRequest', d(P, V), d(E, V), dl(T, V), + d(IC, V), dl(CP, V), dl(CL, V)}; + +d({100, P, E, T}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V)}; +d({101, P, E, T}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({102, P, E, T, IC}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({103, P, E, T, IC, CPA}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), dl(CPA, V), asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({104, P, E, T, IC, CPA, SP}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), dl(CPA, V), d(SP, V), asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({105, P, E, T, IC, CPA, SP, SE}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), dl(CPA, V), d(SP, V), d(SE, V), asn1_NOVALUE, asn1_NOVALUE}; +d({106, P, E, T, IC, CPA, SP, SE, SIC}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), dl(CPA, V), d(SP, V), d(SE, V), d(SIC, V), asn1_NOVALUE}; +d({107, P, E, T, IC, CPA, SP, SE, SIC, SL}, V) -> + {'ContextAttrAuditRequest', d(P, V), d(E, V), d(T, V), + d(IC, V), dl(CPA, V), d(SP, V), d(SE, V), d(SIC, V), d(SL, V)}; + +d({110, Cmd}, V) -> + {'CommandRequest', d(Cmd, V), asn1_NOVALUE, asn1_NOVALUE}; +d({111, Cmd}, V) -> + {'CommandRequest', d(Cmd, V), 'NULL', asn1_NOVALUE}; +d({112, Cmd}, V) -> + {'CommandRequest', d(Cmd, V), asn1_NOVALUE, 'NULL'}; +d({113, Cmd}, V) -> + {'CommandRequest', d(Cmd, V), 'NULL', 'NULL'}; +d({114, Cmd, Opt, WR}, V) -> + {'CommandRequest', d(Cmd, V), d(Opt, V), d(WR, V)}; + +d({120, From, To, Dir}, 1 = V) -> + {'TopologyRequest', d(From, V), d(To, V), d(Dir, V)}; +d({121, From, To, Dir, SID}, 2 = V) -> + {'TopologyRequest', d(From, V), d(To, V), d(Dir, V), d(SID, V)}; +d({122, From, To, Dir, SID}, V) when (V >= 3) -> + {'TopologyRequest', d(From, V), d(To, V), d(Dir, V), d(SID, V), asn1_NOVALUE}; +d({123, From, To, Dir, SID, TDE}, V) when (V >= 3) -> + {'TopologyRequest', d(From, V), d(To, V), d(Dir, V), d(SID, V), d(TDE, V)}; + +d({130, TID}, V) -> + {modReq, {'AmmRequest', dl(TID, V), []}}; +d({131, TID, Desc}, V) -> + {modReq, {'AmmRequest', dl(TID, V), [d(Desc, V)]}}; +d({132, TID, Descs}, V) -> + {modReq, {'AmmRequest', dl(TID, V), dl(Descs, V)}}; +d({133, TID}, V) -> + {addReq, {'AmmRequest', dl(TID, V), []}}; +d({134, TID, Desc}, V) -> + {addReq, {'AmmRequest', dl(TID, V), [d(Desc, V)]}}; +d({135, TID, Descs}, V) -> + {addReq, {'AmmRequest', dl(TID, V), dl(Descs, V)}}; +d({136, TID, Descs}, V) -> + {'AmmRequest', dl(TID, V), dl(Descs, V)}; + +d({140, TID}, V) -> + {subtractReq, {'SubtractRequest', dl(TID, V), asn1_NOVALUE}}; +d({141, TID, AudDesc}, V) -> + {subtractReq, {'SubtractRequest', dl(TID, V), d(AudDesc, V)}}; +d({142, TID}, V) -> + {'SubtractRequest', dl(TID, V), asn1_NOVALUE}; +d({143, TID, AudDesc}, V) -> + {'SubtractRequest', dl(TID, V), d(AudDesc, V)}; + +d({150, AR}, V) -> + {auditValueRequest, d(AR, V)}; + +d({160, TID, AudDesc}, V) when V < 3 -> + {'AuditRequest', d(TID, V), d(AudDesc, V)}; +d({161, TID, AudDesc}, V) when V >= 3 -> + {'AuditRequest', d(TID, V), d(AudDesc, V), asn1_NOVALUE}; +d({162, TID, AudDesc, TIDs}, V) when V >= 3 -> + {'AuditRequest', d(TID, V), d(AudDesc, V), dl(TIDs, V)}; + +d({170, AR}, V) -> + {actionReplies, [d(AR, V)]}; +d({171, ARs}, V) -> + {actionReplies, dl(ARs, V)}; + +d({180, CID, CmdRep}, V) -> + {'ActionReply', CID, asn1_NOVALUE, asn1_NOVALUE, [d(CmdRep, V)]}; +d({181, CID, CmdRep}, V) -> + {'ActionReply', CID, asn1_NOVALUE, asn1_NOVALUE, dl(CmdRep, V)}; +d({182, CID, CtxRep, CmdRep}, V) -> + {'ActionReply', CID, asn1_NOVALUE, d(CtxRep, V), [d(CmdRep, V)]}; +d({183, CID, CtxRep, CmdRep}, V) -> + {'ActionReply', CID, asn1_NOVALUE, d(CtxRep, V), dl(CmdRep, V)}; +d({184, CID, ED, CmdRep}, V) -> + {'ActionReply', CID, d(ED, V), asn1_NOVALUE, [d(CmdRep, V)]}; +d({185, CID, ED, CmdRep}, V) -> + {'ActionReply', CID, d(ED, V), asn1_NOVALUE, dl(CmdRep, V)}; +d({186, CID, ED, CtxRep, CmdRep}, V) -> + {'ActionReply', CID, d(ED, V), d(CtxRep, V), [d(CmdRep, V)]}; +d({187, CID, ED, CtxRep, CmdRep}, V) -> + {'ActionReply', CID, d(ED, V), d(CtxRep, V), dl(CmdRep, V)}; + +d({190}, 1 = _V) -> + {'AuditDescriptor', asn1_NOVALUE}; +d({191, AT}, 1 = V) -> + {'AuditDescriptor', dl(AT, V)}; +d({192}, V) when (V >= 2) -> + {'AuditDescriptor', asn1_NOVALUE, asn1_NOVALUE}; +d({193, AT, APT}, V) when is_list(AT) andalso is_list(APT) andalso (V >= 2) -> + {'AuditDescriptor', dl(AT, V), dl(APT, V)}; +d({194, AT, APT}, V) when is_list(APT) andalso (V >= 2) -> + {'AuditDescriptor', d(AT, V), dl(APT, V)}; +d({195, AT, APT}, V) when is_list(AT) andalso (V >= 2) -> + {'AuditDescriptor', dl(AT, V), d(APT, V)}; +d({196, AT, APT}, V) when (V >= 2) -> + {'AuditDescriptor', d(AT, V), d(APT, V)}; + +d({200, TID, OED}, V) -> + {notifyReq, {'NotifyRequest', dl(TID, V), d(OED, V), asn1_NOVALUE}}; +d({201, TID, OED, ED}, V) -> + {notifyReq, {'NotifyRequest', dl(TID, V), d(OED, V), d(ED, V)}}; +d({202, TID, OED}, V) -> + {'NotifyRequest', dl(TID, V), d(OED, V), asn1_NOVALUE}; +d({203, TID, OED, ED}, V) -> + {'NotifyRequest', dl(TID, V), d(OED, V), d(ED, V)}; + +d({210, RID, OEL}, V) -> + {'ObservedEventsDescriptor', RID, dl(OEL, V)}; + +d({220, EN, SID, EPL, TN}, V) -> + {'ObservedEvent', EN, d(SID, V), dl(EPL, V), d(TN, V)}; + +d({230}, _V) -> + {'EventParameter', "type", ["est"], asn1_NOVALUE}; +d({231, Val}, _V) -> + {'EventParameter', "type", [Val], asn1_NOVALUE}; +d({232, Val}, _V) -> + {'EventParameter', "type", Val, asn1_NOVALUE}; +d({233}, _V) -> + {'EventParameter', "Generalcause", ["NR"], asn1_NOVALUE}; +d({234}, _V) -> + {'EventParameter', "Generalcause", ["UR"], asn1_NOVALUE}; +d({235}, _V) -> + {'EventParameter', "Generalcause", ["FT"], asn1_NOVALUE}; +d({236}, _V) -> + {'EventParameter', "Generalcause", ["FP"], asn1_NOVALUE}; +d({237}, _V) -> + {'EventParameter', "Generalcause", ["IW"], asn1_NOVALUE}; +d({238}, _V) -> + {'EventParameter', "Generalcause", ["UN"], asn1_NOVALUE}; +d({239, Val}, _V) -> + {'EventParameter', "Generalcause", [Val], asn1_NOVALUE}; +d({240, Val}, _V) -> + {'EventParameter', "Generalcause", Val, asn1_NOVALUE}; +d({241, Val}, _V) -> + {'EventParameter', "Failurecause", [Val], asn1_NOVALUE}; +d({242, Val}, _V) -> + {'EventParameter', "Failurecause", Val, asn1_NOVALUE}; +d({243, EPN, Val}, _V) -> + {'EventParameter', EPN, Val, asn1_NOVALUE}; +d({244, EPN, Val, EI}, _V) -> + {'EventParameter', EPN, Val, EI}; + +d({260, TID, SCPs}, V) -> + {serviceChangeReq, {'ServiceChangeRequest', dl(TID, V), d(SCPs, V)}}; +d({261, SCR}, V) -> + {serviceChangeReq, d(SCR, V)}; +d({262, TID, SCPs}, V) -> + {'ServiceChangeRequest', dl(TID, V), d(SCPs, V)}; + +d({270, TID, SCR}, V) -> + {serviceChangeReply, {'ServiceChangeReply', dl(TID, V), d(SCR, V)}}; +d({271, SCR}, V) -> + {serviceChangeReply, d(SCR, V)}; +d({272, TID, SCR}, V) -> %% KOLLA + {'ServiceChangeReply', dl(TID, V), d(SCR, V)}; + +d({280, TSD, S}, V) -> + {mediaDescriptor, {'MediaDescriptor', d(TSD, V), d(S, V)}}; +d({281, MD}, V) -> + {mediaDescriptor, d(MD, V)}; +d({282, TSD, S}, V) -> + {'MediaDescriptor', d(TSD, V), d(S, V)}; + +d({290, S}, V) -> + {oneStream, d(S, V)}; +d({291, S}, V) -> + {multiStream, dl(S, V)}; +d({292, SID, SP}, V) -> + {'StreamDescriptor', d(SID, V), d(SP, V)}; + +d({300, LCD}, V) -> + {'StreamParms', d(LCD, V), asn1_NOVALUE, asn1_NOVALUE}; +d({301, LCD, LD}, V) -> + {'StreamParms', d(LCD, V), d(LD, V), asn1_NOVALUE}; +d({302, LCD, LD, RD}, V) -> + {'StreamParms', d(LCD, V), d(LD, V), d(RD, V)}; + +d({303, LCD}, V) + when V >= 3 -> + {'StreamParms', d(LCD, V), asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({304, LCD, LD}, V) + when V >= 3 -> + {'StreamParms', d(LCD, V), d(LD, V), asn1_NOVALUE, asn1_NOVALUE}; +d({305, LCD, LD, RD}, V) + when V >= 3 -> + {'StreamParms', d(LCD, V), d(LD, V), d(RD, V), asn1_NOVALUE}; +d({306, LCD, LD, RD, SD}, V) + when V >= 3 -> + {'StreamParms', d(LCD, V), d(LD, V), d(RD, V), dl(SD, V)}; + +d({310, SM, RV, RG, PP}, V) -> + {'LocalControlDescriptor', d(SM, V), d(RV, V), d(RG, V), dl(PP, V)}; + +d({320, Val}, _V) -> + {'PropertyParm', "v", [Val], asn1_NOVALUE}; +d({321, Val}, _V) -> + {'PropertyParm', "v", Val, asn1_NOVALUE}; +d({332, Val}, _V) -> + {'PropertyParm', "o", [Val], asn1_NOVALUE}; +d({333, Val}, _V) -> + {'PropertyParm', "o", Val, asn1_NOVALUE}; +d({334, Val}, _V) -> + {'PropertyParm', "s", [Val], asn1_NOVALUE}; +d({335, Val}, _V) -> + {'PropertyParm', "s", Val, asn1_NOVALUE}; +d({336, Val}, _V) -> + {'PropertyParm', "i", [Val], asn1_NOVALUE}; +d({337, Val}, _V) -> + {'PropertyParm', "i", Val, asn1_NOVALUE}; +d({338, Val}, _V) -> + {'PropertyParm', "u", [Val], asn1_NOVALUE}; +d({339, Val}, _V) -> + {'PropertyParm', "u", Val, asn1_NOVALUE}; +d({340, Val}, _V) -> + {'PropertyParm', "e", [Val], asn1_NOVALUE}; +d({341, Val}, _V) -> + {'PropertyParm', "e", Val, asn1_NOVALUE}; +d({342, Val}, _V) -> + {'PropertyParm', "p", [Val], asn1_NOVALUE}; +d({343, Val}, _V) -> + {'PropertyParm', "p", Val, asn1_NOVALUE}; +d({344, Val}, _V) -> + {'PropertyParm', "c", [Val], asn1_NOVALUE}; +d({345, Val}, _V) -> + {'PropertyParm', "c", Val, asn1_NOVALUE}; +d({346, Val}, _V) -> + {'PropertyParm', "b", [Val], asn1_NOVALUE}; +d({347, Val}, _V) -> + {'PropertyParm', "b", Val, asn1_NOVALUE}; +d({348, Val}, _V) -> + {'PropertyParm', "z", [Val], asn1_NOVALUE}; +d({349, Val}, _V) -> + {'PropertyParm', "z", Val, asn1_NOVALUE}; +d({350, Val}, _V) -> + {'PropertyParm', "k", [Val], asn1_NOVALUE}; +d({351, Val}, _V) -> + {'PropertyParm', "k", Val, asn1_NOVALUE}; +d({352, Val}, _V) -> + {'PropertyParm', "a", [Val], asn1_NOVALUE}; +d({353, Val}, _V) -> + {'PropertyParm', "a", Val, asn1_NOVALUE}; +d({354, Val}, _V) -> + {'PropertyParm', "t", [Val], asn1_NOVALUE}; +d({355, Val}, _V) -> + {'PropertyParm', "t", Val, asn1_NOVALUE}; +d({356, Val}, _V) -> + {'PropertyParm', "r", [Val], asn1_NOVALUE}; +d({357, Val}, _V) -> + {'PropertyParm', "r", Val, asn1_NOVALUE}; +d({358, Val}, _V) -> + {'PropertyParm', "m", [Val], asn1_NOVALUE}; +d({359, Val}, _V) -> + {'PropertyParm', "m", Val, asn1_NOVALUE}; +d({360, Val}, _V) -> + {'PropertyParm', "nt/jit", [Val], asn1_NOVALUE}; +d({361, Val}, _V) -> + {'PropertyParm', "nt/jit", Val, asn1_NOVALUE}; +d({362}, _V) -> + {'PropertyParm', "tdmc/ec", ["on"], asn1_NOVALUE}; +d({363}, _V) -> + {'PropertyParm', "tdmc/ec", ["off"], asn1_NOVALUE}; +d({364}, _V) -> + {'PropertyParm', "tdmc/gain", ["automatic"], asn1_NOVALUE}; +d({365, Val}, _V) -> + {'PropertyParm', "tdmc/gain", [Val], asn1_NOVALUE}; +d({366, Val}, _V) -> + {'PropertyParm', "tdmc/gain", Val, asn1_NOVALUE}; +d({367, Val}, _V) -> + {'PropertyParm', "maxNumberOfContexts", [Val], asn1_NOVALUE}; +d({368, Val}, _V) -> + {'PropertyParm', "maxNumberOfContexts", Val, asn1_NOVALUE}; +d({369, Val}, _V) -> + {'PropertyParm', "maxTerminationsPerContext", [Val], asn1_NOVALUE}; +d({370, Val}, _V) -> + {'PropertyParm', "maxTerminationsPerContext", Val, asn1_NOVALUE}; +d({371, Val}, _V) -> + {'PropertyParm', "normalMGExecutionTime", [Val], asn1_NOVALUE}; +d({372, Val}, _V) -> + {'PropertyParm', "normalMGExecutionTime", Val, asn1_NOVALUE}; +d({373, Val}, _V) -> + {'PropertyParm', "normalMGCExecutionTime", [Val], asn1_NOVALUE}; +d({374, Val}, _V) -> + {'PropertyParm', "normalMGCExecutionTime", Val, asn1_NOVALUE}; +d({375, Val}, _V) -> + {'PropertyParm', "MGProvisionalResponseTimerValue", [Val], asn1_NOVALUE}; +d({376, Val}, _V) -> + {'PropertyParm', "MGProvisionalResponseTimerValue", Val, asn1_NOVALUE}; +d({377, Val}, _V) -> + {'PropertyParm', "MGCProvisionalResponseTimerValue", [Val], asn1_NOVALUE}; +d({378, Val}, _V) -> + {'PropertyParm', "MGCProvisionalResponseTimerValue", Val, asn1_NOVALUE}; +d({379, N, Val}, _V) -> + {'PropertyParm', N, [Val], asn1_NOVALUE}; +d({380, N, Val}, _V) -> + {'PropertyParm', N, Val, asn1_NOVALUE}; +d({381, N, Val, EI}, _V) -> + {'PropertyParm', N, Val, EI}; + +d({400, PG}, V) -> + {'LocalRemoteDescriptor', [[d(PG, V)]]}; +d({401, PG}, V) -> + {'LocalRemoteDescriptor', [dl(PG, V)]}; +d({402, PG}, V) -> + {'LocalRemoteDescriptor', dll(PG, V)}; + +d({410, PP, EBC, SS}, V) -> + {'TerminationStateDescriptor', dl(PP, V), d(EBC, V), d(SS, V)}; + +d({420, RID, E}, V) -> + {eventsDescriptor, {'EventsDescriptor', d(RID, V), [d(E, V)]}}; +d({421, RID, EL}, V) -> + {eventsDescriptor, {'EventsDescriptor', d(RID, V), dl(EL, V)}}; +d({422, ED}, V) -> + {eventsDescriptor, d(ED, V)}; +d({423, RID, E}, V) -> + {'EventsDescriptor', d(RID, V), [d(E, V)]}; +d({424, RID, EL}, V) -> + {'EventsDescriptor', d(RID, V), dl(EL, V)}; + +d({425, PN, SID, EA, EPL}, V) -> + {'RequestedEvent', PN, d(SID, V), d(EA, V), dl(EPL, V)}; + +d({430, SED, SD}, V) -> + {'RegulatedEmbeddedDescriptor', d(SED, V), dl(SD, V)}; + +d({435, NI}, V) -> + {notifyImmediate, d(NI, V)}; +d({436, NR}, V) -> + {notifyRegulated, d(NR, V)}; +d({437, NN}, V) -> + {neverNotify, d(NN, V)}; + +d({440, KA, EDM, SE, SD}, V) -> + {'RequestedActions', d(KA, V), d(EDM, V), d(SE, V), d(SD, V)}; +d({441, KA, EDM, SE, SD}, V) + when V >= 3 -> + {'RequestedActions', d(KA, V), d(EDM, V), d(SE, V), d(SD, V), + asn1_NOVALUE, asn1_NOVALUE}; +d({442, KA, EDM, SE, SD, NB}, V) + when V >= 3 -> + {'RequestedActions', d(KA, V), d(EDM, V), d(SE, V), d(SD, V), + d(NB, V), asn1_NOVALUE}; +d({443, KA, EDM, SE, SD, NB, RED}, V) + when V >= 3 -> + {'RequestedActions', d(KA, V), d(EDM, V), d(SE, V), d(SD, V), + d(NB, V), d(RED, V)}; + +d({450, RID, E}, V) -> + {'SecondEventsDescriptor', d(RID, V), [d(E, V)]}; +d({451, RID, EL}, V) -> + {'SecondEventsDescriptor', d(RID, V), dl(EL, V)}; + +d({460, PN, SID, EA, EPL}, V) -> + {'SecondRequestedEvent', PN, d(SID, V), d(EA, V), d(EPL, V)}; + +d({470, KA, EDM, SD}, V) -> + {'SecondRequestedActions', d(KA, V), d(EDM, V), d(SD, V)}; +d({471, KA, EDM, SD}, V) + when V >= 3 -> + {'SecondRequestedActions', d(KA, V), d(EDM, V), d(SD, V), + asn1_NOVALUE, asn1_NOVALUE}; +d({472, KA, EDM, SD, NB}, V) + when V >= 3 -> + {'SecondRequestedActions', d(KA, V), d(EDM, V), d(SD, V), + d(NB, V), asn1_NOVALUE}; +d({473, KA, EDM, SD, NB, RED}, V) + when V >= 3 -> + {'SecondRequestedActions', d(KA, V), d(EDM, V), d(SD, V), + d(NB, V), d(RED, V)}; + +d({480, EN, SID, EPL}, V) -> + {'EventSpec', EN, d(SID, V), dl(EPL, V)}; + +d({490, ID, SL}, V) -> + {'SeqSigList', ID, dl(SL, V)}; + +d({500, S}, V) -> + {signalsDescriptor, dl(S, V)}; + +d({510, S}, V) -> + {signal, d(S, V)}; + +d({520, SN, SID, ST, D, NC, KA, SPL}, V) -> + {'Signal', + SN, d(SID, V), d(ST, V), d(D, V), d(NC, V), d(KA, V), dl(SPL, V)}; +d({521, SN, SID, ST, D, NC, KA, SPL}, V) + when V >= 3 -> + {'Signal', + SN, d(SID, V), d(ST, V), d(D, V), d(NC, V), d(KA, V), dl(SPL, V), + asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({522, SN, SID, ST, D, NC, KA, SPL, SD}, V) + when V >= 3 -> + {'Signal', + SN, d(SID, V), d(ST, V), d(D, V), d(NC, V), d(KA, V), dl(SPL, V), + d(SD, V), asn1_NOVALUE, asn1_NOVALUE}; +d({523, SN, SID, ST, D, NC, KA, SPL, SD, RID}, V) + when V >= 3 -> + {'Signal', + SN, d(SID, V), d(ST, V), d(D, V), d(NC, V), d(KA, V), dl(SPL, V), + d(SD, V), d(RID, V), asn1_NOVALUE}; +d({524, SN, SID, ST, D, NC, KA, SPL, SD, RID, IsD}, V) + when V >= 3 -> + {'Signal', + SN, d(SID, V), d(ST, V), d(D, V), d(NC, V), d(KA, V), dl(SPL, V), + d(SD, V), d(RID, V), d(IsD, V)}; + +d({530, SPN, Val}, _V) -> + {'SigParameter', SPN, Val, asn1_NOVALUE}; +d({531, SPN, Val, EI}, _V) -> + {'SigParameter', SPN, Val, EI}; + +d({550, MD}, V) -> + {modemDescriptor, d(MD, V)}; +d({551, MTL, MPL}, _V) -> + {'ModemDescriptor', MTL, MPL, asn1_NOVALUE}; +d({552, MTL, MPL, NSD}, _V) -> + {'ModemDescriptor', MTL, MPL, NSD}; + +d({560, DMN, DMV}, V) -> + {digitMapDescriptor, {'DigitMapDescriptor', DMN, d(DMV, V)}}; +d({561, DMD}, V) -> + {digitMapDescriptor, d(DMD, V)}; +d({562, DMN, DMV}, V) -> + {'DigitMapDescriptor', DMN, d(DMV, V)}; + +d({570, Start, Stop, Long, DMB}, 1 = V) -> + {'DigitMapValue', d(Start, V), d(Stop, V), d(Long, V), DMB}; +d({571, Start, Stop, Long, DMB, Dur}, V) when V >= 2 -> + {'DigitMapValue', d(Start, V), d(Stop, V), d(Long, V), DMB, d(Dur, V)}; + +d({580, M, A, Ver, Prof, R, D, Id}, V) -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + asn1_NOVALUE, asn1_NOVALUE}; +d({581, M, A, Ver, Prof, R, D, Id, TS}, V) -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), asn1_NOVALUE}; +d({582, M, A, Ver, Prof, R, D, Id, TS, NSD}, V) -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD}; + +d({583, M, A, Ver, Prof, R, D, Id, TS, NSD}, V) + when V == 2 -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD, asn1_NOVALUE}; +d({584, M, A, Ver, Prof, R, D, Id, TS, NSD, Info}, V) + when V == 2 -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD, d(Info, V)}; + +d({585, M, A, Ver, Prof, R, D, Id, TS, NSD}, V) + when V >= 3 -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD, asn1_NOVALUE, asn1_NOVALUE}; +d({586, M, A, Ver, Prof, R, D, Id, TS, NSD, Info}, V) + when V >= 3 -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD, d(Info, V), asn1_NOVALUE}; +d({587, M, A, Ver, Prof, R, D, Id, TS, NSD, Info, Flag}, V) + when V >= 3 -> + {'ServiceChangeParm', + d(M, V), d(A, V), d(Ver, V), d(Prof, V), R, d(D, V), d(Id, V), + d(TS, V), NSD, d(Info, V), d(Flag, V)}; + +d({590, Id, A, Ver, Prof, TS}, V) -> + {serviceChangeResParms, {'ServiceChangeResParm', Id, d(A, V), Ver, d(Prof, V), TS}}; +d({591, SCRP}, V) -> + {serviceChangeResParms, d(SCRP, V)}; +d({592, Id, A, Ver, Prof, TS}, V) -> + {'ServiceChangeResParm', Id, d(A, V), Ver, d(Prof, V), TS}; + +d({600, N}, _V) -> + {portNumber, N}; + +d({610, D, T}, _V) -> + {'TimeNotation', D, T}; + +d({620, N, Ver}, _V) -> + {'ServiceChangeProfile', N, Ver}; + +d({630, N}, _) -> + {digitMapName, N}; + +d({640, Id}, _V) -> + {megaco_term_id, false, Id}; +d({641}, _V) -> + {megaco_term_id, true, [[$*]]}; +d({642}, _V) -> + {megaco_term_id, true, [[$$]]}; +d({643, Id}, _V) -> + {megaco_term_id, true, Id}; +d({644, W, ID}, _V) -> + {'TerminationID', W, ID}; + +d({650, TID}, V) -> + {modReply, {'AmmsReply', dl(TID, V), asn1_NOVALUE}}; +d({651, TID, TA}, V) -> + {modReply, {'AmmsReply', dl(TID, V), [d(TA, V)]}}; +d({652, TID, TA}, V) -> + {modReply, {'AmmsReply', dl(TID, V), dl(TA, V)}}; +d({653, R}, V) -> + {modReply, d(R, V)}; + +d({655, AR}, V) -> + {moveReply, d(AR, V)}; + +d({660, TID}, V) -> + {addReply, {'AmmsReply', dl(TID, V), asn1_NOVALUE}}; +d({661, TID, TA}, V) -> + {addReply, {'AmmsReply', dl(TID, V), [d(TA, V)]}}; +d({662, TID, TA}, V) -> + {addReply, {'AmmsReply', dl(TID, V), dl(TA, V)}}; +d({663, R}, V) -> + {addReply, d(R, V)}; + +d({670, TID}, V) -> + {subtractReply, {'AmmsReply', dl(TID, V), asn1_NOVALUE}}; +d({671, TID, TA}, V) -> + {subtractReply, {'AmmsReply', dl(TID, V), [d(TA, V)]}}; +d({672, TID, TA}, V) -> + {subtractReply, {'AmmsReply', dl(TID, V), dl(TA, V)}}; +d({673, R}, V) -> + {subtractReply, d(R, V)}; + +d({680, TID}, V) -> + {'AmmsReply', dl(TID, V), asn1_NOVALUE}; +d({681, TID, TA}, V) -> + {'AmmsReply', dl(TID, V), [d(TA, V)]}; +d({682, TID, TA}, V) -> + {'AmmsReply', dl(TID, V), dl(TA, V)}; + +d({690, TID}, V) -> + {notifyReply, {'NotifyReply', dl(TID, V), asn1_NOVALUE}}; +d({691, TID, ED}, V) -> + {notifyReply, {'NotifyReply', dl(TID, V), d(ED, V)}}; +d({692, R}, V) -> + {notifyReply, d(R, V)}; +d({693, TID}, V) -> + {'NotifyReply', dl(TID, V), asn1_NOVALUE}; +d({694, TID, ED}, V) -> + {'NotifyReply', dl(TID, V), d(ED, V)}; + +d({700, AVR}, V) -> + {auditValueReply, d(AVR, V)}; + +d({705, TIDs}, V) -> + {contextAuditResult, dl(TIDs, V)}; + +d({710, TID, TAR}, V) -> + {auditResult, {'AuditResult', d(TID, V), [d(TAR, V)]}}; +d({711, TID, TAR}, V) -> + {auditResult, {'AuditResult', d(TID, V), dl(TAR, V)}}; +d({712, AR}, V) -> + {auditResult, d(AR, V)}; +d({713, TID, TAR}, V) -> + {'AuditResult', d(TID, V), [d(TAR, V)]}; +d({714, TID, TAR}, V) -> + {'AuditResult', d(TID, V), dl(TAR, V)}; + +d({715, TIDs, [TAR]}, V) -> + {auditResultTermList, {'TermListAuditResult', dl(TIDs, V), [d(TAR, V)]}}; +d({716, TIDs, TAR}, V) -> + {auditResultTermList, {'TermListAuditResult', dl(TIDs, V), dl(TAR, V)}}; + +d({720, PsD}, V) -> + {packagesDescriptor, dl(PsD, V)}; + +d({730}, _V) -> + {'PackagesItem', "g", 1}; +d({731}, _V) -> + {'PackagesItem', "tonegen", 1}; +d({732}, _V) -> + {'PackagesItem', "tonedet", 1}; +d({733}, _V) -> + {'PackagesItem', "tg", 1}; +d({734}, _V) -> + {'PackagesItem', "dd", 1}; +d({735}, _V) -> + {'PackagesItem', "cg", 1}; +d({736}, _V) -> + {'PackagesItem', "cd", 1}; +d({737}, _V) -> + {'PackagesItem', "al", 1}; +d({738}, _V) -> + {'PackagesItem', "ct", 1}; +d({739}, _V) -> + {'PackagesItem', "nt", 1}; +d({740}, _V) -> + {'PackagesItem', "rtp", 1}; +d({741}, _V) -> + {'PackagesItem', "tdmc", 1}; +d({742, Name, Ver}, _V) -> + {'PackagesItem', Name, Ver}; + +d({760, AD}, V) -> + {emptyDescriptors, d(AD, V)}; + +d({770, SD}, V) -> + {statisticsDescriptor, [d(SD, V)]}; +d({771, SsD}, V) -> + {statisticsDescriptor, dl(SsD, V)}; + +d({780, Name}, _V) -> + {'StatisticsParameter', Name, asn1_NOVALUE}; +d({781, Name, Value}, _V) -> + {'StatisticsParameter', Name, Value}; + +d({800, MT, TL}, V) -> + {'MuxDescriptor', d(MT, V), dl(TL, V), asn1_NOVALUE}; +d({801, MT, TL, NSD}, V) -> + {'MuxDescriptor', d(MT, V), dl(TL, V), NSD}; + +d({900, N, Ver}, V) when (V >= 2) -> + {indAudPackagesDescriptor, {'IndAudPackagesDescriptor', N, Ver}}; +d({900, IAPD}, V) when (V >= 2) -> + {indAudPackagesDescriptor, d(IAPD, V)}; +d({901, N, Ver}, V) when (V >= 2) -> + {'IndAudPackagesDescriptor', N, Ver}; + +d({910, N}, V) when (V >= 2) -> + {indAudStatisticsDescriptor, {'IndAudStatisticsDescriptor', N}}; +d({911, IASD}, V) when (V >= 2) -> + {indAudStatisticsDescriptor, d(IASD, V)}; +d({912, N}, V) when (V >= 2) -> + {'IndAudStatisticsDescriptor', N}; + +d({920, DMN}, V) when (V >= 2) -> + {indAudDigitMapDescriptor, {'IndAudDigitMapDescriptor', DMN}}; +d({921, IADMD}, V) when (V >= 2) -> + {indAudDigitMapDescriptor, d(IADMD, V)}; +d({922, DMN}, V) when (V >= 2) -> + {'IndAudDigitMapDescriptor', DMN}; + +d({930, IASD}, V) when (V >= 2) -> + {indAudSignalsDescriptor, {seqSigList, d(IASD, V)}}; +d({931, IAS}, V) when (V >= 2) -> + {indAudSignalsDescriptor, {signal, d(IAS, V)}}; + +d({940, Id, SL}, V) when (V >= 2) -> + {'IndAudSeqSigList', Id, d(SL, V)}; + +d({950, N, SID}, 2 = V) -> + {'IndAudSignal', N, d(SID, V)}; +d({951, N, SID}, V) when (V >= 3) -> + {'IndAudSignal', N, d(SID, V), asn1_NOVALUE}; +d({952, N, SID, RID}, V) when (V >= 3) -> + {'IndAudSignal', N, d(SID, V), d(RID, V)}; + +d({960, EN, SID}, V) when (V >= 2) -> + {indAudEventBufferDescriptor, + {'IndAudEventBufferDescriptor', EN, d(SID, V)}}; +d({961, IAEBD}, V) when (V >= 2) -> + {indAudEventBufferDescriptor, d(IAEBD, V)}; +d({962, EN, SID}, V) when (V >= 2) -> + {'IndAudEventBufferDescriptor', EN, d(SID, V)}; + +d({970, RID, N, SID}, V) when (V >= 2) -> + {indAudEventsDescriptor, + {'IndAudEventsDescriptor', d(RID, V), N, d(SID, V)}}; +d({971, IAED}, V) when (V >= 2) -> + {indAudEventsDescriptor, d(IAED, V)}; +d({972, RID, N, SID}, V) when (V >= 2) -> + {'IndAudEventsDescriptor', d(RID, V), N, d(SID, V)}; + +d({980, TSD, S}, V) when (V >= 2) -> + {indAudMediaDescriptor, {'IndAudMediaDescriptor', d(TSD, V), d(S, V)}}; +d({981, IAMD}, V) when (V >= 2) -> + {indAudMediaDescriptor, d(IAMD, V)}; +d({982, TSD, S}, V) when (V >= 2) -> + {'IndAudMediaDescriptor', d(TSD, V), d(S, V)}; + +d({990, PP, EBC, SS}, 2 = V) -> + {'IndAudTerminationStateDescriptor', dl(PP, V), d(EBC, V), d(SS, V)}; +d({991, PP, EBC, SS}, V) when V >= 3 -> + {'IndAudTerminationStateDescriptor', dl(PP, V), d(EBC, V), d(SS, V), + asn1_NOVALUE}; +d({992, PP, EBC, SS, SSS}, V) when V >= 3 -> + {'IndAudTerminationStateDescriptor', dl(PP, V), d(EBC, V), d(SS, V), + d(SSS, V)}; + +d({1000, SID, SP}, V) -> + {'IndAudStreamDescriptor', d(SID, V), d(SP, V)}; + +d({1010, LCD}, 2 = V) -> + {'IndAudStreamParms', d(LCD, V), asn1_NOVALUE, asn1_NOVALUE}; +d({1011, LCD, LD, RD}, 2 = V) -> + {'IndAudStreamParms', d(LCD, V), d(LD, V), d(RD, V)}; +d({1012, LCD}, V) when V >= 3 -> + {'IndAudStreamParms', d(LCD, V), asn1_NOVALUE, asn1_NOVALUE, asn1_NOVALUE}; +d({1013, LCD, LD}, V) when V >= 3 -> + {'IndAudStreamParms', d(LCD, V), d(LD, V), asn1_NOVALUE, asn1_NOVALUE}; +d({1014, LCD, LD, RD}, V) when V >= 3 -> + {'IndAudStreamParms', d(LCD, V), d(LD, V), d(RD, V), asn1_NOVALUE}; +d({1015, LCD, LD, RD, SD}, V) when V >= 3 -> + {'IndAudStreamParms', d(LCD, V), d(LD, V), d(RD, V), d(SD, V)}; + +d({1020, SM, RV, RG}, 2 = V) -> + {'IndAudLocalControlDescriptor', + d(SM, V), d(RV, V), d(RG, V), asn1_NOVALUE}; +d({1021, SM, RV, RG, PP}, 2 = V) + when is_list(PP) -> + {'IndAudLocalControlDescriptor', d(SM, V), d(RV, V), d(RG, V), dl(PP, V)}; +d({1022, SM, RV, RG}, V) when (V >= 3) -> + {'IndAudLocalControlDescriptor', + d(SM, V), d(RV, V), d(RG, V), asn1_NOVALUE, asn1_NOVALUE}; +d({1023, SM, RV, RG, PP}, V) + when is_list(PP) andalso (V >= 3) -> + {'IndAudLocalControlDescriptor', d(SM, V), d(RV, V), d(RG, V), dl(PP, V), + asn1_NOVALUE}; +d({1024, SM, RV, RG, PP, SMS}, V) + when is_list(PP) andalso (V >= 3) -> + {'IndAudLocalControlDescriptor', d(SM, V), d(RV, V), d(RG, V), dl(PP, V), + d(SMS, V)}; + +d({1030, N}, 2 = _V) -> + {'IndAudPropertyParm', N}; +d({1031, N}, V) when V >= 3 -> + {'IndAudPropertyParm', N, asn1_NOVALUE}; +d({1032, N, PP}, V) when V >= 3 -> + {'IndAudPropertyParm', N, d(PP, V)}; + +d({1100}, _V) -> + oneway; +d({1101}, _V) -> + bothway; +d({1102}, _V) -> + isolate; +d({1103}, _V) -> + onewayexternal; +d({1104}, _V) -> + onewayboth; + +d(T, _V) -> + %% io:format("d(~w) -> ~nT: ~w~n", [_V, T]), + T. + + +%% i(F, A) -> +%% %% i(get(dbg), F, A). +%% i(true, F, A). + +%% i(true, F, A) -> +%% io:format("DBG:" ++ F ++ "~n", A); +%% i(_, _, _) -> +%% ok. + diff --git a/lib/megaco/src/engine/megaco_filter.erl b/lib/megaco/src/engine/megaco_filter.erl new file mode 100644 index 0000000000..23a91b1f1d --- /dev/null +++ b/lib/megaco/src/engine/megaco_filter.erl @@ -0,0 +1,350 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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 : megaco/H.248 customization of generic event tracer +%%---------------------------------------------------------------------- + +-module(megaco_filter). + +-export([start/0, start/1, filter/1, + pretty_error/1, string_to_term/1]). + +-include_lib("megaco/include/megaco.hrl"). +-include_lib("megaco/include/megaco_message_v1.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). +-include_lib("et/include/et.hrl"). + +start() -> + start([]). + +start(ExtraOptions) -> + Options = + [{event_order, event_ts}, + {scale, 3}, + {max_actors, infinity}, + {trace_pattern, {megaco, max}}, + {trace_global, true}, + {dict_insert, {filter, ?MODULE}, fun filter/1}, + {active_filter, ?MODULE}, + {title, "Megaco tracer - Erlang/OTP"} | ExtraOptions], + et_viewer:start(Options). + +filter(E) when is_record(E, event) -> + From = filter_actor(E#event.from), + To = filter_actor(E#event.to), + E2 = E#event{from = From, to = To}, + E3 = filter_contents(E#event.contents, E2, []), + {true, E3}. + +filter_actors(From, To, E) + when (E#event.from =:= ?APPLICATION) andalso (E#event.to =:= ?APPLICATION) -> + Label = E#event.label, + case lists:prefix("callback:", Label) of + true -> + E#event{from = filter_actor(From), + to = filter_user_actor(From)}; + false -> + case lists:prefix("return:", Label) of + true -> + E#event{from = filter_user_actor(From), + to = filter_actor(From)}; + false -> + case lists:prefix("receive bytes", Label) of + true -> + E#event{from = filter_actor(To), + to = filter_actor(From)}; + false -> + E#event{from = filter_actor(From), + to = filter_actor(To)} + end + end + end; +filter_actors(_From, _To, E) -> + E. + +filter_actor(Actor) -> + String = do_filter_actor(Actor), + if + length(String) > 21 -> + string:substr(String, 1, 21) ++ [$*]; + true -> + String + end. + +filter_user_actor(Actor) -> + String = do_filter_actor(Actor) ++ "@user", + if + length(String) > 21 -> + string:substr(String, 1, 21) ++ [$*]; + true -> + String + end. + +do_filter_actor(CH) when is_record(CH, megaco_conn_handle) -> + Mid = CH#megaco_conn_handle.local_mid, + do_filter_actor(Mid); +do_filter_actor(Actor) -> + case Actor of + {ip4Address, {'IP4Address', [A1,A2,A3,A4], asn1_NOVALUE}} -> + integer_to_list(A1) ++ [$.] ++ + integer_to_list(A2) ++ [$.] ++ + integer_to_list(A3) ++ [$.] ++ + integer_to_list(A4); + {ip4Address, {'IP4Address', [A1,A2,A3,A4], Port}} -> + integer_to_list(A1) ++ [$.] ++ + integer_to_list(A2) ++ [$.] ++ + integer_to_list(A3) ++ [$.] ++ + integer_to_list(A4) ++ [$:] ++ + integer_to_list(Port); + {domainName, {'DomainName', Name, asn1_NOVALUE}} -> + Name; + {domainName, {'DomainName', Name, Port}} -> + Name ++ [$:] ++ integer_to_list(Port); + {deviceName, Name} -> + Name; + unknown_remote_mid -> + "preliminary_mid"; + preliminary_mid -> + "preliminary_mid"; + megaco -> + megaco; + _Other -> + "UNKNOWN" + end. + +filter_contents([], E, Contents) -> + E#event{contents = lists:flatten(lists:reverse(Contents))}; +filter_contents([H | T], E, Contents) -> + case H of + {line, _Mod, _Line} -> + filter_contents(T, E, Contents); + CD when is_record(CD, conn_data) -> + CH = CD#conn_data.conn_handle, + From = CH#megaco_conn_handle.local_mid, + To = CH#megaco_conn_handle.remote_mid, + E2 = filter_actors(From, To, E), + Serial = CD#conn_data.serial, + E3 = append_serial(Serial, E2), + filter_contents(T, E3, Contents); + CH when is_record(CH, megaco_conn_handle) -> + From = CH#megaco_conn_handle.local_mid, + To = CH#megaco_conn_handle.remote_mid, + E2 = filter_actors(From, To, E), + filter_contents(T, E2, Contents); + {orig_conn_handle, _CH} -> + filter_contents(T, E, Contents); + RH when is_record(RH, megaco_receive_handle) -> + Actor = RH#megaco_receive_handle.local_mid, + E2 = filter_actors(Actor, Actor, E), + filter_contents(T, E2, Contents); + {pid, Pid} when is_pid(Pid) -> + filter_contents(T, E, Contents); + pending -> + filter_contents(T, E, Contents); + reply -> + filter_contents(T, E, Contents); + {error, Reason} -> + Pretty = pretty_error({error, Reason}), + E2 = prepend_error(E), + filter_contents(T, E2, [[Pretty, "\n"], Contents]); + {'EXIT', Reason} -> + Pretty = pretty_error({'EXIT', Reason}), + E2 = prepend_error(E), + filter_contents(T, E2, [[Pretty, "\n"], Contents]); + ED when is_record(ED, 'ErrorDescriptor') -> + Pretty = pretty_error(ED), + E2 = prepend_error(E), + filter_contents(T, E2, [[Pretty, "\n"], Contents]); + Trans when is_record(Trans, 'TransactionRequest') -> + Pretty = pretty({trans, {transactionRequest, Trans}}), + filter_contents([], E, [[Pretty, "\n"], Contents]); + Trans when is_record(Trans, 'TransactionReply') -> + Pretty = pretty({trans, {transactionReply, Trans}}), + filter_contents([], E, [[Pretty, "\n"], Contents]); + Trans when is_record(Trans, 'TransactionPending') -> + Pretty = pretty({trans, {transactionPending, Trans}}), + filter_contents([], E, [[Pretty, "\n"], Contents]); + Trans when is_record(Trans, 'TransactionAck') -> + Pretty = pretty({trans, {transactionResponseAck, [Trans]}}), + case Trans#'TransactionAck'.lastAck of + asn1_NOVALUE -> + filter_contents([], E, [[Pretty, "\n"], Contents]); + Last -> + Label = term_to_string(E#event.label), + E2 = E#event{label = Label ++ ".." ++ integer_to_list(Last)}, + filter_contents([], E2, [[Pretty, "\n"], Contents]) + end; + {context_id, _ContextId} -> + Pretty = pretty(H), + filter_contents(T, E, [[Pretty, "\n"], Contents]); + {command_request, CmdReq} -> + Pretty = pretty(CmdReq), + filter_contents(T, E, [[Pretty, "\n"], Contents]); + {user_reply, {ok, ARS}} -> + Pretty = [[pretty(AR), "\n"] || AR <- ARS], + filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]); + {user_reply, Error} -> + Pretty = pretty_error(Error), + filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]); + {actionReplies, ARS} -> + Pretty = [[pretty(AR), "\n"] || AR <- ARS], + filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]); + MegaMsg when is_record(MegaMsg, 'MegacoMessage') -> + Pretty = pretty(MegaMsg), + filter_contents(T, E, [["MESSAGE: \n", Pretty, "\n"], Contents]); + {bytes, Bin} when is_binary(Bin) -> + E2 = + case E#event.label of + [$s, $e, $n, $d, $ , $b, $y, $t, $e, $s | Tail] -> + L = lists:concat(["send ", size(Bin), " bytes", Tail]), + E#event{label = L}; + [$r, $e, $c, $e, $i, $v, $e, $ , $b, $y, $t, $e, $s | Tail] -> + L = lists:concat(["receive ", size(Bin), " bytes", Tail]), + E#event{label = L}; + _ -> + E + end, + CharList = erlang:binary_to_list(Bin), + filter_contents(T, E2, [[CharList , "\n"], Contents]); + [] -> + filter_contents(T, E, Contents); + {test_lib, _Mod, _Fun} -> + filter_contents(T, E, Contents); + Other -> + Pretty = pretty(Other), + filter_contents(T, E, [[Pretty, "\n"], Contents]) + end. + +append_serial(Serial, E) when is_integer(Serial) -> + Label = term_to_string(E#event.label), + E#event{label = Label ++ " #" ++ integer_to_list(Serial)}; +append_serial(_Serial, E) -> + E. + +prepend_error(E) -> + Label = term_to_string(E#event.label), + E#event{label = "<ERROR> " ++ Label}. + +pretty({context_id, ContextId}) -> + if + ContextId =:= ?megaco_null_context_id -> + ["CONTEXT ID: -\n"]; + ContextId =:= ?megaco_choose_context_id -> + ["CONTEXT ID: $\n"]; + ContextId =:= ?megaco_all_context_id -> + ["CONTEXT ID: *\n"]; + is_integer(ContextId) -> + ["CONTEXT ID: ",integer_to_list(ContextId), "\n"] + end; +pretty(MegaMsg) when is_record(MegaMsg, 'MegacoMessage') -> + case catch megaco_pretty_text_encoder:encode_message([], MegaMsg) of + {ok, Bin} -> + term_to_string(Bin); + _Bad -> + term_to_string(MegaMsg) + end; +pretty(CmdReq) when is_record(CmdReq, 'CommandRequest') -> + case catch megaco_pretty_text_encoder:encode_command_request(CmdReq) of + {ok, IoList} -> + IoList2 = lists:flatten(IoList), + term_to_string(IoList2); + _Bad -> + term_to_string(CmdReq) + end; +pretty({complete_success, ContextId, RepList} = Res) -> + ActRep = #'ActionReply'{contextId = ContextId, + commandReply = RepList}, + case catch megaco_pretty_text_encoder:encode_action_reply(ActRep) of + {ok, IoList} -> + IoList2 = lists:flatten(IoList), + term_to_string(IoList2); + _Bad -> + term_to_string(Res) + end; +pretty(AR) when is_record(AR, 'ActionReply') -> + case catch megaco_pretty_text_encoder:encode_action_reply(AR) of + {ok, IoList} -> + IoList2 = lists:flatten(IoList), + term_to_string(IoList2); + _Bad -> + term_to_string(AR) + end; +pretty({partial_failure, ContextId, RepList} = Res) -> + ActRep = #'ActionReply'{contextId = ContextId, + commandReply = RepList}, + case catch megaco_pretty_text_encoder:encode_action_reply(ActRep) of + {ok, IoList} -> + IoList2 = lists:flatten(IoList), + term_to_string(IoList2); + _Bad -> + term_to_string(Res) + end; +pretty({trans, Trans}) -> + case catch megaco_pretty_text_encoder:encode_transaction(Trans) of + {ok, Bin} when is_binary(Bin) -> + IoList2 = lists:flatten(binary_to_list(Bin)), + term_to_string(IoList2); + {ok, IoList} -> + IoList2 = lists:flatten(IoList), + term_to_string(IoList2); + _Bad -> + term_to_string(Trans) + end; +pretty(Other) -> + term_to_string(Other). + +pretty_error({error, Reason}) -> + ["ERROR: ", pretty_error(Reason)]; +pretty_error({'EXIT', Reason}) -> + ["EXIT: ", pretty_error(Reason)]; +pretty_error({'ErrorDescriptor', Code, Reason}) -> + ["CODE: ", integer_to_list(Code), " TEXT: ", pretty_error(Reason)]; +pretty_error(Ugly) -> + case string_to_term(Ugly) of + {ok, Pretty} -> ["\n", Pretty]; + _ -> ["\n", term_to_string(Ugly)] + end. + +string_to_term(Chars) -> + do_string_to_term([], Chars, 1). + +do_string_to_term(Cont, Chars, Line) -> + case catch erl_scan:tokens(Cont, Chars, Line) of + {done, {ok, Tokens, _EndLine}, _Rest} -> + case erl_parse:parse_term(Tokens) of + {ok, Term} -> + {ok, Term}; + {error, Reason} -> + {error, Reason} + end; + {more, Cont2} -> + do_string_to_term(Cont2, ". ", Line); + Other -> + {error, Other} + end. + +term_to_string(Bin) when is_binary(Bin) -> + binary_to_list(Bin); +term_to_string(Term) -> + case catch io_lib:format("~s", [Term]) of + {'EXIT', _} -> lists:flatten(io_lib:format("~p", [Term])); + GoodString -> lists:flatten(GoodString) + end. diff --git a/lib/megaco/src/engine/megaco_message_internal.hrl b/lib/megaco/src/engine/megaco_message_internal.hrl new file mode 100644 index 0000000000..44f38752a9 --- /dev/null +++ b/lib/megaco/src/engine/megaco_message_internal.hrl @@ -0,0 +1,159 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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% +%% + +%% +%%---------------------------------------------------------------------- +%% Generated by the Erlang ASN.1 compiler version:1.2.7 +%% Purpose: Erlang record definitions for each named and unnamed +%% SEQUENCE and SET in module MEDIA-GATEWAY-CONTROL +%%---------------------------------------------------------------------- + +-record('MegacoMessage', + { + authHeader = asn1_NOVALUE, + mess + }). + +-record('AuthenticationHeader', + { + secParmIndex, + seqNum, + ad + }). + +-record('Message', + { + version, + mId, + messageBody + }). % with extension mark + +-record('DomainName', + { + name, + portNumber = asn1_NOVALUE + }). + +-record('IP4Address', + { + address, + portNumber = asn1_NOVALUE + }). + +-record('IP6Address', + { + address, + portNumber = asn1_NOVALUE + }). + +-record('TransactionRequest', + { + transactionId, + actions + }). % with extension mark + +-record('TransactionPending', + { + transactionId + }). % with extension mark + + +%% --- TransactionReply --- + +-record('megaco_transaction_reply', + { + transactionId, + immAckRequired = asn1_NOVALUE, + transactionResult, + segmentNumber = asn1_NOVALUE, + segmentationComplete = asn1_NOVALUE + }). + + +%% %% Pre v3 record def: +%% -record('TransactionReply', +%% { +%% transactionId, +%% immAckRequired = asn1_NOVALUE, +%% transactionResult +%% }). %% with extension mark + +%% %% v3 record def: +%% -record('TransactionReply', +%% { +%% transactionId, +%% immAckRequired = asn1_NOVALUE, +%% transactionResult, +%% %% with extension mark -- v3 -- +%% segmentNumber = asn1_NOVALUE, +%% segmentationComplete = asn1_NOVALUE +%% }). + + +%% -- v3 -- +-record('SegmentReply', + { + transactionId, + segmentNumber, + segmentationComplete = asn1_NOVALUE + }). % with extension mark + + +-record('TransactionAck', + { + firstAck, + lastAck = asn1_NOVALUE + }). + +-record('ErrorDescriptor', + { + errorCode, + errorText = asn1_NOVALUE + }). + +-record('DigitMapDescriptor', + { + digitMapName = asn1_NOVALUE, + digitMapValue = asn1_NOVALUE + }). + +-record('DigitMapValue', + { + startTimer = asn1_NOVALUE, + shortTimer = asn1_NOVALUE, + longTimer = asn1_NOVALUE, + %% BUGBUG BUGBUG + %% Note that there should not really be a default value + %% for this item, but a problem with the flex scanner + %% makes it neccessary to swap the values of digitMapBody + %% and durationTimer. The same is done in the (erl) scanner + %% just so they behave the same. The values are later + %% swapped back by the parser... + digitMapBody = asn1_NOVALUE, + %% with extensions + durationTimer = asn1_NOVALUE + }). + + +-record('TerminationID', + { + wildcard, + id + }). % with extension mark + diff --git a/lib/megaco/src/engine/megaco_messenger.erl b/lib/megaco/src/engine/megaco_messenger.erl new file mode 100644 index 0000000000..a9e4fd67b2 --- /dev/null +++ b/lib/megaco/src/engine/megaco_messenger.erl @@ -0,0 +1,5158 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. 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: Send and process a (sequence of) Megaco/H.248 transactions +%%---------------------------------------------------------------------- + +-module(megaco_messenger). + +%% Application internal export +-export([ + process_received_message/4, process_received_message/5, + receive_message/4, receive_message/5, + connect/4, connect/5, + disconnect/2, + encode_actions/3, + call/3, + cast/3, + cancel/2, + request_timeout/2, + request_keep_alive_timeout/2, + pending_timeout/3, + reply_timeout/3, + segment_timeout/3, + %% segment_reply_timeout/4, + + test_request/5, + test_reply/5 + ]). + +%% MIB stat functions +-export([ + get_stats/0, get_stats/1, get_stats/2, + reset_stats/0, reset_stats/1 + ]). + +%% Misc functions +-export([ + cleanup/2, + which_requests/1, which_replies/1 + ]). + +%% Module internal export +-export([ + process_received_message/6, + handle_request/2, + handle_long_request/2, + connect_remote/3, + disconnect_local/2, + disconnect_remote/3, + send_request_remote/4, + receive_reply_remote/2, receive_reply_remote/3 + ]). + +-include_lib("megaco/include/megaco.hrl"). +-include("megaco_message_internal.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + +%% N.B. Update cancel/1 with '_' when a new field is added +-record(request, + {trans_id, + remote_mid, + timer_ref, % {short, Ref} | {long, Ref} + init_timer, + init_long_timer, + curr_timer, + version, + bytes, % {send, Data} | {no_send, Data}, Data = binary() | tuple() + send_handle, + user_mod, + user_args, + reply_action, % call | cast + reply_data, + seg_recv = [], % [integer()] (received segments) + init_seg_timer, + seg_timer_ref, + keep_alive_timer, % plain | integer() >= 0 + keep_alive_ref % undefined | ref() + }). + + +%% N.B. Update cancel/1 with '_' when a new field is added +-record(reply, + { + trans_id, + local_mid, + state = prepare, % prepare | eval_request | waiting_for_ack | aborted + pending_timer_ref, + handler = undefined, % pid of the proc executing the callback func + timer_ref, + version, + %% bytes: Sent reply data: not acknowledged + bytes, % binary() | [{integer(), binary(), timer_ref()}] + ack_action, % discard_ack | {handle_ack, Data} + send_handle, + %% segments: Not sent reply data (segments) + segments = [] % [{integer(), binary()}] + }). + +-record(trans_id, + { + mid, + serial + }). + + +-ifdef(MEGACO_TEST_CODE). +-define(SIM(Other,Where), + fun(Afun,Bfun) -> + Kfun = {?MODULE,Bfun}, + case (catch ets:lookup(megaco_test_data, Kfun)) of + [{Kfun,Cfun}] -> + Cfun(Afun); + _ -> + Afun + end + end(Other,Where)). +-define(TC_AWAIT_CANCEL_EVENT(), + case megaco_tc_controller:lookup(block_on_cancel) of + {value, {Tag, Pid}} when is_pid(Pid) -> + Pid ! {Tag, self()}, + receive + {Tag, Pid} -> + ok + end; + {value, {sleep, To}} when is_integer(To) andalso (To > 0) -> + receive after To -> ok end; + _ -> + ok + end). +-define(TC_AWAIT_REPLY_EVENT(Info), + case megaco_tc_controller:lookup(block_on_reply) of + {value, {Tag, Pid}} when is_pid(Pid) -> + Pid ! {Tag, self(), Info}, + receive + {Tag, Pid} -> + ok + end; + _Whatever -> + %% io:format("Whatever: ~p~n", [Whatever]), + ok + end). +-else. +-define(SIM(Other,Where),Other). +-define(TC_AWAIT_CANCEL_EVENT(),ok). +-define(TC_AWAIT_REPLY_EVENT(_),ok). +-endif. + + +-define(report_pending_limit_exceeded(ConnData), + ?report_important(ConnData, "<ERROR> pending limit exceeded", [])). + +-ifdef(megaco_extended_trace). +-define(rt1(T,F,A),?report_trace(T,F,A)). +-define(rt2(F,A), ?rt1(ignore,F,A)). +-define(rt3(F), ?rt2(F,[])). +-else. +-define(rt1(T,F,A),ok). +-define(rt2(F,A), ok). +-define(rt3(F), ok). +-endif. + + +%%---------------------------------------------------------------------- +%% SNMP statistics handling functions +%%---------------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: get_stats/0, get_stats/1, get_stats/2 +%% Description: Retreive statistics (counters) for TCP +%%----------------------------------------------------------------- + +get_stats() -> + megaco_stats:get_stats(megaco_stats). + +get_stats(ConnHandleOrCounter) -> + megaco_stats:get_stats(megaco_stats, ConnHandleOrCounter). + +get_stats(ConnHandle, Counter) -> + megaco_stats:get_stats(megaco_stats, ConnHandle, Counter). + + +%%----------------------------------------------------------------- +%% Func: reset_stats/0, reaet_stats/1 +%% Description: Reset statistics (counters) +%%----------------------------------------------------------------- + +reset_stats() -> + megaco_stats:reset_stats(megaco_stats). + +reset_stats(ConnHandleOrCounter) -> + megaco_stats:reset_stats(megaco_stats, ConnHandleOrCounter). + + + +%%---------------------------------------------------------------------- +%% cleanup utility functions +%%---------------------------------------------------------------------- + +cleanup(#megaco_conn_handle{local_mid = LocalMid}, Force) + when (Force =:= true) orelse (Force =:= false) -> + Pat = #reply{trans_id = '$1', + local_mid = LocalMid, + state = '$2', + _ = '_'}, + do_cleanup(Pat, Force); +cleanup(LocalMid, Force) + when (Force =:= true) orelse (Force =:= false) -> + Pat = #reply{trans_id = '$1', + local_mid = LocalMid, + state = '$2', + _ = '_'}, + do_cleanup(Pat, Force). + +do_cleanup(Pat, Force) -> + Match = megaco_monitor:which_replies(Pat), + Reps = [{V1, V2} || [V1, V2] <- Match], + do_cleanup2(Reps, Force). + +do_cleanup2([], _) -> + ok; +do_cleanup2([{TransId, aborted}|T], Force = false) -> + megaco_monitor:delete_reply(TransId), + do_cleanup2(T, Force); +do_cleanup2([_|T], Force = false) -> + do_cleanup2(T, Force); +do_cleanup2([{TransId, _State}|T], Force = true) -> + megaco_monitor:delete_reply(TransId), + do_cleanup2(T, Force). + + +%%---------------------------------------------------------------------- +%% which_requests and which_replies utility functions +%%---------------------------------------------------------------------- + +which_requests(#megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}) -> + Pat1 = #trans_id{mid = LocalMid, + serial = '$1', _ = '_'}, + Pat2 = #request{trans_id = Pat1, + remote_mid = RemoteMid, + _ = '_'}, + Match = megaco_monitor:which_requests(Pat2), + [S || [S] <- Match]; +which_requests(LocalMid) -> + Pat1 = #trans_id{mid = LocalMid, + serial = '$1', _ = '_'}, + Pat2 = #request{trans_id = Pat1, + remote_mid = '$2', _ = '_'}, + Match0 = megaco_monitor:which_requests(Pat2), + Match1 = [{mk_ch(LocalMid, V2), V1} || [V1, V2] <- Match0], + which_requests1(lists:sort(Match1)). + +which_requests1([]) -> + []; +which_requests1([{CH, S}|T]) -> + which_requests2(T, CH, [S], []). + +which_requests2([], CH, Serials, Reqs) -> + lists:reverse([{CH, Serials}|Reqs]); +which_requests2([{CH, S}|T], CH, Serials, Reqs) -> + which_requests2(T, CH, [S|Serials], Reqs); +which_requests2([{CH1, S}|T], CH2, Serials, Reqs) -> + which_requests2(T, CH1, [S], [{CH2, lists:reverse(Serials)}| Reqs]). + + +which_replies(#megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}) -> + Pat1 = #trans_id{mid = RemoteMid, + serial = '$1', _ = '_'}, + Pat2 = #reply{trans_id = Pat1, + local_mid = LocalMid, + state = '$2', + handler = '$3', _ = '_'}, + Match = megaco_monitor:which_replies(Pat2), + [{V1, V2, V3} || [V1, V2, V3] <- Match]; +which_replies(LocalMid) -> + Pat1 = #trans_id{mid = '$1', + serial = '$2', _ = '_'}, + Pat2 = #reply{trans_id = Pat1, + local_mid = LocalMid, + state = '$3', + handler = '$4', _ = '_'}, + Match0 = megaco_monitor:which_replies(Pat2), + Match1 = [{mk_ch(LocalMid,V1),{V2,V3,V4}} || [V1, V2, V3, V4] <- Match0], + which_replies1(lists:sort(Match1)). + +which_replies1([]) -> + []; +which_replies1([{CH, Data}|T]) -> + which_replies2(T, CH, [Data], []). + +which_replies2([], CH, Data, Reps) -> + lists:reverse([{CH, Data}|Reps]); +which_replies2([{CH, Data}|T], CH, Datas, Reps) -> + which_replies2(T, CH, [Data|Datas], Reps); +which_replies2([{CH1, Data}|T], CH2, Datas, Reps) -> + which_replies2(T, CH1, [Data], [{CH2, lists:reverse(Datas)}| Reps]). + + +mk_ch(LM, RM) -> + #megaco_conn_handle{local_mid = LM, remote_mid = RM}. + + +%%---------------------------------------------------------------------- +%% Register/unreister connections +%%---------------------------------------------------------------------- + +%% Returns {ok, ConnHandle} | {error, Reason} +autoconnect(RH, RemoteMid, SendHandle, ControlPid, Extra) + when is_record(RH, megaco_receive_handle) -> + ?rt2("autoconnect", [RH, RemoteMid, SendHandle, ControlPid]), + case megaco_config:autoconnect(RH, RemoteMid, SendHandle, ControlPid) of + {ok, ConnData} -> + do_connect(ConnData, Extra); + {error, Reason} -> + {error, Reason} + end; +autoconnect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) -> + {error, {bad_receive_handle, BadHandle}}. + +connect(RH, RemoteMid, SendHandle, ControlPid) -> + Extra = ?default_user_callback_extra, + connect(RH, RemoteMid, SendHandle, ControlPid, Extra). +connect(RH, RemoteMid, SendHandle, ControlPid, Extra) + when is_record(RH, megaco_receive_handle) -> + ?rt2("connect", [RH, RemoteMid, SendHandle, ControlPid, Extra]), + + %% The purpose of this is to have a temoporary process, to + %% which one can set up a monitor or link and get a + %% notification when process exits. The entire connect is + %% done in the temporary worker process. + %% When it exits, the connect is either successfully done + %% or it failed. + + ConnectorFun = + fun() -> + + ConnectResult = + case megaco_config:connect(RH, RemoteMid, + SendHandle, ControlPid) of + {ok, ConnData} -> + do_connect(ConnData, Extra); + {error, Reason} -> + {error, Reason} + end, + ?rt2("connector: connected", [self(), ConnectResult]), + exit({result, ConnectResult}) + end, + Flag = process_flag(trap_exit, true), + Connector = erlang:spawn_link(ConnectorFun), + receive + {'EXIT', Connector, {result, ConnectResult}} -> + ?rt2("connect result: received expected connector exit signal", + [Connector, ConnectResult]), + process_flag(trap_exit, Flag), + ConnectResult; + {'EXIT', Connector, OtherReason} -> + ?rt2("connect exit: received unexpected connector exit signal", + [Connector, OtherReason]), + process_flag(trap_exit, Flag), + {error, OtherReason} + end; +connect(BadHandle, _CH, _SendHandle, _ControlPid, _Extra) -> + {error, {bad_receive_handle, BadHandle}}. + +do_connect(CD, Extra) -> + CH = CD#conn_data.conn_handle, + Version = CD#conn_data.protocol_version, + UserMod = CD#conn_data.user_mod, + UserArgs = CD#conn_data.user_args, + Args = + case Extra of + ?default_user_callback_extra -> + [CH, Version | UserArgs]; + _ -> + [CH, Version, Extra | UserArgs] + end, + ?report_trace(CD, "callback: connect", [Args]), + Res = (catch apply(UserMod, handle_connect, Args)), + ?report_debug(CD, "return: connect", [{return, Res}]), + case Res of + ok -> + ?SIM(ok, do_connect), % do_encode), + monitor_process(CH, CD#conn_data.control_pid); + error -> + megaco_config:disconnect(CH), + {error, {connection_refused, CD, error}}; + {error, ED} when is_record(ED,'ErrorDescriptor') -> + megaco_config:disconnect(CH), + {error, {connection_refused, CD, ED}}; + _Error -> + warning_msg("connect callback failed: ~w", [Res]), + megaco_config:disconnect(CH), + {error, {connection_refused, CD, Res}} + end. + +finish_connect(#conn_data{control_pid = ControlPid} = CD) + when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) -> + ?rt1(CD, "finish local connect", [ControlPid]), + do_finish_connect(CD); +finish_connect(#conn_data{conn_handle = CH, + control_pid = ControlPid} = CD) + when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) -> + ?rt1(CD, "finish remote connect", [ControlPid]), + RemoteNode = node(ControlPid), + UserMonitorPid = whereis(megaco_monitor), + Args = [CH, ControlPid, UserMonitorPid], + case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of + {ok, ControlMonitorPid} -> + do_finish_connect(CD#conn_data{control_pid = ControlMonitorPid}); + {error, Reason} -> + disconnect(CH, {connect_remote, Reason}), + {error, Reason}; + {badrpc, Reason} -> + Reason2 = {'EXIT', Reason}, + disconnect(CH, {connect_remote, Reason2}), + {error, Reason2} + end. + +do_finish_connect(#conn_data{conn_handle = CH, + send_handle = SendHandle, + control_pid = ControlPid} = CD) -> + M = ?MODULE, + F = disconnect_local, + A = [CH], + MFA = {M, F, A}, + case megaco_config:finish_connect(CH, SendHandle, ControlPid, MFA) of + {ok, Ref} -> + {ok, CD#conn_data{monitor_ref = Ref}}; + {error, Reason} -> + {error, {config_update, Reason}} + end. + + +monitor_process(CH, ControlPid) + when is_pid(ControlPid) andalso (node(ControlPid) =:= node()) -> + M = ?MODULE, + F = disconnect_local, + A = [CH], + Ref = megaco_monitor:apply_at_exit(M, F, A, ControlPid), + case megaco_config:update_conn_info(CH, monitor_ref, Ref) of + ok -> + ?SIM({ok, CH}, monitor_process_local); + {error, Reason} -> + disconnect(CH, {config_update, Reason}), + {error, Reason} + end; +monitor_process(CH, ControlPid) + when is_pid(ControlPid) andalso (node(ControlPid) =/= node()) -> + RemoteNode = node(ControlPid), + UserMonitorPid = whereis(megaco_monitor), + Args = [CH, ControlPid, UserMonitorPid], + case rpc:call(RemoteNode, ?MODULE, connect_remote, Args) of + {ok, ControlMonitorPid} -> + M = ?MODULE, + F = disconnect_local, + A = [CH], + Ref = megaco_monitor:apply_at_exit(M, F, A, ControlMonitorPid), + case megaco_config:update_conn_info(CH, monitor_ref, Ref) of + ok -> + ?SIM({ok, CH}, monitor_process_remote); + {error, Reason} -> + disconnect(CH, {config_update, Reason}), + {error, Reason} + end; + {error, Reason} -> + disconnect(CH, {connect_remote, Reason}), + {error, Reason}; + {badrpc, Reason} -> + Reason2 = {'EXIT', Reason}, + disconnect(CH, {connect_remote, Reason2}), + {error, Reason2} + end; +monitor_process(CH, undefined = _ControlPid) -> + %% We have to do this later (setting up the monitor), + %% when the first message arrives. The 'connected' atom is + %% the indication for the first arriving message to finish + %% the connect. + %% This may be the case when an MGC performs a pre-connect + %% in order to speed up the handling of an (expected) connecting + %% MG. + case megaco_config:update_conn_info(CH, monitor_ref, connected) of + ok -> + ?SIM({ok, CH}, monitor_process_local); + {error, Reason} -> + disconnect(CH, {config_update, Reason}), + {error, Reason} + end. + +connect_remote(CH, ControlPid, UserMonitorPid) + when node(ControlPid) =:= node() andalso node(UserMonitorPid) =/= node() -> + case megaco_config:lookup_local_conn(CH) of + [_ConnData] -> + UserNode = node(UserMonitorPid), + M = ?MODULE, + F = disconnect_remote, + A = [CH, UserNode], + Ref = megaco_monitor:apply_at_exit(M, F, A, UserMonitorPid), + case megaco_config:connect_remote(CH, UserNode, Ref) of + ok -> + ControlMonitorPid = whereis(megaco_monitor), + ?SIM({ok, ControlMonitorPid}, connect_remote); + {error, Reason} -> + {error, Reason} + end; + [] -> + {error, {no_connection, CH}} + end. + +cancel_apply_at_exit({connecting, _ConnectorPid}) -> + ok; +cancel_apply_at_exit(connected) -> + ok; +cancel_apply_at_exit(ControlRef) -> + megaco_monitor:cancel_apply_at_exit(ControlRef). + +node_of_control_pid(Pid) when is_pid(Pid) -> + node(Pid); +node_of_control_pid(_) -> + node(). + +disconnect(ConnHandle, DiscoReason) + when is_record(ConnHandle, megaco_conn_handle) -> + case megaco_config:disconnect(ConnHandle) of + {ok, ConnData, RemoteConnData} -> + ControlRef = ConnData#conn_data.monitor_ref, + cancel_apply_at_exit(ControlRef), + handle_disconnect_callback(ConnData, DiscoReason), + ControlNode = node_of_control_pid(ConnData#conn_data.control_pid), + case ControlNode =:= node() of + true -> + %% Propagate to remote users + CancelFun = + fun(RCD) -> + UserRef = RCD#remote_conn_data.monitor_ref, + cancel_apply_at_exit(UserRef), + RCD#remote_conn_data.user_node + end, + Nodes = lists:map(CancelFun, RemoteConnData), + %% io:format("NODES: ~p~n", [Nodes]), + M = ?MODULE, + F = disconnect, + A = [ConnHandle, DiscoReason], + case rpc:multicall(Nodes, M, F, A) of + {Res, []} -> + Check = fun(ok) -> false; + ({error, {no_connection, _CH}}) -> false; + (_) -> true + end, + case lists:filter(Check, Res) of + [] -> + ok; + Bad -> + {error, {remote_disconnect_error, ConnHandle, Bad}} + end; + {_Res, Bad} -> + {error, {remote_disconnect_crash, ConnHandle, Bad}} + end; + false when (RemoteConnData =:= []) -> + %% Propagate to remote control node + M = ?MODULE, + F = disconnect_remote, + A = [DiscoReason, ConnHandle, node()], + case rpc:call(ControlNode, M, F, A) of + {badrpc, Reason} -> + {error, {'EXIT', Reason}}; + Other -> + Other + end + end; + {error, Reason} -> + {error, Reason} + end; +disconnect(BadHandle, Reason) -> + {error, {bad_conn_handle, BadHandle, Reason}}. + +disconnect_local(Reason, ConnHandle) -> + disconnect(ConnHandle, {no_controlling_process, Reason}). + +disconnect_remote(_Reason, ConnHandle, UserNode) -> + case megaco_config:disconnect_remote(ConnHandle, UserNode) of + [RCD] -> + Ref = RCD#remote_conn_data.monitor_ref, + cancel_apply_at_exit(Ref), + ok; + [] -> + {error, {no_connection, ConnHandle}} + end. + + +%%---------------------------------------------------------------------- +%% Handle incoming message +%%---------------------------------------------------------------------- + +receive_message(ReceiveHandle, ControlPid, SendHandle, Bin) -> + Extra = ?default_user_callback_extra, + receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra). + +receive_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) -> + Opts = [link , {min_heap_size, 5000}], + spawn_opt(?MODULE, + process_received_message, + [ReceiveHandle, ControlPid, SendHandle, Bin, self(), Extra], Opts), + ok. + +%% This function is called via the spawn_opt function with the link +%% option, therefor the unlink before the exit. +process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Receiver, + Extra) -> + process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra), + unlink(Receiver), + exit(normal). + +process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin) -> + Extra = ?default_user_callback_extra, + process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra). + +process_received_message(ReceiveHandle, ControlPid, SendHandle, Bin, Extra) -> + Flag = process_flag(trap_exit, true), + case prepare_message(ReceiveHandle, SendHandle, Bin, ControlPid, Extra) of + {ok, ConnData, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') -> + ?rt1(ConnData, "message prepared", [MegaMsg]), + Mess = MegaMsg#'MegacoMessage'.mess, + case Mess#'Message'.messageBody of + {transactions, Transactions} -> + {AckList, ReqList} = + prepare_trans(ConnData, Transactions, [], [], Extra), + handle_acks(AckList, Extra), + case ReqList of + [] -> + ?rt3("no transaction requests"), + ignore; + [Req|Reqs] when (ConnData#conn_data.threaded =:= true) -> + ?rt3("handle requests (spawned)"), + lists:foreach( + fun(R) -> + spawn(?MODULE, handle_request, [R, Extra]) + end, + Reqs), + handle_request(Req, Extra); + _ -> + ?rt3("handle requests"), + case handle_requests(ReqList, [], Extra) of + [] -> + ignore; + [LongRequest | More] -> + lists:foreach( + fun(LR) -> + spawn(?MODULE, handle_long_request, [LR, Extra]) + end, + More), + handle_long_request(LongRequest, Extra) + end + end; + {messageError, Error} -> + handle_message_error(ConnData, Error, Extra) + end; + {silent_fail, ConnData, {_Code, Reason, Error}} -> + ?report_debug(ConnData, Reason, [no_reply, Error]), + ignore; + {verbose_fail, ConnData, {Code, Reason, Error}} -> + ?report_debug(ConnData, Reason, [Error]), + send_message_error(ConnData, Code, Reason) + end, + process_flag(trap_exit, Flag), + ok. + +prepare_message(RH, SH, Bin, Pid, Extra) + when is_record(RH, megaco_receive_handle) andalso is_pid(Pid) -> + ?report_trace(RH, "receive bytes", [{bytes, Bin}]), + EncodingMod = RH#megaco_receive_handle.encoding_mod, + EncodingConfig = RH#megaco_receive_handle.encoding_config, + ProtVersion = RH#megaco_receive_handle.protocol_version, + case (catch EncodingMod:decode_message(EncodingConfig, ProtVersion, Bin)) of + {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') -> + ?report_trace(RH, "receive message", [{message, MegaMsg}]), + Mess = MegaMsg#'MegacoMessage'.mess, + RemoteMid = Mess#'Message'.mId, + Version = Mess#'Message'.version, + LocalMid = RH#megaco_receive_handle.local_mid, + CH = #megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}, + case megaco_config:lookup_local_conn(CH) of + + %% + %% Message is not of the negotiated version + %% + + [#conn_data{protocol_version = NegVersion, + strict_version = true} = ConnData] + when NegVersion =/= Version -> + %% Use already established connection, + %% but incorrect version + ?rt1(ConnData, "not negotiated version", [Version]), + Error = {error, {not_negotiated_version, + NegVersion, Version}}, + handle_syntax_error_callback(RH, ConnData, + prepare_error(Error), + Extra); + + + [ConnData] -> + + %% + %% Use an already established connection + %% + %% This *may* have been set up in the + %% "non-official" way, so we may need to + %% create the monitor to the control process + %% and store the SendHandle (which is normally + %% done when creating the "temporary" connection). + %% + + ?rt1(ConnData, "use already established connection", []), + ConnData2 = ConnData#conn_data{send_handle = SH, + control_pid = Pid, + protocol_version = Version}, + check_message_auth(CH, ConnData2, MegaMsg, Bin); + + [] -> + %% Setup a temporary connection + ?rt3("setup a temporary connection"), + case autoconnect(RH, RemoteMid, SH, Pid, Extra) of + {ok, _} -> + do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin); + {error, {already_connected, _ConnHandle}} -> + do_prepare_message(RH, CH, SH, MegaMsg, Pid, Bin); + {error, {connection_refused, ConnData, Reason}} -> + Error = prepare_error({error, {connection_refused, Reason}}), + {verbose_fail, ConnData, Error}; + {error, Reason} -> + ConnData = fake_conn_data(RH, RemoteMid, SH, Pid), + ConnData2 = ConnData#conn_data{protocol_version = Version}, + Error = prepare_error({error, Reason}), + {verbose_fail, ConnData2, Error} + end + end; + Error -> + ?rt2("decode error", [Error]), + ConnData = handle_decode_error(Error, + RH, SH, Bin, Pid, + EncodingMod, + EncodingConfig, + ProtVersion), + handle_syntax_error_callback(RH, ConnData, prepare_error(Error), Extra) + end; +prepare_message(RH, SendHandle, _Bin, ControlPid, _Extra) -> + ConnData = fake_conn_data(RH, SendHandle, ControlPid), + Error = prepare_error({'EXIT', {bad_receive_handle, RH}}), + {verbose_fail, ConnData, Error}. + + +handle_decode_error({error, {unsupported_version, _}}, + #megaco_receive_handle{local_mid = LocalMid} = RH, SH, + Bin, Pid, + EM, EC, V) -> + case (catch EM:decode_mini_message(EC, V, Bin)) of + {ok, #'MegacoMessage'{mess = #'Message'{version = _Ver, + mId = RemoteMid}}} -> + ?rt2("erroneous message received", [SH, RemoteMid, _Ver]), + CH = #megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}, + incNumErrors(CH), + %% We cannot put the version into conn-data, that will + %% make the resulting error message impossible to sent + %% (unsupported version) + case megaco_config:lookup_local_conn(CH) of + [ConnData] -> + ?rt3("known to us"), + ConnData#conn_data{send_handle = SH}; + [] -> + ?rt3("unknown to us"), + ConnData = fake_conn_data(RH, SH, Pid), + ConnData#conn_data{conn_handle = CH} + end; + + _ -> + ?rt2("erroneous message received", [SH]), + incNumErrors(), + fake_conn_data(RH, SH, Pid) + end; + +handle_decode_error(_, + #megaco_receive_handle{local_mid = LocalMid} = RH, SH, + Bin, Pid, + EM, EC, V) -> + case (catch EM:decode_mini_message(EC, V, Bin)) of + {ok, #'MegacoMessage'{mess = #'Message'{version = Ver, + mId = RemoteMid}}} -> + ?rt2("erroneous message received", [SH, Ver, RemoteMid]), + CH = #megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}, + incNumErrors(CH), + case megaco_config:lookup_local_conn(CH) of + [ConnData] -> + ?rt3("known to us"), + ConnData#conn_data{send_handle = SH, + protocol_version = Ver}; + [] -> + ?rt3("unknown to us"), + ConnData = fake_conn_data(RH, SH, Pid), + ConnData#conn_data{conn_handle = CH, + protocol_version = Ver} + end; + + _ -> + ?rt2("erroneous message received", [SH]), + incNumErrors(), + fake_conn_data(RH, SH, Pid) + end. + + +do_prepare_message(RH, CH, SendHandle, MegaMsg, ControlPid, Bin) -> + case megaco_config:lookup_local_conn(CH) of + [ConnData] -> + case check_message_auth(CH, ConnData, MegaMsg, Bin) of + {ok, ConnData2, MegaMsg} -> + %% Let the connection be permanent + {ok, ConnData2, MegaMsg}; + {ReplyTag, ConnData, Reason} -> + %% Remove the temporary connection + disconnect(CH, {bad_auth, Reason}), + {ReplyTag, ConnData, Reason} + end; + [] -> + Reason = no_connection, + disconnect(CH, Reason), + RemoteMid = CH#megaco_conn_handle.remote_mid, + ConnData = fake_conn_data(RH, RemoteMid, SendHandle, ControlPid), + Error = prepare_error({error, Reason}), + {silent_fail, ConnData, Error} + end. + +check_message_auth(_ConnHandle, ConnData, MegaMsg, Bin) -> + MsgAuth = MegaMsg#'MegacoMessage'.authHeader, + Mess = MegaMsg#'MegacoMessage'.mess, + Version = Mess#'Message'.version, + ConnData2 = ConnData#conn_data{protocol_version = Version}, + ConnAuth = ConnData2#conn_data.auth_data, + ?report_trace(ConnData2, "check message auth", [{bytes, Bin}]), + if + (MsgAuth =:= asn1_NOVALUE) andalso (ConnAuth =:= asn1_NOVALUE) -> + ?SIM({ok, ConnData2, MegaMsg}, check_message_auth); + true -> + ED = #'ErrorDescriptor'{errorCode = ?megaco_unauthorized, + errorText = "Autentication is not supported"}, + {verbose_fail, ConnData2, prepare_error({error, ED})} + end. + +handle_syntax_error_callback(ReceiveHandle, ConnData, PrepError, Extra) -> + {Code, Reason, Error} = PrepError, + ErrorDesc = #'ErrorDescriptor'{errorCode = Code, errorText = Reason}, + Version = + case Error of + {error, {unsupported_version, UV}} -> + UV; + _ -> + ConnData#conn_data.protocol_version + end, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + ?report_trace(ReceiveHandle, "callback: syntax error", [ErrorDesc, Error]), + Args = + case Extra of + ?default_user_callback_extra -> + [ReceiveHandle, Version, ErrorDesc | UserArgs]; + _ -> + [ReceiveHandle, Version, ErrorDesc, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_syntax_error, Args)), + ?report_debug(ReceiveHandle, "return: syntax error", + [{return, Res}, ErrorDesc, Error]), + case Res of + reply -> + {verbose_fail, ConnData, PrepError}; + {reply,#'ErrorDescriptor'{errorCode = Code1, errorText = Reason1}} -> + {verbose_fail, ConnData, {Code1,Reason1,Error}}; + no_reply -> + {silent_fail, ConnData, PrepError}; + {no_reply,#'ErrorDescriptor'{errorCode=Code2,errorText=Reason2}} -> + {silent_fail, ConnData, {Code2,Reason2,Error}}; %%% OTP-???? + _ -> + warning_msg("syntax error callback failed: ~w", [Res]), + {verbose_fail, ConnData, PrepError} + end. + +fake_conn_data(CH) when is_record(CH, megaco_conn_handle) -> + case (catch megaco_config:conn_info(CH, receive_handle)) of + RH when is_record(RH, megaco_receive_handle) -> + RemoteMid = CH#megaco_conn_handle.remote_mid, + ConnData = + fake_conn_data(RH, RemoteMid, no_send_handle, no_control_pid), + ConnData#conn_data{conn_handle = CH}; + {'EXIT', _} -> + UserMid = CH#megaco_conn_handle.local_mid, + case catch megaco_config:user_info(UserMid, receive_handle) of + {'EXIT', _} -> % No such user + #conn_data{conn_handle = CH, + serial = undefined_serial, + control_pid = no_control_pid, + monitor_ref = undefined_monitor_ref, + send_mod = no_send_mod, + send_handle = no_send_handle, + encoding_mod = no_encoding_mod, + encoding_config = no_encoding_config, + reply_action = undefined, + sent_pending_limit = infinity, + recv_pending_limit = infinity}; + RH -> + ConnData = + fake_conn_data(RH, no_send_handle, no_control_pid), + ConnData#conn_data{conn_handle = CH} + end + end. + +fake_conn_data(RH, SendHandle, ControlPid) -> + fake_conn_data(RH, unknown_remote_mid, SendHandle, ControlPid). + +fake_conn_data(RH, RemoteMid, SendHandle, ControlPid) -> + case catch megaco_config:init_conn_data(RH, RemoteMid, SendHandle, ControlPid) of + {'EXIT', _} -> % No such user + fake_user_data(RH, RemoteMid, SendHandle, ControlPid); + ConnData -> + ConnData + end. + +fake_user_data(RH, RemoteMid, SendHandle, ControlPid) -> + LocalMid = RH#megaco_receive_handle.local_mid, + RH2 = RH#megaco_receive_handle{local_mid = default}, + case catch megaco_config:init_conn_data(RH2, RemoteMid, SendHandle, ControlPid) of + {'EXIT', _} -> % Application stopped? + ConnHandle = #megaco_conn_handle{local_mid = LocalMid, + remote_mid = RemoteMid}, + EncodingMod = RH#megaco_receive_handle.encoding_mod, + EncodingConfig = RH#megaco_receive_handle.encoding_config, + SendMod = RH#megaco_receive_handle.send_mod, + #conn_data{conn_handle = ConnHandle, + serial = undefined_serial, + control_pid = ControlPid, + monitor_ref = undefined_monitor_ref, + send_mod = SendMod, + send_handle = SendHandle, + encoding_mod = EncodingMod, + encoding_config = EncodingConfig, + reply_action = undefined, + sent_pending_limit = infinity, + recv_pending_limit = infinity}; + ConnData -> + ConnData + end. + +prepare_error(Error) -> + case Error of + {error, ED} when is_record(ED, 'ErrorDescriptor') -> + Code = ED#'ErrorDescriptor'.errorCode, + Reason = ED#'ErrorDescriptor'.errorText, + {Code, Reason, Error}; + {error, [{reason, {bad_token, [BadToken, _Acc]}, Line}]} when is_integer(Line) -> + Reason = + lists:flatten( + io_lib:format("Illegal token (~p) on line ~w", [BadToken, Line])), + Code = ?megaco_bad_request, + {Code, Reason, Error}; + {error, [{reason, {bad_token, _}, Line}]} when is_integer(Line) -> + Reason = lists:concat(["Illegal token on line ", Line]), + Code = ?megaco_bad_request, + {Code, Reason, Error}; + {error, [{reason, {Line, _ParserMod, RawReasonString}} | _]} when is_integer(Line) andalso is_list(RawReasonString) -> + Reason = + case RawReasonString of + [[$s, $y, $n, $t, $a, $x | _], TokenString] -> + lists:flatten( + io_lib:format("Syntax error on line ~w before token ~s", [Line, TokenString])); + _ -> + lists:flatten(io_lib:format("Syntax error on line ~w", [Line])) + end, + Code = ?megaco_bad_request, + {Code, Reason, Error}; + {error, [{reason, {Line, _, _}} | _]} when is_integer(Line) -> + Reason = lists:concat(["Syntax error on line ", Line]), + Code = ?megaco_bad_request, + {Code, Reason, Error}; + {error, {connection_refused, ED}} when is_record(ED,'ErrorDescriptor') -> + Code = ED#'ErrorDescriptor'.errorCode, + Reason = ED#'ErrorDescriptor'.errorText, + {Code, Reason, Error}; + {error, {connection_refused, _}} -> + Reason = "Connection refused by user", + Code = ?megaco_unauthorized, + {Code, Reason, Error}; + {error, {unsupported_version, V}} -> + Reason = + lists:flatten(io_lib:format("Unsupported version: ~w",[V])), + Code = ?megaco_version_not_supported, + {Code, Reason, Error}; + {error, {not_negotiated_version, NegV, MsgV}} -> + Reason = + lists:flatten( + io_lib:format("Not negotiated version: ~w [negotiated ~w]", + [MsgV, NegV])), + Code = ?megaco_version_not_supported, + {Code, Reason, Error}; + {error, _} -> + Reason = "Syntax error", + Code = ?megaco_bad_request, + {Code, Reason, Error}; + {ok, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') -> + Reason = "MID does not match config", + Code = ?megaco_incorrect_identifier, + {Code, Reason, Error}; + _ -> + Reason = "Fatal syntax error", + Code = ?megaco_internal_gateway_error, + {Code, Reason, Error} + end. + +prepare_trans(_ConnData, [], AckList, ReqList, _Extra) -> + ?SIM({AckList, ReqList}, prepare_trans_done); + +prepare_trans(ConnData, Trans, AckList, ReqList, Extra) + when ConnData#conn_data.monitor_ref =:= undefined_auto_monitor_ref -> + + ?rt3("prepare_trans - autoconnect"), + + %% <BUGBUG> + %% Do we need something here, if we send more then one + %% trans per message? + %% </BUGBUG> + + %% May occur if another process has already setup a + %% temporary connection, but the handle_connect callback + %% function has not yet returned before the eager MG + %% re-sends its initial service change message. + + prepare_autoconnecting_trans(ConnData, Trans, AckList, ReqList, Extra); + +prepare_trans(#conn_data{monitor_ref = connected} = ConnData, + Trans, AckList, ReqList, Extra) -> + + ?rt3("prepare_trans - connected"), + + %% + %% This will happen when the "MGC" user performs a "pre" connect, + %% instead of waiting for the auto-connect (which normally + %% happen when the MGC receives the first message from the + %% MG). + %% + + %% + %% The monitor_ref will have this value when the pre-connect + %% is complete, so we finish it here and then continue with the + %% normal transaction prepare. + %% + + case finish_connect(ConnData) of + {ok, CD} -> + prepare_normal_trans(CD, Trans, AckList, ReqList, Extra); + {error, Reason} -> + disconnect(ConnData#conn_data.conn_handle, Reason), + {[], []} + end; + +prepare_trans(#conn_data{monitor_ref = {connecting, _}} = _ConnData, + _Trans, _AckList, _ReqList, _Extra) -> + + ?rt3("prepare_trans - connecting"), + + %% + %% This will happen when the "MGC" user performs a "pre" connect, + %% instead of waiting for the auto-connect (which normally + %% happen when the MGC receives the first message from the + %% MG). + %% + + %% + %% The monitor_ref will have this value when the pre-connect + %% is in progress. We drop (ignore) this message and hope the + %% other side (MG) will resend. + %% + + %% prepare_connecting_trans(ConnData, Trans, AckList, ReqList, Extra); + {[], []}; + +prepare_trans(ConnData, Trans, AckList, ReqList, Extra) -> + + ?rt3("prepare_trans - normal"), + + %% Handle transaction in the normal case + + prepare_normal_trans(ConnData, Trans, AckList, ReqList, Extra). + + +prepare_autoconnecting_trans(_ConnData, [], AckList, ReqList, _Extra) -> + ?SIM({AckList, ReqList}, prepare_autoconnecting_trans_done); + +prepare_autoconnecting_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) -> + ?rt1(ConnData, "[autoconnecting] prepare trans", [Trans]), + case Trans of + {transactionRequest, T} when is_record(T, 'TransactionRequest') -> + + Serial = T#'TransactionRequest'.transactionId, + ConnData2 = ConnData#conn_data{serial = Serial}, + ?report_trace(ConnData2, "Pending handle_connect", [T]), + + %% ------------------------------------------ + %% + %% Check pending limit + %% + %% ------------------------------------------ + + Limit = ConnData#conn_data.sent_pending_limit, + TransId = to_remote_trans_id(ConnData2), + case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of + ok -> + send_pending(ConnData2); + error -> + %% Pending limit: + %% In this (granted, highly hypothetical case) + %% we would make the user very confused if we + %% called the abort callback function, since + %% the request callback function has not yet + %% been called. Alas, we skip this call here. + send_pending_limit_error(ConnData); + aborted -> + ignore + end, + prepare_autoconnecting_trans(ConnData2, Rest, AckList, ReqList, + Extra); + _ -> + prepare_autoconnecting_trans(ConnData, Rest, AckList, ReqList, + Extra) + end. + + +%% ================================================================= +%% +%% Note that the TransactionReply record was changed i v3 (two +%% new fields where added), and since we don't know which version, +%% we cannot use the record definition of TransactionReply. +%% Instead we transform the record into our own internal format +%% #megaco_transaction_reply{} +%% +%% ================================================================= + +prepare_normal_trans(_ConnData, [], AckList, ReqList, _Extra) -> + ?SIM({AckList, ReqList}, prepare_normal_trans_done); + +prepare_normal_trans(ConnData, [Trans | Rest], AckList, ReqList, Extra) -> + ?rt1(ConnData, "prepare [normal] trans", [Trans]), + case Trans of + {transactionRequest, #'TransactionRequest'{transactionId = asn1_NOVALUE}} -> + ConnData2 = ConnData#conn_data{serial = 0}, + Code = ?megaco_bad_request, + Reason = "Syntax error in message: transaction id missing", + send_trans_error(ConnData2, Code, Reason), + prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra); + {transactionRequest, T} when is_record(T, 'TransactionRequest') -> + Serial = T#'TransactionRequest'.transactionId, + ConnData2 = ConnData#conn_data{serial = Serial}, + prepare_request(ConnData2, T, Rest, AckList, ReqList, Extra); + {transactionPending, T} when is_record(T, 'TransactionPending') -> + Serial = T#'TransactionPending'.transactionId, + ConnData2 = ConnData#conn_data{serial = Serial}, + handle_pending(ConnData2, T, Extra), + prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra); + {transactionReply, T} when is_tuple(T) andalso + (element(1, T) == 'TransactionReply') -> + T2 = transform_transaction_reply_dec(T), + Serial = T2#megaco_transaction_reply.transactionId, + ConnData2 = ConnData#conn_data{serial = Serial}, + handle_reply(ConnData2, T2, Extra), + prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra); + {transactionResponseAck, List} when is_list(List) -> + prepare_ack(ConnData, List, Rest, AckList, ReqList, Extra); + {segmentReply, SR} when is_record(SR, 'SegmentReply') -> + handle_segment_reply(ConnData, SR, Extra), + prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra) + + end. + + +prepare_request(ConnData, T, Rest, AckList, ReqList, Extra) -> + ?rt2("prepare request", [T]), + ConnHandle = ConnData#conn_data.conn_handle, + LocalMid = ConnHandle#megaco_conn_handle.local_mid, + TransId = to_remote_trans_id(ConnData), + ?rt2("prepare request", [LocalMid, TransId]), + case megaco_monitor:lookup_reply(TransId) of + [] -> + ?rt3("brand new request"), + + %% Brand new request + + %% Check pending limit: + %% + %% We should actually check the pending limit here + %% but since we have to do it later in the + %% handle_request function (just before we call + %% the handle_trans_request callback function) we + %% can just as well wait (this is after all a very + %% unlikely case: see function prepare_trans when + %% monitor_ref == undefined_auto_monitor_ref). + %% + + #conn_data{send_handle = SendHandle, + pending_timer = InitTimer, + protocol_version = Version} = ConnData, + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + M = ?MODULE, + F = pending_timeout, + A = [ConnHandle, TransId, CurrTimer], + PendingRef = megaco_monitor:apply_after(M, F, A, WaitFor), + Rep = #reply{send_handle = SendHandle, + trans_id = TransId, + local_mid = LocalMid, + pending_timer_ref = PendingRef, + handler = self(), + version = Version}, + case megaco_monitor:insert_reply_new(Rep) of + true -> + prepare_normal_trans(ConnData, Rest, AckList, + [{ConnData, TransId, T} | ReqList], + Extra); + false -> + %% Oups - someone got there before we did... + ?report_debug(ConnData, + "prepare request: conflicting requests", + [TransId]), + send_pending(ConnData), + megaco_monitor:cancel_apply_after(PendingRef), + prepare_normal_trans(ConnData, Rest, AckList, ReqList, + Extra) + end; + + [#reply{state = State, + handler = Pid, + pending_timer_ref = Ref} = Rep] + when (State =:= prepare) orelse (State =:= eval_request) -> + + ?rt2("request resend", [State, Pid, Ref]), + + %% Pending limit: + %% We are still preparing/evaluating the request + %% Check if the pending limit has been exceeded... + %% If the pending limit is _not_ exceeded then + %% we shall send a pending (and actually restart + %% the pending timer, but that we cannot do). + %% Don't care about Msg and Rep version diff + + #conn_data{sent_pending_limit = Limit} = ConnData, + + case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of + ok -> + + %% ------------------------------------------ + %% + %% Pending limit not exceeded + %% + %% 1) Increment number of pendings sent + %% (done in the check function above) + %% 2) Send pending message + %% (We should really restart the pending + %% timer, but we have no way of doing that). + %% + %% ------------------------------------------ + + send_pending(ConnData), + prepare_normal_trans(ConnData, Rest, AckList, ReqList, + Extra); + + + error -> + + %% ------------------------------------------- + %% + %% Pending limit exceeded + %% + %% 1) Cancel pending timer + %% 2) Send 506 error message to other side + %% 3) Inform user (depends on state) + %% 4) Set reply in aborted state + %% + %% ------------------------------------------- + + %% + %% State == eval_request: + %% This means that the request is currently beeing + %% evaluated by the user, and the reply timer has + %% not yet been started. + %% Either: + %% a) The "other side" will resend (which will + %% trigger a pending message send) until we pass the + %% pending limit + %% b) We will send pending messages (when the pending + %% timer expire) until we pass the pending limit. + %% In any event, we cannot delete the reply record + %% or the pending counter in this case. Is there + %% a risk we accumulate aborted reply records? + %% + %% State == prepare: + %% The user does not know about this request + %% so we can safely perform cleanup. + %% + megaco_monitor:cancel_apply_after(Ref), + send_pending_limit_error(ConnData), + if + State == eval_request -> + %% + %% What if the user never replies? + %% In that case we will have a record + %% (and counters) that is never cleaned up... + NewFields = + [{#reply.state, aborted}, + {#reply.pending_timer_ref, undefined}], + megaco_monitor:update_reply_fields(TransId, + NewFields), + handle_request_abort_callback(ConnData, + TransId, Pid, Extra); + true -> + %% Since the user does not know about + %% this call yet, it is safe to cleanup. + %% Should we inform? + Rep2 = Rep#reply{state = aborted}, + cancel_reply(ConnData, Rep2, aborted), + ok + end, + prepare_normal_trans(ConnData, Rest, AckList, ReqList, + Extra); + + + aborted -> + + %% ------------------------------------------- + %% + %% Pending limit already exceeded + %% + %% Cleanup, just to make sure: + %% reply record & pending counter + %% + %% ------------------------------------------- + + Rep2 = Rep#reply{state = aborted}, + cancel_reply(ConnData, Rep2, aborted), + prepare_normal_trans(ConnData, Rest, AckList, ReqList, + Extra) + + end; + + [#reply{state = waiting_for_ack, + bytes = Bin, + version = Version} = Rep] -> + ?rt3("request resend when waiting for ack"), + + %% We have already sent a reply, but the receiver + %% has obviously not got it. Resend the reply but + %% don't restart the reply_timer. + ConnData2 = ConnData#conn_data{protocol_version = Version}, + ?report_trace(ConnData2, + "re-send trans reply", [T | {bytes, Bin}]), + case megaco_messenger_misc:send_message(ConnData2, true, Bin) of + {ok, _} -> + ok; + {error, Reason} -> + %% Pass it on to the user (via handle_ack) + cancel_reply(ConnData2, Rep, Reason) + end, + prepare_normal_trans(ConnData2, Rest, AckList, ReqList, Extra); + + [#reply{state = aborted} = Rep] -> + ?rt3("request resend when already in aborted state"), + + %% OTP-4956: + %% Already aborted so ignore. + %% This furthermore means that the abnoxious user at the + %% other end has already been informed (pending-limit + %% passed => error descriptor sent), but keeps sending... + %% + %% Shall we perform a cleanup? + cancel_reply(ConnData, Rep, aborted), + prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra) + end. + +prepare_ack(ConnData, [TA | T], Rest, AckList, ReqList, Extra) + when is_record(TA, 'TransactionAck') -> + First = TA#'TransactionAck'.firstAck, + Last = TA#'TransactionAck'.lastAck, + TA2 = TA#'TransactionAck'{lastAck = asn1_NOVALUE}, + ConnData2 = ConnData#conn_data{serial = First}, + AckList2 = do_prepare_ack(ConnData2, TA2, AckList), + if + Last =:= asn1_NOVALUE -> + prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra); + First < Last -> + TA3 = TA#'TransactionAck'{firstAck = First + 1}, + prepare_ack(ConnData, [TA3 | T], Rest, AckList2, ReqList, Extra); + First =:= Last -> + prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra); + First > Last -> + %% Protocol violation from the sender of this ack + ?report_important(ConnData, "<ERROR> discard trans", + [TA, {error, "firstAck > lastAck"}]), + prepare_ack(ConnData, T, Rest, AckList2, ReqList, Extra) + end; +prepare_ack(ConnData, [], Rest, AckList, ReqList, Extra) -> + prepare_normal_trans(ConnData, Rest, AckList, ReqList, Extra). + +do_prepare_ack(ConnData, T, AckList) -> + TransId = to_remote_trans_id(ConnData), + case megaco_monitor:lookup_reply(TransId) of + [] -> + %% The reply has already been garbage collected. Ignore. + ?report_trace(ConnData, "discard ack (no receiver)", [T]), + AckList; + [Rep] when Rep#reply.state =:= waiting_for_ack -> + %% Don't care about Msg and Rep version diff + [{ConnData, Rep, T} | AckList]; + [_Rep] -> + %% Protocol violation from the sender of this ack + ?report_important(ConnData, "<ERROR> discard trans", + [T, {error, "got ack before reply was sent"}]), + AckList + end. + + +increment_request_keep_alive_counter(#conn_data{conn_handle = CH}, TransId) -> + ?rt1(CH, "increment request keep alive counter", [TransId]), + megaco_config:incr_reply_counter(CH, TransId). + +create_or_maybe_increment_request_keep_alive_counter( + #conn_data{conn_handle = CH}, TransId) -> + ?rt1(CH, "create or maybe increment request keep alive counter", + [TransId]), + try + begin + megaco_config:cre_reply_counter(CH, TransId) + end + catch + _:_ -> + megaco_config:incr_reply_counter(CH, TransId) + end. + + +check_and_maybe_create_pending_limit(infinity, _, _) -> + ok; +check_and_maybe_create_pending_limit(Limit, Direction, TransId) -> + ?rt2("check and maybe create pending limit counter", + [Limit, Direction, TransId]), + try megaco_config:get_pending_counter(Direction, TransId) of + Val when Val =< Limit -> + %% Since we have no intention to increment here, it + %% is ok to be _at_ the limit + ok; + _ -> + aborted + catch + _:_ -> + %% Has not been created yet (connect). + megaco_config:cre_pending_counter(Direction, TransId, 0), + ok + end. + +%% check_and_maybe_create_pending_limit(infinity, _, _) -> +%% ok; +%% check_and_maybe_create_pending_limit(Limit, Direction, TransId) -> +%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of +%% {'EXIT', _} -> +%% %% Has not been created yet (connect). +%% megaco_config:cre_pending_counter(Direction, TransId, 0), +%% ok; +%% Val when Val =< Limit -> +%% %% Since we have no intention to increment here, it +%% %% is ok to be _at_ the limit +%% ok; +%% _ -> +%% aborted +%% end. + + +check_pending_limit(infinity, _, _) -> + {ok, 0}; +check_pending_limit(Limit, Direction, TransId) -> + ?rt2("check pending limit", [Direction, Limit, TransId]), + try megaco_config:get_pending_counter(Direction, TransId) of + Val when Val =< Limit -> + %% Since we have no intention to increment here, it + %% is ok to be _at_ the limit + ?rt2("check pending limit - ok", [Val]), + {ok, Val}; + _Val -> + ?rt2("check pending limit - aborted", [_Val]), + aborted + catch + _:_ -> + %% This function is only called when we "know" the + %% counter to exist. So, the only reason that this + %% would happen is of the counter has been removed. + %% This only happen if the pending limit has been + %% reached. In any case, this is basically the same + %% as aborted! + ?rt2("check pending limit - exit", []), + aborted + end. + +%% check_pending_limit(infinity, _, _) -> +%% {ok, 0}; +%% check_pending_limit(Limit, Direction, TransId) -> +%% ?rt2("check pending limit", [Direction, Limit, TransId]), +%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of +%% {'EXIT', _} -> +%% %% This function is only called when we "know" the +%% %% counter to exist. So, the only reason that this +%% %% would happen is of the counter has been removed. +%% %% This only happen if the pending limit has been +%% %% reached. In any case, this is basically the same +%% %% as aborted! +%% ?rt2("check pending limit - exit", []), +%% aborted; +%% Val when Val =< Limit -> +%% %% Since we have no intention to increment here, it +%% %% is ok to be _at_ the limit +%% ?rt2("check pending limit - ok", [Val]), +%% {ok, Val}; +%% _Val -> +%% ?rt2("check pending limit - aborted", [_Val]), +%% aborted +%% end. + + +check_and_maybe_incr_pending_limit(infinity, _, _) -> + ok; +check_and_maybe_incr_pending_limit(Limit, Direction, TransId) -> + %% + %% We need this kind of test to detect when we _pass_ the limit + %% + ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]), + try megaco_config:get_pending_counter(Direction, TransId) of + Val when Val > Limit -> + ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]), + aborted; % Already passed the limit + Val -> + ?rt2("check and maybe incr - incr", [Direction, Val, Limit]), + megaco_config:incr_pending_counter(Direction, TransId), + if + Val < Limit -> + ok; % Still within the limit + true -> + ?rt2("check and maybe incr - error", + [Direction, Val, Limit]), + error % Passed the limit + end + catch + _:_ -> + %% Has not been created yet (connect). + megaco_config:cre_pending_counter(Direction, TransId, 1), + ok + end. + + +%% check_and_maybe_incr_pending_limit(infinity, _, _) -> +%% ok; +%% check_and_maybe_incr_pending_limit(Limit, Direction, TransId) -> +%% %% +%% %% We need this kind of test to detect when we _pass_ the limit +%% %% +%% ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]), +%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of +%% {'EXIT', _} -> +%% %% Has not been created yet (connect). +%% megaco_config:cre_pending_counter(Direction, TransId, 1), +%% ok; +%% Val when Val > Limit -> +%% ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]), +%% aborted; % Already passed the limit +%% Val -> +%% ?rt2("check and maybe incr - incr", [Direction, Val, Limit]), +%% megaco_config:incr_pending_counter(Direction, TransId), +%% if +%% Val < Limit -> +%% ok; % Still within the limit +%% true -> +%% ?rt2("check and maybe incr - error", +%% [Direction, Val, Limit]), +%% error % Passed the limit +%% end +%% end. + + +%% BUGBUG BUGBUG BUGBUG +%% +%% Do we know that the Rep is still valid? A previous transaction +%% could have taken a lot of time. +%% +handle_request({ConnData, TransId, T}, Extra) -> + case handle_request(ConnData, TransId, T, Extra) of + {pending, _RequestData} -> + handle_long_request(ConnData, TransId, T, Extra); + Else -> + Else + end. + +handle_request(ConnData, TransId, T, Extra) -> + ?report_trace(ConnData, "handle request", [TransId, T]), + + %% Pending limit: + %% Ok, before we begin, lets check that this request + %% has not been aborted. I.e. exceeded the pending + %% limit, so go check it... + + #conn_data{sent_pending_limit = Limit} = ConnData, + + case check_and_maybe_create_pending_limit(Limit, sent, TransId) of + ok -> + %% Ok so far, now update state + case megaco_monitor:update_reply_field(TransId, + #reply.state, + eval_request) of + true -> + Actions = T#'TransactionRequest'.actions, + {AckAction, SendReply} = + handle_request_callback(ConnData, TransId, Actions, + T, Extra), + + %% Next step, while we where in the callback function, + %% the pending limit could have been exceeded, so check + %% it again... + do_handle_request(AckAction, SendReply, + ConnData, TransId); + + false -> + %% Ugh? + ignore + end; + + aborted -> + %% Pending limit + %% Already exceeded the limit + %% The user does not yet know about this request, so + %% don't bother telling that it has been aborted... + %% Furthermore, the reply timer has not been started, + %% so do the cleanup now + ?rt1(ConnData, "pending limit already passed", [TransId]), + case megaco_monitor:lookup_reply(TransId) of + [Rep] -> + cancel_reply(ConnData, Rep, aborted); + _ -> + ok + end, + ignore + end. + +do_handle_request(_, ignore, _ConnData, _TransId) -> + ?rt1(_ConnData, "ignore: don't reply", [_TransId]), + ignore; +do_handle_request(_, ignore_trans_request, ConnData, TransId) -> + ?rt1(ConnData, "ignore trans request: don't reply", [TransId]), + case megaco_monitor:lookup_reply(TransId) of + [#reply{} = Rep] -> + cancel_reply(ConnData, Rep, ignore); + _ -> + ignore + end; +do_handle_request({pending, _RequestData}, {aborted, ignore}, _, _) -> + ?rt2("handle request: pending - aborted - ignore => don't reply", []), + ignore; +do_handle_request({pending, _RequestData}, {aborted, _SendReply}, _, _) -> + ?rt2("handle request: pending - aborted => don't reply", []), + ignore; +do_handle_request({pending, RequestData}, _SendReply, _ConnData, _) -> + ?rt2("handle request: pending", [RequestData]), + {pending, RequestData}; +do_handle_request(AckAction, {ok, Bin}, ConnData, TransId) + when is_binary(Bin) -> + ?rt1(ConnData, "handle request - ok", [AckAction, TransId]), + case megaco_monitor:lookup_reply(TransId) of + [#reply{pending_timer_ref = PendingRef} = Rep] -> + + #conn_data{reply_timer = InitTimer, + conn_handle = ConnHandle} = ConnData, + + %% Pending limit update: + %% - Cancel the pending timer, if running + %% - Delete the pending counter + %% + + megaco_monitor:cancel_apply_after(PendingRef), + megaco_config:del_pending_counter(sent, TransId), + + Method = timer_method(AckAction), + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + OptBin = opt_garb_binary(CurrTimer, Bin), + M = ?MODULE, + F = reply_timeout, + A = [ConnHandle, TransId, CurrTimer], + Ref = megaco_monitor:apply_after(Method, M, F, A, + WaitFor), + Rep2 = Rep#reply{pending_timer_ref = undefined, + handler = undefined, + bytes = OptBin, + state = waiting_for_ack, + timer_ref = Ref, + ack_action = AckAction}, + megaco_monitor:insert_reply(Rep2), % Timing problem? + ignore; + + _ -> + %% Been removed already? + ignore + end; +do_handle_request(AckAction, {ok, {Sent, NotSent}}, ConnData, TransId) + when is_list(Sent) andalso is_list(NotSent) -> + ?rt1(ConnData, "handle request - ok [segmented reply]", + [AckAction, TransId]), + + case megaco_monitor:lookup_reply(TransId) of + [#reply{pending_timer_ref = PendingRef} = Rep] -> + + %% d("do_handle_request -> found reply record:" + %% "~n Rep: ~p", [Rep]), + + #conn_data{reply_timer = InitTimer, + conn_handle = ConnHandle} = ConnData, + + %% Pending limit update: + %% - Cancel the pending timer, if running + %% - Delete the pending counter + %% + + megaco_monitor:cancel_apply_after(PendingRef), + megaco_config:del_pending_counter(sent, TransId), + + Method = timer_method(AckAction), + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + Garb = fun(Bin) -> opt_garb_binary(CurrTimer, Bin) end, + OptBins = [{SN, Garb(Bin), undefined} || {SN, Bin} <- Sent], + + M = ?MODULE, + F = reply_timeout, + A = [ConnHandle, TransId, CurrTimer], + Ref = megaco_monitor:apply_after(Method, M, F, A, WaitFor), + + Rep2 = Rep#reply{pending_timer_ref = undefined, + handler = undefined, + bytes = OptBins, + state = waiting_for_ack, + timer_ref = Ref, + ack_action = AckAction, + segments = NotSent}, + megaco_monitor:insert_reply(Rep2), % Timing problem? + + ignore; + _ -> + %% Been removed already? + ignore + end; +do_handle_request(_, {error, aborted}, ConnData, TransId) -> + ?report_trace(ConnData, "aborted during our absence", [TransId]), + case megaco_monitor:lookup_reply(TransId) of + [Rep] -> + cancel_reply(ConnData, Rep, aborted); + _ -> + ok + end, + ignore; +do_handle_request(AckAction, {error, Reason}, ConnData, TransId) -> + ?report_trace(ConnData, "error", [TransId, Reason]), + case megaco_monitor:lookup_reply(TransId) of + [Rep] -> + Rep2 = Rep#reply{state = waiting_for_ack, + ack_action = AckAction}, + cancel_reply(ConnData, Rep2, Reason); + _ -> + ok + end, + ignore; +do_handle_request(AckAction, SendReply, ConnData, TransId) -> + ?report_trace(ConnData, "unknown send trans reply result", + [TransId, AckAction, SendReply]), + ignore. + + +handle_requests([{ConnData, TransId, T} | Rest], Pending, Extra) -> + ?rt2("handle requests", [TransId]), + case handle_request(ConnData, TransId, T, Extra) of + {pending, RequestData} -> + handle_requests(Rest, [{ConnData,TransId,RequestData} | Pending], Extra); + _ -> + handle_requests(Rest, Pending, Extra) + end; +handle_requests([], Pending, _Extra) -> + ?rt2("handle requests - done", [Pending]), + Pending. + +%% opt_garb_binary(timeout, _Bin) -> garb_binary; % Need msg at restart of timer +opt_garb_binary(_Timer, Bin) -> Bin. + +timer_method(discard_ack) -> + apply_method; +timer_method(_) -> + spawn_method. + + +handle_long_request({ConnData, TransId, RequestData}, Extra) -> + + ?rt2("handle long request", [TransId, RequestData]), + + %% Pending limit: + %% We need to check the pending limit, in case it was + %% exceeded before we got this far... + %% We dont need to be able to create the counter here, + %% since that was done in the handle_request function. + + #conn_data{sent_pending_limit = Limit} = ConnData, + + case check_pending_limit(Limit, sent, TransId) of + {ok, _} -> + handle_long_request(ConnData, TransId, RequestData, Extra); + _ -> + %% Already exceeded the limit + ignore + end. + +handle_long_request(ConnData, TransId, RequestData, Extra) -> + ?report_trace(ConnData, "callback: trans long request", + [TransId, {request_data, RequestData}]), + + %% Attempt to update the handler field for this reply record + %% (if there is one). + case megaco_monitor:update_reply_field(TransId, #reply.handler, self()) of + true -> + {AckAction, Res} = + handle_long_request_callback(ConnData, TransId, + RequestData, Extra), + do_handle_long_request(AckAction, Res, ConnData, TransId); + false -> + %% Been removed already? + ignore + end. + + +do_handle_long_request(AckAction, {ok, Bin}, ConnData, TransId) -> + case megaco_monitor:lookup_reply_field(TransId, #reply.trans_id) of + {ok, _} -> + Method = timer_method(AckAction), + InitTimer = ConnData#conn_data.reply_timer, + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + OptBin = opt_garb_binary(CurrTimer, Bin), + ConnHandle = ConnData#conn_data.conn_handle, + M = ?MODULE, + F = reply_timeout, + A = [ConnHandle, TransId, CurrTimer], + Ref = megaco_monitor:apply_after(Method, M, F, A, WaitFor), + NewFields = + [{#reply.bytes, OptBin}, + {#reply.state, waiting_for_ack}, + {#reply.timer_ref, Ref}, + {#reply.ack_action, AckAction}], + megaco_monitor:update_reply_fields(TransId, NewFields); % Timing problem? + _ -> + %% Been removed already? + ignore + end; +do_handle_long_request(_, {error, Reason}, ConnData, TransId) -> + ?report_trace(ConnData, "send trans reply", [TransId, {error, Reason}]), + ignore. + +handle_request_abort_callback(ConnData, TransId, Pid) -> + Extra = ?default_user_callback_extra, + handle_request_abort_callback(ConnData, TransId, Pid, Extra). + +handle_request_abort_callback(ConnData, TransId, Pid, Extra) -> + ?report_trace(ConnData, "callback: trans request aborted", [TransId, Pid]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Serial = TransId#trans_id.serial, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, Serial, Pid | UserArgs]; + _ -> + [ConnHandle, Version, Serial, Pid, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_trans_request_abort, Args)), + ?report_debug(ConnData, "return: trans request aborted", + [TransId, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("transaction request abort callback failed: ~w", + [Res]), + ok + end. + +handle_request_callback(ConnData, TransId, Actions, T, Extra) -> + ?report_trace(ConnData, "callback: trans request", [T]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, Actions | UserArgs]; + _ -> + [ConnHandle, Version, Actions, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_trans_request, Args)), + ?report_debug(ConnData, "return: trans request", [T, {return, Res}]), + case Res of + ignore -> %% NOTE: Only used for testing!! + {discard_ack, ignore}; + + ignore_trans_request -> + {discard_ack, ignore_trans_request}; + + {discard_ack, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {discard_ack, SendReply}; + {discard_ack, Error} when is_record(Error, 'ErrorDescriptor') -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {discard_ack, SendReply}; + {discard_ack, Replies, SendOpts} when is_list(Replies) andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, asn1_NOVALUE), + {discard_ack, SendReply}; + {discard_ack, Error, SendOpts} + when is_record(Error, 'ErrorDescriptor') andalso + is_list(SendOpts) -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, asn1_NOVALUE), + {discard_ack, SendReply}; + + {{handle_pending_ack, AckData}, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], when_pending_sent), + {{handle_ack, AckData}, SendReply}; + {{handle_pending_ack, AckData}, Error} + when is_record(Error, 'ErrorDescriptor') -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], when_pending_sent), + {{handle_ack, AckData}, SendReply}; + {{handle_pending_ack, AckData}, Replies, SendOpts} + when is_list(Replies) andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, when_pending_sent), + {{handle_ack, AckData}, SendReply}; + {{handle_pending_ack, AckData}, Error, SendOpts} + when is_record(Error, 'ErrorDescriptor') andalso + is_list(SendOpts) -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, when_pending_sent), + {{handle_ack, AckData}, SendReply}; + + {{handle_ack, AckData}, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], 'NULL'), + {{handle_ack, AckData}, SendReply}; + {{handle_ack, AckData}, Error} + when is_record(Error, 'ErrorDescriptor') -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], 'NULL'), + {{handle_ack, AckData}, SendReply}; + {{handle_ack, AckData}, Replies, SendOpts} + when is_list(Replies) andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, 'NULL'), + {{handle_ack, AckData}, SendReply}; + {{handle_ack, AckData}, Error, SendOpts} + when is_record(Error, 'ErrorDescriptor') andalso + is_list(SendOpts) -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, 'NULL'), + {{handle_ack, AckData}, SendReply}; + + {{handle_sloppy_ack, AckData}, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {{handle_ack, AckData}, SendReply}; + {{handle_sloppy_ack, AckData}, Error} + when is_record(Error, 'ErrorDescriptor') -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {{handle_ack, AckData}, SendReply}; + {{handle_sloppy_ack, AckData}, Replies, SendOpts} + when is_list(Replies) andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, asn1_NOVALUE), + {{handle_ack, AckData}, SendReply}; + {{handle_sloppy_ack, AckData}, Error, SendOpts} + when is_record(Error, 'ErrorDescriptor') andalso + is_list(SendOpts) -> + Reply = {transactionError, Error}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, asn1_NOVALUE), + {{handle_ack, AckData}, SendReply}; + + {pending, RequestData} -> + %% The user thinks that this request will take + %% quite a while to evaluate. Maybe respond with + %% a pending trans (depends on the pending limit) + SendReply = maybe_send_pending(ConnData, TransId), + {{pending, RequestData}, SendReply}; + + Error -> + ErrorText = atom_to_list(UserMod), + ED = #'ErrorDescriptor'{ + errorCode = ?megaco_internal_gateway_error, + errorText = ErrorText}, + ?report_important(ConnData, + "callback: <ERROR> trans request", + [ED, {error, Error}]), + error_msg("transaction request callback failed: ~w", [Error]), + Reply = {transactionError, ED}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {discard_ack, SendReply} + end. + +handle_long_request_callback(ConnData, TransId, RequestData, Extra) -> + ?report_trace(ConnData, "callback: trans long request", [RequestData]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, RequestData | UserArgs]; + _ -> + [ConnHandle, Version, RequestData, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_trans_long_request, Args)), + ?report_debug(ConnData, "return: trans long request", + [{request_data, RequestData}, {return, Res}]), + case Res of + ignore -> + {discard_ack, ignore}; + + {discard_ack, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {discard_ack, SendReply}; + {discard_ack, Replies, SendOpts} when is_list(Replies) andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, asn1_NOVALUE), + {discard_ack, SendReply}; + + {{handle_ack, AckData}, Replies} when is_list(Replies) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], 'NULL'), + {{handle_ack, AckData}, SendReply}; + {{handle_ack, AckData}, Replies, SendOpts} when is_list(Replies) + andalso + is_list(SendOpts) -> + Reply = {actionReplies, Replies}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + SendOpts, 'NULL'), + {{handle_ack, AckData}, SendReply}; + + Error -> + ErrorText = atom_to_list(UserMod), + ED = #'ErrorDescriptor'{errorCode = ?megaco_internal_gateway_error, + errorText = ErrorText}, + ?report_important(ConnData, "callback: <ERROR> trans long request", + [ED, {error, Error}]), + error_msg("long transaction request callback failed: ~w", [Error]), + Reply = {transactionError, ED}, + SendReply = maybe_send_reply(ConnData, TransId, Reply, + [], asn1_NOVALUE), + {discard_ack, SendReply} + end. + +handle_pending(ConnData, T, Extra) -> + TransId = to_local_trans_id(ConnData), + ?rt2("handle pending", [T, TransId]), + case megaco_monitor:lookup_request(TransId) of + [Req] -> + + %% ------------------------------------------ + %% + %% Check received pending limit + %% + %% ------------------------------------------ + + Limit = ConnData#conn_data.recv_pending_limit, + case check_and_maybe_incr_pending_limit(Limit, + recv, TransId) of + + ok -> + %% ---------------------------------------------------- + %% + %% Received pending limit not exceeded + %% + %% ---------------------------------------------------- + + handle_recv_pending(ConnData, TransId, Req, T); + + error -> + %% ---------------------------------------------------- + %% + %% Received pending limit exceeded + %% + %% Time to give up on this transaction + %% 1) Delete request record + %% 2) Cancel timers + %% 3) Delete the (receive) pending counter + %% 4) Inform the user (handle_trans_reply) + %% + %% ---------------------------------------------------- + + handle_recv_pending_error(ConnData, TransId, Req, T, Extra); + + + aborted -> + %% ---------------------------------------------------- + %% + %% Received pending limit already exceeded + %% + %% BMK BMK BMK -- can this really happen? + %% + %% The user has already been notified about this + %% (see error above) + %% + %% ---------------------------------------------------- + + ok + + end; + + [] -> + ?report_trace(ConnData, "remote pending (no receiver)", [T]), + return_unexpected_trans(ConnData, T, Extra) + end. + +handle_recv_pending(#conn_data{long_request_resend = LRR, + conn_handle = ConnHandle} = ConnData, + TransId, + #request{timer_ref = {short, Ref}, + init_long_timer = InitTimer}, T) -> + + ?rt2("handle pending - long request", [LRR, InitTimer]), + + %% The request seems to take a while, + %% let's reset our transmission timer. + %% We now know the other side has got + %% the request and is working on it, + %% so there is no need to keep the binary + %% message for re-transmission. + + %% Start using the long timer. + %% We can now drop the "bytes", since we will + %% not resend from now on. + + megaco_monitor:cancel_apply_after(Ref), + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + ConnHandle = ConnData#conn_data.conn_handle, + M = ?MODULE, + F = request_timeout, + A = [ConnHandle, TransId], + Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor), + NewFields = + case LRR of + true -> + [{#request.timer_ref, {long, Ref2}}, + {#request.curr_timer, CurrTimer}]; + false -> + [{#request.bytes, {no_send, garb_binary}}, + {#request.timer_ref, {long, Ref2}}, + {#request.curr_timer, CurrTimer}] + end, + ?report_trace(ConnData, "trans pending (timer restarted)", [T]), + megaco_monitor:update_request_fields(TransId, NewFields); % Timing problem? + +handle_recv_pending(_ConnData, _TransId, + #request{timer_ref = {long, _Ref}, + curr_timer = timeout}, _T) -> + + ?rt3("handle pending - timeout"), + + %% The request seems to take a while, + %% let's reset our transmission timer. + %% We now know the other side has got + %% the request and is working on it, + %% so there is no need to keep the binary + %% message for re-transmission. + + %% This can happen if the timer is running for the last + %% time. I.e. next time it expires, will be the last. + %% Therefor we really do not need to do anything here. + %% The cleanup will be done in request_timeout. + + ok; + +handle_recv_pending(#conn_data{conn_handle = ConnHandle} = ConnData, TransId, + #request{timer_ref = {long, Ref}, + curr_timer = CurrTimer}, T) -> + + ?rt2("handle pending - still waiting", [CurrTimer]), + + %% The request seems to take a while, + %% let's reset our transmission timer. + %% We now know the other side has got + %% the request and is working on it, + %% so there is no need to keep the binary + %% message for re-transmission. + + %% We just need to recalculate the timer, i.e. + %% increment the timer (one "slot" has been consumed). + + megaco_monitor:cancel_apply_after(Ref), + {WaitFor, Timer2} = megaco_timer:restart(CurrTimer), + ConnHandle = ConnData#conn_data.conn_handle, + M = ?MODULE, + F = request_timeout, + A = [ConnHandle, TransId], + Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor), + NewFields = + [{#request.timer_ref, {long, Ref2}}, + {#request.curr_timer, Timer2}], + ?report_trace(ConnData, + "long trans pending" + " (timer restarted)", [T]), + %% Timing problem? + megaco_monitor:update_request_fields(TransId, NewFields). + + +handle_recv_pending_error(ConnData, TransId, Req, T, Extra) -> + %% 1) Delete the request record + megaco_monitor:delete_request(TransId), + + %% 2) Possibly cancel the timer + case Req#request.timer_ref of + {_, Ref} -> + megaco_monitor:cancel_apply_after(Ref); + _ -> + ok + end, + + %% 3) Delete the (receive) pending counter + megaco_config:del_pending_counter(recv, TransId), + + %% 4) Inform the user that his/her request reached + %% the receive pending limit + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = {error, exceeded_recv_pending_limit}, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + + ?report_trace(ConnData, "receive pending limit reached", [T]), + return_reply(ConnData2, TransId, UserReply, Extra). + + +%% +%% This _is_ a segmented message. +%% +%% Since this is not the last segment, we shall not send any ack. +%% (even if three-way-handshake has been configured). +%% +handle_reply( + ConnData, + #megaco_transaction_reply{segmentNumber = SN, + segmentationComplete = asn1_NOVALUE} = T, Extra) + when is_integer(SN) -> + TransId = to_local_trans_id(ConnData), + ?rt2("handle segmented reply", [T, TransId, SN]), + case megaco_monitor:lookup_request(TransId) of + + %% --------------------------------------------------------- + %% The first segment, so stop the request timer. No longer + %% needed when the segment(s) start to arrive. + + [#request{timer_ref = {_Type, Ref}, + seg_recv = [], + seg_timer_ref = undefined} = Req] -> + + %% Don't care about Req and Rep version diff + ?report_trace(ConnData, "[segmented] trans reply - first seg", + [T]), + + %% Stop the request timer + megaco_monitor:cancel_apply_after(Ref), %% OTP-4843 + + %% Acknowledge the segment + send_segment_reply(ConnData, SN), + + %% First segment for this reply + NewFields = + [{#request.timer_ref, undefined}, + {#request.seg_recv, [SN]}], + megaco_monitor:update_request_fields(TransId, NewFields), + + %% Handle the reply + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, {SN, false, Reason}}; + {actionReplies, Replies} -> + {ok, {SN, false, Replies}} + end, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra); + + + %% --------------------------------------------------------- + %% This is not the first segment. + %% The segment timer has not been started, so the last + %% segment have been received. + %% We must check that this is not a re-transmission! + + [#request{seg_recv = Segs, + seg_timer_ref = undefined} = Req] -> + %% Don't care about Req and Rep version diff + ?report_trace(ConnData, "[segmented] trans reply - no seg acc", + [T]), + + %% Acknowledge the segment + send_segment_reply(ConnData, SN), + + %% Updated/handle received segment + case lists:member(SN, Segs) of + true -> + %% This is a re-transmission, so we shall not pass + %% it on to the user (or update the request record). + ok; + false -> + %% First time for this segment + megaco_monitor:update_request_field(TransId, + #request.seg_recv, + [ SN | Segs ]), + + %% Handle the reply + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, {SN, false, Reason}}; + {actionReplies, Replies} -> + {ok, {SN, false, Replies}} + end, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra) + + end; + + + %% --------------------------------------------------------- + %% The segment timer is running! + %% This could be the last (out-of-order) segment! + %% We must check that this is not a re-transmission! + + [#request{seg_recv = Segs, + seg_timer_ref = SegRef} = Req] -> + %% Don't care about Req and Rep version diff + ?report_trace(ConnData, "[segmented] trans reply - no seg acc", + [T]), + + %% Acknowledge the segment + send_segment_reply(ConnData, SN), + + %% Updated received segments + case lists:member(SN, Segs) of + true -> + %% This is a re-transmission + ok; + false -> + %% First time for this segment, + %% we may now have a complete set + Last = + case is_all_segments([SN | Segs]) of + {true, _Sorted} -> + megaco_monitor:cancel_apply_after(SegRef), + megaco_monitor:delete_request(TransId), + send_ack(ConnData), + true; + {false, Sorted} -> + megaco_monitor:update_request_field(TransId, + #request.seg_recv, + Sorted), + false + end, + + %% Handle the reply + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, {SN, Last, Reason}}; + {actionReplies, Replies} -> + {ok, {SN, Last, Replies}} + end, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra) + + end; + + + [] -> + ?report_trace(ConnData, "trans reply (no receiver)", [T]), + return_unexpected_trans(ConnData, T, Extra) + end; + + +%% +%% This _is_ a segmented message and it's the last segment of the +%% message. +%% +handle_reply( + ConnData, + #megaco_transaction_reply{segmentNumber = SN, + segmentationComplete = 'NULL'} = T, Extra) + when is_integer(SN) -> + TransId = to_local_trans_id(ConnData), + ?rt2("handle (last) segmented reply", [T, TransId, SN]), + case megaco_monitor:lookup_request(TransId) of + + %% --------------------------------------------------------- + %% The first segment, so stop the request timer. No longer + %% needed when the segment(s) start to arrive. + + [#request{timer_ref = {_Type, Ref}, + seg_recv = [], + seg_timer_ref = undefined} = Req] -> + + %% Don't care about Req and Rep version diff + ?report_trace(ConnData, "[segmented] trans reply - " + "first/complete seg", [T]), + + %% Stop the request timer + megaco_monitor:cancel_apply_after(Ref), %% OTP-4843 + + %% Acknowledge the ("last") segment + send_segment_reply_complete(ConnData, SN), + + %% It is ofcourse pointless to split + %% a transaction into just one segment, + %% but just to be sure, we handle that + %% case also + Last = + if + SN > 1 -> + %% More then one segment + %% First time for this segment + ConnHandle = ConnData#conn_data.conn_handle, + InitSegTmr = Req#request.init_seg_timer, + {WaitFor, CurrTimer} = megaco_timer:init(InitSegTmr), + M = ?MODULE, + F = segment_timeout, + A = [ConnHandle, TransId, CurrTimer], + SegRef = + megaco_monitor:apply_after(M, F, A, WaitFor), + NewFields = + [{#request.timer_ref, undefined}, + {#request.seg_recv, [SN]}, + {#request.seg_timer_ref, SegRef}], + megaco_monitor:update_request_fields(TransId, NewFields), + false; + true -> + %% Just one segment! + megaco_monitor:delete_request(TransId), + send_ack(ConnData), + true + end, + + %% Handle the reply + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, {SN, Last, Reason}}; + {actionReplies, Replies} -> + {ok, {SN, Last, Replies}} + end, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra); + + + [#request{seg_recv = Segs} = Req] -> + %% Don't care about Req and Rep version diff + ?report_trace(ConnData, "[segmented] trans reply - no seg acc", + [T]), + + %% Acknowledge the ("last") segment + send_segment_reply_complete(ConnData, SN), + + %% Updated received segments + %% This is _probably_ the last segment, but some of + %% the previous segments may have been lost, so we + %% may not have a complete set! + case lists:member(SN, Segs) of + true -> + %% This is a re-transmission + ok; + false -> + Last = + case is_all_segments([SN | Segs]) of + {true, _Sorted} -> + ?report_trace(ConnData, + "[segmented] trans reply - " + "complete set", [T]), + megaco_monitor:delete_request(TransId), + send_ack(ConnData), + true; + {false, Sorted} -> + ConnHandle = ConnData#conn_data.conn_handle, + InitSegTmr = Req#request.init_seg_timer, + {WaitFor, CurrTimer} = + megaco_timer:init(InitSegTmr), + M = ?MODULE, + F = segment_timeout, + A = [ConnHandle, TransId, CurrTimer], + SegRef = + megaco_monitor:apply_after(M, F, A, + WaitFor), + NewFields = + [{#request.seg_recv, Sorted}, + {#request.seg_timer_ref, SegRef}], + megaco_monitor:update_request_fields(TransId, NewFields), + false + end, + + %% Handle the reply + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, {SN, Last, Reason}}; + {actionReplies, Replies} -> + {ok, {SN, Last, Replies}} + end, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra) + + end; + + [] -> + ?report_trace(ConnData, "trans reply (no receiver)", [T]), + return_unexpected_trans(ConnData, T, Extra) + end; + + +%% +%% This is _not_ a segmented message, +%% i.e. it's an ordinary transaction reply +%% +handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) -> + TransId = to_local_trans_id(CD), + ?rt2("handle reply", [T, TransId]), + case megaco_monitor:lookup_request(TransId) of + [Req] when (is_record(Req, request) andalso + (CD#conn_data.cancel =:= true)) -> + ?TC_AWAIT_REPLY_EVENT(true), + do_handle_reply_cancel(CD, Req, T); + + [#request{remote_mid = RMid} = Req] when ((RMid =:= preliminary_mid) orelse + (RMid =:= CH#megaco_conn_handle.remote_mid)) -> + ?TC_AWAIT_REPLY_EVENT(false), + %% Just in case conn_data got update after our lookup + %% but before we looked up the request record, we + %% check the cancel field again. + case megaco_config:conn_info(CD, cancel) of + true -> + do_handle_reply_cancel(CD, Req, T); + false -> + do_handle_reply(CD, Req, TransId, T, Extra) + end; + + [#request{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData, + remote_mid = RMid}] -> + ?report_trace(CD, + "received trans reply with invalid remote mid", + [T, RMid]), + WrongMid = CH#megaco_conn_handle.remote_mid, + T2 = transform_transaction_reply_enc(CD#conn_data.protocol_version, + T), + UserReply = {error, {wrong_mid, WrongMid, RMid, T2}}, + CD2 = CD#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(CD2, TransId, UserReply, Extra); + + [] -> + ?TC_AWAIT_REPLY_EVENT(undefined), + ?report_trace(CD, "trans reply (no receiver)", [T]), + return_unexpected_trans(CD, T, Extra) + end. + +do_handle_reply_cancel(CD, #request{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, T) -> + CD2 = CD#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_unexpected_trans(CD2, T). + +%% Plain old handling of incomming replies +do_handle_reply(CD, + #request{timer_ref = {_Type, Ref}, % OTP-4843 + user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData, + keep_alive_timer = RKAT}, + TransId, T, Extra) + when ((RKAT =:= plain) orelse (Action =:= call)) -> + %% Don't care about Req and Rep version diff + ?report_trace(CD, "trans reply", [T]), + + %% This is the first reply (maybe of many) + megaco_monitor:delete_request(TransId), + megaco_monitor:cancel_apply_after(Ref), % OTP-4843 + megaco_config:del_pending_counter(recv, TransId), % OTP-7189 + + %% Send acknowledgement + maybe_send_ack(T#megaco_transaction_reply.immAckRequired, CD), + + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, Reason}; + {actionReplies, Replies} -> + {ok, Replies} + end, + CD2 = CD#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(CD2, TransId, UserReply, Extra); + +%% This may be the first reply (of maybe many) +do_handle_reply(CD, + #request{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData, + keep_alive_ref = undefined} = Req, + TransId, T, Extra) -> + %% Don't care about Req and Rep version diff + ?report_trace(CD, "trans reply", [T]), + + %% Could be the first reply, in which case we shall start the + %% Request Keep Alive timer... + %% This could happen for more than one (1) reply though, so + %% we need to check if the counter value actually equals one (1)! + + ReplyNo = + create_or_maybe_increment_request_keep_alive_counter(CD, TransId), + if + (ReplyNo =:= 1) -> + %% This *is* the first reply!! + %% 1) Stop resend timer + {_Type, Ref} = Req#request.timer_ref, % OTP-4843 + megaco_monitor:cancel_apply_after(Ref), % OTP-4843 + + %% 2) Delete pending counter + megaco_config:del_pending_counter(recv, TransId), % OTP-7189 + + %% 3) Start request keep alive timer + ConnHandle = CD#conn_data.conn_handle, + RKATimer = Req#request.keep_alive_timer, + {RKAWaitFor, _} = megaco_timer:init(RKATimer), + RKARef = megaco_monitor:apply_after(?MODULE, + request_keep_alive_timeout, + [ConnHandle, TransId], + RKAWaitFor), + + %% 4) Maybe send acknowledgement (three-way-handshake) + maybe_send_ack(T#megaco_transaction_reply.immAckRequired, CD), + + %% 5) And finally store the updated request record + Req2 = Req#request{keep_alive_ref = RKARef}, + megaco_monitor:insert_request(Req2); + + true -> + ok + end, + + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, ReplyNo, Reason}; + {actionReplies, Replies} -> + {ok, ReplyNo, Replies} + end, + CD2 = CD#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(CD2, TransId, UserReply, Extra); + +%% This is *not* the first reply (of many) +do_handle_reply(CD, #request{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, TransId, T, Extra) -> + %% Don't care about Req and Rep version diff + ?report_trace(CD, "trans reply (first reply already delivered)", [T]), + + ReplyNo = increment_request_keep_alive_counter(CD, TransId), + + UserReply = + case T#megaco_transaction_reply.transactionResult of + {transactionError, Reason} -> + {error, ReplyNo, Reason}; + {actionReplies, Replies} -> + {ok, ReplyNo, Replies} + end, + CD2 = CD#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(CD2, TransId, UserReply, Extra). + +is_all_segments(Segs) -> + Sorted = lists:sort(Segs), + {is_all_segments(Sorted, 1, lists:last(Sorted)), Sorted}. + +is_all_segments([Last], Last, Last) -> + true; +is_all_segments([_], _, _) -> + false; +is_all_segments([SN|Segs], SN, Last) when (SN < Last) -> + is_all_segments(Segs, SN+1, Last); +is_all_segments([SN1|_], SN2, _Last) when SN1 =/= SN2 -> + false. + + +handle_segment_reply(CD, + #'SegmentReply'{transactionId = TransId, + segmentNumber = SN, + segmentationComplete = SC}, Extra) -> + ?rt2("handle segment reply", [{trans_id, TransId}, + {segment_no, SN}, + {segmentation_complete, SC}]), + TransId2 = to_remote_trans_id(CD#conn_data{serial = TransId}), + case megaco_monitor:lookup_reply(TransId2) of + [#reply{bytes = Sent, + segments = []} = Rep] when is_list(Sent) -> + ?rt2("no unsent segments", [Sent]), + handle_segment_reply_callback(CD, TransId, SN, SC, Extra), + case lists:keysearch(SN, 1, Sent) of + {value, {SN, _Bin, SegTmr}} -> + megaco_monitor:cancel_apply_after(SegTmr), %% BMK BMK + case lists:keydelete(SN, 1, Sent) of + [] -> %% We are done + Ref = Rep#reply.timer_ref, + megaco_monitor:cancel_apply_after(Ref), + megaco_monitor:update_reply_field(TransId2, + #reply.bytes, + []), + ok; + NewSent -> + megaco_monitor:update_reply_field(TransId2, + #reply.bytes, + NewSent), + ok + end; + _ -> + ok + end; + + [#reply{bytes = Sent, + segments = NotSent}] when is_list(Sent) andalso + is_list(NotSent) -> + ?rt2("unsent segments", [Sent, NotSent]), + handle_segment_reply_callback(CD, TransId, SN, SC, Extra), + case lists:keysearch(SN, 1, Sent) of + {value, {SN, _Bin, SegTmr}} -> + megaco_monitor:cancel_apply_after(SegTmr), %% BMK BMK + NewSent = lists:keydelete(SN, 1, Sent), + [{SN2, Bin2}|NewNotSent] = NotSent, + case send_reply_segment(CD, "send trans reply segment", + SN2, Bin2) of + {ok, Bin3} -> + ?rt2("another segment sent", [Bin3]), + NewSent2 = [{SN2, Bin3, undefined}|NewSent], + NewFields = + [{#reply.bytes, NewSent2}, + {#reply.segments, NewNotSent}], + megaco_monitor:update_reply_fields(TransId2, + NewFields), + ok; + Error -> + incNumErrors(CD#conn_data.conn_handle), + ?report_important(CD, "failed sending segment", + [{segment_no, SN2}, + {error, Error}]), + error_msg("failed sending transaction reply [~w] " + "segment [~w]: ~w", + [TransId, SN2, Error]), + megaco_monitor:update_reply_field(TransId2, + #reply.bytes, + NewSent), + ok + end; + _ -> + ok + end; + + [#reply{state = State}] -> + %% We received a segment reply for a segmented reply we have + %% not yet sent? This is either some sort of race condition + %% or the "the other side" is really confused. + %% Ignore the message but issue a warning just in case... + warning_msg("received unexpected segment reply: " + "~n Transaction Id: ~p" + "~n Segment Number: ~p" + "~n Segmentation Complete: ~p" + "~n Reply state: ~p", + [TransId2, SN, SC, State]), + ignore; + + [] -> + ignore + + end. + + +%% +%% This should be passed on to the user only if the user wish it +%% (sri = segment reply indication) +%% +handle_segment_reply_callback(#conn_data{segment_reply_ind = true, + conn_handle = ConnHandle, + protocol_version = Version, + user_mod = UserMod, + user_args = UserArgs}, + TransId, SN, SC, Extra) -> + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, TransId, SN, SC | UserArgs]; + _ -> + [ConnHandle, Version, TransId, SN, SC, Extra | UserArgs] + end, + (catch apply(UserMod, handle_segment_reply, Args)); +handle_segment_reply_callback(_CD, _TransId, _SN, _SC, _Extra) -> + ok. + + +handle_acks([{ConnData, Rep, T} | Rest], Extra) + when Rep#reply.state == waiting_for_ack -> + handle_ack(ConnData, ok, Rep, T, Extra), + handle_acks(Rest, Extra); +handle_acks([], _Extra) -> + ok. + +%% If the reply to which this is the ack was segmented, +%% then we also need to check that we have received all +%% the segment-replies. If not, an error callback call +%% shall be made instead. +handle_ack(ConnData, AckStatus, + #reply{trans_id = TransId, + bytes = Bytes, + timer_ref = ReplyRef, + pending_timer_ref = PendingRef, %% BMK Still running? + ack_action = AckAction}, T, Extra) + when is_binary(Bytes) orelse (Bytes =:= undefined) -> + handle_ack_cleanup(TransId, ReplyRef, PendingRef), + handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra); + +handle_ack(ConnData, AckStatus, + #reply{trans_id = TransId, + bytes = [], + segments = [], + timer_ref = ReplyRef, + pending_timer_ref = PendingRef, %% BMK Still running? + ack_action = AckAction}, T, Extra) -> + handle_ack_cleanup(TransId, ReplyRef, PendingRef), + handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra); + +handle_ack(ConnData, OrigAckStatus, + #reply{trans_id = TransId, + bytes = SegSent, + segments = NotSent, + timer_ref = ReplyRef, + pending_timer_ref = PendingRef, %% BMK Still running? + ack_action = OrigAckAction}, T, Extra) + when is_list(SegSent) andalso is_list(NotSent) -> + SN_NotAcked = [SN || {SN, _, _} <- SegSent], + SN_NotSent = [SN || {SN, _} <- NotSent], + AckStatus = {error, {segment_failure, + [{original_ack_status, OrigAckStatus}, + {segments_not_acked, SN_NotAcked}, + {segments_not_sent, SN_NotSent}]}}, + AckAction = + case OrigAckAction of + {handle_ack, _} -> + OrigAckAction; + _ -> + {handle_ack, segmented_reply} + end, + cancel_segment_timers(SegSent), + handle_ack_cleanup(TransId, ReplyRef, PendingRef), + handle_ack_callback(ConnData, AckStatus, AckAction, T, Extra). + +handle_ack_cleanup(TransId, ReplyRef, PendingRef) -> + megaco_monitor:cancel_apply_after(ReplyRef), + megaco_monitor:cancel_apply_after(PendingRef), + megaco_monitor:delete_reply(TransId), + megaco_config:del_pending_counter(sent, TransId). %% BMK: Still existing? + +cancel_segment_timers(SegSent) when is_list(SegSent) -> + Cancel = fun({_, _, Ref}) -> + megaco_monitor:cancel_apply_after(Ref) + end, + lists:foreach(Cancel, SegSent); +cancel_segment_timers(_) -> + ok. + +handle_ack_callback(_CD, ok = _AS, discard_ack = _AA, _T, _Extra) -> + ok; +handle_ack_callback(ConnData, {error, Reason}, discard_ack = AckAction, T, Extra) -> + ?report_trace(ConnData, "handle ack (no callback)", + [T, AckAction, {error, Reason}, Extra]); +handle_ack_callback(ConnData, AckStatus, {handle_ack, AckData}, T, Extra) -> + ?report_trace(ConnData, "callback: trans ack", [{ack_data, AckData}]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, AckStatus, AckData | UserArgs]; + _ -> + [ConnHandle, Version, AckStatus, AckData, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_trans_ack, Args)), + ?report_debug(ConnData, "return: trans ack", [T, AckData, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("transaction ack callback failed: ~w", [Res]), + ok + end, + Res. + + +handle_message_error(ConnData, _Error, _Extra) + when ConnData#conn_data.monitor_ref == undefined_monitor_ref -> + %% May occur if another process already has setup a + %% temporary connection, but the handle_connect callback + %% function has not yet returned before the eager MG + %% re-sends its initial service change message. + ignore; +handle_message_error(ConnData, Error, Extra) -> + ?report_trace(ConnData, "callback: message error", [Error]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, Error | UserArgs]; + _ -> + [ConnHandle, Version, Error, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_message_error, Args)), + ?report_debug(ConnData, "return: message error", [Error, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("message error callback failed: ~w", [Res]), + ok + end, + Res. + +handle_disconnect_callback(ConnData, UserReason) + when is_record(ConnData, conn_data) -> + ?report_trace(ConnData, "callback: disconnect", [{reason, UserReason}]), + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + Args = [ConnHandle, Version, UserReason | UserArgs], + Res = (catch apply(UserMod, handle_disconnect, Args)), + ?report_debug(ConnData, "return: disconnect", [{reason, UserReason}, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("disconnect callback failed: ~w", [Res]), + ok + end, + Res. + + +%%---------------------------------------------------------------------- +%% Test "outgoing" messages +%%---------------------------------------------------------------------- + +%% test_request/5 -> {MegacoMessage, EncodingRes} +%% +%% This function is only intended for testing +%% (e.g. answer the question: have I constructed a valid action request?) +%% +%% It's not exactly the same code as a call to 'call' +%% or 'cast' but close enough. +%% +test_request(ConnHandle, Actions, + Version, EncodingMod, EncodingConfig) + when is_record(ConnHandle, megaco_conn_handle) and + is_integer(Version) andalso is_atom(EncodingMod) -> + %% Create a fake conn_data structure + ConnData = #conn_data{serial = 1, + protocol_version = Version, + conn_handle = ConnHandle, + auth_data = asn1_NOVALUE, + encoding_mod = EncodingMod, + encoding_config = EncodingConfig}, + + TRs = test_req_compose_transactions(ConnData, Actions), + Body = {transactions, TRs}, + MegaMsg = megaco_messenger_misc:compose_message(ConnData, Version, Body), + EncodeRes = megaco_messenger_misc:encode_message(ConnData, MegaMsg), + {MegaMsg, EncodeRes}. + + +test_req_compose_transactions(ConnData, [A|_] = ActionsList) when is_list(A) -> + LastSerial = ConnData#conn_data.serial, + test_req_compose_transactions(LastSerial, lists:reverse(ActionsList), []); +test_req_compose_transactions(#conn_data{serial = Serial}, Actions) -> + TR = #'TransactionRequest'{transactionId = Serial, + actions = Actions}, + [{transactionRequest, TR}]. + +test_req_compose_transactions(_Serial, [], Acc) -> + lists:reverse(Acc); +test_req_compose_transactions(Serial, [A|As], Acc) -> + TR = #'TransactionRequest'{transactionId = Serial, + actions = A}, + test_req_compose_transactions(Serial, As, [{transactionRequest, TR}|Acc]). + + +test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Error) + when is_record(Error, 'ErrorDescriptor') -> + Reply = {transactionError, Error}, + test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply); +test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Replies) + when is_list(Replies) -> + Reply = {actionReplies, Replies}, + test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply). + +test_reply_encode(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> + ImmAck = asn1_NOVALUE, + Serial = 1, + %% Create a fake conn_data structure + CD = #conn_data{serial = Serial, + protocol_version = Version, + conn_handle = ConnHandle, + auth_data = asn1_NOVALUE, + encoding_mod = EncodingMod, + encoding_config = EncodingConfig}, + TR0 = #megaco_transaction_reply{transactionId = Serial, + immAckRequired = ImmAck, + transactionResult = Reply}, + TR = megaco_messenger_misc:transform_transaction_reply(CD, TR0), + Body = {transactions, [{transactionReply, TR}]}, + MegaMsg = megaco_messenger_misc:compose_message(CD, Version, Body), + EncodeRes = megaco_messenger_misc:encode_message(CD, MegaMsg), + {MegaMsg, EncodeRes}. + + +%%---------------------------------------------------------------------- +%% Send (or prepare) outgoing messages +%%---------------------------------------------------------------------- + +%% Description: +%% Encode a list of actions or a list of list of actions for +%% later sending (using call or cast). +%% +%% encode_actions(CH, Acts, Opts) -> {ok, encoded_actions()} | {error, Reason} +%% CH -> connection_handle() +%% Acts -> action_reqs() | [action_reqs()] +%% action_reqs() -> [action_req()] +%% action_req() -> #'ActionRequest'{} +%% Opts -> [option()] +%% option() -> {Tab, Val} +%% Tag -> atom() +%% Val -> term() +%% encoded_actions() -> binary() | [binary()] +%% Reason -> term() +encode_actions(CH, [A|_] = ActionsList, Opts) + when is_record(CH, megaco_conn_handle) andalso is_list(A) -> + (catch encode_multi_actions(CH, ActionsList, Opts)); + +encode_actions(CH, [A|_] = Actions, Opts) + when is_record(CH, megaco_conn_handle) andalso is_tuple(A) -> + do_encode_actions(CH, Actions, Opts). + +encode_multi_actions(CH, ActionsList, Opts) -> + case prepare_req_send_options(CH, Opts) of + {ok, CD} -> + ActsList = [encode_multi_actions(CD, Acts) || Acts <- ActionsList], + {ok, ActsList}; + Error -> + Error + end. + +encode_multi_actions(CD, Actions) -> + case megaco_messenger_misc:encode_actions(CD, + "encode multi actions", + Actions) of + {ok, Bin} -> + Bin; + Error -> + throw(Error) + end. + +do_encode_actions(CH, Actions, Opts) + when is_record(CH, megaco_conn_handle) -> + case prepare_req_send_options(CH, Opts) of + {ok, CD} -> + megaco_messenger_misc:encode_actions(CD, + "encode actions", + Actions); + Error -> + Error + end. + +prepare_req_send_options(CH, Opts) -> + case megaco_config:lookup_local_conn(CH) of + [CD] -> + override_req_send_options(any, CD, Opts); + [] -> + {error, {not_found, conn_data}} + end. + + +call(ConnHandle, Actions, Options) -> + case lists:keymember(reply_data, 1, Options) of + true -> + {error, {bad_option, reply_data}}; + false -> + Self = self(), + ProxyFun = fun() -> call_proxy(Self) end, + {Proxy, MRef} = erlang:spawn_monitor(ProxyFun), + Options2 = [{reply_data, Proxy} | Options], + call_or_cast(call, ConnHandle, Actions, Options2, MRef) + end. + +cast(ConnHandle, Actions, Options) -> + call_or_cast(cast, ConnHandle, Actions, Options, undefined). + +%% In a transaction there can be several actions, so if the +%% First element of the Actions list is an ''ActionRequest'' +%% record this a list of ActionRequest's for one Transaction +%% request. If on the other hand this is not the case, then +%% the Actions list is assumed to be a list of list of +%% ActionRequest. That is, action requests for several transactions. +%% It could also be a binary or a list of binaries (if +%% the actions has already been encoded). +call_or_cast(CallOrCast, ConnHandle, [A|_] = Actions, Options, ProxyMon) + when is_tuple(A) -> + %% Just one transaction + case call_or_cast(CallOrCast, ConnHandle, [Actions], Options, ProxyMon) of + ok -> + ok; + {error, Reason} -> + {error, Reason}; + {Version, [Reply]} when is_integer(Version) -> + {Version, Reply}; + {Version, Error} when is_integer(Version) -> + {Version, Error} + end; + +call_or_cast(CallOrCast, ConnHandle, Actions, Options, ProxyMon) + when is_binary(Actions) -> + %% Just one transaction (although the actions has already been encoded) + case call_or_cast(CallOrCast, ConnHandle, [Actions], Options, ProxyMon) of + ok -> + ok; + {error, Reason} -> + {error, Reason}; + {Version, [Reply]} when is_integer(Version) -> + {Version, Reply}; + {Version, Error} when is_integer(Version) -> + {Version, Error} + end; + +call_or_cast(CallOrCast, ConnHandle, ActionsList, Options, ProxyMon) + when is_record(ConnHandle, megaco_conn_handle) -> + case prepare_req_send_options(CallOrCast, + ConnHandle, Options, ActionsList) of + {ok, ConnData} -> + ?report_trace(ConnData, "call_or_cast - options prepared", []), + case encode_requests(ConnData, ActionsList) of + {ok, TRs, BinOrBins} -> + ?report_trace(ConnData, + "call_or_cast - request encoded", []), + send_request(ConnData, ConnHandle, + TRs, CallOrCast, BinOrBins), + case CallOrCast of + call -> + TransIds = to_local_trans_id(ConnData, TRs), + wait_for_reply(ConnData, TransIds, ProxyMon); + cast -> + ok + end; + {error, Reason} -> + call_proxy_cleanup(ConnData, ProxyMon), + Version = ConnData#conn_data.protocol_version, + return_error(CallOrCast, Version, {error, Reason}) + end; + {error, Reason} -> + call_proxy_cleanup(Options, ProxyMon), + return_error(CallOrCast, 1, {error, Reason}) + end; +call_or_cast(CallOrCast, ConnHandle, _Actions, Options, ProxyMon) -> + call_proxy_cleanup(Options, ProxyMon), + return_error(CallOrCast, 1, {error, {bad_megaco_conn_handle, ConnHandle}}). + + +return_error(Action, Version, Error) -> + case Action of + call -> {Version, Error}; + cast -> Error + end. + +wait_for_reply(CD, TransIds, ProxyMon) -> + ProxyPid = CD#conn_data.reply_data, + ProxyPid ! {go, self(), CD, TransIds}, + receive + {reply, ProxyPid, Reply} -> + erlang:demonitor(ProxyMon, [flush]), + Reply; + {'DOWN', ProxyMon, process, ProxyPid, Info} -> + UserReply = {error, {call_proxy_crash, Info}}, + {CD#conn_data.protocol_version, UserReply} + end. + + +call_proxy_cleanup(#conn_data{reply_data = ProxyPid}, ProxyMon) -> + do_call_proxy_cleanup(ProxyPid, ProxyMon); +call_proxy_cleanup(Options, ProxyMon) when is_list(Options) -> + ProxyPid = + case lists:keysearch(reply_data, 1, Options) of + {value, {reply_data, Data}} -> + Data; + _ -> + undefined + end, + do_call_proxy_cleanup(ProxyPid, ProxyMon); +call_proxy_cleanup(ProxyPid, ProxyMon) -> + do_call_proxy_cleanup(ProxyPid, ProxyMon). + +do_call_proxy_cleanup(ProxyPid, ProxyMon) -> + maybe_demonitor(ProxyMon), + maybe_stop_proxy(ProxyPid), + ok. + +maybe_demonitor(undefined) -> + ok; +maybe_demonitor(Mon) -> + (catch erlang:demonitor(Mon, [flush])), + ok. + +maybe_stop_proxy(Pid) when is_pid(Pid) -> + Pid ! {stop, self()}, + ok; +maybe_stop_proxy(_) -> + ok. + + +call_proxy(Parent) -> + receive + {go, Parent, CD, TransIds} -> + call_proxy(Parent, CD, TransIds); + {stop, Parent} -> + exit(normal) + end. + +call_proxy(Parent, CD, TransIds) -> + Reply = proxy_wait_for_reply(CD, TransIds, []), + Parent ! {reply, self(), Reply}, + call_proxy_gc(CD, CD#conn_data.call_proxy_gc_timeout). + +call_proxy_gc(CD, Timeout) when (Timeout > 0) -> + T = t(), + receive + {?MODULE, TransId, Version, Result} -> % Old format + CD2 = CD#conn_data{protocol_version = Version}, + Extra = ?default_user_callback_extra, + return_unexpected_trans_reply(CD2, TransId, Result, Extra), + call_proxy_gc(CD, Timeout - (t() - T)); + + {?MODULE, TransId, Version, Result, Extra} -> + CD2 = CD#conn_data{protocol_version = Version}, + return_unexpected_trans_reply(CD2, TransId, Result, Extra), + call_proxy_gc(CD, Timeout - (t() - T)) + + after Timeout -> + exit(normal) + end; +call_proxy_gc(_CD, _Timeout) -> + exit(normal). + +proxy_wait_for_reply(_CD, [], Replies0) -> + % Make sure they come in the same order as the requests where sent + Replies1 = lists:keysort(2, Replies0), + %% Must all be the same version + [{Version, _, _}|_] = Replies1, + Replies2 = [Result || {_Version, _TransId, Result} <- Replies1], + {Version, Replies2}; +proxy_wait_for_reply(CD, TransIds, Replies) -> + receive + {?MODULE, TransId, Version, Reply} -> % Old format + {TransIds2, Replies2} = + wfr_handle_reply(CD, + TransIds, TransId, + Version, Replies, Reply), + proxy_wait_for_reply(CD, TransIds2, Replies2); + + {?MODULE, TransId, Version, Reply, Extra} -> + {TransIds2, Replies2} = + wfr_handle_reply(CD, + TransIds, TransId, + Version, Replies, Reply, Extra), + proxy_wait_for_reply(CD, TransIds2, Replies2) + end. + +wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply) -> + Extra = ?default_user_callback_extra, + wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply, Extra). + +wfr_handle_reply(CD, TransIds, TransId, Version, Replies, Reply, Extra) -> + %% Is this meant for us? + case lists:member(TransId, TransIds) of + true -> % Yep + wfr_update(TransIds, TransId, Version, Replies, Reply, Extra); + false -> % Nop + CD2 = CD#conn_data{protocol_version = Version}, + return_unexpected_trans_reply(CD2, TransId, Reply, Extra), + {TransIds, Replies} + end. + +wfr_mk_reply(Version, TransId, Result, ?default_user_callback_extra = _Extra) -> + {Version, TransId, Result}; +wfr_mk_reply(Version, TransId, Result0, Extra) -> + Result = list_to_tuple(lists:append(tuple_to_list(Result0), [Extra])), + {Version, TransId, Result}. + +%% Last segment of a reply +%% transactionResult "=" actionReplies +wfr_update(TransIds, TransId, Version, Results, {ok, {SegNo, Last, ARs}}, Extra) + when is_integer(SegNo) andalso (Last == true) -> + TransIds2 = lists:delete(TransId, TransIds), + case lists:keysearch(TransId, 2, Results) of + + %% All segments ok (actionReplies) + {value, {V, TransId, {ok, SegReps}}} -> + SegReps2 = lists:keysort(1, [{SegNo, ARs}|SegReps]), + Rep = wfr_mk_reply(V, TransId, {ok, SegReps2}, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + %% Atleast one segment error (transactionError) + {value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} -> + OkSegs2 = lists:keysort(1, [{SegNo, ARs}|OkSegs]), + ErrSegs2 = lists:keysort(1, ErrSegs), + Error = {error, {segment, OkSegs2, ErrSegs2}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + false -> + %% First and only segment + Rep = wfr_mk_reply(Version, TransId, {ok, [{SegNo, ARs}]}, Extra), + {TransIds2, [Rep | Results]} + + end; + +%% Last segment of a reply +%% transactionResult "=" transactionError +wfr_update(TransIds, TransId, Version, Results, {error, {SegNo, Last, ED}}, Extra) + when is_integer(SegNo) andalso (Last == true) -> + TransIds2 = lists:delete(TransId, TransIds), + case lists:keysearch(TransId, 2, Results) of + + %% First segment with error (transactionError) + {value, {V, TransId, {ok, SegReps}}} -> + OkSegs = lists:keysort(1, [{SegNo, ED}|SegReps]), + ErrSegs = [{SegNo, ED}], + Error = {error, {segment, OkSegs, ErrSegs}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + %% Another segment with error (transactionError) + {value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} -> + OkSegs2 = lists:keysort(1, OkSegs), + ErrSegs2 = lists:keysort(1, [{SegNo, ED}|ErrSegs]), + Error = {error, {segment, OkSegs2, ErrSegs2}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + false -> + %% First and only segment + OkSegs = [], + ErrSegs = [{SegNo, ED}], + Error = {error, {segment, OkSegs, ErrSegs}}, + Rep = wfr_mk_reply(Version, TransId, Error, Extra), + {TransIds2, [Rep]} + + end; + +%% One segment of a reply +%% transactionResult "=" actionReplies +wfr_update(TransIds, TransId, Version, Results, {ok, {SegNo, _Last, ARs}}, Extra) + when is_integer(SegNo) -> + case lists:keysearch(TransId, 2, Results) of + + %% All segments ok (actionReplies) + {value, {V, TransId, {ok, SegReps}}} -> + SegReps2 = [{SegNo, ARs}|SegReps], + Rep = wfr_mk_reply(V, TransId, {ok, SegReps2}, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds, Results2}; + + %% Atleast one segment error (transactionError) + {value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} -> + OkSegs2 = [{SegNo, ARs}|OkSegs], + Error = {error, {segment, OkSegs2, ErrSegs}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds, Results2}; + + false -> + %% First and only segment + Rep = wfr_mk_reply(Version, TransId, {ok, [{SegNo, ARs}]}, Extra), + {TransIds, [Rep | Results]} + + end; + +%% One segment of a reply +%% transactionResult "=" transactionError +wfr_update(TransIds, TransId, Version, Results, {error, {SegNo, _Last, ED}}, Extra) + when is_integer(SegNo) -> + case lists:keysearch(TransId, 2, Results) of + + %% First segment with error (transactionError) + {value, {V, TransId, {ok, OkSegs}}} -> + ErrSegs = [{SegNo, ED}], + Error = {error, {segment, OkSegs, ErrSegs}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds, Results2}; + + %% Another segment with error (transactionError) + {value, {V, TransId, {error, {OkSegs, ErrSegs}}}} -> + ErrSegs2 = [{SegNo, ED}|ErrSegs], + Error = {error, {segment, OkSegs, ErrSegs2}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds, Results2}; + + false -> + %% First segment + OkSegs = [], + ErrSegs = [{SegNo, ED}], + Error = {error, {segment, OkSegs, ErrSegs}}, + Rep = wfr_mk_reply(Version, TransId, Error, Extra), + {TransIds, [Rep]} + + end; + +%% This means that some segments did not make it in time +wfr_update(TransIds, TransId, Version, Results, + {error, {segment_timeout, Missing}}, Extra) -> + TransIds2 = lists:delete(TransId, TransIds), + case lists:keysearch(TransId, 2, Results) of + + %% First segment with error (transactionError) + {value, {V, TransId, {ok, OkSegs}}} -> + Error = {error, {segment_timeout, Missing, OkSegs, []}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + %% Another segment with error (transactionError) + {value, {V, TransId, {error, {segment, OkSegs, ErrSegs}}}} -> + Error = {error, {segment_timeout, Missing, OkSegs, ErrSegs}}, + Rep = wfr_mk_reply(V, TransId, Error, Extra), + Results2 = lists:keyreplace(TransId, 2, Results, Rep), + {TransIds2, Results2}; + + false -> + %% First segment + Error = {error, {segment_timeout, Missing, [], []}}, + Rep = wfr_mk_reply(Version, TransId, Error, Extra), + {TransIds2, [Rep]} + + end; + +%% And all other results (presumably results without segments). +wfr_update(TransIds, TransId, Version, Results, Result, Extra) -> + TransIds2 = lists:delete(TransId, TransIds), + Results2 = [wfr_mk_reply(Version, TransId, Result, Extra)|Results], + {TransIds2, Results2}. + + +%% TransInfo is either [trans_id()] or a [trans_req()] + +%% This is the normal case where we have just one +%% transaction to be sent (using call or cast) using +%% the transaction sender process. +send_request(#conn_data{control_pid = CP, + trans_req = true, + trans_sender = Pid} = CD, + CH, [Serial], Action, [Bin]) + when is_pid(Pid) andalso + is_integer(Serial) andalso + (node(CP) =:= node()) -> + + ?report_trace(CD, + "send_request - one transaction via trans-sender", + [Serial]), + + #conn_data{request_timer = InitTimer, + long_request_timer = LongTimer} = CD, + TransId = to_local_trans_id(CH, Serial), + insert_request(CD, CH, TransId, Action, {Serial, Bin}, + InitTimer, LongTimer), + megaco_trans_sender:send_req(Pid, Serial, Bin); + +%% This is the general case where we have several transactions +%% beeing sent (using call or cast) at once using +%% the transaction sender process. +send_request(#conn_data{control_pid = CP, + trans_req = true, + trans_sender = Pid} = CD, + CH, TransInfo, Action, Bins) + when is_pid(Pid) andalso + is_list(Bins) andalso + (node(CP) =:= node()) -> + + ?report_trace(CD, + "send_request - multi transactions via trans_sender", + [TransInfo, Pid]), + + #conn_data{request_timer = InitTimer, + long_request_timer = LongTimer} = CD, + insert_requests(CD, CH, TransInfo, Action, Bins, + InitTimer, LongTimer), + megaco_trans_sender:send_reqs(Pid, TransInfo, Bins); + +%% This is the case when one or more transactions is +%% beeing sent in one message immediatelly (not using +%% the transaction sender process. E.g. the binary is +%% this encoded message. +send_request(#conn_data{control_pid = CP} = CD, + CH, TRs, Action, Bin) + when is_list(TRs) andalso + is_binary(Bin) andalso + (node(CP) =:= node()) -> + + %% d("send_request -> entry with" + %% "~n TRs: ~p", [TRs]), + + ?report_trace(CD, "send_request - multi transaction", [TRs]), + + #conn_data{request_timer = InitTimer, + long_request_timer = LongTimer} = CD, + insert_requests(CD, CH, TRs, Action, Bin, + InitTimer, LongTimer), + case megaco_messenger_misc:send_message(CD, false, Bin) of + {error, Reason} -> + cancel_requests(CD, TRs, Reason); + {ok, _} -> + ignore + end; + +%% This is the case where we are not on the node where the +%% transport process run. +send_request(#conn_data{control_pid = CP} = CD, + CH, TransInfo, Action, Bin) + when node(CP) =/= node() -> + + ?report_trace(CD, "send_request - remote", [TransInfo]), + + InitTimer = infinity, + LongTimer = infinity, + insert_requests(CD, CH, TransInfo, Action, Bin, + InitTimer, LongTimer), + Node = node(CP), + Args = [node(), CD, TransInfo, Bin], + rpc:cast(Node, ?MODULE, send_request_remote, Args). + + +insert_requests(_, _, [], _, _, _, _) -> + ok; + +insert_requests(ConnData, ConnHandle, [Serial|Serials], + Action, [Bin|Bins], InitTimer, LongTimer) + when is_integer(Serial) andalso is_binary(Bin) -> + TransId = to_local_trans_id(ConnHandle, Serial), + insert_request(ConnData, ConnHandle, + TransId, Action, Bin, InitTimer, LongTimer), + + insert_requests(ConnData, ConnHandle, Serials, Action, Bins, + InitTimer, LongTimer); + +insert_requests(ConnData, ConnHandle, + [{transactionRequest, TR}|TRs], + Action, Bin, InitTimer, LongTimer) + when is_record(TR, 'TransactionRequest') andalso is_binary(Bin) -> + #'TransactionRequest'{transactionId = Serial} = TR, + TransId = to_local_trans_id(ConnHandle, Serial), + insert_request(ConnData, ConnHandle, + TransId, Action, TR, InitTimer, LongTimer), + + insert_requests(ConnData, ConnHandle, TRs, Action, Bin, + InitTimer, LongTimer). + + +insert_request(ConnData, ConnHandle, TransId, + Action, Data, InitTimer, LongTimer) -> + #megaco_conn_handle{remote_mid = RemoteMid} = ConnHandle, + #conn_data{protocol_version = Version, + user_mod = UserMod, + user_args = UserArgs, + send_handle = SendHandle, + reply_data = ReplyData, + segment_recv_timer = InitSegTimer, + request_keep_alive_timeout = RKATimer} = ConnData, + {WaitFor, CurrTimer} = megaco_timer:init(InitTimer), + M = ?MODULE, + F = request_timeout, + A = [ConnHandle, TransId], + Ref = megaco_monitor:apply_after(M, F, A, WaitFor), + Req = #request{trans_id = TransId, + remote_mid = RemoteMid, + timer_ref = ?SIM({short, Ref}, init_request_timer), + init_timer = InitTimer, + init_long_timer = LongTimer, + curr_timer = CurrTimer, + version = Version, + bytes = {send, Data}, + send_handle = SendHandle, + user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = ReplyData, + init_seg_timer = InitSegTimer, + keep_alive_timer = RKATimer}, + megaco_monitor:insert_request(Req). % Timing problem? + + +send_request_remote(ReplyNode, ConnData, TransInfo, Bin) -> + Action = remote, + ConnHandle = ConnData#conn_data.conn_handle, + ConnData2 = ConnData#conn_data{reply_data = ReplyNode}, + send_request(ConnData2, ConnHandle, TransInfo, Action, Bin). + +prepare_req_send_options(CallOrCast, ConnHandle, Options, Actions) -> + %% Ensures that two processes cannot get same transaction id. + %% Bad send options may cause spurious transaction id to be consumed. + Incr = number_of_transactions(Actions), + case megaco_config:incr_trans_id_counter(ConnHandle, Incr) of + {ok, ConnData} -> + override_req_send_options(CallOrCast, ConnData, Options); + {error, Reason} -> + {error, Reason} + end. + +number_of_transactions([Action|_]) when is_tuple(Action) -> + 1; +number_of_transactions(ActionsList) -> + length(ActionsList). + +override_req_send_options(ReplyAction, ConnData, [{Key, Val} | Tail]) -> + case Key of + protocol_version -> + ConnData2 = ConnData#conn_data{protocol_version = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + send_handle -> + ConnData2 = ConnData#conn_data{send_handle = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + request_timer -> + case megaco_config:verify_val(Key, Val) of + true -> + ConnData2 = ConnData#conn_data{request_timer = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + false -> + {error, {bad_send_option, {Key, Val}}} + end; + long_request_timer -> + case megaco_config:verify_val(Key, Val) of + true -> + ConnData2 = ConnData#conn_data{long_request_timer = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + false -> + {error, {bad_send_option, {Key, Val}}} + end; + call_proxy_gc_timeout when (ReplyAction =:= call) orelse + (ReplyAction =:= any) -> + case megaco_config:verify_val(Key, Val) of + true -> + ConnData2 = + ConnData#conn_data{call_proxy_gc_timeout = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + false -> + {error, {bad_send_option, {Key, Val}}} + end; + request_keep_alive_timeout when (ReplyAction =:= cast) orelse + (ReplyAction =:= any) -> + case megaco_config:verify_val(Key, Val) of + true -> + ConnData2 = + ConnData#conn_data{request_keep_alive_timeout = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + false -> + {error, {bad_send_option, {Key, Val}}} + end; + reply_data -> + ConnData2 = ConnData#conn_data{reply_data = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + user_mod when is_atom(Val) -> + ConnData2 = ConnData#conn_data{user_mod = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + user_args when is_list(Val) -> + ConnData2 = ConnData#conn_data{user_args = Val}, + override_req_send_options(ReplyAction, ConnData2, Tail); + trans_req when Val =:= false -> + %% We only allow turning the transaction-sender off, since + %% the opposite (turning it on) would causing to much headake... + %% This will allow not using the transaction sender for + %% occasional messages + ConnData2 = ConnData#conn_data{trans_req = Val, + trans_sender = undefined}, + override_req_send_options(ReplyAction, ConnData2, Tail); + _Bad -> + {error, {bad_send_option, {Key, Val}}} + end; +override_req_send_options(_ReplyAction, ConnData, []) -> + {ok, ConnData}. + +override_rep_send_options(ConnData, [{Key, Val} | Tail]) -> + case Key of + protocol_version -> + ConnData2 = ConnData#conn_data{protocol_version = Val}, + override_rep_send_options(ConnData2, Tail); + send_handle -> + ConnData2 = ConnData#conn_data{send_handle = Val}, + override_rep_send_options(ConnData2, Tail); + reply_timer -> + case megaco_config:verify_val(Key, Val) of + true -> + ConnData2 = ConnData#conn_data{reply_timer = Val}, + override_rep_send_options(ConnData2, Tail); + false -> + {error, {bad_send_option, {Key, Val}}} + end; + trans_req when Val =:= false -> + %% We only allow turning the transaction-sender off, since + %% the opposite (turning it on) would causing to much headake... + %% This will allow not using the transaction sender for + %% occasional messages + ConnData2 = ConnData#conn_data{trans_req = Val, + trans_sender = undefined}, + override_rep_send_options(ConnData2, Tail); + _Bad -> + {error, {bad_send_option, {Key, Val}}} + end; +override_rep_send_options(ConnData, []) -> + {ok, ConnData}. + + +%% ---- +%% This list is allways atleast one (list of actions) long. +%% ---- +%% The proper number of transaction id numbers has already +%% been "allocated", and the connection data record is +%% updated accordingly. +encode_requests(#conn_data{trans_req = true, + trans_sender = Pid, + serial = LastSerial} = CD, ActionsList) + when is_pid(Pid) -> + (catch encode_requests(CD, LastSerial, + lists:reverse(ActionsList), [], [])); +encode_requests(#conn_data{serial = LastSerial} = CD, ActionsList) -> + %% We shall not accumulate transactions. + %% This means that we shall not encode + %% the transactions individually (and send + %% them to the sender process, which + %% accumulate transactions for later sending), + %% Instead we encode the entire message directly. + %% => We shall return one binary, containing, + %% possibly, many transactions + encode_requests_in_msg(CD, LastSerial, lists:reverse(ActionsList)). + + +%% This means that we shall compose and encode one complete +%% megaco message, containing one or more transactions. +encode_requests_in_msg(CD, LastSerial, ActionsList) -> + TRs = compose_requests_in_msg(LastSerial, ActionsList, []), + Body = {transactions, TRs}, + Res = megaco_messenger_misc:encode_body(CD, + "encode trans request(s) msg", + Body), + case Res of + {ok, Bin} -> + {ok, TRs, Bin}; + Error -> + Error + end. + +compose_requests_in_msg(_S, [], TRs) -> + TRs; +compose_requests_in_msg(Serial, [A|As], Acc) -> + TR = #'TransactionRequest'{transactionId = Serial, + actions = A}, + compose_requests_in_msg(Serial - 1, As, [{transactionRequest, TR}|Acc]). + + +%% We have done the encoding in reverse order, so there +%% is no need to reverse now. +encode_requests(_, _, [], Serials, EncodedTRs) -> + {ok, Serials, EncodedTRs}; +encode_requests(CD, Serial, [Actions|ActionsList], Serials, EncodedTRs) -> + case do_encode_request(CD, Serial, Actions) of + {ok, Bin} -> + encode_requests(CD, Serial - 1, ActionsList, + [Serial|Serials], [Bin|EncodedTRs]); + Error -> + throw(Error) + end. + + +do_encode_request(CD, Serial, Actions) -> + TR = #'TransactionRequest'{transactionId = Serial, + actions = Actions}, + megaco_messenger_misc:encode_trans_request(CD, TR). + + +imm_ack_req(Counter, when_pending_sent) when (Counter > 0) -> 'NULL'; +imm_ack_req(_Counter, when_pending_sent) -> asn1_NOVALUE; +imm_ack_req(_Counter, ImmAck) -> ImmAck. + +maybe_send_reply(#conn_data{sent_pending_limit = Limit} = ConnData, + TransId, Result, SendOpts, ImmAck) -> + + %% d("maybe_send_reply -> entry with" + %% "~n Limit: ~p" + %% "~n TransId: ~p" + %% "~n Result: ~p" + %% "~n SendOpts: ~p" + %% "~n ImmAck: ~p", [Limit, TransId, Result, SendOpts, ImmAck]), + + %% Pending limit + %% Before we can send the reply we must check that we have + %% not passed the pending limit (and sent an error message). + case check_pending_limit(Limit, sent, TransId) of + {ok, Counter} -> + case override_rep_send_options(ConnData, SendOpts) of + {ok, ConnData2} -> + send_reply(ConnData2, Result, + imm_ack_req(Counter, ImmAck)); + Error -> + Error + end; + aborted -> + {error, aborted} + end. + +encode_reply(CD, TR) -> + megaco_messenger_misc:encode_trans_reply(CD, TR). + +send_reply(#conn_data{serial = Serial, + trans_req = TransReq, + trans_sender = TransSnd} = CD, TransRes, ImmAck) -> + + %% Encapsule the transaction result into a reply message + + %% d("send_reply -> entry with" + %% "~n Serial: ~p" + %% "~n TransRes: ~p" + %% "~n ImmAck: ~p", [Serial, TransRes, ImmAck]), + + TR = #megaco_transaction_reply{transactionId = Serial, + immAckRequired = ImmAck, + transactionResult = TransRes}, + case encode_reply(CD, TR) of + {ok, Bin} when is_binary(Bin) andalso (TransReq =:= true) -> + ?rt2("send_reply - pass it on to the transaction sender", + [size(Bin)]), + megaco_trans_sender:send_reply(TransSnd, Bin), + {ok, Bin}; + + {ok, Bin} when is_binary(Bin) -> + ?rt2("send_reply - encoded", [size(Bin)]), + TraceLabel = "send trans reply", + Body = {transactions, [Bin]}, + megaco_messenger_misc:send_body(CD, TraceLabel, Body); + + {ok, Bins} when is_list(Bins) -> + ?rt2("send_reply - encoded (segmented)", [length(Bins)]), + Res = send_reply_segments(CD, Bins), + {ok, Res}; + + {error, not_implemented} -> + %% Oups, we cannot segment regardless the config, + %% so pack it all into one message and hope for + %% the best... + ?rt2("send_reply - cannot encode separate transactions", []), + TR2 = megaco_messenger_misc:transform_transaction_reply(CD, TR), + Body = {transactions, [{transactionReply, TR2}]}, + megaco_messenger_misc:send_body(CD, "encode trans reply", Body); + + {error, Reason} = Error -> + Code = ?megaco_internal_gateway_error, + Text = "encode transaction reply", + ED = #'ErrorDescriptor'{errorCode = Code, + errorText = Text}, + Res = {transactionError, ED}, + TR2 = #megaco_transaction_reply{transactionId = Serial, + transactionResult = Res}, + TR3 = megaco_messenger_misc:transform_transaction_reply(CD, TR2), + TraceLabel = "<ERROR> encode trans reply body failed", + ?report_important(CD, TraceLabel, [TR, TR3, ED, Error]), + error_msg("failed encoding transaction reply body: ~s", + [format_encode_error_reason(Reason)]), + Body = {transactions, [{transactionReply, TR3}]}, + megaco_messenger_misc:send_body(CD, TraceLabel, Body), + Error + end. + +send_reply_segments(CD, Bins) -> + TraceLabelPre = "send segmented trans reply", + (catch send_reply_segments(CD, TraceLabelPre, Bins)). + +send_reply_segments(#conn_data{segment_send = infinity} = CD, Label, Bins) -> + send_reply_segments(CD, Label, length(Bins), Bins); + +send_reply_segments(#conn_data{segment_send = K} = CD, Label, Bins) + when is_integer(K) andalso (K =< length(Bins)) -> + send_reply_segments(CD, Label, K, Bins); + +send_reply_segments(#conn_data{segment_send = K} = CD, Label, Bins) + when is_integer(K) -> + send_reply_segments(CD, Label, length(Bins), Bins). + +send_reply_segments(CD, Label, K, Bins) -> + send_reply_segments(CD, Label, K, Bins, []). + +send_reply_segments(_CD, _Label, 0, Bins, Sent) -> + ?rt2("send_reply_segments - done", [Sent, Bins]), + {Sent, Bins}; +send_reply_segments(CD, TraceLabelPre, K, [{SN, Bin}|Bins], Sent) -> + case send_reply_segment(CD, TraceLabelPre, SN, Bin) of + {ok, Bin2} -> + ?rt2("send_reply_segments - send", [K, SN]), + send_reply_segments(CD, TraceLabelPre, K-1, + Bins, [{SN, Bin2}|Sent]); + Error -> + throw(Error) + end. + +send_reply_segment(CD, TraceLabelPre, SN, Bin) -> + Label = lists:flatten(io_lib:format("~s[~w]", [TraceLabelPre, SN])), + Body = {transactions, [Bin]}, + megaco_messenger_misc:send_body(CD, Label, Body). + + +format_encode_error_reason(Reason) -> + FS = + case Reason of + {Mod, Func, [EC, Msg], {AE, CS}} when is_atom(Mod) andalso + is_atom(Func) andalso + is_list(EC) and + is_tuple(Msg) and + is_list(CS) -> + io_lib:format("~n Encode module: ~w" + "~n Func: ~w" + "~n Encode config: ~w" + "~n Message part: ~p" + "~n Actual error: ~p" + "~n Call stack: ~w", + [Mod, Func, EC, Msg, AE, CS]); + + {Mod, Func, [EC, Msg], AE} when is_atom(Mod) andalso + is_atom(Func) andalso + is_list(EC) andalso + is_tuple(Msg) -> + io_lib:format("~n Encode module: ~w" + "~n Func: ~w" + "~n Encode config: ~w" + "~n Message part: ~p" + "~n Actual error: ~p", + [Mod, Func, EC, Msg, AE]); + + {Mod, [EC, Msg], {AE, CS}} when is_atom(Mod) andalso + is_list(EC) andalso + is_tuple(Msg) andalso + is_list(CS) -> + io_lib:format("~n Encode module: ~w" + "~n Encode config: ~w" + "~n Message part: ~p" + "~n Actual error: ~p" + "~n Call stack: ~w", + [Mod, EC, Msg, AE, CS]); + + {Mod, [EC, Msg], AE} when is_atom(Mod) andalso + is_list(EC) andalso + is_tuple(Msg) -> + io_lib:format("~n Encode module: ~w" + "~n Encode config: ~w" + "~n Message part: ~p" + "~n Actual error: ~p", + [Mod, EC, Msg, AE]); + + Error -> + io_lib:format("~n ~w", [Error]) + end, + lists:flatten(FS). + + +%% Presumably the user would return immediately (with {pending, Data}) if it +%% knows or suspects a request to take a long time to process. +%% For this reason we assume that handling a resent request +%% could not have caused an update of the pending limit counter. +maybe_send_pending(#conn_data{sent_pending_limit = Limit} = ConnData, + TransId) -> + case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of + ok -> + send_pending(ConnData); + error -> + SendReply = send_pending_limit_error(ConnData), + {aborted, SendReply}; + aborted -> + {aborted, ignore} + end. + + +send_pending(#conn_data{serial = Serial, + trans_req = true, + trans_sender = Pid}) -> + megaco_trans_sender:send_pending(Pid, Serial); +send_pending(#conn_data{serial = Serial} = CD) -> + %% Encapsule the transaction result into a pending message + TP = #'TransactionPending'{transactionId = Serial}, + Body = {transactions, [{transactionPending, TP}]}, + megaco_messenger_misc:send_body(CD, "send trans pending", Body). + + +maybe_send_ack('NULL', #conn_data{serial = Serial, + trans_ack = true, + trans_sender = Pid}) -> + megaco_trans_sender:send_ack_now(Pid, Serial); +maybe_send_ack('NULL', CD) -> + send_ack(CD); +maybe_send_ack(_, #conn_data{auto_ack = false}) -> + ignore; +maybe_send_ack(_, #conn_data{serial = Serial, + trans_ack = true, + trans_sender = Pid}) + when is_pid(Pid) -> + %% Send (later) via the transaction sender + megaco_trans_sender:send_ack(Pid, Serial), + ok; +maybe_send_ack(_, CD) -> + %% Send now + send_ack(CD). + + +send_ack(#conn_data{serial = Serial} = CD) -> + %% Encapsule the transaction result into a ack message + TRA = #'TransactionAck'{firstAck = Serial}, + Body = {transactions, [{transactionResponseAck, [TRA]}]}, + megaco_messenger_misc:send_body(CD, "send trans ack", Body). + + +send_segment_reply(#conn_data{serial = Serial} = CD, SegNo) -> + SR = #'SegmentReply'{transactionId = Serial, + segmentNumber = SegNo}, + Body = {transactions, [{segmentReply, SR}]}, + megaco_messenger_misc:send_body(CD, "send segment reply", Body). + +send_segment_reply(#conn_data{serial = Serial} = CD, SegNo, Complete) -> + SR = #'SegmentReply'{transactionId = Serial, + segmentNumber = SegNo, + segmentationComplete = Complete}, + Body = {transactions, [{segmentReply, SR}]}, + megaco_messenger_misc:send_body(CD, "send segment reply", Body). + +send_segment_reply_complete(CD, SegNo) -> + send_segment_reply(CD, SegNo, 'NULL'). + + +send_pending_limit_error(ConnData) -> + ?report_pending_limit_exceeded(ConnData), + Code = ?megaco_number_of_transactionpending_exceeded, + Reason = "Pending limit exceeded", + send_trans_error(ConnData, Code, Reason). + +send_trans_error(ConnData, Code, Reason) -> + %% Encapsulate the transaction error into a reply message + ED = #'ErrorDescriptor'{errorCode = Code, errorText = Reason}, + Serial = ConnData#conn_data.serial, + %% Version = ConnData#conn_data.protocol_version, + TransRes = {transactionError, ED}, + TR = #megaco_transaction_reply{transactionId = Serial, + transactionResult = TransRes}, + TR2 = megaco_messenger_misc:transform_transaction_reply(ConnData, TR), + Body = {transactions, [{transactionReply, TR2}]}, + case megaco_messenger_misc:send_body(ConnData, "send trans error", Body) of + {error, Reason2} -> + ?report_important(ConnData, + "<ERROR> failed sending transaction error", + [Body, {error, Reason2}]), + error; + _ -> + ok + end. + + +send_message_error(ConnData, Code, Reason) -> + ED = #'ErrorDescriptor'{errorCode = Code, errorText = Reason}, + Body = {messageError, ED}, + case megaco_messenger_misc:send_body(ConnData, "send trans error", Body) of + {error, Reason2} -> + ?report_important(ConnData, + "<ERROR> failed sending message error", + [Body, {error, Reason2}]), + error; + _ -> + ok + end. + + +cancel(ConnHandle, Reason) when is_record(ConnHandle, megaco_conn_handle) -> + case megaco_config:lookup_local_conn(ConnHandle) of + [CD] -> + megaco_config:update_conn_info(CD, cancel, true), + do_cancel(ConnHandle, Reason, CD#conn_data{cancel = true}), + megaco_config:update_conn_info(CD, cancel, false), + ok; + [] -> + ConnData = fake_conn_data(ConnHandle), + do_cancel(ConnHandle, Reason, ConnData) + end. + +do_cancel(ConnHandle, Reason, ConnData) -> + ?report_trace(ConnData, "cancel", [ConnHandle, Reason]), + LocalMid = ConnHandle#megaco_conn_handle.local_mid, + RemoteMid = ConnHandle#megaco_conn_handle.remote_mid, + ReqTransIdPat = #trans_id{mid = LocalMid, _ = '_'}, + ReqPat = #request{trans_id = ReqTransIdPat, + remote_mid = RemoteMid, + _ = '_'}, + CancelReq = fun(Req) -> + cancel_request(ConnData, Req, Reason), + {_Type, Ref} = Req#request.timer_ref, %% OTP-4843 + megaco_monitor:cancel_apply_after(Ref) + end, + Requests = megaco_monitor:match_requests(ReqPat), + lists:foreach(CancelReq, Requests), + RemoteMid = ConnHandle#megaco_conn_handle.remote_mid, + RepTransIdPat = #trans_id{mid = RemoteMid, _ = '_'}, % BUGBUG List here? + RepPat = #reply{trans_id = RepTransIdPat, + local_mid = LocalMid, + _ = '_'}, + CancelRep = fun(Rep) -> + cancel_reply(ConnData, Rep, Reason) + end, + Replies = megaco_monitor:match_replies(RepPat), + lists:foreach(CancelRep, Replies), + ok. + +cancel_requests(_ConnData, [], _Reason) -> + ok; +cancel_requests(ConnData, [{transactionRequest,TR}|TRs], Reason) -> + #'TransactionRequest'{transactionId = TransId0} = TR, + TransId = to_local_trans_id(ConnData#conn_data.conn_handle, TransId0), + case megaco_monitor:lookup_request(TransId) of + [] -> + ignore; + [Req] when is_record(Req, request) -> + cancel_request(ConnData, Req, Reason) + end, + cancel_requests(ConnData, TRs, Reason). + +cancel_request(ConnData, Req, Reason) -> + ?report_trace(ignore, "cancel request", [Req]), + ?TC_AWAIT_CANCEL_EVENT(), + TransId = Req#request.trans_id, + Version = Req#request.version, + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = {error, Reason}, + ConnData2 = ConnData#conn_data{protocol_version = Version, + user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + cancel_request2(ConnData2, TransId, UserReply). + +cancel_request2(ConnData, TransId, UserReply) -> + megaco_monitor:delete_request(TransId), + megaco_config:del_pending_counter(recv, TransId), % OTP-7189 + Serial = TransId#trans_id.serial, + ConnData2 = ConnData#conn_data{serial = Serial}, + return_reply(ConnData2, TransId, UserReply). + + +return_reply(ConnData, TransId, UserReply) -> + Extra = ?default_user_callback_extra, + return_reply(ConnData, TransId, UserReply, Extra). + +return_reply(ConnData, TransId, UserReply, Extra) -> + ?report_trace(ConnData, "callback: trans reply", [UserReply]), + Version = ConnData#conn_data.protocol_version, + UserData = ConnData#conn_data.reply_data, + case ConnData#conn_data.reply_action of + call when is_pid(UserData) -> + ?report_trace(ConnData, "callback: (call) trans reply", + [UserReply]), + Pid = UserData, + Pid ! {?MODULE, TransId, Version, UserReply, Extra}; + cast -> + ?report_trace(ConnData, "callback: (cast) trans reply", [UserReply]), + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + ConnHandle = ConnData#conn_data.conn_handle, + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, UserReply, UserData | UserArgs]; + _ -> + [ConnHandle, Version, UserReply, UserData, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_trans_reply, Args)), + ?report_debug(ConnData, "return: (cast) trans reply", + [UserReply, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("transaction reply callback failed: ~w", + [Res]), + ok + end, + Res; + remote -> + ?report_trace(ConnData, "callback: (remote) trans reply", [UserReply]), + Node = UserData, + Args = [ConnData, UserReply, Extra], + rpc:cast(Node, ?MODULE, receive_reply_remote, Args) + end. + +receive_reply_remote(ConnData, UserReply) -> + Extra = ?default_user_callback_extra, + receive_reply_remote(ConnData, UserReply, Extra). + +receive_reply_remote(ConnData, UserReply, Extra) -> + TransId = to_local_trans_id(ConnData), + case (catch megaco_monitor:lookup_request(TransId)) of + [#request{timer_ref = {_Type, Ref}} = Req] -> %% OTP-4843 + %% Don't care about Req and Rep version diff + megaco_monitor:delete_request(TransId), + megaco_monitor:cancel_apply_after(Ref), % OTP-4843 + megaco_config:del_pending_counter(recv, TransId), % OTP-7189 + + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply, Extra); + + _ -> + ?report_trace(ConnData, "remote reply (no receiver)", + [UserReply]), + return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra) + end. + +cancel_reply(ConnData, #reply{state = waiting_for_ack} = Rep, Reason) -> + ?report_trace(ignore, "cancel reply [waiting_for_ack]", [Rep]), + megaco_monitor:cancel_apply_after(Rep#reply.pending_timer_ref), + Serial = (Rep#reply.trans_id)#trans_id.serial, + ConnData2 = ConnData#conn_data{serial = Serial}, + T = #'TransactionAck'{firstAck = Serial}, + Extra = ?default_user_callback_extra, + handle_ack(ConnData2, {error, Reason}, Rep, T, Extra); + +cancel_reply(_ConnData, #reply{state = aborted} = Rep, _Reason) -> + ?report_trace(ignore, "cancel reply [aborted]", [Rep]), + #reply{trans_id = TransId, + timer_ref = ReplyRef, + pending_timer_ref = PendingRef} = Rep, + megaco_monitor:delete_reply(TransId), + megaco_monitor:cancel_apply_after(ReplyRef), + megaco_monitor:cancel_apply_after(PendingRef), % Still running? + megaco_config:del_pending_counter(sent, TransId), % Still existing? + ok; + +cancel_reply(_ConnData, Rep, ignore) -> + ?report_trace(ignore, "cancel reply [ignore]", [Rep]), + #reply{trans_id = TransId, + timer_ref = ReplyRef, + pending_timer_ref = PendingRef} = Rep, + megaco_monitor:delete_reply(TransId), + megaco_monitor:cancel_apply_after(ReplyRef), + megaco_monitor:cancel_apply_after(PendingRef), % Still running? + megaco_config:del_pending_counter(sent, TransId), % Still existing? + ok; + +cancel_reply(_CD, _Rep, _Reason) -> + ok. + + +request_keep_alive_timeout(ConnHandle, TransId) -> + megaco_config:del_pending_counter(ConnHandle, TransId), + megaco_monitor:lookup_request(TransId), + ok. + + +request_timeout(ConnHandle, TransId) -> + ?rt1(ConnHandle, "request timeout", [TransId]), + case megaco_monitor:lookup_request(TransId) of + [] -> + request_not_found_ignore; + [Req] when is_record(Req, request) -> + case megaco_config:lookup_local_conn(ConnHandle) of + [CD] when (CD#conn_data.cancel =:= true) -> + cancel_in_progress_ignore; + [CD] -> + incNumTimerRecovery(ConnHandle), + do_request_timeout(ConnHandle, TransId, CD, Req); + [] when ConnHandle#megaco_conn_handle.remote_mid =:= preliminary_mid -> + %% There are two possibillities: + %% 1) The connection has just been upgraded from a + %% preliminary to a real connection. So this timeout + %% is just a glitch. E.g. between the removel of this + %% ConnHandle and the timer. + %% 2) The first message sent, the service-change, got no + %% reply (UDP without three-way-handshake). + %% And then the other side (MGC) sends a request, + %% which causes an auto-upgrade + request_timeout_upgraded(ConnHandle, Req); + [] -> + incNumTimerRecovery(ConnHandle), + ConnData = fake_conn_data(ConnHandle), + do_request_timeout(ConnHandle, TransId, ConnData, Req) + end + end. + +request_timeout_upgraded(ConnHandle, Req) -> + CD = fake_conn_data(ConnHandle), + cancel_request(CD, Req, timeout). + +do_request_timeout(ConnHandle, TransId, ConnData, + #request{curr_timer = CurrTimer} = Req) -> + + ?rt1(ConnHandle, "process request timeout", [TransId, CurrTimer]), + + SendHandle = Req#request.send_handle, + Version = Req#request.version, + ConnData2 = ConnData#conn_data{send_handle = SendHandle, + protocol_version = Version}, + case CurrTimer of + timeout -> %%%%%%% + cancel_request(ConnData2, Req, timeout), + timeout1; + + %% Restartable timer + %% (max_retries = infinity_restartable) + {_, timeout} -> + cancel_request(ConnData2, Req, timeout), + timeout2; + + Timer -> + {SendOrNoSend, Data} = Req#request.bytes, + case SendOrNoSend of + send -> + case maybe_encode(ConnData2, Data) of + {ok, Bin} -> + ?report_trace(ConnData2, "re-send trans request", + [{bytes, Bin}]), + case maybe_send_message(ConnData2, true, Bin) of + ok -> + sent1_ignore; + {ok, _} -> + sent2_ignore; + {error, Reason} -> + ?report_important(ConnData2, + "<ERROR> " + "re-send trans " + "request failed", + [{bytes, Bin}, + {error, Reason}]) + end; + + {error, Reason} -> + %% Since it was possible to encode the original + %% message this should really never happen... + ?report_important(ConnData2, + "<ERROR> " + "re-send trans request failed", + [{transaction, + Req#request.bytes}, + {error, Reason}]) + end; + no_send -> + not_sent_ok + end, + {WaitFor, Timer2} = megaco_timer:restart(Timer), + OptBin = opt_garb_binary(Timer2, Data), + {Type, _} = Req#request.timer_ref, + M = ?MODULE, + F = request_timeout, + A = [ConnHandle, TransId], + Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor), + NewFields = + [{#request.bytes, {SendOrNoSend, OptBin}}, + {#request.timer_ref, {Type, Ref2}}, + {#request.curr_timer, Timer2}], + megaco_monitor:update_request_fields(TransId, NewFields), % Timing problem + {restarted, WaitFor, Timer2} + + end. + +maybe_encode(#conn_data{trans_req = false} = CD, {_Serial, Bin}) + when is_binary(Bin) -> + Body = {transactions, [{transactionRequest, Bin}]}, + megaco_messenger_misc:encode_body(CD, "encode trans request msg", Body); +maybe_encode(_CD, {_Serial, Bin} = D) when is_binary(Bin) -> + {ok, D}; +maybe_encode(#conn_data{trans_req = true, + trans_sender = Pid} = CD, + #'TransactionRequest'{transactionId = Serial} = TR) + when is_pid(Pid) -> + case megaco_messenger_misc:encode_trans_request(CD, TR) of + {ok, Bin} -> + {ok, {Serial, Bin}}; + Error -> + Error + end; +maybe_encode(CD, TR) + when is_record(TR, 'TransactionRequest') -> + Body = {transactions, [{transactionRequest, TR}]}, + megaco_messenger_misc:encode_body(CD, "encode trans request msg", Body); +maybe_encode(_CD, Trash) -> + {error, {invalid_bin, Trash}}. + +maybe_send_message(CD, Resend, Bin) when is_binary(Bin) -> + megaco_messenger_misc:send_message(CD, Resend, Bin); +maybe_send_message(#conn_data{trans_sender = Pid}, _Resend, {Serial, Bin}) + when is_pid(Pid) andalso is_integer(Serial) andalso is_binary(Bin) -> + megaco_trans_sender:send_req(Pid, Serial, Bin). + + +reply_timeout(ConnHandle, TransId, timeout) -> + handle_reply_timer_timeout(ConnHandle, TransId); + +%% This means that infinity_restartable was used for max_retries. +%% There is currently no reason to use this for the reply_timeout, +%% since there is no external event to restart the timer! +reply_timeout(ConnHandle, TransId, {_, timeout}) -> + handle_reply_timer_timeout(ConnHandle, TransId); + +reply_timeout(ConnHandle, TransId, Timer) -> + ?report_trace(ConnHandle, "reply timeout", [Timer, TransId]), + + case megaco_monitor:lookup_reply(TransId) of + [] -> + reply_not_found_ignore; + + [#reply{state = waiting_for_ack, + ack_action = {handle_ack, _}} = Rep] -> + case megaco_config:lookup_local_conn(ConnHandle) of + [CD] when (CD#conn_data.cancel =:= true) -> + cancel_in_progress_ignore; + [CD] -> + incNumTimerRecovery(ConnHandle), + do_reply_timeout(ConnHandle, TransId, CD, Timer, Rep); + [] -> + incNumTimerRecovery(ConnHandle), + CD = fake_conn_data(ConnHandle), + do_reply_timeout(ConnHandle, TransId, CD, Timer, Rep) + end; + + [#reply{state = waiting_for_ack, + bytes = Sent} = Rep] when is_list(Sent) -> + case megaco_config:lookup_local_conn(ConnHandle) of + [ConnData] -> + incNumTimerRecovery(ConnHandle), + do_reply_timeout(ConnHandle, TransId, ConnData, + Timer, Rep); + [] -> + incNumTimerRecovery(ConnHandle), + ConnData = fake_conn_data(ConnHandle), + do_reply_timeout(ConnHandle, TransId, ConnData, + Timer, Rep) + end; + + [#reply{state = waiting_for_ack} = Rep] -> + do_reply_timeout(ConnHandle, TransId, Timer, Rep); + + [#reply{state = aborted} = Rep] -> + do_reply_timeout(ConnHandle, TransId, Timer, Rep); + + _ -> + ignore + + end. + +do_reply_timeout(ConnHandle, TransId, ConnData, Timer, + #reply{send_handle = SH, + version = V, + bytes = Bytes} = Rep) when is_binary(Bytes) -> + +%% d("do_reply_timeout -> entry with" +%% "~n ConnHandle: ~p" +%% "~n TransId: ~p" +%% "~n Timer: ~p" +%% "~n Rep: ~p" +%% "~n", [ConnHandle, TransId, Timer, Rep]), + + CD = ConnData#conn_data{send_handle = SH, + protocol_version = V}, + + ?rt1(CD, "re-send trans reply", [{bytes, Bytes}]), + case megaco_messenger_misc:send_message(CD, true, Bytes) of + {ok, _} -> + ignore; + {error, Reason} -> + ?report_important(CD, "<ERROR> re-send trans reply failed", + [{bytes, Bytes}, {error, Reason}]) + end, + do_reply_timeout(ConnHandle, TransId, Timer, Rep); + +do_reply_timeout(ConnHandle, TransId, ConnData, Timer, + #reply{send_handle = SH, + version = V, + bytes = Sent} = Rep) when is_list(Sent) -> + +%% d("do_reply_timeout -> entry with" +%% "~n ConnHandle: ~p" +%% "~n TransId: ~p" +%% "~n Timer: ~p" +%% "~n Rep: ~p" +%% "~n", [ConnHandle, TransId, Timer, Rep]), + + CD = ConnData#conn_data{send_handle = SH, + protocol_version = V}, + + ReSend = + fun({SN, Bytes}) -> + ?rt1(CD, "re-send segmented trans reply", + [{segment_no, SN}, {bytes, Bytes}]), + case megaco_messenger_misc:send_message(CD, true, Bytes) of +%% ok -> +%% ignore; + {ok, _} -> + ignore; + {error, Reason} -> + ?report_important(CD, + "<ERROR> re-send segmented " + "trans reply failed", + [{segment_no, SN}, + {bytes, Bytes}, + {error, Reason}]) + end + end, + lists:foreach(ReSend, Sent), + do_reply_timeout(ConnHandle, TransId, Timer, Rep). + +do_reply_timeout(ConnHandle, TransId, Timer, #reply{bytes = Bytes}) -> + {WaitFor, Timer2} = megaco_timer:restart(Timer), + OptBin = case Bytes of + Bin when is_binary(Bin) -> + opt_garb_binary(Timer2, Bin); + Sent when is_list(Sent) -> + Garb = fun(Bin) -> opt_garb_binary(Timer2, Bin) end, + [{SN, Garb(Bin)} || {SN, Bin} <- Sent] + end, + M = ?MODULE, + F = reply_timeout, + A = [ConnHandle, TransId, Timer2], + Ref2 = megaco_monitor:apply_after(M, F, A, WaitFor), + NewFields = + [{#reply.bytes, OptBin}, + {#reply.timer_ref, Ref2}], + megaco_monitor:update_reply_fields(TransId, NewFields), % Timing problem? + {restarted, WaitFor, Timer2}. + + +handle_reply_timer_timeout(ConnHandle, TransId) -> + ?report_trace(ConnHandle, "handle reply timeout", [timeout, TransId]), + incNumTimerRecovery(ConnHandle), + %% OTP-4378 + case megaco_monitor:lookup_reply(TransId) of + [#reply{state = waiting_for_ack} = Rep] -> + Serial = (Rep#reply.trans_id)#trans_id.serial, + ConnData = + case megaco_config:lookup_local_conn(ConnHandle) of + [ConnData0] -> + ConnData0; + [] -> + fake_conn_data(ConnHandle) + end, + ConnData2 = ConnData#conn_data{serial = Serial}, + T = #'TransactionAck'{firstAck = Serial}, + Extra = ?default_user_callback_extra, + handle_ack(ConnData2, {error, timeout}, Rep, T, Extra); + [#reply{pending_timer_ref = Ref, % aborted? + bytes = SegSent}] -> % may be a binary + megaco_monitor:cancel_apply_after(Ref), + cancel_segment_timers(SegSent), + megaco_monitor:delete_reply(TransId), + megaco_config:del_pending_counter(sent, TransId); + [] -> + ignore_reply_removed + end. + +%% segment_reply_timeout(ConnHandle, TransId, SN, timeout) -> +%% ?report_trace(ConnHandle, "segment reply timeout", [timeout, SN, TransId]), +%% D = fun({_, _, SegRef}) -> +%% megaco_monitor:cancel_apply_after(SegRef) +%% end, +%% incNumTimerRecovery(ConnHandle), +%% %% OTP-4378 +%% case megaco_monitor:lookup_reply(TransId) of +%% [#reply{state = waiting_for_ack, +%% bytes = Sent} = Rep] -> +%% Serial = (Rep#reply.trans_id)#trans_id.serial, +%% ConnData = +%% case megaco_config:lookup_local_conn(ConnHandle) of +%% [ConnData0] -> +%% ConnData0; +%% [] -> +%% fake_conn_data(ConnHandle) +%% end, +%% ConnData2 = ConnData#conn_data{serial = Serial}, +%% T = #'TransactionAck'{firstAck = Serial}, +%% lists:foreach(D, Sent), +%% Extra = ?default_user_callback_extra, +%% handle_ack(ConnData2, {error, timeout}, Rep, T, Extra); +%% [#reply{pending_timer_ref = Ref, +%% bytes = Sent}] -> % aborted? +%% lists:foreach(D, Sent), +%% megaco_monitor:cancel_apply_after(Ref), +%% megaco_monitor:delete_reply(TransId), +%% megaco_config:del_pending_counter(sent, TransId); + +%% [] -> +%% ignore + +%% end. + +%% segment_reply_timeout(ConnHandle, TransId, SN, Timer) -> +%% ?report_trace(ConnHandle, "reply timeout", [Timer, SN, TransId]), + +%% %% d("reply_timeout -> entry with" +%% %% "~n ConnHandle: ~p" +%% %% "~n TransId: ~p" +%% %% "~n Timer: ~p", [ConnHandle, TransId, Timer]), + +%% case megaco_monitor:lookup_reply(TransId) of +%% [] -> +%% ignore; % Trace ?? + +%% [#reply{state = waiting_for_ack, +%% bytes = ack_action = {handle_ack, _}} = Rep] -> +%% case megaco_config:lookup_local_conn(ConnHandle) of +%% [ConnData] -> +%% incNumTimerRecovery(ConnHandle), +%% do_reply_timeout(ConnHandle, TransId, ConnData, +%% Timer, Rep); +%% [] -> +%% incNumTimerRecovery(ConnHandle), +%% ConnData = fake_conn_data(ConnHandle), +%% do_reply_timeout(ConnHandle, TransId, ConnData, +%% Timer, Rep) +%% end; + +%% [#reply{state = waiting_for_ack} = Rep] -> +%% do_reply_timeout(ConnHandle, TransId, Timer, Rep); + +%% [#reply{state = aborted} = Rep] -> +%% do_reply_timeout(ConnHandle, TransId, Timer, Rep); + +%% _ -> +%% ignore + +%% end. + + +%% This clause is to catch the timers started prior to the code-upgrade +pending_timeout(#conn_data{conn_handle = CH}, TransId, Timer) -> + ?report_trace(CH, "pending timeout(1)", [Timer, TransId]), + pending_timeout(CH, TransId, Timer); + +pending_timeout(ConnHandle, TransId, Timer) -> + ?report_trace(ConnHandle, "pending timeout(2)", [Timer, TransId]), + case megaco_config:lookup_local_conn(ConnHandle) of + [CD] when (CD#conn_data.cancel == true) -> + cancel_in_progress_ignore; + [CD] -> + Serial = TransId#trans_id.serial, + handle_pending_timeout(CD#conn_data{serial = Serial}, + TransId, Timer); + [] -> + no_such_connection_ignore + end. + +handle_pending_timeout(CD, TransId, Timer) -> + ?report_trace(CD, "handle pending timeout", []), + case megaco_monitor:lookup_reply(TransId) of + [#reply{state = State, + handler = Pid} = Rep] when (State =:= prepare) orelse + (State =:= eval_request) -> + + #conn_data{sent_pending_limit = Limit, + conn_handle = ConnHandle} = CD, + + %% ------------------------------------------ + %% + %% Check pending limit + %% + %% ------------------------------------------ + + case check_and_maybe_incr_pending_limit(Limit, sent, TransId) of + ok -> + + %% --------------------------------------------- + %% + %% 1) Send pending message + %% 2) Possibly restart the pending timer + %% + %% --------------------------------------------- + + send_pending(CD), + case Timer of + timeout -> + %% We are done + incNumTimerRecovery(ConnHandle), + timeout1; + {_, timeout} -> + %% We are done + incNumTimerRecovery(ConnHandle), + timeout2; + _ -> + {WaitFor, Timer2} = megaco_timer:restart(Timer), + M = ?MODULE, + F = pending_timeout, + A = [ConnHandle, TransId, Timer2], + PendingRef = + megaco_monitor:apply_after(M, F, A, WaitFor), + %% Timing problem? + megaco_monitor:update_reply_field(TransId, + #reply.pending_timer_ref, + PendingRef), + {restarted, WaitFor, Timer2} + end; + + + error -> + + %% ------------------------------------------ + %% + %% 1) Send 506 error message to other side + %% 2) Notify user + %% 3) Set reply data in aborted state + %% + %% ------------------------------------------- + + send_pending_limit_error(CD), + handle_request_abort_callback(CD, TransId, Pid), + %% Timing problem? + Rep2 = Rep#reply{state = aborted}, + cancel_reply(CD, Rep2, aborted), + pending_limit_error; + + + aborted -> + + %% ------------------------------------------ + %% + %% Pending limit already passed + %% + %% ------------------------------------------- + Rep2 = Rep#reply{state = aborted}, + cancel_reply(CD, Rep2, aborted), + pending_limit_aborted + + end; + [] -> + reply_not_found; % Trace ?? + + [#reply{state = waiting_for_ack}] -> + %% The reply has already been sent + %% No need for any pending trans reply + reply_has_been_sent; + + [#reply{state = aborted} = Rep] -> + %% glitch, but cleanup just the same + cancel_reply(CD, Rep, aborted), + reply_aborted_state + + end. + + +segment_timeout(ConnHandle, TransId, timeout = Timer) -> + ?report_trace(ConnHandle, "segment timeout", [TransId, Timer]), + incNumTimerRecovery(ConnHandle), + case megaco_monitor:lookup_request(TransId) of + [] -> + timeout_not_found_ignore; + + [#request{seg_recv = Segs} = Req] -> + ConnData = + case megaco_config:lookup_local_conn(ConnHandle) of + [ConnData0] -> + ConnData0; + [] -> + fake_conn_data(ConnHandle) + end, + Last = lists:last(lists:sort(Segs)), + All = lists:seq(1,Last), + case All -- Segs of + [] -> + %% The last segment has just arrived, ignore + ok; + Missing -> + %% Send the error message + Code = ?megaco_segments_not_received, + Reason = missing_to_str(Missing), + send_message_error(ConnData, Code, Reason), + + %% Report to the user + UserMod = Req#request.user_mod, + UserArgs = Req#request.user_args, + Action = Req#request.reply_action, + UserData = Req#request.reply_data, + UserReply = {error, {segment_timeout, Missing}}, + ConnData2 = ConnData#conn_data{user_mod = UserMod, + user_args = UserArgs, + reply_action = Action, + reply_data = UserData}, + return_reply(ConnData2, TransId, UserReply) + end + end; + +segment_timeout(ConnHandle, TransId, Timer) -> + ?report_trace(ConnHandle, "segment timeout", [TransId, Timer]), + case megaco_monitor:lookup_request_field(TransId, #request.trans_id) of + {ok, _} -> + {WaitFor, Timer2} = megaco_timer:restart(Timer), + M = ?MODULE, + F = segment_timeout, + A = [ConnHandle, TransId, Timer2], + Ref = megaco_monitor:apply_after(M, F, A, WaitFor), + %% Timing problem? + megaco_monitor:update_request_field(TransId, + #request.seg_timer_ref, + Ref), + {restarted, WaitFor, Timer2}; + _ -> + not_found_ignore + end. + +%% segment_reply_timeout() -> +%% ok. + +missing_to_str(Missing) -> + lists:flatten(missing_to_str2(Missing)). + +missing_to_str2([X]) -> + [integer_to_list(X)]; +missing_to_str2([H|T]) -> + [integer_to_list(H) , "," | missing_to_str2(T)]. + +return_unexpected_trans_reply(ConnData, TransId, + {actionReplies, _} = UserReply, Extra) -> + Trans = make_transaction_reply(ConnData, TransId, UserReply), + return_unexpected_trans(ConnData, Trans, Extra); +return_unexpected_trans_reply(ConnData, TransId, + {transactionError, _} = UserReply, Extra) -> + Trans = make_transaction_reply(ConnData, TransId, UserReply), + return_unexpected_trans(ConnData, Trans, Extra); +return_unexpected_trans_reply(CD, TransId, {error, Reason}, Extra) -> + ?report_important(CD, "unexpected trans reply with error", + [TransId, Reason, Extra]), + ok; +return_unexpected_trans_reply(CD, TransId, Crap, Extra) -> + ?report_important(CD, "unexpected trans reply with crap", + [TransId, Crap, Extra]), + ok. + +return_unexpected_trans(ConnData, Trans) -> + Extra = ?default_user_callback_extra, + return_unexpected_trans(ConnData, Trans, Extra). + +return_unexpected_trans(ConnData, Trans0, Extra) -> + UserMod = ConnData#conn_data.user_mod, + UserArgs = ConnData#conn_data.user_args, + ConnHandle = ConnData#conn_data.conn_handle, + Version = ConnData#conn_data.protocol_version, + Trans = transform_transaction_reply_enc(Version, Trans0), + Args = + case Extra of + ?default_user_callback_extra -> + [ConnHandle, Version, Trans | UserArgs]; + _ -> + [ConnHandle, Version, Trans, Extra | UserArgs] + end, + Res = (catch apply(UserMod, handle_unexpected_trans, Args)), + ?report_debug(ConnData, "return: unexpected trans", + [Trans, {return, Res}]), + case Res of + ok -> + ok; + _ -> + warning_msg("unexpected transaction callback failed: ~w", [Res]), + ok + end, + Res. + + +%%----------------------------------------------------------------- + +to_remote_trans_id(#conn_data{conn_handle = CH, serial = Serial}) -> + Mid = CH#megaco_conn_handle.remote_mid, + #trans_id{mid = Mid, serial = Serial}. + +to_local_trans_id(#conn_data{conn_handle = CH, serial = Serial}) -> + Mid = CH#megaco_conn_handle.local_mid, + #trans_id{mid = Mid, serial = Serial}. + +to_local_trans_id(#conn_data{conn_handle = CH}, [S|_] = Serials) + when is_integer(S) -> + Mid = CH#megaco_conn_handle.local_mid, + [#trans_id{mid = Mid, serial = Serial} || Serial <- Serials]; +to_local_trans_id(#conn_data{conn_handle = CH}, + [{transactionRequest, TR}|_] = TRs) + when is_record(TR, 'TransactionRequest') -> + Mid = CH#megaco_conn_handle.local_mid, + [#trans_id{mid = Mid, serial = Serial} || + {transactionRequest, + #'TransactionRequest'{transactionId = Serial}} <- TRs]; + +to_local_trans_id(#megaco_conn_handle{local_mid = Mid}, Serial) + when is_integer(Serial) -> + #trans_id{mid = Mid, serial = Serial}; +to_local_trans_id(#conn_data{conn_handle = CH}, Serial) + when is_integer(Serial) -> + Mid = CH#megaco_conn_handle.local_mid, + #trans_id{mid = Mid, serial = Serial}. + + +%%----------------------------------------------------------------- + +transform_transaction_reply_dec({'TransactionReply', + TransId, IAR, TransRes}) -> + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes}; +transform_transaction_reply_dec({'TransactionReply', + TransId, IAR, TransRes, + SegNo, SegComplete}) -> + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes, + segmentNumber = SegNo, + segmentationComplete = SegComplete}. + +transform_transaction_reply_enc( + 3, + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes, + segmentNumber = SegNo, + segmentationComplete = SegComplete}) -> + {'TransactionReply', TransId, IAR, TransRes, SegNo, SegComplete}; +transform_transaction_reply_enc( + Version, + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes}) + when (Version < 3) -> + {'TransactionReply', TransId, IAR, TransRes}; +transform_transaction_reply_enc(_, TR) -> + TR. + +make_transaction_reply(#conn_data{protocol_version = Version}, + TransId, TransRes) -> + make_transaction_reply(Version, TransId, asn1_NOVALUE, TransRes). + +%% make_transaction_reply(#conn_data{protocol_version = Version}, +%% TransId, IAR, TransRes) -> +%% make_transaction_reply(Version, TransId, IAR, TransRes); + +make_transaction_reply(3, TransId, IAR, TransRes) -> + {'TransactionReply', TransId, IAR, TransRes, asn1_NOVALUE, asn1_NOVALUE}; +make_transaction_reply(_, TransId, IAR, TransRes) -> + {'TransactionReply', TransId, IAR, TransRes}. + + +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +warning_msg(F, A) -> + ?megaco_warning(F, A). + +error_msg(F, A) -> + ?megaco_error(F, A). + + +%%----------------------------------------------------------------- + +%% d(F) -> +%% d(F,[]). +%% +%% d(F,A) -> +%% d(true,F,A). +%% %% d(get(dbg),F,A). +%% +%% d(true,F,A) -> +%% io:format("*** [~s] ~p:~p ***" +%% "~n " ++ F ++ "~n", +%% [format_timestamp(now()), self(),?MODULE|A]); +%% d(_, _, _) -> +%% ok. +%% +%% format_timestamp({_N1, _N2, N3} = Now) -> +%% {Date, Time} = calendar:now_to_datetime(Now), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", +%% [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), +%% lists:flatten(FormatDate). + +%% Time in milli seconds +t() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + + +%%----------------------------------------------------------------- +%% Func: incNumErrors/0, incNumErrors/1, incNumTimerRecovery/1 +%% Description: SNMP counter increment functions +%%----------------------------------------------------------------- +incNumErrors() -> + incNum(medGwyGatewayNumErrors). + +incNumErrors(CH) -> + incNum({CH, medGwyGatewayNumErrors}). + +incNumTimerRecovery(CH) -> + incNum({CH, medGwyGatewayNumTimerRecovery}). + +incNum(Cnt) -> + case (catch ets:update_counter(megaco_stats, Cnt, 1)) of + {'EXIT', {badarg, _Reason}} -> + ets:insert(megaco_stats, {Cnt, 1}); + Old -> + Old + end. + diff --git a/lib/megaco/src/engine/megaco_messenger_misc.erl b/lib/megaco/src/engine/megaco_messenger_misc.erl new file mode 100644 index 0000000000..3c340a8484 --- /dev/null +++ b/lib/megaco/src/engine/megaco_messenger_misc.erl @@ -0,0 +1,409 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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: Misc functions used both from the megaco_messenger module +%% and the megaco_ack_sender module. +%% +%%---------------------------------------------------------------------- + +-module(megaco_messenger_misc). + +%% Application internal export +-export([encode_body/3, + encode_trans_request/2, + encode_trans_reply/2, + encode_actions/3, + send_body/3, + send_message/3, + + transform_transaction_reply/2 + ]). + +%% Test functions +-export([compose_message/3, encode_message/2]). + + +-include_lib("megaco/include/megaco.hrl"). +-include("megaco_message_internal.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + +-define(MSG_HDR_SZ, 128). % This is just a guess... + +-ifdef(MEGACO_TEST_CODE). +-define(SIM(Other,Where), + fun(Afun,Bfun) -> + Kfun = {?MODULE,Bfun}, + case (catch ets:lookup(megaco_test_data, Kfun)) of + [{Kfun,Cfun}] -> + Cfun(Afun); + _ -> + Afun + end + end(Other,Where)). +-define(TC_AWAIT_SEND_EVENT(SendFunction), + case megaco_tc_controller:lookup(send_function) of + {value, {Tag, Pid}} when is_pid(Pid) -> + Pid ! {Tag, self(), SendFuncion}, + receive + {Tag, Pid} -> + ok + end; + _ -> + ok + end). +-else. +-define(SIM(Other,Where),Other). +-define(TC_AWAIT_SEND_EVENT(_),ok). +-endif. + + +%%---------------------------------------------------------------------- +%% Encode the transaction request +%%---------------------------------------------------------------------- + +encode_trans_request(CD, TR) when is_record(TR, 'TransactionRequest') -> + ?report_debug(CD, "encode trans request", [TR]), + Trans = {transactionRequest, TR}, + encode_transaction(CD, Trans). + +encode_trans_reply(#conn_data{segment_send = SegSend, + max_pdu_size = Max, + protocol_version = V} = CD, Reply) + when (SegSend == infinity) or (is_integer(SegSend) and (SegSend > 0)) and + is_integer(V) and (V >= 3) and + is_integer(Max) and (Max >= ?MSG_HDR_SZ) -> + (catch encode_segmented_trans_reply(CD, Reply)); +encode_trans_reply(CD, TR) when is_record(TR, megaco_transaction_reply) -> + ?report_debug(CD, "encode trans reply", [TR]), + Trans = {transactionReply, transform_transaction_reply(CD, TR)}, + encode_transaction(CD, Trans); +encode_trans_reply(CD, TR) when is_tuple(TR) and + (element(1, TR) == 'TransactionReply') -> + ?report_debug(CD, "encode trans reply", [TR]), + Trans = {transactionReply, TR}, + encode_transaction(CD, Trans). + + +encode_segmented_trans_reply(#conn_data{max_pdu_size = Max} = CD, Rep) -> + #megaco_transaction_reply{transactionResult = Res1} = Rep, + case Res1 of + {actionReplies, AR} when is_list(AR) andalso (length(AR) >= 1) -> + case encode_action_replies(CD, AR) of + {Size, EncodedARs} when Size =< (Max - ?MSG_HDR_SZ) -> + ?report_debug(CD, "action replies encoded size ok", + [Size, Max]), + %% No need to segment message: within size limit + Res2 = {actionReplies, EncodedARs}, + TR = Rep#megaco_transaction_reply{transactionResult = Res2}, + TR2 = transform_transaction_reply(CD, TR), + Trans = {transactionReply, TR2}, + encode_transaction(CD, Trans); + + {Size, EncodecARs} -> + ?report_debug(CD, + "action replies encoded size to large - " + "segment", + [Size, Max]), + %% Over size limit, so go segment the message + encode_segments(CD, Rep, EncodecARs) + end; + _ -> + TR = transform_transaction_reply(CD, Rep), + Trans = {transactionReply, TR}, + encode_transaction(CD, Trans) + end. + +encode_segments(CD, Reply, EncodecARs) -> + encode_segments(CD, Reply, EncodecARs, 1, []). + +encode_segments(CD, Reply, [EncodedAR], SN, EncodedSegs) -> + Bin = encode_segment(CD, Reply, EncodedAR, SN, 'NULL'), + {ok, lists:reverse([{SN, Bin}|EncodedSegs])}; +encode_segments(CD, Reply, [EncodedAR|EncodedARs], SN, EncodedSegs) -> + Bin = encode_segment(CD, Reply, EncodedAR, SN, asn1_NOVALUE), + encode_segments(CD, Reply, EncodedARs, SN + 1, [{SN, Bin}|EncodedSegs]). + +encode_segment(CD, Reply, EncodedAR, SN, SC) -> + Res = {actionReplies, [EncodedAR]}, + TR0 = Reply#megaco_transaction_reply{transactionResult = Res, + segmentNumber = SN, + segmentationComplete = SC}, + TR = transform_transaction_reply(CD, TR0), + Trans = {transactionReply, TR}, + case encode_transaction(CD, Trans) of + {ok, Bin} -> + Bin; + Error -> + throw(Error) + end. + + +encode_transaction(#conn_data{protocol_version = V, + encoding_mod = EM, + encoding_config = EC} = CD, Trans) -> + case (catch EM:encode_transaction(EC, V, Trans)) of + {ok, Bin} -> + ?SIM({ok, Bin}, encode_trans); + {'EXIT', {undef, _}} -> + {error, not_implemented}; + {error, not_implemented} = Error1 -> + Error1; + {error, Reason} -> + incNumErrors(CD#conn_data.conn_handle), + {error, {EM, encode_transaction, [EC, V, Trans], Reason}}; + Error2 -> + incNumErrors(CD#conn_data.conn_handle), + {error, {EM, encode_transaction, [EC, V, Trans], Error2}} + end. + + +%%---------------------------------------------------------------------- +%% Encode the action request's +%%---------------------------------------------------------------------- + +encode_actions(#conn_data{protocol_version = V} = CD, TraceLabel, ARs) -> + ?report_debug(CD, TraceLabel, [ARs]), + + %% Encode the actions + EM = CD#conn_data.encoding_mod, + EC = CD#conn_data.encoding_config, + case (catch EM:encode_action_requests(EC, V, ARs)) of + {ok, Bin} when is_binary(Bin) -> + ?SIM({ok, Bin}, encode_actions); + {'EXIT', {undef, _}} -> + incNumErrors(CD#conn_data.conn_handle), + Reason = not_implemented, + {error, {EM, encode_action_requests, [EC, ARs], Reason}}; + {error, Reason} -> + incNumErrors(CD#conn_data.conn_handle), + {error, {EM, encode_action_requests, [EC, ARs], Reason}}; + Error -> + incNumErrors(CD#conn_data.conn_handle), + {error, {EM, encode_action_requests, [EC, ARs], Error}} + end. + + +%%---------------------------------------------------------------------- +%% Encode the action reply's +%%---------------------------------------------------------------------- + +encode_action_replies(CD, AR) -> + encode_action_replies(CD, AR, 0, []). + +encode_action_replies(_, [], Size, Acc) -> + {Size, lists:reverse(Acc)}; +encode_action_replies(#conn_data{protocol_version = V, + encoding_mod = Mod, + encoding_config = Conf} = CD, + [AR|ARs], Size, Acc) -> + case (catch Mod:encode_action_reply(Conf, V, AR)) of + {ok, Bin} when is_binary(Bin) -> + encode_action_replies(CD, ARs, Size + size(Bin), [Bin|Acc]); + {'EXIT', {undef, _}} -> + throw({error, not_implemented}); + {error, not_implemented} = Error1 -> + throw(Error1); + {error, Reason} -> + incNumErrors(CD#conn_data.conn_handle), + throw({error, {Mod, encode_action_reply, [Conf, AR], Reason}}); + Error -> + incNumErrors(CD#conn_data.conn_handle), + throw({error, {Mod, encode_action_reply, [Conf, AR], Error}}) + end. + + +%%---------------------------------------------------------------------- +%% Encode the message body +%%---------------------------------------------------------------------- + +encode_body(#conn_data{protocol_version = V} = ConnData, + TraceLabel, Body) -> + %% Create the message envelope + MegaMsg = compose_message(ConnData, V, Body), + + ?report_debug(ConnData, TraceLabel, [MegaMsg]), + + %% Encode the message + EM = ConnData#conn_data.encoding_mod, + EC = ConnData#conn_data.encoding_config, + case (catch EM:encode_message(EC, V, MegaMsg)) of + {ok, Bin} when is_binary(Bin) -> + ?SIM({ok, Bin}, encode_body); + {error, Reason} -> + incNumErrors(ConnData#conn_data.conn_handle), + {error, {EM, [EC, MegaMsg], Reason}}; + Error -> + incNumErrors(ConnData#conn_data.conn_handle), + {error, {EM, [EC, MegaMsg], Error}} + end. + + +%%---------------------------------------------------------------------- +%% Compose and encode a message +%%---------------------------------------------------------------------- +compose_message(#conn_data{conn_handle = CH, + auth_data = MsgAuth}, V, Body) -> + LocalMid = CH#megaco_conn_handle.local_mid, + Msg = #'Message'{version = V, + mId = LocalMid, + messageBody = Body}, + MegaMsg = #'MegacoMessage'{authHeader = MsgAuth, % BUGBUG: Compute? + mess = Msg}, + MegaMsg. + + +encode_message(#conn_data{protocol_version = Version, + encoding_mod = EncodingMod, + encoding_config = EncodingConfig}, MegaMsg) -> + (catch EncodingMod:encode_message(EncodingConfig, Version, MegaMsg)). + + +%%---------------------------------------------------------------------- +%% Send the message body +%%---------------------------------------------------------------------- + +send_body(ConnData, TraceLabel, Body) -> + case encode_body(ConnData, TraceLabel, Body) of + {ok, Bin} -> + send_message(ConnData, false, Bin); + {error, Reason} -> + {error, Reason} + end. + + +%%---------------------------------------------------------------------- +%% Send the (encoded) message +%%---------------------------------------------------------------------- + +send_message(#conn_data{resend_indication = flag} = ConnData, + Resend, Bin) -> + do_send_message(ConnData, send_message, Bin, [Resend]); + +send_message(#conn_data{resend_indication = true} = ConnData, + true, Bin) -> + do_send_message(ConnData, resend_message, Bin, []); + +send_message(ConnData, _Resend, Bin) -> + do_send_message(ConnData, send_message, Bin, []). + +do_send_message(ConnData, SendFunc, Bin, Extra) -> + %% Send the message + #conn_data{send_mod = SendMod, + send_handle = SendHandle} = ConnData, + + ?TC_AWAIT_SEND_EVENT(SendFunc), + + ?report_trace(ConnData, "send bytes", [{bytes, Bin}, + {send_func, SendFunc}]), + + Args = [SendHandle, Bin | Extra], + case (catch apply(SendMod, SendFunc, Args)) of + ok -> + ?SIM({ok, Bin}, send_message); + {cancel, Reason} -> + ?report_trace(ConnData, "<CANCEL> send_message callback", + [{bytes, Bin}, {cancel, Reason}]), + {error, {send_message_cancelled, Reason}}; + {error, Reason} -> + incNumErrors(ConnData#conn_data.conn_handle), + ?report_important(ConnData, "<ERROR> send_message callback", + [{bytes, Bin}, {error, Reason}]), + error_msg("failed (error) sending message [using ~w] (~p):" + "~n~w", [SendFunc, SendHandle, Reason]), + {error, {send_message_failed, Reason}}; + {'EXIT', Reason} = Error -> + incNumErrors(ConnData#conn_data.conn_handle), + ?report_important(ConnData, "<ERROR> send_message callback", + [{bytes, Bin}, {exit, Reason}]), + error_msg("failed (exit) sending message [using ~w] (~p):" + "~n~w", [SendFunc, SendHandle, Reason]), + {error, {send_message_failed, Error}}; + Reason -> + incNumErrors(ConnData#conn_data.conn_handle), + ?report_important(ConnData, "<ERROR> send_message callback", + [{bytes, Bin}, {error, Reason}]), + error_msg("failed sending message [using ~w] on (~p): " + "~n~w", [SendFunc, SendHandle, Reason]), + {error, {send_message_failed, Reason}} + end. + + +%%%----------------------------------------------------------------- +%%% Misc internal util functions +%%%----------------------------------------------------------------- + +transform_transaction_reply(#conn_data{protocol_version = V}, TR) + when is_integer(V) and (V >= 3) -> + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes, + segmentNumber = SegNo, + segmentationComplete = SegComplete} = TR, + {'TransactionReply', TransId, IAR, TransRes, SegNo, SegComplete}; +transform_transaction_reply(_, TR) -> + #megaco_transaction_reply{transactionId = TransId, + immAckRequired = IAR, + transactionResult = TransRes} = TR, + {'TransactionReply', TransId, IAR, TransRes}. + + +%%----------------------------------------------------------------- +%% Func: error_msg/2 +%% Description: Send an error message +%%----------------------------------------------------------------- + +error_msg(F, A) -> + ?megaco_error(F, A). + + +%%----------------------------------------------------------------- +%% Func: incNumErrors/0, incNumErrors/1, incNumTimerRecovery/1 +%% Description: SNMP counter increment functions +%%----------------------------------------------------------------- + +incNumErrors(CH) -> + incNum({CH, medGwyGatewayNumErrors}). + +incNum(Cnt) -> + case (catch ets:update_counter(megaco_stats, Cnt, 1)) of + {'EXIT', {badarg, _R}} -> + ets:insert(megaco_stats, {Cnt, 1}); + Old -> + Old + end. + +%% p(F, A) -> +%% print(now(), F, A). + +%% print(Ts, F, A) -> +%% io:format("*** [~s] ~p ***" +%% "~n " ++ F ++ "~n", +%% [format_timestamp(Ts), self() | A]). + +%% format_timestamp(Now) -> +%% {_N1, _N2, N3} = Now, +%% {Date, Time} = calendar:now_to_datetime(Now), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", +%% [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), +%% lists:flatten(FormatDate). diff --git a/lib/megaco/src/engine/megaco_misc_sup.erl b/lib/megaco/src/engine/megaco_misc_sup.erl new file mode 100644 index 0000000000..07fe96871d --- /dev/null +++ b/lib/megaco/src/engine/megaco_misc_sup.erl @@ -0,0 +1,77 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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: The top supervisor for the Megaco/H.248 application +%%---------------------------------------------------------------------- + +-module(megaco_misc_sup). + +-behaviour(supervisor). + +%% public +-export([start/0, start/2, stop/1, init/1]). +-export([start_permanent_worker/4]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% application and supervisor callback functions + +start(normal, Args) -> + SupName = {local,?MODULE}, + case supervisor:start_link(SupName, ?MODULE, [Args]) of + {ok, Pid} -> + {ok, Pid, {normal, Args}}; + Error -> + Error + end; +start(_, _) -> + {error, badarg}. + +start() -> + SupName = {local,?MODULE}, + supervisor:start_link(SupName, ?MODULE, []). + +stop(_StartArgs) -> + ok. + +init([]) -> % Supervisor + init(); +init(BadArg) -> + {error, {badarg, BadArg}}. + +init() -> + Flags = {one_for_one, 0, 1}, + Workers = [], + {ok, {Flags, Workers}}. + + +%%---------------------------------------------------------------------- +%% Function: start_permanent_worker/3 +%% Description: Starts a permanent worker (child) process +%%---------------------------------------------------------------------- + +start_permanent_worker(M, F, A, Modules) -> + Spec = {M, {M,F,A}, permanent, timer:seconds(1), worker, [M] ++ Modules}, + supervisor:start_child(?MODULE, Spec). + + + + diff --git a/lib/megaco/src/engine/megaco_monitor.erl b/lib/megaco/src/engine/megaco_monitor.erl new file mode 100644 index 0000000000..f95a20cf58 --- /dev/null +++ b/lib/megaco/src/engine/megaco_monitor.erl @@ -0,0 +1,365 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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: Monitor connections and timers +%%---------------------------------------------------------------------- + +-module(megaco_monitor). + +-behaviour(gen_server). + + +%%----------------------------------------------------------------- +%% Include files +%%----------------------------------------------------------------- + +-include_lib("megaco/src/app/megaco_internal.hrl"). + + +%% Application internal exports +-export([ + start_link/0, + stop/0, + + apply_after/4, + apply_after/5, + cancel_apply_after/1, + + lookup_request/1, + lookup_request_field/2, + match_requests/1, + which_requests/1, + insert_request/1, + update_request_field/3, update_request_fields/2, + delete_request/1, + + lookup_reply/1, + lookup_reply_field/2, + match_replies/1, + which_replies/1, + insert_reply/1, insert_reply_new/1, + update_reply_field/3, update_reply_fields/2, + delete_reply/1, + + apply_at_exit/4, + cancel_apply_at_exit/1 + ]). + +%% 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}). +-record(apply_at_exit, {ref, pid, module, function, arguments}). + + + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start_link() -> + ?d("start -> entry", []), + gen_server:start_link({local, ?SERVER}, ?MODULE, [self()], []). + +stop() -> + call(stop). + +lookup_request(Key) -> + ets:lookup(megaco_requests, Key). + +lookup_request_field(Key, Field) -> + try + begin + {ok, ets:lookup_element(megaco_requests, Key, Field)} + end + catch + error:badarg -> + {error, not_found} + end. + +match_requests(Pat) -> + ets:match_object(megaco_requests, Pat). + +which_requests(Pat) -> + Spec = [{Pat, [], ['$$']}], + ets:select(megaco_requests, Spec). + +insert_request(Rec) -> + ets:insert(megaco_requests, Rec). + +update_request_field(Key, Field, NewValue) -> + ets:update_element(megaco_requests, Key, {Field, NewValue}). + +update_request_fields(Key, NewFields) when is_list(NewFields) -> + ets:update_element(megaco_requests, Key, NewFields). + +delete_request(Key) -> + ets:delete(megaco_requests, Key). + +lookup_reply(Key) -> + ets:lookup(megaco_replies, Key). + +lookup_reply_field(Key, Field) -> + try + begin + {ok, ets:lookup_element(megaco_replies, Key, Field)} + end + catch + error:badarg -> + {error, not_found} + end. + +match_replies(Pat) -> + ets:match_object(megaco_replies, Pat). + +which_replies(Pat) -> + Spec = [{Pat, [], ['$$']}], + ets:select(megaco_replies, Spec). + +insert_reply(Rec) -> + ets:insert(megaco_replies, Rec). + +insert_reply_new(Rec) -> + ets:insert_new(megaco_replies, Rec). + +update_reply_field(Key, Field, NewValue) -> + ets:update_element(megaco_replies, Key, {Field, NewValue}). + +update_reply_fields(Key, NewFields) when is_list(NewFields) -> + ets:update_element(megaco_replies, Key, NewFields). + +delete_reply(Key) -> + ets:delete(megaco_replies, Key). + +apply_after(M, F, A, Time) -> + apply_after(spawn_method, M, F, A, Time). + +apply_after(Method, M, F, A, Time) + when is_atom(M) andalso is_atom(F) andalso is_list(A) -> + if + Time =:= infinity -> + apply_after_infinity; + is_integer(Time) -> + Msg = {apply_after, Method, M, F, A}, + Ref = erlang:send_after(Time, whereis(?SERVER), Msg), + {apply_after, Ref} + end. + +cancel_apply_after({apply_after, Ref}) -> + case erlang:cancel_timer(Ref) of + TimeLeft when is_integer(TimeLeft) -> + {ok, TimeLeft}; + _ -> + {ok, 0} + end; +cancel_apply_after(apply_after_infinity) -> + ok; +cancel_apply_after(BadRef) -> + {error, {bad_ref, BadRef}}. + +%% Performs apply(M, F, [Reason | A]) when process Pid dies +apply_at_exit(M, F, A, Pid) + when is_atom(M) andalso is_atom(F) andalso is_list(A) andalso is_pid(Pid) -> + Ref = call({apply_at_exit, M, F, A, Pid}), + {apply_at_exit, Ref}. + +cancel_apply_at_exit({apply_at_exit, Ref}) -> + cast({cancel_apply_at_exit, Ref}); +cancel_apply_at_exit(BadRef) -> + {error, {bad_ref, BadRef}}. + +call(Request) -> + gen_server:call(?SERVER, Request, infinity). + +cast(Msg) -> + ?SERVER ! Msg, ok. + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- + +init([Parent]) -> + ?d("init -> entry", []), + process_flag(trap_exit, true), + ets:new(megaco_requests, [public, named_table, {keypos, 2}]), + ets:new(megaco_replies, [public, named_table, {keypos, 2}]), + ?d("init -> done", []), + {ok, #state{parent_pid = Parent}}. + + +%%---------------------------------------------------------------------- +%% 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({apply_at_exit, M, F, A, Pid}, _From, S) -> + Ref = erlang:monitor(process, Pid), + AAE = #apply_at_exit{ref = Ref, + pid = Pid, + module = M, + function = F, + arguments = A}, + put({?MODULE, Ref}, AAE), + Reply = Ref, + {reply, Reply, S}; + +handle_call(stop, {Parent, _} = _From, #state{parent_pid = Parent} = S) -> + {stop, normal, ok, 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(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({cancel_apply_at_exit, Ref}, S) -> + case erase({?MODULE, Ref}) of + undefined -> + %% Reply = {error, {already_cancelled, {apply_at_exit, Ref}}}, + {noreply, S}; + _AAE -> + erlang:demonitor(Ref), + {noreply, S} + end; + +handle_info({apply_after, Method, M, F, A}, S) -> + handle_apply(Method, M, F, A, apply_after), + {noreply, S}; + +%% Handle the old format also... +handle_info({apply_after, M, F, A}, S) -> + handle_apply(M, F, A, apply_after), + {noreply, S}; + +handle_info({'DOWN', Ref, process, _Pid, Reason}, S) -> + case erase({?MODULE, Ref}) of + undefined -> + {noreply, S}; + AAE -> + M = AAE#apply_at_exit.module, + F = AAE#apply_at_exit.function, + A = AAE#apply_at_exit.arguments, + handle_apply(M, F, [Reason | A], apply_at_exit), + {noreply, S} + end; + +handle_info({'EXIT', Pid, Reason}, S) when Pid == S#state.parent_pid -> + %% [megaco_messenger:disconnect(CH, {stopped, Reason}) + %% || CH <- megaco:lookup_system_info(connections)], + {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, _Extra) -> + {ok, S}. + + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +handle_apply(M, F, A, _ErrorTag) -> + spawn(M, F, A). + +handle_apply(spawn_method, M, F, A, _ErrorTag) -> + spawn(M, F, A); +handle_apply(_Method, M, F, A, _ErrorTag) -> + (catch apply(M, F, A)). + + +warning_msg(F, A) -> + ?megaco_warning("Monitor server: " ++ F, A). + + +% d(F) -> +% d(F,[]). + +% d(F,A) -> +% %% d(true,F,A). +% d(get(dbg),F,A). + +% d(true,F,A) -> +% io:format("*** [~s] ~p:~p ***" +% "~n " ++ F ++ "~n", +% [format_timestamp(now()), self(),?MODULE|A]); +% d(_, _, _) -> +% ok. + +% format_timestamp(Now) -> +% {N1, N2, N3} = Now, +% {Date, Time} = calendar:now_to_datetime(Now), +% {YYYY,MM,DD} = Date, +% {Hour,Min,Sec} = Time, +% FormatDate = +% io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", +% [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), +% lists:flatten(FormatDate). + + diff --git a/lib/megaco/src/engine/megaco_sdp.erl b/lib/megaco/src/engine/megaco_sdp.erl new file mode 100644 index 0000000000..90911fe24a --- /dev/null +++ b/lib/megaco/src/engine/megaco_sdp.erl @@ -0,0 +1,1645 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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: RFC 4566 +%%---------------------------------------------------------------------- + +-module(megaco_sdp). + +%%---------------------------------------------------------------------- +%% Include files +%%---------------------------------------------------------------------- + +-include_lib("megaco/include/megaco.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). +-include_lib("megaco/include/megaco_message_v1.hrl"). +-include_lib("megaco/include/megaco_sdp.hrl"). + + +%%---------------------------------------------------------------------- +%% External exports +%%---------------------------------------------------------------------- + +-export([ + decode/1, encode/1, + get_sdp_record_from_PropertyGroup/2 + ]). + + +%%---------------------------------------------------------------------- +%% Internal exports +%%---------------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% Macros +%%---------------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% Records +%%---------------------------------------------------------------------- + + +%%====================================================================== +%% External functions +%%====================================================================== + +%% --------------------------------------------------------------------- +%% decode(PP) -> {ok, SDP} | {error, Reason} +%% +%% This function performs the following conversion: +%% property_parm() -> sdp() +%% property_group() -> sdp_property_group() +%% property_groups() -> sdp_property_groups() +%% +%% --------------------------------------------------------------------- + +decode(SDP) -> + case (catch do_decode(SDP)) of + {ok, _} = OK -> + OK; + {error, _} = ERR -> + ERR; + {'EXIT', Reason} -> + {error, {exit, Reason}}; + CRAP -> + {error, {crap, CRAP}} + end. + +do_decode(PP) when is_record(PP, 'PropertyParm') -> + decode_PropertyParm(PP); +do_decode([PP|_] = PG) when is_record(PP, 'PropertyParm') -> + decode_PropertyGroup(PG); +do_decode([H|_] = PGs) when is_list(H) -> + decode_PropertyGroups(PGs); +do_decode(asn1_NOVALUE = V) -> + {ok, V}; +do_decode(Bad) -> + {error, {bad_sdp, Bad}}. + + +%% --------------------------------------------------------------------- +%% encode(SDPs) -> {ok, PP} | {error, Reason} +%% +%% This function performs the following conversion: +%% sdp() -> property_parm() +%% sdp_property_group() -> property_group() +%% sdp_property_groups() -> property_groups() +%% +%% --------------------------------------------------------------------- + +encode(SDP) -> + case (catch do_encode(SDP)) of + {ok, _} = OK -> + OK; + {error, _} = ERR -> + ERR; + {'EXIT', Reason} -> + {error, {exit, Reason}}; + CRAP -> + {error, {crap, CRAP}} + end. + +do_encode(SDP) when is_tuple(SDP) -> + {ok, encode_PropertyParm(SDP)}; +do_encode([SDP|_] = PG) when is_tuple(SDP) -> + encode_PropertyGroup(PG); +do_encode([H|_] = PGs) when is_list(H) -> + encode_PropertyGroups(PGs); +do_encode(asn1_NOVALUE = V) -> + {ok, V}. + + +%%----------------------------------------------------------------- +%% Generate sdp records from PropertyParm records +%%----------------------------------------------------------------- + +decode_PropertyGroups(PGs) -> + decode_PropertyGroups(PGs, []). + +decode_PropertyGroups([], DecodedPGs) -> + {ok, lists:reverse(DecodedPGs)}; + +decode_PropertyGroups([PG | PGs], DecodedPGs) -> + {ok, DecodedPG} = decode_PropertyGroup(PG), + decode_PropertyGroups(PGs, [DecodedPG | DecodedPGs]). + + +decode_PropertyGroup(PG) -> + {ok, decode_PropertyGroup(PG, [])}. + +decode_PropertyGroup([], Acc) -> + lists:reverse(Acc); + +decode_PropertyGroup([PP | PG], Acc) -> + case (catch decode_PropertyParm(PP)) of + {ok, PP2} -> + decode_PropertyGroup(PG, [PP2 | Acc]); + {error, Reason} -> + decode_PropertyGroup(PG, [{PP, Reason} | Acc]); + Error -> + decode_PropertyGroup(PG, [{PP, Error} | Acc]) + end. + + +%% ===== Protocol Version ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "v", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_protocol_version(V); + + +%% ===== Origin ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "o", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_origin(V); + + +%% ===== Session Name ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "s", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_session_name(V); + + +%% ===== Session and Media Information ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "i", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_session_media_id(V); + + +%% ===== URI ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "u", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_uri(V); + + +%% ===== Email Address and Phone Number ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "e", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_email(V); + +decode_PropertyParm(#'PropertyParm'{name = "p", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_phone(V); + + +%% ===== Connection Data ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "c", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_connection_data(V); + + +%% ===== Bandwidth ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "b", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_bandwidth(V); + + +%% ===== Times, Repeat Times and Time Zones ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "t", + value = [V], + extraInfo = asn1_NOVALUE }) -> + decode_pp_times(V); +decode_PropertyParm(#'PropertyParm'{name = "r", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_rtimes(V); +decode_PropertyParm(#'PropertyParm'{name = "z", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_tzones(V); + + +%% ===== Encryption Keys ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "k", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_encryption_keys(V); + + +%% ===== Attributes ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "a", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_attribute(V); + + +%% ===== Media Announcements ===== +%% +decode_PropertyParm(#'PropertyParm'{name = "m", + value = [V], + extraInfo = asn1_NOVALUE}) -> + decode_pp_media_announcement(V); + + +decode_PropertyParm(_PP) -> + ?d("decode_PropertyParm -> entry with" + "~n _PP: ~p", [_PP]), + {error, undefined_PropertyParm}. + + +%%----------------------------------------------------------------- +%% Generate PropertyParm records from sdp records +%%----------------------------------------------------------------- + +encode_PropertyGroups(PGs) -> + encode_PropertyGroups(PGs, []). + + +encode_PropertyGroups([], Acc) -> + {ok, lists:reverse(Acc)}; +encode_PropertyGroups([PG | PGs], Acc) -> + {ok, EncodedPG} = encode_PropertyGroup(PG), + encode_PropertyGroups(PGs, [EncodedPG | Acc]). + + +encode_PropertyGroup(PG) -> + encode_PropertyGroup(PG, []). + +encode_PropertyGroup([], Acc) -> + {ok, lists:reverse(Acc)}; +encode_PropertyGroup([PP | PG], Acc) -> + EncodedPP = encode_PropertyParm(PP), + encode_PropertyGroup(PG, [EncodedPP | Acc]). + + +%% ===== Protocol Version ===== +%% +encode_PropertyParm(#megaco_sdp_v{version = Version}) -> + encode_pp_protocol_version(Version); + + +%% ===== Origin ===== +%% +encode_PropertyParm(#megaco_sdp_o{user_name = User, + session_id = SessionId, + version = Version, + network_type = Network, + address_type = AddrType, + address = Addr}) -> + encode_pp_origin(User, SessionId, Version, Network, AddrType, Addr); + + +%% ===== Session Name ===== +%% +encode_PropertyParm(#megaco_sdp_s{name = Name}) -> + encode_pp_session_name(Name); + + +%% ===== Session and Media Information ===== +%% +encode_PropertyParm(#megaco_sdp_i{session_descriptor = SD}) -> + encode_pp_session_media_id(SD); + + +%% ===== URI ===== +%% +encode_PropertyParm(#megaco_sdp_u{uri = URI}) -> + encode_pp_uri(URI); + + +%% ===== Email Address and Phone Number ===== +%% +encode_PropertyParm(#megaco_sdp_e{email = Email}) -> + encode_pp_email(Email); + +encode_PropertyParm(#megaco_sdp_p{phone_number = Num}) -> + encode_pp_phone(Num); + + +%% ===== Connection Data ===== +%% +encode_PropertyParm(#megaco_sdp_c{network_type = NetType, + address_type = AddressType, + connection_addr = ConnectionAddr}) -> + encode_pp_connection_data(NetType, AddressType, ConnectionAddr); + + +%% ===== Bandwidth ===== +%% +encode_PropertyParm(#megaco_sdp_b{bwtype = BwType, + bandwidth = Bandwidth}) -> + encode_pp_bandwidth(BwType, Bandwidth); + + +%% ===== Times, Repeat Times and Time Zones ===== +%% +encode_PropertyParm(#megaco_sdp_t{start = Start, stop = Stop}) -> + encode_pp_times(Start, Stop); + +encode_PropertyParm(#megaco_sdp_r{repeat_interval = Repeat, + active_duration = Duration, + list_of_offsets = ListOfOffsets}) -> + encode_pp_rtimes(Repeat, Duration, ListOfOffsets); + +encode_PropertyParm(#megaco_sdp_z{list_of_adjustments = LOA}) -> + encode_pp_tzones(LOA); + + +%% ===== Encryption Keys ===== +%% +encode_PropertyParm(#megaco_sdp_k{method = Method, + encryption_key = EncryptionKey}) -> + encode_pp_encryption_keys(Method, EncryptionKey); + + +%% ===== Attributes ===== +%% +encode_PropertyParm(#megaco_sdp_a_cat{category = Category}) -> + encode_pp_attribute_cat(Category); + +encode_PropertyParm(#megaco_sdp_a_keywds{keywords = Keywords}) -> + encode_pp_attribute_keywds(Keywords); + +encode_PropertyParm(#megaco_sdp_a_tool{name_and_version = NameAndVersion}) -> + encode_pp_attribute_tool(NameAndVersion); + +encode_PropertyParm(#megaco_sdp_a_ptime{packet_time = PacketTime}) -> + encode_pp_attribute_ptime(PacketTime); + +encode_PropertyParm( + #megaco_sdp_a_maxptime{maximum_packet_time = MaxPacketTime}) -> + encode_pp_attribute_maxptime(MaxPacketTime); + +encode_PropertyParm(#megaco_sdp_a_rtpmap{payload_type = Payload, + encoding_name = EncName, + clock_rate = ClockRate, + encoding_parms = EncPar}) -> + encode_pp_attribute_rtpmap(Payload, EncName, ClockRate, EncPar); + +encode_PropertyParm(#megaco_sdp_a_orient{orientation = Orientation}) -> + encode_pp_attribute_orient(Orientation); + +encode_PropertyParm(#megaco_sdp_a_type{conf_type = CType}) -> + encode_pp_attribute_type(CType); + +encode_PropertyParm(#megaco_sdp_a_charset{char_set = CharSet}) -> + encode_pp_attribute_charset(CharSet); + +encode_PropertyParm(#megaco_sdp_a_sdplang{tag = Tag}) -> + encode_pp_attribute_sdplang(Tag); + +encode_PropertyParm(#megaco_sdp_a_lang{tag = Tag}) -> + encode_pp_attribute_lang(Tag); + +encode_PropertyParm(#megaco_sdp_a_framerate{frame_rate = FrameRate}) -> + encode_pp_attribute_framerate(FrameRate); + +encode_PropertyParm(#megaco_sdp_a_quality{quality = Quality}) -> + encode_pp_attribute_quality(Quality); + +encode_PropertyParm(#megaco_sdp_a_fmtp{format = Fmt, param = Params}) -> + encode_pp_attribute_fmtp(Fmt, Params); + +encode_PropertyParm(#megaco_sdp_a{attribute = Attr, value = Value}) -> + encode_pp_attribute(Attr, Value); + + +%% ===== Media Announcements ===== +%% +encode_PropertyParm(#megaco_sdp_m{media = Media, + port = Port, + num_ports = NOP, + transport = Transport, + fmt_list = FMT}) -> + encode_pp_media_announcement(Media, Port, NOP, Transport, FMT); + + +%% This is a "manually" encoded PropertyParm, leave it as is. +%% +encode_PropertyParm(PP) when is_record(PP, 'PropertyParm') -> + PP; + + +%% Bad data +encode_PropertyParm(SDP) -> + error({unknown_sdp, SDP}). + + +%%----------------------------------------------------------------- +%% Func: get_sdp_record_from_PropertGroup/2 +%% Description: Get all sdp records of a certain type from a +%% property group +%%----------------------------------------------------------------- + +get_sdp_record_from_PropertyGroup(Type, PG) + when is_atom(Type) and is_list(PG) -> + F = fun(R) -> not is_pg_record(Type, R) end, + lists:filter(F, PG). + +is_pg_record(v, R) when is_record(R, megaco_sdp_v) -> true; +is_pg_record(c, R) when is_record(R, megaco_sdp_c) -> true; +is_pg_record(m, R) when is_record(R, megaco_sdp_m) -> true; +is_pg_record(o, R) when is_record(R, megaco_sdp_o) -> true; +is_pg_record(a, R) when is_record(R, megaco_sdp_a) -> true; +is_pg_record(a, R) when is_record(R, megaco_sdp_a_ptime) -> true; +is_pg_record(a, R) when is_record(R, megaco_sdp_a_rtpmap) -> true; +is_pg_record(b, R) when is_record(R, megaco_sdp_b) -> true; +is_pg_record(t, R) when is_record(R, megaco_sdp_t) -> true; +is_pg_record(r, R) when is_record(R, megaco_sdp_r) -> true; +is_pg_record(z, R) when is_record(R, megaco_sdp_z) -> true; +is_pg_record(k, R) when is_record(R, megaco_sdp_k) -> true; +is_pg_record(s, R) when is_record(R, megaco_sdp_s) -> true; +is_pg_record(i, R) when is_record(R, megaco_sdp_i) -> true; +is_pg_record(u, R) when is_record(R, megaco_sdp_u) -> true; +is_pg_record(e, R) when is_record(R, megaco_sdp_e) -> true; +is_pg_record(p, R) when is_record(R, megaco_sdp_p) -> true; +is_pg_record(_, _) -> false. + + +%%====================================================================== +%% Internal functions +%%====================================================================== + +%% ===== Protocol Version ===== +%% +decode_pp_protocol_version(Value) when is_list(Value) -> + ?d("decode_pp_protocol_version -> entry with" + "~n Value: ~p", [Value]), + Version = s2i(Value, invalid_protocol_version), + ?d("decode_pp_protocol_version -> entry with" + "~n Version: ~w", [Version]), + decode_pp_protocol_version(Version); +decode_pp_protocol_version(Version) when is_integer(Version) -> + ?d("decode_pp_protocol_version -> entry with" + "~n Version: ~w", [Version]), + {ok, #megaco_sdp_v{version = Version}}. + +encode_pp_protocol_version(Version) when is_integer(Version) -> + ?d("encode_pp_protocol_version -> entry with" + "~n Version: ~w", [Version]), + #'PropertyParm'{name = "v", + value = [integer_to_list(Version)]}; +encode_pp_protocol_version(Version) -> + error({invalid_protocol_version, Version}). + + +%% ===== Origin ===== +%% +decode_pp_origin(Value) -> + ?d("decode_pp_origin -> entry with" + "~n Value: ~p", [Value]), + case string:tokens(Value, " \t") of + [User, SessId, SessVersion, NetType, AddrType, UnicastAddress] -> + ?d("decode_pp_origin -> entry with" + "~n User: ~p" + "~n SessionId: ~p" + "~n Version: ~p" + "~n NetType: ~p" + "~n AddrType: ~p" + "~n UnicastAddress: ~p", + [User, SessId, SessVersion, NetType, AddrType, UnicastAddress]), + F = fun(X, R) -> + case (catch list_to_integer(X)) of + I when is_integer(I) -> + I; + _ -> + error({invalid_origin, {R, X}}) + end + end, + SID = F(SessId, sess_id), + V = F(SessVersion, sess_version), + SDP = #megaco_sdp_o{user_name = User, + session_id = SID, + version = V, + network_type = decode_network_type(NetType), + address_type = decode_address_type(AddrType), + address = UnicastAddress}, + {ok, SDP}; + Err -> + ?d("decode_pp_origin -> " + "~n Err: ~p", [Err]), + invalid_pp(origin, Value, Err) + end. + +encode_pp_origin(User0, SessionId0, SessVersion0, + NetType0, AddrType0, UnicastAddr0) -> + ?d("do_encode_pp_origin -> entry with" + "~n User0: ~p" + "~n SessionId0: ~p" + "~n SessVersion0: ~p" + "~n NetType0: ~p" + "~n AddrType0: ~p" + "~n UnicastAddr0: ~p", + [User0, SessionId0, SessVersion0, NetType0, AddrType0, UnicastAddr0]), + User = encode_origin_user(User0), + SessionId = encode_origin_session_id(SessionId0), + SessVersion = encode_origin_sess_version(SessVersion0), + NetType = encode_origin_network_type(NetType0), + AddrType = encode_origin_address_type(AddrType0), + UnicastAddr = encode_origin_unicast_address(UnicastAddr0), + #'PropertyParm'{name = "o", + value = [ + User ++ " " ++ + SessionId ++ " " ++ + SessVersion ++ " " ++ + NetType ++ " " ++ + AddrType ++ " " ++ + UnicastAddr + ]}. + +encode_origin_user(User) when is_list(User) -> + User; +encode_origin_user(BadUser) -> + error({invalid_origin_user, BadUser}). + +encode_origin_session_id(SID) when is_integer(SID) -> + integer_to_list(SID); +encode_origin_session_id(BadSID) -> + error({invalid_origin_session_id, BadSID}). + +encode_origin_sess_version(SessVersion) when is_integer(SessVersion) -> + integer_to_list(SessVersion); +encode_origin_sess_version(BadSessVersion) -> + error({invalid_origin_sess_version, BadSessVersion}). + +encode_origin_network_type(NT) -> + case (catch encode_network_type(NT)) of + {error, _} -> + error({invalid_origin_network_type, NT}); + Val -> + Val + end. + +encode_origin_address_type(AT) -> + case (catch encode_address_type(AT)) of + {error, _} -> + error({invalid_origin_address_type, AT}); + Val -> + Val + end. + +encode_origin_unicast_address(UnicastAddr) when is_list(UnicastAddr) -> + UnicastAddr; +encode_origin_unicast_address(BadUnicastAddr) -> + error({invalid_origin_unicast_address, BadUnicastAddr}). + + +%% ===== Session Name ===== +%% +decode_pp_session_name(Value) -> + ?d("decode_pp_session_name -> entry with" + "~n Value: ~p", [Value]), + {ok, #megaco_sdp_s{name = Value}}. + +encode_pp_session_name(Name) when is_list(Name) -> + ?d("encode_pp_session_name -> entry with" + "~n Name: '~s'", [Name]), + #'PropertyParm'{name = "s", + value = [Name]}; +encode_pp_session_name(BadName) -> + error({invalid_session_name, BadName}). + + +%% ===== Session and Media Information ===== +%% +decode_pp_session_media_id(Value) -> + ?d("decode_pp_session_media_id -> entry with" + "~n Value: ~p", [Value]), + {ok, #megaco_sdp_i{session_descriptor = Value}}. + +encode_pp_session_media_id(SD) when is_list(SD) -> + ?d("encode_pp_session_media_id -> entry with" + "~n SD: '~s'", [SD]), + #'PropertyParm'{name = "i", + value = [SD]}; +encode_pp_session_media_id(BadSD) -> + error({invalid_session_media_id, BadSD}). + + +%% ===== URI ===== +%% +decode_pp_uri(Value) -> + ?d("decode_pp_uri -> entry with" + "~n Value: ~p", [Value]), + {ok, #megaco_sdp_u{uri = Value}}. + +encode_pp_uri(URI) when is_list(URI) -> + ?d("encode_pp_uri -> entry with" + "~n URI: ~p", [URI]), + #'PropertyParm'{name = "u", + value = [URI]}; +encode_pp_uri(BadUri) -> + error({invalid_uri, BadUri}). + + +%% ===== Email Address and Phone Number ===== +%% +decode_pp_email(Value) -> + ?d("decode_pp_email -> entry with" + "~n Value: ~p", [Value]), + {ok, #megaco_sdp_e{email = Value}}. + +encode_pp_email(Email) when is_list(Email) -> + ?d("encode_pp_email -> entry with" + "~n Email: ~p", [Email]), + #'PropertyParm'{name = "e", + value = [Email]}; +encode_pp_email(BadEmail) -> + error({invalid_email, BadEmail}). + +decode_pp_phone(Value) -> + ?d("decode_pp_phone -> entry with" + "~n Value: ~p", [Value]), + {ok, #megaco_sdp_p{phone_number = Value}}. + +encode_pp_phone(Phone) when is_list(Phone) -> + ?d("encode_pp_phone -> entry with" + "~n Phone: ~p", [Phone]), + #'PropertyParm'{name = "p", + value = [Phone]}; +encode_pp_phone(BadPhone) -> + error({invalid_phone, BadPhone}). + + +%% ===== Connection Data ===== +%% +decode_pp_connection_data(Value) -> + ?d("decode_pp_connection_data -> entry with" + "~n Value: ~p", [Value]), + case string:tokens(Value, " \t") of + [NetType, AddrType, ConnectionAddr] -> + ?d("decode_pp_connection_data -> " + "~n NetType: ~p" + "~n AddrType: ~p" + "~n ConnectionAddr: ~p", [NetType, AddrType, ConnectionAddr]), + NT = decode_network_type(NetType), + AT = decode_address_type(AddrType), + case AT of + ip4 -> + ?d("decode_pp_connection_data -> ip4", []), + ConnAddr = + case string:tokens(ConnectionAddr, "\/") of + [Base, TtlStr, NumOfAddrs] -> + ?d("decode_pp_connection_data -> " + "~n Base: ~p" + "~n TtlStr: ~p" + "~n NumOfAddrs:~p", + [Base, TtlStr, NumOfAddrs]), + TTL = s2i(TtlStr, + invalid_connection_data_ttl), + ?d("decode_pp_connection_data -> TTL: ~p", + [TTL]), + NOA = + s2i(NumOfAddrs, + invalid_connection_data_conn_addr_num_of), + ?d("decode_pp_connection_data -> NOA: ~p", + [NOA]), + #megaco_sdp_c_conn_addr{base = Base, + ttl = TTL, + num_of = NOA}; + [Base, TtlStr] -> + ?d("decode_pp_connection_data -> " + "~n Base: ~p" + "~n TtlStr: ~p", + [Base, TtlStr]), + TTL = + s2i(TtlStr, + invalid_connection_data_conn_addr_ttl), + ?d("decode_pp_connection_data -> TTL: ~p", + [TTL]), + #megaco_sdp_c_conn_addr{base = Base, + ttl = TTL}; + [Base] -> + Base + end, + ?d("decode_pp_connection_data -> " + "~n ConnAddr: ~p", [ConnAddr]), + SDP = #megaco_sdp_c{network_type = NT, + address_type = AT, + connection_addr = ConnAddr}, + {ok, SDP}; + ip6 -> + ?d("decode_pp_connection_data -> ip6", []), + SDP = #megaco_sdp_c{network_type = NT, + address_type = AT, + connection_addr = ConnectionAddr}, + {ok, SDP}; + _ -> + SDP = #megaco_sdp_c{network_type = NT, + address_type = AT, + connection_addr = ConnectionAddr}, + {ok, SDP} + end; + Err -> + invalid_pp(connection_data, Value, Err) + end. + +encode_pp_connection_data(NetType0, AddrType0, ConnAddr0) -> + ?d("encode_pp_connection_data -> entry with" + "~n NetType0: ~p" + "~n AddrType0: ~p" + "~n ConnAddr0: ~p", [NetType0, AddrType0, ConnAddr0]), + NetType = encode_conn_data_network_type(NetType0), + AddrType = encode_conn_data_address_type(AddrType0), + ConnAddr = encode_conn_data_conn_addr(AddrType0, ConnAddr0), + Val = NetType ++ " " ++ AddrType ++ " " ++ ConnAddr, + #'PropertyParm'{name = "c", + value = [Val]}. + +encode_conn_data_network_type(NT) -> + case (catch encode_network_type(NT)) of + {error, _} -> + error({invalid_connection_data_network_type, NT}); + Val -> + Val + end. + +encode_conn_data_address_type(AT) -> + case (catch encode_address_type(AT)) of + {error, _} -> + error({invalid_connection_data_address_type, AT}); + Val -> + Val + end. + +encode_conn_data_conn_addr(_, CA) when is_list(CA) -> + CA; +encode_conn_data_conn_addr(ip4, CA) + when is_record(CA, megaco_sdp_c_conn_addr) -> + encode_conn_data_conn_addr(CA); +encode_conn_data_conn_addr(AT, CA) + when is_list(AT) and is_record(CA, megaco_sdp_c_conn_addr) -> + case tolower(AT) of + "ip4" -> + encode_conn_data_conn_addr(CA); + _ -> + error({invalid_connection_data_conn_addr, {AT, CA}}) + end; +encode_conn_data_conn_addr(_, BadCA) -> + error({invalid_connection_data_conn_addr, BadCA}). + +encode_conn_data_conn_addr(#megaco_sdp_c_conn_addr{base = Base0, + ttl = TTL0, + num_of = undefined}) -> + Base = encode_conn_data_conn_addr_base(Base0), + TTL = encode_conn_data_conn_addr_ttl(TTL0), + Base ++ "/" ++ TTL; +encode_conn_data_conn_addr(#megaco_sdp_c_conn_addr{base = Base0, + ttl = TTL0, + num_of = NumOf0}) -> + Base = encode_conn_data_conn_addr_base(Base0), + TTL = encode_conn_data_conn_addr_ttl(TTL0), + NumOf = encode_conn_data_conn_addr_num_of(NumOf0), + Base ++ "/" ++ TTL ++ "/" ++ NumOf. + +encode_conn_data_conn_addr_base(Base) when is_list(Base) -> + Base; +encode_conn_data_conn_addr_base(BadBase) -> + error({invalid_connection_data_conn_addr_base, BadBase}). + +encode_conn_data_conn_addr_ttl(TTL) when is_integer(TTL) -> + integer_to_list(TTL); +encode_conn_data_conn_addr_ttl(BadTTL) -> + error({invalid_connection_data_conn_addr_ttl, BadTTL}). + +encode_conn_data_conn_addr_num_of(NumOf) when is_integer(NumOf) -> + integer_to_list(NumOf); +encode_conn_data_conn_addr_num_of(BadNumOf) -> + error({invalid_connection_data_conn_addr_num_of, BadNumOf}). + + +%% ===== Bandwidth ===== +%% +decode_pp_bandwidth(Value) -> + ?d("decode_pp_bandwidth -> entry with" + "~n Value: ~p", [Value]), + case string:tokens(Value, ":") of + [BwTypeStr, BandwidthStr] -> + ?d("decode_pp_bandwidth -> " + "~n BwTypeStr: ~p" + "~n BandwidthStr: ~p", [BwTypeStr, BandwidthStr]), + BwType = decode_bandwidth_bwt(BwTypeStr), + ?d("decode_pp_bandwidth -> " + "~n BwType: ~w", [BwType]), + Bandwidth = decode_bandwidth_bw(BandwidthStr), + ?d("decode_pp_bandwidth -> " + "~n Bandwidth: ~w", [Bandwidth]), + SDP = #megaco_sdp_b{bwtype = BwType, + bandwidth = Bandwidth}, + {ok, SDP}; + Err -> + invalid_pp(bandwidth_info, Value, Err) + end. + +encode_pp_bandwidth(BwType0, Bandwidth0) -> + ?d("encode_pp_bandwidth -> entry with" + "~n BwType0: ~p" + "~n Bandwidth0: ~p", [BwType0, Bandwidth0]), + BwType = encode_bandwidth_bwt(BwType0), + Bandwidth = encode_bandwidth_bw(Bandwidth0), + Val = BwType ++ ":" ++ Bandwidth, + #'PropertyParm'{name = "b", + value = [Val]}. + +decode_bandwidth_bwt("CT") -> + ct; +decode_bandwidth_bwt("AS") -> + as; +decode_bandwidth_bwt(BwType) when is_list(BwType) -> + BwType; +decode_bandwidth_bwt(BadBwType) -> + error({invalid_bandwidth_bwtype, BadBwType}). + +encode_bandwidth_bwt(ct) -> + "CT"; +encode_bandwidth_bwt(as) -> + "AS"; +encode_bandwidth_bwt(BwType) when is_list(BwType) -> + BwType; +encode_bandwidth_bwt(BadBwType) -> + error({invalid_bandwidth_bwtype, BadBwType}). + +decode_bandwidth_bw(Bandwidth) -> + s2i(Bandwidth, invalid_bandwidth_bandwidth). + +encode_bandwidth_bw(Bandwidth) when is_integer(Bandwidth) -> + integer_to_list(Bandwidth); +encode_bandwidth_bw(BadBandwidth) -> + error({invalid_bandwidth_bandwidth, BadBandwidth}). + + +%% ===== Times ===== +%% +decode_pp_times(Value) -> + ?d("decode_pp_times -> entry with" + "~n Value: ~p", [Value]), + case string:tokens(Value, " \t") of + [StartStr, StopStr] -> + ?d("decode_pp_times -> " + "~n StartStr: ~p" + "~n StopStr: ~p", [StartStr, StopStr]), + Start = decode_times_start(StartStr), + ?d("decode_pp_times -> entry with" + "~n Stop: ~w", [Start]), + Stop = decode_times_stop(StopStr), + ?d("decode_pp_times -> entry with" + "~n Stop: ~w", [Stop]), + SDP = #megaco_sdp_t{start = Start, + stop = Stop}, + {ok, SDP}; + Err -> + invalid_pp(times, Value, Err) + end. + +encode_pp_times(Start0, Stop0) -> + ?d("encode_pp_times -> entry with" + "~n Start0: ~p" + "~n Stop0: ~p", [Start0, Stop0]), + Start = encode_times_start(Start0), + Stop = encode_times_stop(Stop0), + Val = Start ++ " " ++ Stop, + #'PropertyParm'{name = "t", + value = [Val]}. + +decode_times_start(Time) -> + s2i(Time, invalid_times_start). + +encode_times_start(Time) when is_integer(Time) -> + integer_to_list(Time); +encode_times_start(BadTime) -> + error({invalid_times_start, BadTime}). + +decode_times_stop(Time) -> + s2i(Time, invalid_times_stop). + +encode_times_stop(Time) when is_integer(Time) -> + integer_to_list(Time); +encode_times_stop(BadTime) -> + error({invalid_times_stop, BadTime}). + + +%% ===== Repeat Times ===== +%% +decode_pp_rtimes(Value) -> + ?d("decode_pp_rtimes -> entry with" + "~n Value: ~p", [Value]), + case string:tokens(Value, " \t") of + [Repeat, Duration | ListOfOffsets] -> + ?d("decode_pp_rtimes -> " + "~n Repeat: ~p" + "~n Duration: ~p" + "~n ListOfOffsets: ~p", [Repeat, Duration, ListOfOffsets]), + SDP = #megaco_sdp_r{repeat_interval = Repeat, + active_duration = Duration, + list_of_offsets = ListOfOffsets}, + {ok, SDP}; + Err -> + invalid_pp(repeat_times, Value, Err) + end. + +encode_pp_rtimes(Repeat0, Duration0, ListOfOffsets0) -> + ?d("encode_pp_rtimes -> entry with" + "~n Repeat0: ~p" + "~n Duration0: ~p" + "~n ListOfOffsets0: ~p", [Repeat0, Duration0, ListOfOffsets0]), + Repeat = encode_rtimes_repeat(Repeat0), + Duration = encode_rtimes_duration(Duration0), + ListOfOffsets = encode_rtimes_list_of_offsets(ListOfOffsets0), + Val = Repeat ++ " " ++ Duration ++ ListOfOffsets, + #'PropertyParm'{name = "r", + value = [Val]}. + +encode_rtimes_repeat(Repeat) when is_list(Repeat) -> + Repeat; +encode_rtimes_repeat(BadRepeat) -> + error({invalid_rtimes_repeat, BadRepeat}). + +encode_rtimes_duration(Duration) when is_list(Duration) -> + Duration; +encode_rtimes_duration(BadDuration) -> + error({invalid_rtimes_duration, BadDuration}). + +encode_rtimes_list_of_offsets(LOO) when is_list(LOO) -> + F = fun(Off, Acc) when is_list(Off) -> + Acc ++ " " ++ Off; + (BadOff, Acc) -> + error({invalid_rtimes_list_of_offsets, {BadOff, Acc}}) + end, + lists:foldl(F, [], LOO); +encode_rtimes_list_of_offsets(BadLoo) -> + error({invalid_rtimes_list_of_offsets, BadLoo}). + + +%% ===== Time Zones ===== +%% +decode_pp_tzones(Value) when is_list(Value) and (length(Value) > 0) -> + ?d("decode_pp_ztimes -> entry with" + "~n Value: ~p", [Value]), + LOA = decode_tzones_list_of_adjustments(string:tokens(Value, " \t"), []), + {ok, #megaco_sdp_z{list_of_adjustments = LOA}}; +decode_pp_tzones(BadValue) -> + error({invalid_tzones_list_of_adjustments, BadValue}). + +encode_pp_tzones(LOA) -> + ?d("encode_pp_ztimes -> entry with" + "~n LOA: ~p", [LOA]), + Val = encode_tzones_list_of_adjustments(LOA), + #'PropertyParm'{name = "z", + value = [Val]}. + +decode_tzones_list_of_adjustments([], Acc) -> + lists:reverse(Acc); +decode_tzones_list_of_adjustments([Adj], Acc) -> + error({invalid_tzones_list_of_adjustments, Adj, lists:reverse(Acc)}); +decode_tzones_list_of_adjustments([Time, Offset | LOA], Acc) -> + Adj = #megaco_sdp_z_adjustement{time = Time, offset = Offset}, + decode_tzones_list_of_adjustments(LOA, [Adj | Acc]). + +encode_tzones_list_of_adjustments([H|_] = LOA) + when is_record(H, megaco_sdp_z_adjustement) -> + F = fun(#megaco_sdp_z_adjustement{time = T, offset = O}, Acc) -> + Acc ++ " " ++ T ++ " " ++ O; + (BadAdjustment, Acc) -> + error({invalid_tzones_list_of_adjustments, + {BadAdjustment, Acc}}) + end, + lists:foldl(F, [], LOA); +encode_tzones_list_of_adjustments(LOA) -> + error({invalid_tzones_list_of_adjustments, LOA}). + + +%% ===== Encryption Keys ===== +%% +decode_pp_encryption_keys(Value) -> + ?d("decode_pp_encryption_keys -> entry with" + "~n Value: ~p", [Value]), + {M, E} = + case string:tokens(Value, ":") of + [Method, EncryptionKey] -> + ?d("decode_pp_encryption_keys -> " + "~n Method: ~p" + "~n EncryptionKey: ~p", [Method, EncryptionKey]), + {Method, EncryptionKey}; + [Method] -> + ?d("decode_pp_encryption_keys -> " + "~n Method: ~p", [Method]), + {Method, undefined}; + Err -> + invalid_pp(encryption_key, Value, Err) + end, + M2 = + case tolower(M) of + "clear" -> + clear; + "base64" -> + base64; + "uri" -> + uri; + "prompt" -> + prompt; + _ -> + M + end, + ?d("decode_pp_encryption_keys -> " + "~n M2: ~p", [M2]), + SDP = #megaco_sdp_k{method = M2, + encryption_key = E}, + {ok, SDP}. + +encode_pp_encryption_keys(prompt = _Method, undefined) -> + ?d("encode_pp_encryption_keys(prompt) -> entry", []), + #'PropertyParm'{name = "k", + value = ["prompt"]}; +encode_pp_encryption_keys(clear = _Method, EncryptionKey) + when is_list(EncryptionKey) -> + ?d("encode_pp_encryption_keys(clear) -> entry with" + "~n EncryptionKey: ~p", [EncryptionKey]), + #'PropertyParm'{name = "k", + value = ["clear:" ++ EncryptionKey]}; +encode_pp_encryption_keys(base64 = _Method, EncryptionKey) + when is_list(EncryptionKey) -> + ?d("encode_pp_encryption_keys(base64) -> entry with" + "~n EncryptionKey: ~p", [EncryptionKey]), + #'PropertyParm'{name = "k", + value = ["base64:" ++ EncryptionKey]}; +encode_pp_encryption_keys(uri = _Method, EncryptionKey) + when is_list(EncryptionKey) -> + ?d("encode_pp_encryption_keys(uri) -> entry with" + "~n EncryptionKey: ~p", [EncryptionKey]), + #'PropertyParm'{name = "k", + value = ["uri:" ++ EncryptionKey]}; +encode_pp_encryption_keys(Method, EncryptionKey) + when is_list(Method) and is_list(EncryptionKey) -> + ?d("encode_pp_encryption_keys -> entry with" + "~n Method: ~p" + "~n EncryptionKey: ~p", [Method, EncryptionKey]), + #'PropertyParm'{name = "k", + value = [Method ++ ":" ++ EncryptionKey]}; +encode_pp_encryption_keys(BadMethod, BadEK) -> + error({invalid_encryption_keys, {BadMethod, BadEK}}). + + +%% ===== Attributes ===== +%% +decode_pp_attribute(Value) -> + ?d("decode_pp_attribute -> entry with" + "~n Value: ~p", [Value]), + First = string:chr(Value, $:), + if + (First > 0) and (First < length(Value)) -> + ?d("decode_pp_attribute -> value attribute", []), + Attr = string:substr(Value, 1, First -1), + AttrValue = string:substr(Value, First + 1), + ?d("decode_pp_attribute -> " + "~n Attr: ~p" + "~n AttrValue: ~p", [Attr, AttrValue]), + decode_pp_attribute_value(Attr, AttrValue); + + First > 0 -> + ?d("decode_pp_attribute -> value attribute (empty)", []), + Attr = string:substr(Value, 1, First -1), + ?d("decode_pp_attribute -> " + "~n Attr: ~p", [Attr]), + decode_pp_attribute_value(Attr, []); + + true -> + ?d("decode_pp_attribute -> binary attribute", []), + {ok, #megaco_sdp_a{attribute = Value}} + + end. + +decode_pp_attribute_value("cat", AttrValue) -> + ?d("decode_pp_attribute -> cat", []), + SDP = #megaco_sdp_a_cat{category = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("keywds", AttrValue) -> + ?d("decode_pp_attribute -> keywds", []), + SDP = #megaco_sdp_a_keywds{keywords = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("tool", AttrValue) -> + ?d("decode_pp_attribute -> tool", []), + SDP = #megaco_sdp_a_tool{name_and_version = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("ptime", AttrValue) -> + ?d("decode_pp_attribute -> ptime", []), + PacketTimeStr = string:strip(AttrValue, both, $ ), + PacketTime = + s2i(PacketTimeStr, invalid_ptime_packet_time), + ?d("decode_pp_attribute -> PacketTime: ~w", [PacketTime]), + SDP = #megaco_sdp_a_ptime{packet_time = PacketTime}, + {ok, SDP}; + +decode_pp_attribute_value("maxptime", AttrValue) -> + ?d("decode_pp_attribute -> maxptime", []), + MaxPacketTimeStr = string:strip(AttrValue, both, $ ), + MaxPacketTime = + s2i(MaxPacketTimeStr, invalid_maxptime_maximum_packet_time), + ?d("decode_pp_attribute -> MaxPacketTime: ~w", [MaxPacketTime]), + SDP = #megaco_sdp_a_maxptime{maximum_packet_time = MaxPacketTime}, + {ok, SDP}; + +decode_pp_attribute_value("rtpmap", AttrValue) -> + ?d("decode_pp_attribute -> rtpmap", []), + case string:tokens(AttrValue, "\/ \t") of + [PayloadStr, EncName, ClockRateStr | EncPar] -> + ?d("decode_pp_attribute -> " + "~n PayloadStr: ~p" + "~n EncName: ~p" + "~n ClockRateStr: ~p" + "~n EncPar: ~p", + [PayloadStr, EncName, ClockRateStr, EncPar]), + Payload = + s2i(PayloadStr, invalid_rtpmap_payload), + ?d("decode_pp_attribute -> Payload: ~w", + [Payload]), + ClockRate = + s2i(ClockRateStr, invalid_rtpmap_payload), + ?d("decode_pp_attribute -> ClockRate: ~w", + [ClockRate]), + SDP = + #megaco_sdp_a_rtpmap{payload_type = Payload, + encoding_name = EncName, + clock_rate = ClockRate, + encoding_parms = EncPar}, + {ok, SDP}; + _ -> + error({invalid_rtpmap, AttrValue}) + end; + +decode_pp_attribute_value("orient", AttrValue) -> + ?d("decode_pp_attribute -> orient", []), + Orientation = decode_attribute_orientation(AttrValue), + SDP = #megaco_sdp_a_orient{orientation = Orientation}, + {ok, SDP}; + +decode_pp_attribute_value("type", AttrValue) -> + ?d("decode_pp_attribute -> type", []), + SDP = #megaco_sdp_a_type{conf_type = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("charset", AttrValue) -> + ?d("decode_pp_attribute -> charset", []), + SDP = #megaco_sdp_a_charset{char_set = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("sdplang", AttrValue) -> + ?d("decode_pp_attribute -> sdplang", []), + SDP = #megaco_sdp_a_sdplang{tag = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("lang", AttrValue) -> + ?d("decode_pp_attribute -> lang", []), + SDP = #megaco_sdp_a_lang{tag = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("framerate", AttrValue) -> + ?d("decode_pp_attribute -> framerate", []), + SDP = #megaco_sdp_a_framerate{frame_rate = AttrValue}, + {ok, SDP}; + +decode_pp_attribute_value("quality", AttrValue) -> + ?d("decode_pp_attribute -> quality", []), + QualityStr = AttrValue, + Quality = s2i(QualityStr, invalid_quality_quality), + ?d("decode_pp_attribute -> Quality: ~w", [Quality]), + SDP = #megaco_sdp_a_quality{quality = Quality}, + {ok, SDP}; + +decode_pp_attribute_value("fmtp", AttrValue) -> + ?d("decode_pp_attribute -> fmtp", []), + FMTP = AttrValue, + First = string:chr(FMTP, $ ), + if + (First > 0) and (First < length(FMTP)) -> + ?d("decode_pp_attribute_value -> valid fmtp with params", []), + Format = string:substr(FMTP, 1, First - 1), + Params = string:substr(FMTP, First + 1), + ?d("decode_pp_attribute_value -> " + "~n Format: ~p" + "~n Params: ~p", [Format, Params]), + SDP = #megaco_sdp_a_fmtp{format = Format, + param = Params}, + {ok, SDP}; + + First > 0 -> + ?d("decode_pp_attribute -> valid fmtp", []), + Format = string:substr(FMTP, 1, First - 1), + ?d("decode_pp_attribute -> " + "~n Format: ~p", [Format]), + {ok, #megaco_sdp_a_fmtp{format = Format, param = []}}; + + true -> + ?d("decode_pp_attribute_value -> no params", []), + {ok, #megaco_sdp_a_fmtp{format = FMTP, param = []}} + + end; + +decode_pp_attribute_value(Attr, AttrValue) -> + ?d("decode_pp_attribute -> unknown value attribute", []), + {ok, #megaco_sdp_a{attribute = Attr, value = AttrValue}}. + +decode_attribute_orientation("portrait") -> + portrait; +decode_attribute_orientation("landscape") -> + landscape; +decode_attribute_orientation("seascape") -> + seascape; +decode_attribute_orientation(BadOrientation) -> + error({invalid_orient_orientation, BadOrientation}). + +encode_attribute_orientation(portrait) -> + "portrait"; +encode_attribute_orientation(landscape) -> + "landscape"; +encode_attribute_orientation(seascape) -> + "seascape"; +encode_attribute_orientation(BadOrientation) -> + error({invalid_orient_orientation, BadOrientation}). + + +encode_pp_attribute_cat(Cat) when is_list(Cat) -> + ?d("encode_pp_attribute_cat -> entry with" + "~n Cat: ~p", [Cat]), + #'PropertyParm'{name = "a", + value = ["cat:" ++ Cat]}; +encode_pp_attribute_cat(BadCat) -> + error({invalid_cat_category, BadCat}). + + +encode_pp_attribute_keywds(Keywords) when is_list(Keywords) -> + ?d("encode_pp_attribute_keywds -> entry with" + "~n Keywords: ~p", [Keywords]), + #'PropertyParm'{name = "a", + value = ["keywds:" ++ Keywords]}; +encode_pp_attribute_keywds(BadKeywords) -> + error({invalid_keywds_keywords, BadKeywords}). + + +encode_pp_attribute_tool(NameAndVersion) when is_list(NameAndVersion) -> + ?d("encode_pp_attribute_tool -> entry with" + "~n NameAndVersion: ~p", [NameAndVersion]), + #'PropertyParm'{name = "a", + value = ["tool:" ++ NameAndVersion]}; +encode_pp_attribute_tool(BadNameAndVersion) -> + error({invalid_tool_name_and_version, BadNameAndVersion}). + + +encode_pp_attribute_ptime(PacketTime) when is_integer(PacketTime) -> + ?d("encode_pp_attribute_ptime -> entry with" + "~n PacketTime: ~w", [PacketTime]), + #'PropertyParm'{name = "a", + value = ["ptime:" ++ integer_to_list(PacketTime)]}; +encode_pp_attribute_ptime(BadPT) -> + error({invalid_ptime_packet_time, BadPT}). + + +encode_pp_attribute_maxptime(MaxPacketTime) when is_integer(MaxPacketTime) -> + ?d("encode_pp_attribute_maxptime -> entry with" + "~n MaxPacketTime: ~w", [MaxPacketTime]), + #'PropertyParm'{name = "a", + value = ["maxptime:" ++ integer_to_list(MaxPacketTime)]}; +encode_pp_attribute_maxptime(BadMPT) -> + error({invalid_maxptime_maximum_packet_time, BadMPT}). + + +encode_pp_attribute_rtpmap(Payload0, EncName0, ClockRate0, EncPar0) -> + ?d("encode_pp_attribute_rtpmap -> entry with" + "~n Payload0: ~p" + "~n EncName0: ~p" + "~n ClockRate0: ~p" + "~n EncPar0: ~p", [Payload0, EncName0, ClockRate0, EncPar0]), + Payload = encode_rtpmap_payload(Payload0), + EncName = encode_rtpmap_encoding_name(EncName0), + ClockRate = encode_rtpmap_clockrate(ClockRate0), + EncPar = encode_rtpmap_encoding_parms(EncPar0), + Val = "rtpmap:" ++ Payload ++ " " ++ + EncName ++ "/" ++ ClockRate ++ EncPar, + #'PropertyParm'{name = "a", + value = [Val]}. + +encode_rtpmap_payload(Payload) when is_integer(Payload) -> + integer_to_list(Payload); +encode_rtpmap_payload(BadPayload) -> + error({invalid_rtpmap_payload, BadPayload}). + +encode_rtpmap_encoding_name(EncName) when is_list(EncName) -> + EncName; +encode_rtpmap_encoding_name(BadEncName) -> + error({invalid_rtpmap_encoding_name, BadEncName}). + +encode_rtpmap_clockrate(ClockRate) when is_integer(ClockRate) -> + integer_to_list(ClockRate); +encode_rtpmap_clockrate(BadClockRate) -> + error({invalid_rtpmap_clockrate, BadClockRate}). + +encode_rtpmap_encoding_parms(EncPar) when is_list(EncPar) -> + F = fun(EP, Acc) when is_list(EP) -> + Acc ++ "/" ++ EP; + (BadEP, Acc) -> + error({invalid_rtpmap_encoding_parms, {BadEP, Acc}}) + end, + lists:foldl(F, [], EncPar); +encode_rtpmap_encoding_parms(BadEncPar) -> + error({invalid_rtpmap_encoding_parms, BadEncPar}). + + +encode_pp_attribute_orient(Orientation0) -> + ?d("encode_pp_attribute_orient -> entry with" + "~n Orientation0: ~w", [Orientation0]), + Orientation = encode_attribute_orientation(Orientation0), + #'PropertyParm'{name = "a", + value = ["orient:" ++ Orientation]}. + + +encode_pp_attribute_type(CType) when is_list(CType) -> + ?d("encode_pp_attribute_type -> entry with" + "~n CType: ~p", [CType]), + #'PropertyParm'{name = "a", + value = ["type:" ++ CType]}; +encode_pp_attribute_type(BadCType) -> + error({invalid_type_conf_type, BadCType}). + + +encode_pp_attribute_charset(CharSet) when is_list(CharSet) -> + ?d("encode_pp_attribute_charset -> entry with" + "~n CharSet: ~p", [CharSet]), + #'PropertyParm'{name = "a", + value = ["charset:" ++ CharSet]}; +encode_pp_attribute_charset(BadCharSet) -> + error({invalid_charset_char_set, BadCharSet}). + + +encode_pp_attribute_sdplang(SdpLang) when is_list(SdpLang) -> + ?d("encode_pp_attribute_sdplang -> entry with" + "~n SdpLang: ~p", [SdpLang]), + #'PropertyParm'{name = "a", + value = ["sdplang:" ++ SdpLang]}; +encode_pp_attribute_sdplang(BadSdpLang) -> + error({invalid_sdplang_tag, BadSdpLang}). + + +encode_pp_attribute_lang(Lang) when is_list(Lang) -> + ?d("encode_pp_attribute_lang -> entry with" + "~n Lang: ~p", [Lang]), + #'PropertyParm'{name = "a", + value = ["lang:" ++ Lang]}; +encode_pp_attribute_lang(BadLang) -> + error({invalid_lang_tag, BadLang}). + + +encode_pp_attribute_framerate(FrameRate) when is_list(FrameRate) -> + ?d("encode_pp_attribute_framerate -> entry with" + "~n FrameRate: ~p", [FrameRate]), + #'PropertyParm'{name = "a", + value = ["framerate:" ++ FrameRate]}; +encode_pp_attribute_framerate(BadFrameRate) -> + error({invalid_framerate_frame_rate, BadFrameRate}). + + +encode_pp_attribute_quality(Quality) when is_integer(Quality) -> + ?d("encode_pp_attribute_quality -> entry with" + "~n Quality: ~w", [Quality]), + #'PropertyParm'{name = "a", + value = ["quality:" ++ integer_to_list(Quality)]}; +encode_pp_attribute_quality(BadQ) -> + error({invalid_quality_quality, BadQ}). + + +encode_pp_attribute_fmtp(Fmt0, Params0) -> + ?d("encode_pp_attribute_rtpmap -> entry with" + "~n Fmt0: ~p" + "~n Params0: ~p", [Fmt0, Params0]), + Fmt = encode_fmtp_format(Fmt0), + Params = encode_fmtp_param(Params0), + Val = "fmtp:" ++ Fmt ++ " " ++ Params, + #'PropertyParm'{name = "a", + value = [Val]}. + +encode_fmtp_format(Fmt) when is_list(Fmt) -> + Fmt; +encode_fmtp_format(BadFmt) -> + error({invalid_fmtp_format, BadFmt}). + +encode_fmtp_param(Param) when is_list(Param) -> + Param; +encode_fmtp_param(BadParam) -> + error({invalid_fmtp_param, BadParam}). + + +encode_pp_attribute(Attr, undefined) when is_list(Attr) -> + ?d("encode_pp_attribute_rtpmap -> entry with" + "~n Attr: ~p", [Attr]), + #'PropertyParm'{name = "a", + value = [Attr]}; +encode_pp_attribute(Attr, Value) when is_list(Attr) and is_list(Value) -> + ?d("encode_pp_attribute_rtpmap -> entry with" + "~n Attr: ~p" + "~n Value: ~p", [Attr, Value]), + #'PropertyParm'{name = "a", + value = [Attr ++ ":" ++ Value]}; +encode_pp_attribute(BadAttr, BadAttrValue) -> + error({invalid_attribute, {BadAttr, BadAttrValue}}). + + +%% ===== Media Announcements ===== +%% +decode_pp_media_announcement(Value) -> + case string:tokens(Value, " \t") of + [Media0, PortInfo, Transport | FMT] -> + Media = + case tolower(Media0) of + "audio" -> audio; + "video" -> video; + "application" -> application; + "data" -> data; + "control" -> control; + _ -> Media0 + end, + {Port, NoOfPorts} = + case string:tokens(PortInfo, "/") of + [P1, NP] -> + {s2i(P1, invalid_media_announcement_port), + s2i(NP, invalid_media_announcement_nof_ports)}; + [P2] -> + {s2i(P2, invalid_media_announcement_port), + undefined}; + Err -> + invalid_pp(mnta_port_info, Value, Err) + end, + SDP = #megaco_sdp_m{media = Media, + port = Port, + num_ports = NoOfPorts, + transport = Transport, + fmt_list = FMT}, + {ok, SDP}; + Err -> + invalid_pp(media_name_transp_addr, Value, Err) + end. + + +encode_pp_media_announcement(Media0, Port0, undefined, Transport0, FMT0) -> + ?d("encode_pp_media_announcement -> entry with" + "~n Media0: ~p" + "~n Port0: ~p" + "~n Transport0: ~p" + "~n FMT0: ~p", [Media0, Port0, Transport0, FMT0]), + Media = encode_media_announcement_media(Media0), + Port = encode_media_announcement_port(Port0), + Transport = encode_media_announcement_transport(Transport0), + FMT = encode_media_announcement_fmt_list(FMT0), + do_encode_pp_media_announcement(Media, Port, "", Transport, FMT); +encode_pp_media_announcement(Media0, Port0, NumPorts0, Transport0, FMT0) -> + ?d("encode_pp_media_announcement -> entry with" + "~n Media0: ~p" + "~n Port0: ~p" + "~n NumPorts0: ~p" + "~n Transport0: ~p" + "~n FMT0: ~p", [Media0, Port0, NumPorts0, Transport0, FMT0]), + Media = encode_media_announcement_media(Media0), + Port = encode_media_announcement_port(Port0), + NumPorts = encode_media_announcement_num_port(NumPorts0), + Transport = encode_media_announcement_transport(Transport0), + FMT = encode_media_announcement_fmt_list(FMT0), + do_encode_pp_media_announcement(Media, Port, NumPorts, Transport, FMT). + +do_encode_pp_media_announcement(Media, Port, NumOfPorts, Transport, FMT) -> + Val = Media ++ " " ++ Port ++ NumOfPorts ++ " " ++ Transport ++ FMT, + #'PropertyParm'{name = "m", + value = [Val]}. + +encode_media_announcement_media(Media) when is_atom(Media) -> + MaMedia = [audio, video, application, data, control], + case lists:member(Media, MaMedia) of + true -> + atom_to_list(Media); + false -> + error({invalid_media_announcement_media, Media}) + end; +encode_media_announcement_media(Media) when is_list(Media) -> + Media; +encode_media_announcement_media(BadMedia) -> + error({invalid_media_announcement_media, BadMedia}). + +encode_media_announcement_port(Port) when is_integer(Port) -> + integer_to_list(Port); +encode_media_announcement_port(BadPort) -> + error({invalid_media_announcement_port, BadPort}). + +encode_media_announcement_num_port(NumPort) when is_integer(NumPort) -> + "/" ++ integer_to_list(NumPort); +encode_media_announcement_num_port(BadNumPort) -> + error({invalid_media_announcement_num_port, BadNumPort}). + +encode_media_announcement_transport(Transport) when is_list(Transport) -> + Transport; +encode_media_announcement_transport(BadTransport) -> + error({invalid_media_announcement_transport, BadTransport}). + +encode_media_announcement_fmt_list(FmtList) when is_list(FmtList) -> + F = fun(FMT, Acc) when is_list(FMT) -> + Acc ++ " " ++ FMT; + (BadFMT, Acc) -> + error({invalid_media_announcement_fmt_list, {BadFMT, Acc}}) + end, + lists:foldl(F, [], FmtList); +encode_media_announcement_fmt_list(BadFmtList) -> + error({invalid_media_announcement_fmt_list, BadFmtList}). + + +decode_network_type(NT) when is_list(NT) -> + case tolower(NT) of + "in" -> in; + _ -> NT + end. + +encode_network_type(in) -> "IN"; +encode_network_type(NT) when is_list(NT) -> NT; +encode_network_type(Bad) -> + {error, {invalid_network_type, Bad}}. + + +decode_address_type(AT) when is_list(AT) -> + case tolower(AT) of + "ip4" -> ip4; + "ip6" -> ip6; + _ -> AT + end. + +encode_address_type(ip4) -> "IP4"; +encode_address_type(ip6) -> "IP6"; +encode_address_type(AT) when is_list(AT) -> + case toupper(AT) of + "IP4" -> "IP4"; + "IP6" -> "IP6"; + _ -> AT + end; +encode_address_type(Crap) -> + {error, {invalid_address_type, Crap}}. + + +s2i(S, E) -> + case (catch list_to_integer(S)) of + I when is_integer(I) -> + I; + _ -> + error({E, S}) + end. + +-define(LOWER(Char), + if + Char >= $A, Char =< $Z -> + Char - ($A - $a); + true -> + Char + end). +tolower(Chars) -> + [?LOWER(Char) || Char <- Chars]. + +-define(UPPER(Char), + if + Char >= $a, Char =< $z -> + Char + ($A - $a); + true -> + Char + end). +toupper(Chars) -> + [?UPPER(Char) || Char <- Chars]. + +invalid_pp(What, Value, Error) -> + {error, {invalid_PropertyParm, {What, Value, Error}}}. + +error(Reason) -> + throw({error, Reason}). + diff --git a/lib/megaco/src/engine/megaco_stats.erl b/lib/megaco/src/engine/megaco_stats.erl new file mode 100644 index 0000000000..af180fb9d6 --- /dev/null +++ b/lib/megaco/src/engine/megaco_stats.erl @@ -0,0 +1,207 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2009. 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: Functions for handling statistic counters +%%----------------------------------------------------------------- +-module(megaco_stats). + + +%%----------------------------------------------------------------- +%% Application internal exports +%%----------------------------------------------------------------- + +-export([init/1, init/2]). + +-export([inc/2, inc/3, inc/4]). + +-export([get_stats/1, get_stats/2, get_stats/3, + reset_stats/1, reset_stats/2]). + +%% -include_lib("megaco/include/megaco.hrl"). + + +%%----------------------------------------------------------------- +%% Func: init/1, init/2 +%% Description: Initiate the statistics. Creates the stats table +%% and the global counters. +%%----------------------------------------------------------------- +init(Name) -> + init(Name, []). + +init(Name, GlobalCounters) -> + ets:new(Name, [public, named_table, {keypos, 1}]), + ets:insert(Name, {global_counters, GlobalCounters}), + create_global_snmp_counters(Name, GlobalCounters). + + +create_global_snmp_counters(_Name, []) -> + ok; +create_global_snmp_counters(Name, [Counter|Counters]) -> + ets:insert(Name, {Counter, 0}), + create_global_snmp_counters(Name, Counters). + + +%%----------------------------------------------------------------- +%% Func: inc/2, inc/3, inc/4 +%% Description: Increment counter value. Default increment is one +%% (1). +%%----------------------------------------------------------------- +inc(Tab, GlobalCnt) when is_atom(GlobalCnt) -> + inc(Tab, GlobalCnt, 1). + +inc(Tab, GlobalCnt, Incr) + when is_atom(GlobalCnt) andalso (is_integer(Incr) andalso (Incr > 0)) -> + do_inc(Tab, GlobalCnt, Incr); +inc(Tab, Handle, Cnt) + when is_atom(Cnt) -> + inc(Tab, Handle, Cnt, 1). + +inc(Tab, Handle, Cnt, Incr) + when is_atom(Cnt) andalso (is_integer(Incr) andalso (Incr > 0)) -> + Key = {Handle, Cnt}, + do_inc(Tab, Key, Incr). + +do_inc(Tab, Key, Incr) -> + case (catch ets:update_counter(Tab, Key, Incr)) of + {'EXIT', {badarg, _Reason}} -> + ets:insert(Tab, {Key, Incr}), + Incr; + Val -> + Val + end. + + +%%----------------------------------------------------------------- +%% Func: get_stats/1, get_stats/2, get_stats/3 +%% Description: Get statistics +%%----------------------------------------------------------------- +get_stats(Ets) -> + Handles = get_handles_and_global_counters(Ets), + (catch do_get_stats(Ets, Handles, [])). + +do_get_stats(_Ets, [], Acc) -> + {ok, lists:reverse(Acc)}; +do_get_stats(Ets, [Handle|Handles], Acc) -> + case get_stats(Ets, Handle) of + {ok, Stats} -> + do_get_stats(Ets, Handles, [{Handle, Stats}|Acc]); + {error, Reason} -> + throw({error, Reason}) + end. + +get_stats(Ets, GlobalCounter) when is_atom(GlobalCounter) -> + case (catch ets:lookup(Ets, GlobalCounter)) of + [{GlobalCounter, Val}] -> + {ok, Val}; + [] -> + {error, {no_such_counter, GlobalCounter}} + end; + +get_stats(Ets, Handle) -> + case (catch ets:match(Ets, {{Handle, '$1'},'$2'})) of + CounterVals when is_list(CounterVals) -> + {ok, [{Counter, Val} || [Counter, Val] <- CounterVals]}; + Other -> + {error, {unexpected_result, Other}} + end. + + +get_stats(Ets, Handle, Counter) when is_atom(Counter) -> + Key = {Handle, Counter}, + case (catch ets:lookup(Ets, Key)) of + [{Key, Val}] -> + {ok, Val}; + _ -> + {error, {undefined_counter, Counter}} + end. + + +%%----------------------------------------------------------------- +%% Funcs: reset_stats/1, reset_stats/2 +%% Description: Reset statistics +%%----------------------------------------------------------------- +reset_stats(Ets) -> + Handles = get_handles_and_global_counters(Ets), + (catch do_reset_stats(Ets, Handles, [])). + +do_reset_stats(_Ets, [], Acc) -> + {ok, lists:reverse(Acc)}; +do_reset_stats(Ets, [Handle|Handles], Acc) -> + case reset_stats(Ets, Handle) of + {ok, OldStats} -> + do_reset_stats(Ets, Handles, [{Handle, OldStats}|Acc]); + {error, Reason} -> + throw({error, Reason}) + end. + +reset_stats(Ets, GlobalCounter) when is_atom(GlobalCounter) -> + %% First get the current value of the counter + case (catch ets:lookup(Ets, GlobalCounter)) of + [{GlobalCounter, Val}] -> + ets:insert(Ets, {GlobalCounter, 0}), + {ok, Val}; + [] -> %% Oooups + {error, {no_such_counter, GlobalCounter}} + end; + +reset_stats(Ets, Handle) -> + case (catch ets:match(Ets, {{Handle, '$1'},'$2'})) of + CounterVals when is_list(CounterVals) -> + CVs = [{Counter, Val} || [Counter, Val] <- CounterVals], + reset_stats(Ets, Handle, CVs), + {ok, CVs}; + Other -> + {error, {unexpected_result, Other}} + end. + +reset_stats(_Ets, _Handle, []) -> + ok; +reset_stats(Ets, Handle, [{Counter, _}|CVs]) -> + ets:insert(Ets, {{Handle, Counter}, 0}), + reset_stats(Ets, Handle, CVs). + + + +%%----------------------------------------------------------------- +%% Internal functions +%%----------------------------------------------------------------- +get_handles_and_global_counters(Ets) -> + GlobalCounters = + case ets:lookup(Ets, global_counters) of + [{global_counters, GC}] -> + GC; + [] -> + [] + end, + L1 = ets:match(Ets, {{'$1', '_'}, '_'}), + GlobalCounters ++ + lists:sort([Handle || [Handle] <- remove_duplicates(L1, [])]). + +remove_duplicates([], L) -> + L; +remove_duplicates([H|T], L) -> + case lists:member(H,T) of + true -> + remove_duplicates(T, L); + false -> + remove_duplicates(T, [H|L]) + end. + diff --git a/lib/megaco/src/engine/megaco_sup.erl b/lib/megaco/src/engine/megaco_sup.erl new file mode 100644 index 0000000000..c61a1da298 --- /dev/null +++ b/lib/megaco/src/engine/megaco_sup.erl @@ -0,0 +1,116 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. 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: The top supervisor for the Megaco/H.248 application +%%---------------------------------------------------------------------- + +-module(megaco_sup). + +-behaviour(application). +-behaviour(supervisor). + +%% public +-export([start/0, start/2, stop/1]). +-export([start_sup_child/1, stop_sup_child/1]). + +%% internal +-export([init/1]). + +%% debug +-export([supervisor_timeout/1]). + + +%% -define(d(F,A), io:format("~p~p:" ++ F ++ "~n", [self(),?MODULE|A])). +-define(d(F,A), ok). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% application and supervisor callback functions + +start(normal, Args) -> + ?d("start(normal) -> entry with" + "~n Args: ~p", [Args]), + SupName = {local, ?MODULE}, + case supervisor:start_link(SupName, ?MODULE, [Args]) of + {ok, Pid} -> + {ok, Pid, {normal, Args}}; + Error -> + Error + end; +start(_, _) -> + {error, badarg}. + +start() -> + ?d("start -> entry", []), + SupName = {local,?MODULE}, + supervisor:start_link(SupName, ?MODULE, []). + +stop(_StartArgs) -> + ok. + +init([]) -> % Supervisor + init(); +init([[]]) -> % Application + init(); +init(BadArg) -> + ?d("init -> entry when" + "~n BadArg: ~p",[BadArg]), + {error, {badarg, BadArg}}. + +init() -> + ?d("init -> entry", []), + Flags = {one_for_one, 0, 1}, + Sups = [sup_spec(megaco_misc_sup), + sup_spec(megaco_trans_sup), + worker_spec(megaco_config, [gen_server]), + worker_spec(megaco_monitor, [gen_server])], + ?d("init -> done when" + "~n Flags: ~p" + "~n Sups: ~p", [Flags, Sups]), + {ok, {Flags, Sups}}. + + +start_sup_child(Name) -> + ?d("start_sup_child -> entry with Name: ~p", [Name]), + Spec = sup_spec(Name), + supervisor:start_child(?MODULE, Spec). + + +stop_sup_child(Name) -> + ?d("stop_sup_child -> entry with Name: ~p", [Name]), + ok = supervisor:terminate_child(?MODULE, Name), + ok = supervisor:delete_child(?MODULE, Name). + + + +sup_spec(Name) -> + {Name, {Name, start, []}, permanent, 2000, supervisor,[Name, supervisor]}. + +worker_spec(Name, Modules) -> + {Name, {Name, start_link, []}, permanent, 2000, worker, [Name] ++ Modules}. + +-ifdef(debug_shutdown). +supervisor_timeout(_KillAfter) -> timer:hours(500). +-else. +supervisor_timeout(KillAfter) -> KillAfter. +-endif. + + diff --git a/lib/megaco/src/engine/megaco_timer.erl b/lib/megaco/src/engine/megaco_timer.erl new file mode 100644 index 0000000000..9f524523a8 --- /dev/null +++ b/lib/megaco/src/engine/megaco_timer.erl @@ -0,0 +1,117 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2009. 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: Timer handling +%%---------------------------------------------------------------------- + +-module(megaco_timer). + +%% Application internal export +-export([ + init/1, + restart/1, + verify/1 + ]). + + +-include_lib("megaco/include/megaco.hrl"). + + +%%----------------------------------------------------------------- + +%% init(Timer) -> {TimeoutTime, NewTimer} +%% Timer = megaco_timer() +%% NewTimer = megaco_timer() +%% TimeoutTime = infinity | integer() +%% +init(SingleWaitFor) when SingleWaitFor == infinity -> + {SingleWaitFor, timeout}; +init(SingleWaitFor) when is_integer(SingleWaitFor) and (SingleWaitFor >= 0) -> + {SingleWaitFor, timeout}; +init(Timer) when is_record(Timer, megaco_incr_timer) -> + return_incr(Timer). + + +%% Returns {WaitFor, NewTimer} | {WaitFor, timeout} +restart(#megaco_incr_timer{wait_for = Old, + factor = Factor, + incr = Incr, + max_retries = MaxRetries} = Timer) -> + New = wait_for(Old, Factor, Incr), + Max = decr(MaxRetries), + Timer2 = Timer#megaco_incr_timer{wait_for = New, + max_retries = Max}, + return_incr(Timer2); +restart({Timer, timeout}) when is_record(Timer, megaco_incr_timer) -> + restart(Timer). + +wait_for(Old, Factor, Incr) -> + New = (Old * Factor) + Incr, + if + New < 0 -> + 0; + true -> + New + end. + +verify(#megaco_incr_timer{wait_for = WaitFor, + factor = Factor, + incr = Incr, + max_retries = MaxRetries}) -> + (megaco_config:verify_strict_uint(WaitFor) and + megaco_config:verify_strict_uint(Factor) and + megaco_config:verify_strict_int(Incr) and + verify_max_retries(MaxRetries)); +verify(Timer) -> + megaco_config:verify_uint(Timer). + +verify_max_retries(infinity_restartable) -> + true; +verify_max_retries(Val) -> + megaco_config:verify_uint(Val). + + +%%----------------------------------------------------------------- + + +return_incr(#megaco_incr_timer{wait_for = WaitFor, + max_retries = infinity} = Timer) -> + {WaitFor, Timer}; + +return_incr(#megaco_incr_timer{wait_for = WaitFor, + max_retries = infinity_restartable} = Timer) -> + {WaitFor, {Timer, timeout}}; + +return_incr(#megaco_incr_timer{wait_for = WaitFor, + max_retries = Int} = Timer) + when is_integer(Int) and (Int > 0) -> + {WaitFor, Timer}; + +return_incr(#megaco_incr_timer{wait_for = WaitFor, + max_retries = 0} = _Timer) -> + {WaitFor, timeout}. + + +decr(infinity = V) -> V; +decr(infinity_restartable = V) -> V; +decr(Int) when is_integer(Int) -> Int - 1. + + diff --git a/lib/megaco/src/engine/megaco_trans_sender.erl b/lib/megaco/src/engine/megaco_trans_sender.erl new file mode 100644 index 0000000000..710fef405a --- /dev/null +++ b/lib/megaco/src/engine/megaco_trans_sender.erl @@ -0,0 +1,699 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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: Transaction sender process +%%---------------------------------------------------------------------- + +-module(megaco_trans_sender). + +-export([start_link/5, + stop/1, + upgrade/2, + send_req/3, + send_reqs/3, + send_ack/2, + send_ack_now/2, + send_pending/2, + send_reply/2, + timeout/2, + ack_maxcount/2, + req_maxcount/2, + req_maxsize/2]). +-export([system_continue/3, system_terminate/4, system_code_change/4]). +-export([init/6]). + + +-include_lib("megaco/include/megaco.hrl"). +-include("megaco_message_internal.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + + +-record(state, + { + parent, + conn_handle, + timeout, + req_sz = 0, + req_maxsize, %% Max total size of all accumulated reqs + req_maxcount, + ack_maxcount, + reqs = [], + acks = [] + }). + + +%%%----------------------------------------------------------------- +%%% Public API +%%%----------------------------------------------------------------- +start_link(CH, To, MaxSzReqs, MaxNoReqs, MaxNoAcks) -> + ?d("start_link -> entry with" + "~n CH: ~p" + "~n To: ~p" + "~n MaxSzReqs: ~p" + "~n MaxNoReqs: ~p" + "~n MaxNoAcks: ~p", [CH, To, MaxSzReqs, MaxNoReqs, MaxNoAcks]), + Args = [self(), CH, To, MaxSzReqs, MaxNoReqs, MaxNoAcks], + proc_lib:start_link(?MODULE, init, Args). + +stop(Pid) when is_pid(Pid) -> + Pid ! stop, + ok. + +upgrade(Pid, CH) when is_pid(Pid) -> + Pid ! {upgrade, CH}, + ok. + +send_req(Pid, Tid, Req) when is_pid(Pid) andalso is_binary(Req) -> + Pid ! {send_req, Tid, Req}, + ok. + +send_reqs(Pid, Tids, Reqs) + when is_pid(Pid) andalso + is_list(Tids) andalso + is_list(Reqs) andalso + (length(Tids) =:= length(Reqs)) -> + Pid ! {send_reqs, Tids, Reqs}, + ok. + +send_ack(Pid, Serial) when is_pid(Pid) andalso is_integer(Serial) -> + Pid ! {send_ack, Serial}, + ok. + +send_ack_now(Pid, Serial) when is_pid(Pid) andalso is_integer(Serial) -> + Pid ! {send_ack_now, Serial}, + ok. + +send_pending(Pid, Serial) when is_pid(Pid) andalso is_integer(Serial) -> + Pid ! {send_pending, Serial}, + ok. + +send_reply(Pid, Reply) when is_pid(Pid) andalso is_binary(Reply) -> + Pid ! {send_reply, Reply}. + +ack_maxcount(Pid, Max) when is_pid(Pid) andalso is_integer(Max) -> + Pid ! {ack_maxcount, Max}, + ok. + +req_maxcount(Pid, Max) when is_pid(Pid) andalso is_integer(Max) -> + Pid ! {req_maxcount, Max}, + ok. + +req_maxsize(Pid, Max) when is_pid(Pid) andalso is_integer(Max) -> + Pid ! {req_maxsize, Max}, + ok. + +timeout(Pid, Timeout) when is_pid(Pid) -> + Pid ! {timeout, Timeout}, + ok. + + + +%%%----------------------------------------------------------------- +%%% Internal exports +%%%----------------------------------------------------------------- + +init(Parent, CH, To, MaxSzReqs, MaxNoReqs, MaxNoAcks) -> + ?d("init -> entry with" + "~n Parent: ~p" + "~n CH: ~p" + "~n To: ~p" + "~n MaxSzReqs: ~p" + "~n MaxNoReqs: ~p" + "~n MaxNoAcks: ~p", [Parent, CH, To, MaxSzReqs, MaxNoReqs, MaxNoAcks]), + process_flag(trap_exit, true), + proc_lib:init_ack(Parent, {ok, self()}), + S = #state{parent = Parent, + conn_handle = CH, + timeout = To, + req_maxsize = MaxSzReqs, + req_maxcount = MaxNoReqs, + ack_maxcount = MaxNoAcks}, + loop(S, To). + + +%%%----------------------------------------------------------------- +%%% Internal functions +%%%----------------------------------------------------------------- +%% idle (= empty) +loop(#state{reqs = [], acks = [], timeout = Timeout} = S, _) -> + receive + {send_ack, Serial} -> + ?d("loop(empty) -> received send_ack [~w] request", [Serial]), + loop(S#state{acks = [Serial]}, Timeout); + + {send_ack_now, Serial} -> + ?d("loop(empty) -> received send_ack_now [~w] request", [Serial]), + send_msg(S#state.conn_handle, [], [Serial]), + loop(S, Timeout); + + {send_req, Tid, Req} when size(Req) >= S#state.req_maxsize -> + ?d("loop(empty) -> received (big) send_req request ~w", [Tid]), + send_msg(S#state.conn_handle, [{Tid, Req}], []), + loop(S, Timeout); + + {send_req, Tid, Req} -> + ?d("loop(empty) -> received send_req request ~w", [Tid]), + loop(S#state{req_sz = size(Req), reqs = [{Tid,Req}]}, Timeout); + + {send_reqs, Tids, Reqs} -> + ?d("loop(empty) -> received send_reqs request: ~w", [Tids]), + {NewS, _} = handle_send_reqs(Tids, Reqs, S), + loop(NewS, Timeout); + + {send_pending, Serial} -> + ?d("loop(empty) -> received send_pending [~w] request", [Serial]), + handle_send_result( + send_pending(S#state.conn_handle, Serial, [], []) + ), + loop(S, Timeout); + + {send_reply, Reply} -> + ?d("loop(empty) -> received send_reply request", []), + #state{conn_handle = CH, req_maxsize = MaxSz} = S, + handle_send_result( send_reply(CH, Reply, MaxSz, 0, [], []) ), + loop(S, Timeout); + + {upgrade, CH} -> + ?d("loop(empty) -> received upgrade request:" + "~n CH: ~p", [CH]), + loop(S#state{conn_handle = CH}, Timeout); + + {ack_maxcount, NewMax} -> + ?d("loop(empty) -> received ack_maxcount request", []), + loop(S#state{ack_maxcount = NewMax}, Timeout); + + {req_maxcount, NewMax} -> + ?d("loop(empty) -> received req_maxcount request", []), + loop(S#state{req_maxcount = NewMax}, Timeout); + + {req_maxsize, NewMax} -> + ?d("loop(empty) -> received req_maxsize request", []), + loop(S#state{req_maxsize = NewMax}, Timeout); + + {timeout, NewTimeout} -> + ?d("loop(empty) -> received timeout request", []), + loop(S#state{timeout = NewTimeout}, NewTimeout); + + stop -> + ?d("loop(empty) -> received stop request", []), + exit(normal); + + {system, From, Msg} -> + ?d("loop(empty) -> received system message:" + "~n From: ~p" + "~n Msg: ~p", [From, Msg]), + Parent = S#state.parent, + sys:handle_system_msg(Msg, From, Parent, + ?MODULE, [], {S, Timeout}); + + {'EXIT', Parent, Reason} when S#state.parent == Parent -> + ?d("loop(empty) -> received upgrade request", []), + exit(Reason); + + M -> + warning_msg("received unexpected message (ignoring): " + "~n~p", [M]), + loop(S, Timeout) + + end; + +%% active (= some acks or reqs waiting to to be sent) +loop(#state{reqs = Reqs, acks = Acks, ack_maxcount = MaxAcks, + timeout = Timeout} = S, To) + when To >= 0 -> + Start = t(), + receive + {send_ack, Serial} when length(Acks) + 1 >= MaxAcks -> + ?d("loop(active,~w) -> " + "received [~w] send_ack [~w] request", + [To, length(Acks), Serial]), + handle_send_result( + send_msg(S#state.conn_handle, Reqs, [Serial|Acks]) + ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout); + + {send_ack, Serial} -> + ?d("loop(active,~w) -> received send_ack [~w] request", + [To, Serial]), + loop(S#state{acks = [Serial|Acks]}, to(To, Start)); + + {send_ack_now, Serial} -> + ?d("loop(active,~w) -> [~w,~w] " + "received send_ack_now [~w] request", + [To, length(Reqs), length(Acks), Serial]), + handle_send_result( + send_msg(S#state.conn_handle, Reqs, [Serial|Acks]) + ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout); + + %% We need to check that this is not a resend!! + %% In that case, send whatever we have in store + {send_req, Tid, Req} -> + ?d("loop(active,~w) -> received send_req request ~w", [To,Tid]), + {NewS, NewT} = + case handle_send_req(Tid, Req, S) of + {S1, true} -> + {S1, Timeout}; + {S1, false} -> + {S1, to(To, Start)} + end, + loop(NewS, NewT); + + {send_reqs, Tids, NewReqs} -> + ?d("loop(active,~w) -> received send_reqs request ~w", [To,Tids]), + {NewS, NewT} = + case handle_send_reqs(Tids, NewReqs, S) of + {S1, true} -> + {S1, Timeout}; + {S1, false} -> + {S1, to(To, Start)} + end, + loop(NewS, NewT); + + {send_pending, Serial} -> + ?d("loop(active,~w) -> received send_pending [~w] request", + [To, Serial]), + handle_send_result( + send_pending(S#state.conn_handle, Serial, Reqs, Acks) + ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout); + + {send_reply, Reply} -> + ?d("loop(active,~w) -> received send_reply request", [To]), + #state{conn_handle = CH, req_maxsize = MaxSz, req_sz = ReqSz} = S, + handle_send_result( + send_reply(CH, Reply, MaxSz, ReqSz, Reqs, Acks) + ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout); + + {upgrade, CH} -> + ?d("loop(active,~w) -> received upgrade request", [To]), + loop(S#state{conn_handle = CH}, to(To, Start)); + + {req_maxsize, NewMax} -> + ?d("loop(active,~w) -> received req_maxsize request", [To]), + loop(S#state{req_maxsize = NewMax}, to(To, Start)); + + {req_maxcount, NewMax} -> + ?d("loop(active,~w) -> received req_maxcount request", [To]), + loop(S#state{req_maxcount = NewMax}, to(To, Start)); + + {ack_maxcount, NewMax} -> + ?d("loop(active,~w) -> received ack_maxcount request", [To]), + loop(S#state{ack_maxcount = NewMax}, to(To, Start)); + + {timeout, NewTimeout} when NewTimeout > Timeout -> + ?d("loop(active,~w) -> received timeout request: ~w", + [To, NewTimeout]), + %% We need to recalculate To + NewTo = NewTimeout - (Timeout - to(To, Start)), + loop(S#state{timeout = NewTimeout}, NewTo); + + {timeout, NewTimeout} -> + ?d("loop(active,~w) -> received timeout request: ~w", + [To, NewTimeout]), + %% We need to recalculate To + NewTo = to(To, Start) - (Timeout - NewTimeout), + loop(S#state{timeout = NewTimeout}, NewTo); + + stop -> + ?d("loop(active,~w) -> received stop request", [To]), + handle_send_result( send_msg(S#state.conn_handle, Reqs, Acks) ), + exit(normal); + + {system, From, Msg} -> + ?d("loop(active,~w) -> received system message:" + "~n From: ~p" + "~n Msg: ~p", [To, From, Msg]), + Parent = S#state.parent, + sys:handle_system_msg(Msg, From, Parent, + ?MODULE, [], {S, to(To, Start)}); + + {'EXIT', Parent, Reason} when S#state.parent == Parent -> + ?d("loop(active,~w) -> received exit request", [To]), + exit(Reason); + + M -> + warning_msg("received unexpected message (ignoring): " + "~n~p", [M]), + loop(S, to(To, Start)) + + after To -> + ?d("loop(active,~w) -> timeout - time to send", [To]), + handle_send_result( send_msg(S#state.conn_handle, Reqs, Acks) ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout) + end; + +loop(#state{reqs = Reqs, acks = Acks, timeout = Timeout} = S, _To) -> + ?d("loop(active) -> timeout [~w, ~w]", [length(Reqs),length(Acks)]), + handle_send_result( send_msg(S#state.conn_handle, Reqs, Acks) ), + loop(S#state{req_sz = 0, reqs = [], acks = []}, Timeout). + + +%%%----------------------------------------------------------------- + +%% The request is itself larger then the max size, so first send +%% everything we have stored in one message, and then the new request +%% in another. +%% Note that it does not matter if we with this request +%% passed the maxcount limit. +%% Note that this message cannot be a re-sent, since +%% such a request would have been stored, but sent immediatly. +handle_send_req(Tid, Req, + #state{conn_handle = CH, + req_maxsize = MaxSz, reqs = Reqs, acks = Acks} = S) + when size(Req) >= MaxSz -> + ?d("handle_send_req -> request bigger then maxsize ~w", [MaxSz]), + handle_send_result( send_msg(CH, Reqs, Acks) ), + handle_send_result( send_msg(CH, [{Tid, Req}], []) ), + {S#state{req_sz = 0, reqs = [], acks = []}, true}; + +%% And handle all the other cases +handle_send_req(Tid, Req, + #state{conn_handle = CH, req_sz = ReqSz, + req_maxcount = MaxReqs, req_maxsize = MaxSz, + reqs = Reqs, acks = Acks} = S) -> + case lists:keymember(Tid, 1, Reqs) of + true -> + %% A re-send, time to send whatever we have in the store + ?d("handle_send_req -> was a re-send, so flush",[]), + handle_send_result( send_msg(CH, Reqs, Acks) ), + {S#state{req_sz = 0, reqs = [], acks = []}, true}; + + false when length(Reqs) + 1 >= MaxReqs -> + %% We finally passed the req-maxcount limit + ?d("handle_send_req -> maxcount ~w passed", [MaxReqs]), + handle_send_result( + send_msg(S#state.conn_handle, [{Tid, Req}|Reqs], Acks) + ), + {S#state{req_sz = 0, reqs = [], acks = []}, true}; + + false when size(Req) + ReqSz >= MaxSz -> + %% We finally passed the req-maxsize limit + ?d("handle_send_req -> maxsize ~w passed", [MaxSz]), + handle_send_result( + send_msg(S#state.conn_handle, [{Tid, Req}|Reqs], Acks) + ), + {S#state{req_sz = 0, reqs = [], acks = []}, true}; + + false -> + %% Still not time to send + ?d("handle_send_req -> nothing to be sent",[]), + {S#state{req_sz = ReqSz + size(Req), reqs = [{Tid, Req}|Reqs]}, + false} + end. + + +%% We passed the req-maxcount limit: Time to send, atleast some of +%% the stuff... +handle_send_reqs(Tids, Reqs0, + #state{conn_handle = CH, + req_maxsize = MaxSz, req_sz = ReqSz, + req_maxcount = MaxReqs, reqs = Reqs, acks = Acks} = S) + when length(Reqs0) + length(Reqs) >= MaxReqs -> + ?d("handle_send_reqs -> maxcount ~w: ~w, ~w", + [MaxSz,length(Reqs0),length(Reqs)]), + Reqs1 = merge_tids_and_reqs(Tids, Reqs0, []), + {NewReqs, NewReqSz} = send_reqs(CH, Reqs1, Acks, Reqs, ReqSz, MaxSz), + ?d("handle_send_reqs -> sent:" + "~n NewReqSz: ~w" + "~n length(NewReqs): ~w", [NewReqSz, length(NewReqs)]), + {S#state{req_sz = NewReqSz, reqs = NewReqs, acks = []}, true}; + +%% We did not pass the req-maxcount limit, but we could have passed the +%% req-maxsize limit, so maybe send... +handle_send_reqs(Tids, Reqs0, #state{conn_handle = CH, + req_maxsize = MaxSz, req_sz = ReqSz, + reqs = Reqs, acks = Acks} = S) -> + ?d("handle_send_reqs -> not maxcount - maybe maxsize (~w)", [MaxSz]), + Reqs1 = merge_tids_and_reqs(Tids, Reqs0, []), + + case maybe_send_reqs(CH, Reqs1, Acks, Reqs, ReqSz, MaxSz, false) of + {NewReqs, NewReqSz, true} -> + ?d("handle_send_reqs -> sent:" + "~n NewReqSz: ~w" + "~n length(NewReqs): ~w", [NewReqSz, length(NewReqs)]), + {S#state{req_sz = NewReqSz, reqs = NewReqs, acks = []}, true}; + {NewReqs, NewReqSz, false} -> + ?d("handle_send_reqs -> not sent:" + "~n NewReqSz: ~w" + "~n length(NewReqs): ~w", [NewReqSz, length(NewReqs)]), + {S#state{req_sz = NewReqSz, reqs = NewReqs}, false} + end. + +merge_tids_and_reqs([], [], Reqs) -> + Reqs; +merge_tids_and_reqs([Tid|Tids], [Req|Reqs], Acc) -> + merge_tids_and_reqs(Tids, Reqs, [{Tid,Req}|Acc]). + +%% We know that we shall send, so if maybe_send_reqs does not, +%% we send it our self... +send_reqs(CH, Reqs, Acks, Acc, AccSz, MaxSz) -> + ?d("send_reqs -> entry when" + "~n length(Reqs): ~w" + "~n Acks: ~w" + "~n length(Acc): ~w" + "~n AccSz: ~w", [length(Reqs), Acks, length(Acc), AccSz]), + case maybe_send_reqs(CH, Reqs, Acks, Acc, AccSz, MaxSz, false) of + {NewReqs, _NewReqSz, false} -> + ?d("send_reqs -> nothing sent yet" + "~n length(NewReqs): ~w", [length(NewReqs)]), + handle_send_result( send_msg(CH, NewReqs, Acks) ), + {[], 0}; + {NewReqs, NewReqSz, true} -> + ?d("send_reqs -> something sent" + "~n length(NewReqs): ~w" + "~n NewReqSz: ~w", [length(NewReqs), NewReqSz]), + {NewReqs, NewReqSz} + end. + + +maybe_send_reqs(_CH, [], _Acks, Acc, AccSz, _MaxSz, Sent) -> + ?d("maybe_send_reqs -> done when" + "~n Sent: ~w" + "~n AccSz: ~w" + "~n length(Acc): ~w", [Sent, AccSz, length(Acc)]), + {Acc, AccSz, Sent}; +maybe_send_reqs(CH, [{Tid, Req}|Reqs], Acks, Acc, _AccSz, MaxSz, _Sent) + when size(Req) >= MaxSz -> + %% The request was above the maxsize limit, so first send + %% what's in store and the the big request. + ?d("maybe_send_reqs -> entry when request [~w] size (~w) > max size" + "~n Acks: ~w" + "~n length(Acc): ~w", [Tid, size(Req), Acks, length(Acc)]), + handle_send_result( send_msg(CH, Acc, Acks) ), + handle_send_result( send_msg(CH, [{Tid, Req}], []) ), + maybe_send_reqs(CH, Reqs, [], [], 0, MaxSz, true); +maybe_send_reqs(CH, [{Tid, Req}|Reqs], Acks, Acc, AccSz, MaxSz, _Sent) + when AccSz + size(Req) >= MaxSz -> + %% We _did_ pass the maxsize limit with this request, so send + ?d("maybe_send_reqs -> entry when sum of requests (~w) > max size" + "~n Tid: ~w" + "~n Acks: ~w" + "~n length(Acc): ~w", [Tid, size(Req) + AccSz, Acks, length(Acc)]), + handle_send_result( send_msg(CH, [{Tid, Req}|Acc], Acks) ), + maybe_send_reqs(CH, Reqs, [], [], 0, MaxSz, true); +maybe_send_reqs(CH, [{Tid, Req}|Reqs], Acks, Acc, AccSz, MaxSz, Sent) -> + ?d("maybe_send_reqs -> entry when" + "~n Tid: ~w" + "~n size(Req): ~w" + "~n Acks: ~w" + "~n length(Acc): ~w" + "~n AccSz: ~w", [Tid, size(Req), Acks, length(Acc), AccSz]), + NewAcc = [{Tid,Req}|Acc], + NewAccSz = AccSz + size(Req), + maybe_send_reqs(CH, Reqs, Acks, NewAcc, NewAccSz, MaxSz, Sent). + + +%%%----------------------------------------------------------------- + +send_pending(CH, Serial, Reqs, Acks) -> + ?d("send_pending -> entry with" + "~n Serial: ~w" + "~n length(Reqs): ~w" + "~n length(Acks): ~w", [Serial, length(Reqs), length(Acks)]), + case megaco_config:lookup_local_conn(CH) of + [CD] -> + TP = #'TransactionPending'{transactionId = Serial}, + Pend = {transactionPending, TP}, + do_send_msg(CD, Pend, lists:reverse(Reqs), Acks); + [] -> + ok + end. + + +%% We need to check the size of the reply. If the reply itself is +%% larger then the max limit, then it is sent in a separate message. +send_reply(CH, Reply, MaxSz, _ReqSz, Reqs, Acks) -> + ?d("send_reply -> entry with" + "~n length(Reqs): ~w" + "~n length(Acks): ~w", [length(Reqs), length(Acks)]), + case megaco_config:lookup_local_conn(CH) of + [CD] when size(Reply) > MaxSz -> + handle_send_result( send_msg(CD, lists:reverse(Reqs), Acks) ), + Rep = {transactionReply, Reply}, + do_send_msg(CD, Rep, [], []); + [CD] -> + Rep = {transactionReply, Reply}, + do_send_msg(CD, Rep, lists:reverse(Reqs), Acks); + [] -> + ok + end. + +do_send_msg(CD, Trans, [], []) -> + Body = {transactions, [Trans]}, + Slogan = "send trans reply/pending", + ?d("do_send_msg -> ~s", [Slogan]), + megaco_messenger_misc:send_body(CD, Slogan, Body); +do_send_msg(CD, Trans, Reqs0, []) -> + Reqs = [{transactionRequest, Req} || {_, Req} <- Reqs0], + Body = {transactions, [Trans|Reqs]}, + Slogan = "send trans reply/pending and reqs", + ?d("do_send_msg -> ~s", [Slogan]), + megaco_messenger_misc:send_body(CD, Slogan, Body); +do_send_msg(CD, Trans, [], SerialRanges) -> + Acks = make_acks(ranges(SerialRanges), []), + Body = {transactions, [Trans, {transactionResponseAck, Acks}]}, + Slogan = "send trans reply/pending and acks", + ?d("do_send_msg -> ~s", [Slogan]), + megaco_messenger_misc:send_body(CD, Slogan, Body); +do_send_msg(CD, Trans, Reqs0, SerialRanges) -> + Acks = make_acks(ranges(SerialRanges), []), + Reqs = [{transactionRequest, Req} || {_, Req} <- Reqs0], + Body = {transactions, [Trans, {transactionResponseAck, Acks}|Reqs]}, + Slogan = "send trans reply/pending, reqs and acks", + ?d("do_send_msg -> ~s", [Slogan]), + megaco_messenger_misc:send_body(CD, Slogan, Body). + + + +send_msg(_, [], []) -> + ok; +send_msg(CH, Reqs, Serials) -> + case megaco_config:lookup_local_conn(CH) of + [ConnData] -> + do_send_msg(ConnData, lists:reverse(Reqs), Serials); + [] -> + ok + end. + + +do_send_msg(CD, Reqs0, []) -> + ?d("do_send_msg -> entry with" + "~n length(Reqs0): ~p", [length(Reqs0)]), + Reqs = [{transactionRequest, Req} || {_, Req} <- Reqs0], + %% ?d("do_send_msg -> Reqs: ~n~p", [Reqs]), + Body = {transactions, Reqs}, + megaco_messenger_misc:send_body(CD, "send trans reqs", Body); +do_send_msg(CD, [], SerialRanges) -> + ?d("do_send_msg -> entry with" + "~n SerialRanges: ~p", [SerialRanges]), + Acks = make_acks(ranges(SerialRanges), []), + %% ?d("do_send_msg -> Reqs: ~n~p", [Reqs]), + Body = {transactions, [{transactionResponseAck, Acks}]}, + megaco_messenger_misc:send_body(CD, "send trans acks", Body); +do_send_msg(CD, Reqs0, SerialRanges) -> + ?d("do_send_msg -> entry with" + "~n length(Reqs0): ~p" + "~n SerialRanges: ~p", [length(Reqs0), SerialRanges]), + Acks = make_acks(ranges(SerialRanges), []), + Reqs = [{transactionRequest, Req} || {_, Req} <- Reqs0], + %% ?d("do_send_msg -> Reqs: ~n~p", [Reqs]), + Body = {transactions, [{transactionResponseAck, Acks}|Reqs]}, + megaco_messenger_misc:send_body(CD, "send trans reqs and acks", Body). + + +handle_send_result(ok) -> + ok; +handle_send_result({ok, _}) -> + ok; +handle_send_result({error, {send_message_cancelled, _Reason}}) -> + ok; +handle_send_result({error, {send_message_failed, Reason}}) -> + error_msg("Failed sending message: ~n ~p", [Reason]), + error; +handle_send_result(Error) -> + error_msg("Failed sending message: ~n ~p", [Error]), + error. + + +ranges(L) -> + lists:reverse(ranges(lists:sort(L), [], [])). + +ranges([], Range, Ranges) -> + ranges2(Range, Ranges); +ranges([S1|Sn], [S2|_] = Range, Ranges) when S1 == (S2+1) -> + ranges(Sn, [S1|Range], Ranges); +ranges([S|Sn], Range, Ranges) -> + ranges(Sn, [S], ranges2(Range, Ranges)). + +ranges2([], Ranges) -> + Ranges; +ranges2([S], Ranges) -> + [{S,S}|Ranges]; +ranges2(Range0, Ranges) -> + Range = lists:reverse(Range0), + [{hd(Range),lists:last(Range)}|Ranges]. + + +make_acks([], Acks) -> + lists:reverse(Acks); +make_acks([{S,S}|SerialRanges], Acks) -> + TRA = #'TransactionAck'{firstAck = S}, + make_acks(SerialRanges, [TRA|Acks]); +make_acks([{F,L}|SerialRanges], Acks) -> + TRA = #'TransactionAck'{firstAck = F, lastAck = L}, + make_acks(SerialRanges, [TRA|Acks]). + + + +%%%----------------------------------------------------------------- + +to(To, Start) -> + To - (t() - Start). + +%% Time in milli seconds +t() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + +warning_msg(F, A) -> + ?megaco_warning("Transaction sender: " ++ F, A). + +error_msg(F, A) -> + ?megaco_error("Transaction sender: " ++ F, A). + + +%%%----------------------------------------------------------------- +%%% System messages handled here +%%%----------------------------------------------------------------- + +system_continue(_Parent, _Dbg, {S,To}) -> + loop(S, To). + +system_terminate(Reason, _Parent, _Dbg, {S, _}) -> + #state{conn_handle = CH, reqs = Reqs, acks = Acks} = S, + send_msg(CH, Reqs, Acks), + exit(Reason). + +system_code_change(S, _Module, _OLdVsn, _Extra) -> + ?d("system_code_change -> entry", []), + {ok, S}. diff --git a/lib/megaco/src/engine/megaco_trans_sup.erl b/lib/megaco/src/engine/megaco_trans_sup.erl new file mode 100644 index 0000000000..6c4a0b7145 --- /dev/null +++ b/lib/megaco/src/engine/megaco_trans_sup.erl @@ -0,0 +1,88 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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: The supervisor for the Megaco/H.248 transaction sender +%% processes. +%%---------------------------------------------------------------------- + +-module(megaco_trans_sup). + +-behaviour(supervisor). + +%% public +-export([start/0, stop/1, init/1]). +-export([start_trans_sender/5]). + +%% -define(d(F,A), io:format("~p~p:" ++ F ++ "~n", [self(),?MODULE|A])). +-define(d(F,A), ok). + +start() -> + ?d("start -> entry",[]), + SupName = {local,?MODULE}, + supervisor:start_link(SupName, ?MODULE, []). + +stop(_StartArgs) -> + ok. + +init([]) -> + init(); +init(BadArg) -> + {error, {badarg, BadArg}}. + +init() -> + ?d("init -> entry",[]), + Flags = {one_for_one, 500, 100}, + Workers = [], + ?d("init -> done",[]), + {ok, {Flags, Workers}}. + + +%%---------------------------------------------------------------------- +%% Function: start_ack_sender/3 +%% Description: Starts a transient worker (child) process +%%---------------------------------------------------------------------- + +start_trans_sender(CH, To, ReqsMaxSz, ReqsMax, AcksMax) -> + ?d("start_ack_sender -> entry with" + "~n CH: ~p" + "~n To: ~p" + "~n ReqsMaxSz: ~p" + "~n ReqsMax: ~p" + "~n AxksMax: ~p", [CH, To, ReqsMaxSz, ReqsMax, AcksMax]), + M = megaco_trans_sender, + F = start_link, + A = [CH, To, ReqsMaxSz, ReqsMax, AcksMax], + N = {M,CH}, + Spec = {N, + {M,F,A}, + temporary, timer:seconds(1), worker, [M,gen_server]}, + case supervisor:start_child(?MODULE, Spec) of + {error, already_present} -> + supervisor:restart_child(?MODULE, N); + Else -> + Else + end. + + + + + + diff --git a/lib/megaco/src/engine/megaco_transport.erl b/lib/megaco/src/engine/megaco_transport.erl new file mode 100644 index 0000000000..79cec8781c --- /dev/null +++ b/lib/megaco/src/engine/megaco_transport.erl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. 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: Megaco transport behaviour module +%%---------------------------------------------------------------------- + +-module(megaco_transport). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{send_message,2}]; +behaviour_info(_) -> + undefined. diff --git a/lib/megaco/src/engine/megaco_user_default.erl b/lib/megaco/src/engine/megaco_user_default.erl new file mode 100644 index 0000000000..ff98107d57 --- /dev/null +++ b/lib/megaco/src/engine/megaco_user_default.erl @@ -0,0 +1,185 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. 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: Default implementation of user callbacks +%%---------------------------------------------------------------------- + +-module(megaco_user_default). + +-behaviour(megaco_user). + +-export([ + handle_connect/2, handle_connect/3, + handle_disconnect/3, + handle_syntax_error/3, handle_syntax_error/4, + handle_message_error/3, handle_message_error/4, + handle_trans_request/3, handle_trans_request/4, + handle_trans_long_request/3, handle_trans_long_request/4, + handle_trans_reply/4, handle_trans_reply/5, + handle_trans_ack/4, handle_trans_ack/5, + handle_unexpected_trans/3, handle_unexpected_trans/4, + handle_trans_request_abort/4, handle_trans_request_abort/5, + handle_segment_reply/5, handle_segment_reply/6 + ]). + +-include_lib("megaco/include/megaco.hrl"). +-include_lib("megaco/include/megaco_message_v1.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + + +%%---------------------------------------------------------------------- +%% Invoked when a new connection is established +%%---------------------------------------------------------------------- + +handle_connect(_ConnHandle, _ProtocolVersion) -> + ok. + +handle_connect(_ConnHandle, _ProtocolVersion, _ConnectInfo) -> + ok. + + +%%---------------------------------------------------------------------- +%% Invoked when a connection is teared down +%%---------------------------------------------------------------------- + +handle_disconnect(ConnHandle, _ProtocolVersion, Reason) -> + megaco:cancel(ConnHandle, Reason), % Cancel the outstanding messages + ok. + + +%%---------------------------------------------------------------------- +%% Invoked when a received message had syntax errors +%%---------------------------------------------------------------------- + +handle_syntax_error(_ReceiveHandle, _ProtocolVersion, _ErrorDescriptor) -> + reply. + +handle_syntax_error(_ReceiveHandle, _ProtocolVersion, _ErrorDescriptor, _Extra) -> + reply. + + +%%---------------------------------------------------------------------- +%% Invoked when a received message contained no transactions +%%---------------------------------------------------------------------- + +handle_message_error(_ConnHandle, _ProtocolVersion, _ErrorDescriptor) -> + no_reply. + +handle_message_error(_ConnHandle, _ProtocolVersion, _ErrorDescriptor, _Extra) -> + no_reply. + + +%%---------------------------------------------------------------------- +%% Invoked for each transaction request +%%---------------------------------------------------------------------- + +handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> + Extra = ?default_user_callback_extra, + handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra). + +handle_trans_request(_ConnHandle, ProtocolVersion, _ActionRequests, _Extra) -> + case ProtocolVersion of + 1 -> + ED = #'ErrorDescriptor'{errorCode = ?megaco_not_implemented, + errorText = "Trans requests not handled"}, + {discard_ack, ED}; + _ -> + ED = #'ErrorDescriptor'{errorCode = ?megaco_version_not_supported, + errorText = "Only version 1 is supported"}, + {discard_ack, ED} + end. + + +%%---------------------------------------------------------------------- +%% Optionally invoked for a time consuming transaction request +%%---------------------------------------------------------------------- + +handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> + Extra = ?default_user_callback_extra, + handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra). + +handle_trans_long_request(_ConnHandle, _ProtocolVersion, _ReqData, _Extra) -> + ED = #'ErrorDescriptor'{errorCode = ?megaco_not_implemented, + errorText = "Long trans requests not handled"}, + {discard_ack, ED}. + + +%%---------------------------------------------------------------------- +%% Optionally invoked for a transaction reply +%%---------------------------------------------------------------------- + +handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData) -> + Extra = ?default_user_callback_extra, + handle_trans_reply(ConnHandle, ProtocolVersion, + ActualReply, ReplyData, Extra). + +handle_trans_reply(ConnHandle, _, {error, {send_message_failed, Reason}}, _, _Extra) -> + megaco:disconnect(ConnHandle, {send_message_failed, Reason}), + ok; +handle_trans_reply(_ConnHandle, _ProtocolVersion, _ActualReply, _ReplyData, _Extra) -> + ok. + + +%%---------------------------------------------------------------------- +%% Optionally invoked for a transaction acknowledgement +%%---------------------------------------------------------------------- + +handle_trans_ack(_ConnHandle, _ProtocolVersion, _AckStatus, _AckData) -> + ok. + +handle_trans_ack(_ConnHandle, _ProtocolVersion, _AckStatus, _AckData, _Extra) -> + ok. + + +%%---------------------------------------------------------------------- +%% Invoked when an unexpected message has been received +%%---------------------------------------------------------------------- + +handle_unexpected_trans(_ConnHandle, _ProtocolVersion, _Trans) -> + ok. + +handle_unexpected_trans(_ConnHandle, _ProtocolVersion, _Trans, _Extra) -> + ok. + + +%%---------------------------------------------------------------------- +%% Invoked when an transaction has been aborted +%% This happens when the originating pending limit has been exceeded +%%---------------------------------------------------------------------- + +handle_trans_request_abort(_ConnHandle, _ProtocolVersion, _TransId, _Pid) -> + ok. + +handle_trans_request_abort(_ConnHandle, _ProtocolVersion, _TransId, _Pid, _Extra) -> + ok. + + +%%---------------------------------------------------------------------- +%% Invoked a segment reply has been received and the user has set +%% config option segment_reply_ind = true. +%%---------------------------------------------------------------------- + +handle_segment_reply(_ConnHandle, _ProtocolVersion, _TransId, _SN, _SC) -> + ok. + +handle_segment_reply(_ConnHandle, _ProtocolVersion, _TransId, _SN, _SC, _Extra) -> + ok. + diff --git a/lib/megaco/src/engine/modules.mk b/lib/megaco/src/engine/modules.mk new file mode 100644 index 0000000000..44bcadc37b --- /dev/null +++ b/lib/megaco/src/engine/modules.mk @@ -0,0 +1,47 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2001-2009. 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% + +BEHAVIOUR_MODULES = \ + megaco_edist_compress \ + megaco_encoder \ + megaco_transport + +MODULES = \ + $(BEHAVIOUR_MODULES) \ + megaco_config \ + megaco_digit_map \ + megaco_erl_dist_encoder \ + megaco_erl_dist_encoder_mc \ + megaco_filter \ + megaco_messenger \ + megaco_messenger_misc \ + megaco_misc_sup \ + megaco_monitor \ + megaco_sdp \ + megaco_sup \ + megaco_stats \ + megaco_timer \ + megaco_trans_sender \ + megaco_trans_sup \ + megaco_user_default + +INTERNAL_HRL_FILES = \ + megaco_message_internal.hrl + + |