1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%%----------------------------------------------------------------------
23%% Purpose: Misc functions used both from the megaco_messenger module
24%%          and the megaco_ack_sender module.
25%%
26%%----------------------------------------------------------------------
27
28-module(megaco_messenger_misc).
29
30%% Application internal export
31-export([encode_body/3,
32	 encode_trans_request/2,
33	 encode_trans_reply/2,
34	 encode_actions/3,
35	 send_body/3,
36	 send_message/3,
37
38	 transform_transaction_reply/2
39        ]).
40
41%% Test functions
42-export([compose_message/3, encode_message/2]).
43
44
45-include_lib("megaco/include/megaco.hrl").
46-include("megaco_message_internal.hrl").
47-include_lib("megaco/src/app/megaco_internal.hrl").
48
49-define(MSG_HDR_SZ, 128). % This is just a guess...
50
51-ifdef(MEGACO_TEST_CODE).
52-define(SIM(Other,Where),
53	fun(Afun,Bfun) ->
54		Kfun = {?MODULE,Bfun},
55		case (catch ets:lookup(megaco_test_data, Kfun)) of
56		    [{Kfun,Cfun}] ->
57			Cfun(Afun);
58		    _ ->
59			Afun
60		end
61	end(Other,Where)).
62-define(TC_AWAIT_SEND_EVENT(SendFunction),
63        case megaco_tc_controller:lookup(send_function) of
64            {value, {Tag, Pid}} when is_pid(Pid) ->
65                Pid ! {Tag, self(), SendFuncion},
66                receive
67                    {Tag, Pid} ->
68                        ok
69                end;
70            _ ->
71                ok
72        end).
73-else.
74-define(SIM(Other,Where),Other).
75-define(TC_AWAIT_SEND_EVENT(_),ok).
76-endif.
77
78
79%%----------------------------------------------------------------------
80%% Encode the transaction request
81%%----------------------------------------------------------------------
82
83encode_trans_request(CD, TR) when is_record(TR, 'TransactionRequest') ->
84    ?report_debug(CD, "encode trans request", [TR]),
85    Trans = {transactionRequest, TR},
86    encode_transaction(CD, Trans).
87
88encode_trans_reply(#conn_data{segment_send     = SegSend,
89			      max_pdu_size     = Max,
90			      protocol_version = V} = CD, Reply)
91  when (SegSend == infinity) or (is_integer(SegSend) and (SegSend > 0)) and
92       is_integer(V) and (V >= 3) and
93       is_integer(Max) and (Max >= ?MSG_HDR_SZ) ->
94    (catch encode_segmented_trans_reply(CD, Reply));
95encode_trans_reply(CD, TR) when is_record(TR, megaco_transaction_reply) ->
96    ?report_debug(CD, "encode trans reply", [TR]),
97    Trans = {transactionReply, transform_transaction_reply(CD, TR)},
98    encode_transaction(CD, Trans);
99encode_trans_reply(CD, TR) when is_tuple(TR) and
100				(element(1, TR) == 'TransactionReply') ->
101    ?report_debug(CD, "encode trans reply", [TR]),
102    Trans = {transactionReply, TR},
103    encode_transaction(CD, Trans).
104
105
106encode_segmented_trans_reply(#conn_data{max_pdu_size = Max} = CD, Rep) ->
107    #megaco_transaction_reply{transactionResult = Res1} = Rep,
108    case Res1 of
109	{actionReplies, AR} when is_list(AR) andalso (length(AR) >= 1) ->
110	    case encode_action_replies(CD, AR) of
111		{Size, EncodedARs} when Size =< (Max - ?MSG_HDR_SZ) ->
112		    ?report_debug(CD, "action replies encoded size ok",
113				  [Size, Max]),
114		    %% No need to segment message: within size limit
115		    Res2 = {actionReplies, EncodedARs},
116		    TR = Rep#megaco_transaction_reply{transactionResult = Res2},
117		    TR2   = transform_transaction_reply(CD, TR),
118		    Trans = {transactionReply, TR2},
119		    encode_transaction(CD, Trans);
120
121		{Size, EncodecARs} ->
122		    ?report_debug(CD,
123				  "action replies encoded size to large - "
124				  "segment",
125				  [Size, Max]),
126		    %% Over size limit, so go segment the message
127		    encode_segments(CD, Rep, EncodecARs)
128	    end;
129	_ ->
130	    TR    = transform_transaction_reply(CD, Rep),
131	    Trans = {transactionReply, TR},
132	    encode_transaction(CD, Trans)
133    end.
134
135encode_segments(CD, Reply, EncodecARs) ->
136    encode_segments(CD, Reply, EncodecARs, 1, []).
137
138encode_segments(CD, Reply, [EncodedAR], SN, EncodedSegs) ->
139    Bin = encode_segment(CD, Reply, EncodedAR, SN, 'NULL'),
140    {ok, lists:reverse([{SN, Bin}|EncodedSegs])};
141encode_segments(CD, Reply, [EncodedAR|EncodedARs], SN, EncodedSegs) ->
142    Bin = encode_segment(CD, Reply, EncodedAR, SN, asn1_NOVALUE),
143    encode_segments(CD, Reply, EncodedARs, SN + 1, [{SN, Bin}|EncodedSegs]).
144
145encode_segment(CD, Reply, EncodedAR, SN, SC) ->
146    Res   = {actionReplies, [EncodedAR]},
147    TR0   = Reply#megaco_transaction_reply{transactionResult    = Res,
148					   segmentNumber        = SN,
149					   segmentationComplete = SC},
150    TR    = transform_transaction_reply(CD, TR0),
151    Trans = {transactionReply, TR},
152    case encode_transaction(CD, Trans) of
153	{ok, Bin} ->
154	    Bin;
155	Error ->
156	    throw(Error)
157    end.
158
159
160encode_transaction(#conn_data{protocol_version = V,
161			      encoding_mod     = EM,
162			      encoding_config  = EC} = CD, Trans) ->
163    case (catch EM:encode_transaction(EC, V, Trans)) of
164	{ok, Bin} ->
165	    ?SIM({ok, Bin}, encode_trans);
166        {'EXIT', {undef, _}} ->
167            {error, not_implemented};
168	{error, not_implemented} = Error1 ->
169	    Error1;
170	{error, Reason} ->
171	    incNumErrors(CD#conn_data.conn_handle),
172            {error, {EM, encode_transaction, [EC, V, Trans], Reason}};
173	Error2 ->
174	    incNumErrors(CD#conn_data.conn_handle),
175            {error, {EM, encode_transaction, [EC, V, Trans], Error2}}
176    end.
177
178
179%%----------------------------------------------------------------------
180%% Encode the action request's
181%%----------------------------------------------------------------------
182
183encode_actions(#conn_data{protocol_version = V} = CD, TraceLabel, ARs) ->
184    ?report_debug(CD, TraceLabel, [ARs]),
185
186    %% Encode the actions
187    EM = CD#conn_data.encoding_mod,
188    EC = CD#conn_data.encoding_config,
189    case (catch EM:encode_action_requests(EC, V, ARs)) of
190        {ok, Bin} when is_binary(Bin) ->
191            ?SIM({ok, Bin}, encode_actions);
192        {'EXIT', {undef, _}} ->
193            incNumErrors(CD#conn_data.conn_handle),
194            Reason = not_implemented,
195            {error, {EM, encode_action_requests, [EC, ARs], Reason}};
196        {error, Reason} ->
197	    incNumErrors(CD#conn_data.conn_handle),
198            {error, {EM, encode_action_requests, [EC, ARs], Reason}};
199        Error ->
200	    incNumErrors(CD#conn_data.conn_handle),
201            {error, {EM, encode_action_requests, [EC, ARs], Error}}
202    end.
203
204
205%%----------------------------------------------------------------------
206%% Encode the action reply's
207%%----------------------------------------------------------------------
208
209encode_action_replies(CD, AR) ->
210    encode_action_replies(CD, AR, 0, []).
211
212encode_action_replies(_, [], Size, Acc) ->
213    {Size, lists:reverse(Acc)};
214encode_action_replies(#conn_data{protocol_version = V,
215				 encoding_mod     = Mod,
216				 encoding_config  = Conf} = CD,
217		      [AR|ARs], Size, Acc) ->
218    case (catch Mod:encode_action_reply(Conf, V, AR)) of
219	{ok, Bin} when is_binary(Bin) ->
220	    encode_action_replies(CD, ARs, Size + size(Bin), [Bin|Acc]);
221        {'EXIT', {undef, _}} ->
222            throw({error, not_implemented});
223	{error, not_implemented} = Error1 ->
224	    throw(Error1);
225	{error, Reason} ->
226            incNumErrors(CD#conn_data.conn_handle),
227	    throw({error, {Mod, encode_action_reply, [Conf, AR], Reason}});
228	Error ->
229            incNumErrors(CD#conn_data.conn_handle),
230	    throw({error, {Mod, encode_action_reply, [Conf, AR], Error}})
231    end.
232
233
234%%----------------------------------------------------------------------
235%% Encode the message body
236%%----------------------------------------------------------------------
237
238encode_body(#conn_data{protocol_version = V} = ConnData,
239	    TraceLabel, Body) ->
240    %% Create the message envelope
241    MegaMsg = compose_message(ConnData, V, Body),
242
243    ?report_debug(ConnData, TraceLabel, [MegaMsg]),
244
245    %% Encode the message
246    EM = ConnData#conn_data.encoding_mod,
247    EC = ConnData#conn_data.encoding_config,
248    case (catch EM:encode_message(EC, V, MegaMsg)) of
249        {ok, Bin} when is_binary(Bin) ->
250            ?SIM({ok, Bin}, encode_body);
251        {error, Reason} ->
252	    incNumErrors(ConnData#conn_data.conn_handle),
253            {error, {EM, [EC, MegaMsg], Reason}};
254        Error ->
255	    incNumErrors(ConnData#conn_data.conn_handle),
256            {error, {EM, [EC, MegaMsg], Error}}
257    end.
258
259
260%%----------------------------------------------------------------------
261%% Compose and encode a message
262%%----------------------------------------------------------------------
263compose_message(#conn_data{conn_handle = CH,
264			   auth_data   = MsgAuth}, V, Body) ->
265    LocalMid = CH#megaco_conn_handle.local_mid,
266    Msg      = #'Message'{version     = V,
267			  mId         = LocalMid,
268			  messageBody = Body},
269    MegaMsg  = #'MegacoMessage'{authHeader = MsgAuth, % BUGBUG: Compute?
270				mess       = Msg},
271    MegaMsg.
272
273
274encode_message(#conn_data{protocol_version = Version,
275			  encoding_mod     = EncodingMod,
276			  encoding_config  = EncodingConfig},  MegaMsg) ->
277    (catch EncodingMod:encode_message(EncodingConfig, Version, MegaMsg)).
278
279
280%%----------------------------------------------------------------------
281%% Send the message body
282%%----------------------------------------------------------------------
283
284send_body(ConnData, TraceLabel, Body) ->
285    case encode_body(ConnData, TraceLabel, Body) of
286        {ok, Bin} ->
287            send_message(ConnData, false, Bin);
288        {error, Reason} ->
289            {error, Reason}
290    end.
291
292
293%%----------------------------------------------------------------------
294%% Send the (encoded) message
295%%----------------------------------------------------------------------
296
297send_message(#conn_data{resend_indication = flag} = ConnData,
298	     Resend, Bin) ->
299    do_send_message(ConnData, send_message, Bin, [Resend]);
300
301send_message(#conn_data{resend_indication = true} = ConnData,
302	     true, Bin) ->
303    do_send_message(ConnData, resend_message, Bin, []);
304
305send_message(ConnData, _Resend, Bin) ->
306    do_send_message(ConnData, send_message, Bin, []).
307
308do_send_message(ConnData, SendFunc, Bin, Extra) ->
309    %% Send the message
310    #conn_data{send_mod    = SendMod,
311	       send_handle = SendHandle} = ConnData,
312
313    ?TC_AWAIT_SEND_EVENT(SendFunc),
314
315    ?report_trace(ConnData, "send bytes", [{bytes,     Bin},
316					   {send_func, SendFunc}]),
317
318    Args = [SendHandle, Bin | Extra],
319    case (catch apply(SendMod, SendFunc, Args)) of
320        ok ->
321            ?SIM({ok, Bin}, send_message);
322        {cancel, Reason} ->
323            ?report_trace(ConnData, "<CANCEL> send_message callback",
324			  [{bytes, Bin}, {cancel, Reason}]),
325            {error, {send_message_cancelled, Reason}};
326        {error, Reason} ->
327	    incNumErrors(ConnData#conn_data.conn_handle),
328            ?report_important(ConnData, "<ERROR> send_message callback",
329                              [{bytes, Bin}, {error, Reason}]),
330	    error_msg("failed (error) sending message [using ~w] (~p):"
331		      "~n~w", [SendFunc, SendHandle, Reason]),
332            {error, {send_message_failed, Reason}};
333        {'EXIT', Reason} = Error ->
334	    incNumErrors(ConnData#conn_data.conn_handle),
335            ?report_important(ConnData, "<ERROR> send_message callback",
336                              [{bytes, Bin}, {exit, Reason}]),
337	    error_msg("failed (exit) sending message [using ~w] (~p):"
338		      "~n~w", [SendFunc, SendHandle, Reason]),
339            {error, {send_message_failed, Error}};
340        Reason ->
341	    incNumErrors(ConnData#conn_data.conn_handle),
342            ?report_important(ConnData, "<ERROR> send_message callback",
343                              [{bytes, Bin}, {error, Reason}]),
344	    error_msg("failed sending message [using ~w] on (~p): "
345		      "~n~w", [SendFunc, SendHandle, Reason]),
346            {error, {send_message_failed, Reason}}
347    end.
348
349
350%%%-----------------------------------------------------------------
351%%% Misc internal util functions
352%%%-----------------------------------------------------------------
353
354transform_transaction_reply(#conn_data{protocol_version = V}, TR)
355  when is_integer(V) and (V >= 3) ->
356    #megaco_transaction_reply{transactionId        = TransId,
357			      immAckRequired       = IAR,
358			      transactionResult    = TransRes,
359			      segmentNumber        = SegNo,
360			      segmentationComplete = SegComplete} = TR,
361    {'TransactionReply', TransId, IAR, TransRes, SegNo, SegComplete};
362transform_transaction_reply(_, TR) ->
363    #megaco_transaction_reply{transactionId        = TransId,
364			      immAckRequired       = IAR,
365			      transactionResult    = TransRes} = TR,
366    {'TransactionReply', TransId, IAR, TransRes}.
367
368
369%%-----------------------------------------------------------------
370%% Func: error_msg/2
371%% Description: Send an error message
372%%-----------------------------------------------------------------
373
374error_msg(F, A) ->
375    ?megaco_error(F, A).
376
377
378%%-----------------------------------------------------------------
379%% Func: incNumErrors/0, incNumErrors/1, incNumTimerRecovery/1
380%% Description: SNMP counter increment functions
381%%-----------------------------------------------------------------
382
383incNumErrors(CH) ->
384    incNum({CH, medGwyGatewayNumErrors}).
385
386incNum(Cnt) ->
387    case (catch ets:update_counter(megaco_stats, Cnt, 1)) of
388	{'EXIT', {badarg, _R}} ->
389	    ets:insert(megaco_stats, {Cnt, 1});
390	Old ->
391	    Old
392    end.
393
394%% p(F, A) ->
395%%     print(now(), F, A).
396
397%% print(Ts, F, A) ->
398%%     io:format("*** [~s] ~p ***"
399%% 		 "~n   " ++ F ++ "~n",
400%% 		 [format_timestamp(Ts), self() | A]).
401
402%% format_timestamp(Now) ->
403%%     {_N1, _N2, N3}   = Now,
404%%     {Date, Time}   = calendar:now_to_datetime(Now),
405%%     {YYYY,MM,DD}   = Date,
406%%     {Hour,Min,Sec} = Time,
407%%     FormatDate =
408%% 	   io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
409%% 			 [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),
410%%     lists:flatten(FormatDate).
411