aboutsummaryrefslogtreecommitdiffstats
path: root/lib/megaco/src/engine
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/megaco/src/engine
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/megaco/src/engine')
-rw-r--r--lib/megaco/src/engine/Makefile107
-rw-r--r--lib/megaco/src/engine/depend.mk81
-rw-r--r--lib/megaco/src/engine/megaco_config.erl2113
-rw-r--r--lib/megaco/src/engine/megaco_digit_map.erl856
-rw-r--r--lib/megaco/src/engine/megaco_edist_compress.erl33
-rw-r--r--lib/megaco/src/engine/megaco_encoder.erl37
-rw-r--r--lib/megaco/src/engine/megaco_erl_dist_encoder.erl275
-rw-r--r--lib/megaco/src/engine/megaco_erl_dist_encoder_mc.erl1894
-rw-r--r--lib/megaco/src/engine/megaco_filter.erl350
-rw-r--r--lib/megaco/src/engine/megaco_message_internal.hrl159
-rw-r--r--lib/megaco/src/engine/megaco_messenger.erl5158
-rw-r--r--lib/megaco/src/engine/megaco_messenger_misc.erl409
-rw-r--r--lib/megaco/src/engine/megaco_misc_sup.erl77
-rw-r--r--lib/megaco/src/engine/megaco_monitor.erl365
-rw-r--r--lib/megaco/src/engine/megaco_sdp.erl1645
-rw-r--r--lib/megaco/src/engine/megaco_stats.erl207
-rw-r--r--lib/megaco/src/engine/megaco_sup.erl116
-rw-r--r--lib/megaco/src/engine/megaco_timer.erl117
-rw-r--r--lib/megaco/src/engine/megaco_trans_sender.erl699
-rw-r--r--lib/megaco/src/engine/megaco_trans_sup.erl88
-rw-r--r--lib/megaco/src/engine/megaco_transport.erl32
-rw-r--r--lib/megaco/src/engine/megaco_user_default.erl185
-rw-r--r--lib/megaco/src/engine/modules.mk47
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
+
+