1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-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 : Megaco/H.248 customization of the Event Tracer tool
24%%----------------------------------------------------------------------
25%%
26
27-module(megaco_filter).
28
29-export([start/0, start/1, filter/1, raw_filter/1,
30	 pretty_error/1, string_to_term/1]).
31
32
33-include_lib("megaco/include/megaco.hrl").
34-include_lib("megaco/include/megaco_message_v1.hrl").
35-include_lib("megaco/src/app/megaco_internal.hrl").
36-include_lib("et/include/et.hrl").
37
38
39%%----------------------------------------------------------------------
40%% BUGBUG: There are some opportunities for improvements:
41%%
42%% * This version of the module does only handle version 1 of the messages.
43%%
44%% * The record definition of megaco_transaction_reply is copied from
45%%   megaco_message_internal.hrl as that header file contains some
46%%   records that already are defined in megaco_message_{v1,v2,v3}.hrl.
47%% * The records megaco_udp and megaco_tcp are copied from the files
48%%   megaco_udp.hrl and megaco_tcp.hrl respectively, as we cannot include
49%%   both header files.
50%%   They both defines the macros HEAP_SIZE and GC_MSG_LIMIT.
51
52%%-include("megaco_message_internal.hrl").
53-record('megaco_transaction_reply',
54	{
55	  transactionId,
56	  immAckRequired       = asn1_NOVALUE,
57	  transactionResult,
58	  segmentNumber        = asn1_NOVALUE,
59	  segmentationComplete = asn1_NOVALUE
60	 }).
61
62%% -include_lib("megaco/src/udp/megaco_udp.hrl").
63-record(megaco_udp,
64	{port,
65	 options   = [],
66	 socket,
67	 receive_handle,
68	 module    = megaco,
69	 serialize = false  % false: Spawn a new process for each message
70	}).
71
72%% -include_lib("megaco/src/tcp/megaco_tcp.hrl").
73-record(megaco_tcp,
74	{host,
75	 port,
76	 options   = [],
77	 socket,
78	 proxy_pid,
79	 receive_handle,
80	 module    = megaco,
81	 serialize = false  % false: Spawn a new process for each message
82	}).
83
84
85%%----------------------------------------------------------------------
86
87start() ->
88    start([]).
89
90start(ExtraOptions) ->
91    Options =
92	[{event_order, event_ts},
93	 {scale, 3},
94	 {max_actors, infinity},
95	 {trace_pattern, {megaco, max}},
96	 {trace_global, true},
97	 {dict_insert, {filter, ?MODULE}, fun filter/1},
98	 {active_filter, ?MODULE},
99	 {title, "Megaco tracer - Erlang/OTP"} | ExtraOptions],
100    et_viewer:start(Options).
101
102filter(E) ->
103    case catch raw_filter(E) of
104	{'EXIT', Reason} = Error->
105	    io:format("~p: ~p\n", [?MODULE, Error]),
106	    exit(Reason);
107	E2 ->
108	    E2
109    end.
110
111raw_filter(E) when is_record(E, event) ->
112    From = filter_actor(E#event.from),
113    To 	 = filter_actor(E#event.to),
114    E2 	 = E#event{from = From, to = To},
115    E3 	 = filter_contents(E#event.contents, E2),
116    {true, E3}.
117
118filter_actors(From, To, E)
119  when (E#event.from =:= ?APPLICATION) andalso (E#event.to =:= ?APPLICATION) ->
120    Label = E#event.label,
121    case lists:prefix("callback:", Label) of
122	true ->
123	    E#event{from = filter_actor(From),
124		    to   = filter_user_actor(From)};
125	false ->
126	    case lists:prefix("return:", Label) of
127		true ->
128		    E#event{from = filter_user_actor(From),
129			    to   = filter_actor(From)};
130		false ->
131		    case lists:prefix("receive bytes", Label) of
132			true ->
133			    E#event{from = filter_actor(To),
134				    to   = filter_actor(From)};
135			false ->
136			    E#event{from = filter_actor(From),
137				    to   = filter_actor(To)}
138		    end
139	    end
140    end;
141filter_actors(_From, _To, E) ->
142    E.
143
144filter_actor(Actor) ->
145    String = do_filter_actor(Actor),
146    if
147	length(String) > 21 ->
148	    string:substr(String, 1, 21) ++ [$*];
149	true ->
150	    String
151    end.
152
153filter_user_actor(Actor) ->
154    String = do_filter_actor(Actor) ++ "@user",
155    if
156	length(String) > 21 ->
157	    string:substr(String, 1, 21) ++ [$*];
158	true ->
159	    String
160    end.
161
162do_filter_actor(CH) when is_record(CH, megaco_conn_handle) ->
163    Mid = CH#megaco_conn_handle.local_mid,
164    do_filter_actor(Mid);
165do_filter_actor(RH) when is_record(RH, megaco_receive_handle) ->
166    Mid = RH#megaco_receive_handle.local_mid,
167    do_filter_actor(Mid);
168do_filter_actor(Actor) ->
169    case Actor of
170	{ip4Address, {'IP4Address', [A1,A2,A3,A4], asn1_NOVALUE}} ->
171	    integer_to_list(A1) ++ [$.] ++
172	    integer_to_list(A2) ++ [$.] ++
173	    integer_to_list(A3) ++ [$.] ++
174	    integer_to_list(A4);
175	{ip4Address, {'IP4Address', [A1,A2,A3,A4], Port}} ->
176	    integer_to_list(A1) ++ [$.] ++
177	    integer_to_list(A2) ++ [$.] ++
178	    integer_to_list(A3) ++ [$.] ++
179	    integer_to_list(A4) ++ [$:] ++
180	    integer_to_list(Port);
181	{domainName, {'DomainName', Name, asn1_NOVALUE}} ->
182	    Name;
183	{domainName, {'DomainName', Name, Port}} ->
184	    Name ++ [$:] ++ integer_to_list(Port);
185	{deviceName, Name} ->
186	    Name;
187	unknown_remote_mid ->
188	    "preliminary_mid";
189	preliminary_mid ->
190	    "preliminary_mid";
191	megaco ->
192	    megaco;
193	_Other ->
194	    "UNKNOWN"
195    end.
196
197
198filter_contents(Contents, E) ->
199    do_filter_contents(Contents, E, missing_conn_data, []).
200
201do_filter_contents([H | T], E, ConnData, Contents) ->
202    case H of
203	Udp when is_record(Udp, megaco_udp) ->
204	    RH = Udp#megaco_udp.receive_handle,
205	    Actor = filter_actor(RH),
206	    E2 = E#event{from = Actor, to = Actor},
207	    Pretty =
208		["Port:    ", integer_to_list(Udp#megaco_udp.port), "\n",
209		 "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)],
210	    do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]);
211	Tcp when is_record(Tcp, megaco_tcp) ->
212	    RH = Tcp#megaco_tcp.receive_handle,
213	    Actor = filter_actor(RH),
214	    E2 = E#event{from = Actor, to = Actor},
215	    Pretty =
216		["Port:    ", integer_to_list(Tcp#megaco_tcp.port), "\n",
217		 "Encoder: ", atom_to_list(RH#megaco_receive_handle.encoding_mod)],
218	    do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]);
219	CD when is_record(CD, conn_data) ->
220	    CH = CD#conn_data.conn_handle,
221	    From = CH#megaco_conn_handle.local_mid,
222	    To = CH#megaco_conn_handle.remote_mid,
223	    E2 = filter_actors(From, To, E),
224	    Serial = CD#conn_data.serial,
225	    E3 = append_serial(Serial, E2),
226	    do_filter_contents(T, E3, CD, Contents);
227	CH when is_record(CH, megaco_conn_handle) ->
228	    From = CH#megaco_conn_handle.local_mid,
229	    To = CH#megaco_conn_handle.remote_mid,
230	    E2 = filter_actors(From, To, E),
231	    do_filter_contents(T, E2, ConnData, Contents);
232	RH when is_record(RH, megaco_receive_handle) ->
233	    Actor = RH#megaco_receive_handle.local_mid,
234	    E2 = filter_actors(Actor, Actor, E),
235	    do_filter_contents(T, E2, ConnData, Contents);
236	{error, Reason} ->
237	    Pretty = pretty_error({error, Reason}),
238	    E2 = prepend_error(E),
239	    do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]);
240	{'EXIT', Reason} ->
241	    Pretty = pretty_error({'EXIT', Reason}),
242	    E2 = prepend_error(E),
243	    do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]);
244	ED when is_record(ED, 'ErrorDescriptor') ->
245	    Pretty = pretty_error(ED),
246	    E2 = prepend_error(E),
247	    do_filter_contents(T, E2, ConnData, [[Pretty, "\n"], Contents]);
248	Trans when is_record(Trans, 'TransactionRequest') ->
249	    Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}),
250	    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
251	{transactionRequest, Trans} when is_record(Trans, 'TransactionRequest') ->
252	    Pretty = pretty(ConnData, {trans, {transactionRequest, Trans}}),
253	    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
254	Trans when is_record(Trans, 'TransactionReply') ->
255	    Pretty = pretty(ConnData, {trans, {transactionReply, Trans}}),
256	    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
257	Trans when is_record(Trans, megaco_transaction_reply) ->
258	    %% BUGBUG: Version 1 special
259	    TransV1 =
260		#'TransactionReply'{transactionId     = Trans#megaco_transaction_reply.transactionId,
261				    immAckRequired    = Trans#megaco_transaction_reply.immAckRequired,
262				    transactionResult = Trans#megaco_transaction_reply.transactionResult},
263	    Pretty = pretty(ConnData, {trans, {transactionReply, TransV1}}),
264	    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
265	Trans when is_record(Trans, 'TransactionPending') ->
266	    Pretty = pretty(ConnData, {trans, {transactionPending, Trans}}),
267	    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
268	Trans when is_record(Trans, 'TransactionAck') ->
269	    Pretty = pretty(ConnData, {trans, {transactionResponseAck, [Trans]}}),
270	    case Trans#'TransactionAck'.lastAck of
271		asn1_NOVALUE ->
272		    do_filter_contents([], E, ConnData, [[Pretty, "\n"], Contents]);
273		Last ->
274		    Label = term_to_string(E#event.label),
275		    E2 = E#event{label = Label ++ ".." ++ integer_to_list(Last)},
276		    do_filter_contents([], E2, ConnData, [[Pretty, "\n"], Contents])
277	    end;
278	{context_id, _ContextId} ->
279	    Pretty = pretty(ConnData, H),
280	    do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]);
281	{command_request, CmdReq} ->
282	    Pretty = pretty(ConnData, CmdReq),
283	    do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents]);
284	{user_reply, {ok, ARS}} ->
285	    Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS],
286	    do_filter_contents(T, E, ConnData, [["USER REPLY OK: \n", Pretty, "\n"], Contents]);
287	{user_reply, Error} ->
288	    Pretty = pretty_error(Error),
289	    do_filter_contents(T, E, ConnData, [["USER REPLY ERROR: \n", Pretty, "\n"], Contents]);
290	{actionReplies, ARS} ->
291	    Pretty = [[pretty(ConnData, AR), "\n"] || AR <- ARS],
292	    do_filter_contents(T, E, ConnData, [["ACTION REPLIES: \n", Pretty, "\n"], Contents]);
293	MegaMsg when is_record(MegaMsg, 'MegacoMessage') ->
294	    Pretty = pretty(ConnData, MegaMsg),
295	    do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]);
296	{message, MegaMsg} when is_record(MegaMsg, 'MegacoMessage') ->
297	    Pretty = pretty(ConnData, MegaMsg),
298	    do_filter_contents(T, E, ConnData, [Pretty, "\n", Contents]);
299	{bytes, Bin} when is_binary(Bin) ->
300            E2 =
301		case E#event.label of
302		    [$s, $e, $n, $d, $ , $b, $y, $t, $e, $s | Tail] ->
303			L = lists:concat(["send ", size(Bin), " bytes", Tail]),
304			E#event{label = L};
305		    [$r, $e, $c, $e, $i, $v, $e, $ , $b, $y, $t, $e, $s | Tail] ->
306			L = lists:concat(["receive ", size(Bin), " bytes", Tail]),
307			E#event{label = L};
308		    _ ->
309			E
310		end,
311	    CharList = erlang:binary_to_list(Bin),
312	    do_filter_contents(T, E2, ConnData, [[CharList , "\n"], Contents]);
313	List when is_list(List) ->
314	    %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces
315	    do_filter_contents(List ++ T, E, ConnData, Contents);
316	Int when is_integer(Int) ->
317	    %% BUGBUG: Workaround as megaco_messenger puts nested lists in its traces
318	    do_filter_contents(T, E, ConnData, Contents);
319	{line, _Mod, _Line} ->
320	    do_filter_contents(T, E, ConnData, Contents);
321	{orig_conn_handle, _CH} ->
322	    do_filter_contents(T, E, ConnData, Contents);
323	{pid, Pid} when is_pid(Pid) ->
324	    do_filter_contents(T, E, ConnData, Contents);
325	pending ->
326	    do_filter_contents(T, E, ConnData, Contents);
327	reply ->
328	    do_filter_contents(T, E, ConnData, Contents);
329	{test_lib, _Mod, _Fun} ->
330	    do_filter_contents(T, E, ConnData, Contents);
331	{trans_id, _TransId} ->
332	    do_filter_contents(T, E, ConnData, Contents);
333	{send_func, _FunName} ->
334	    do_filter_contents(T, E, ConnData, Contents);
335	Pid when is_pid(Pid) ->
336	    do_filter_contents(T, E, ConnData, Contents);
337	Other ->
338	    Pretty = pretty(ConnData, Other),
339	    do_filter_contents(T, E, ConnData, [[Pretty, "\n"], Contents])
340    end;
341do_filter_contents([], E, _ConnData, Contents) ->
342    E#event{contents = lists:flatten(lists:reverse(Contents))}.
343
344append_serial(Serial, E) when is_integer(Serial) ->
345    Label = term_to_string(E#event.label),
346    E#event{label = Label ++ " #" ++ integer_to_list(Serial)};
347append_serial(_Serial, E) ->
348    E.
349
350prepend_error(E) ->
351    Label = term_to_string(E#event.label),
352    E#event{label = "<ERROR> " ++ Label}.
353
354pretty(_ConnData, {context_id, ContextId}) ->
355     if
356	 ContextId =:= ?megaco_null_context_id ->
357	     ["CONTEXT ID: -\n"];
358	 ContextId =:= ?megaco_choose_context_id ->
359	     ["CONTEXT ID: $\n"];
360	 ContextId =:= ?megaco_all_context_id ->
361	     ["CONTEXT ID: *\n"];
362	 is_integer(ContextId) ->
363	     ["CONTEXT ID: ",integer_to_list(ContextId), "\n"]
364     end;
365pretty(_ConnData, MegaMsg) when is_record(MegaMsg, 'MegacoMessage') ->
366    {ok, Bin} = megaco_pretty_text_encoder:encode_message([], MegaMsg),
367    term_to_string(Bin);
368pretty(_ConnData, CmdReq) when is_record(CmdReq, 'CommandRequest') ->
369    {ok, Bin} = megaco_pretty_text_encoder:encode_command_request(CmdReq),
370    term_to_string(Bin);
371pretty(_ConnData, {complete_success, ContextId, RepList}) ->
372    ActRep = #'ActionReply'{contextId    = ContextId,
373			    commandReply = RepList},
374    {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep),
375    term_to_string(Bin);
376pretty(_ConnData, AR) when is_record(AR, 'ActionReply') ->
377    {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(AR),
378    term_to_string(Bin);
379pretty(_ConnData, {partial_failure, ContextId, RepList}) ->
380    ActRep = #'ActionReply'{contextId    = ContextId,
381			    commandReply = RepList},
382    {ok, Bin} = megaco_pretty_text_encoder:encode_action_reply(ActRep),
383    term_to_string(Bin);
384pretty(_ConnData, {trans, Trans}) ->
385    {ok, Bin} = megaco_pretty_text_encoder:encode_transaction(Trans),
386    term_to_string(Bin);
387pretty(__ConnData, Other) ->
388    term_to_string(Other).
389
390pretty_error({error, Reason}) ->
391    ["ERROR: ", pretty_error(Reason)];
392pretty_error({'EXIT', Reason}) ->
393    ["EXIT: ", pretty_error(Reason)];
394pretty_error({'ErrorDescriptor', Code, Reason}) ->
395    ["CODE: ", integer_to_list(Code), " TEXT: ", pretty_error(Reason)];
396pretty_error(Ugly) ->
397    case string_to_term(Ugly) of
398	{ok, Pretty} -> ["\n", Pretty];
399	_            -> ["\n", term_to_string(Ugly)]
400    end.
401
402string_to_term(Chars) ->
403    do_string_to_term([], Chars, 1).
404
405do_string_to_term(Cont, Chars, Line) ->
406    case catch erl_scan:tokens(Cont, Chars, Line) of
407	{done, {ok, Tokens, _EndLine}, _Rest} ->
408	    case erl_parse:parse_term(Tokens) of
409		{ok, Term} ->
410		    {ok, Term};
411		{error, Reason} ->
412		    {error, Reason}
413	    end;
414	{more, Cont2} ->
415	    do_string_to_term(Cont2, ". ", Line);
416	Other ->
417	    {error, Other}
418    end.
419
420term_to_string(Bin) when is_binary(Bin) ->
421    binary_to_list(Bin);
422term_to_string(Term) ->
423    case catch io_lib:format("~s", [Term]) of
424        {'EXIT', _} -> lists:flatten(io_lib:format("~p", [Term]));
425        GoodString  -> lists:flatten(GoodString)
426    end.
427