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