1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2001-2020. 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%% Purpose : Scanner for text encoded Megaco/H.248 messages
23%%----------------------------------------------------------------------
24
25-module(megaco_flex_scanner).
26
27-export([is_enabled/0, is_reentrant_enabled/0, is_scanner_port/2]).
28-export([start/0, start/1, stop/1, scan/2]).
29
30-define(NUM_SCHED(),           erlang:system_info(schedulers)).
31-define(SCHED_ID(),            erlang:system_info(scheduler_id)).
32-define(SMP_SUPPORT_DEFAULT(), erlang:system_info(smp_support)).
33
34-dialyzer({nowarn_function, is_enabled/0}).
35is_enabled() ->
36    (true =:= ?ENABLE_MEGACO_FLEX_SCANNER).
37
38-dialyzer({nowarn_function, is_reentrant_enabled/0}).
39is_reentrant_enabled() ->
40    (true =:= ?MEGACO_REENTRANT_FLEX_SCANNER).
41
42is_scanner_port(Port, Port) when is_port(Port) ->
43    true;
44is_scanner_port(Port, Ports) when is_tuple(Ports) ->
45    is_own_port(Port, Ports);
46is_scanner_port(_, _) ->
47    false.
48
49is_own_port(Port, Ports) ->
50    is_own_port(Port, size(Ports), Ports).
51
52is_own_port(_Port, 0, _Ports) ->
53    false;
54is_own_port(Port, N, Ports) when (N > 0) ->
55    case element(N, Ports) of
56	Port ->
57	    true;
58	_ ->
59	    is_own_port(Port, N-1, Ports)
60    end.
61
62
63%%----------------------------------------------------------------------
64%% Start the flex scanner
65%%----------------------------------------------------------------------
66
67start() ->
68    start(?SMP_SUPPORT_DEFAULT()).
69
70start(SMP) when ((SMP =:= true) orelse (SMP =:= false)) ->
71    (catch do_start(is_reentrant_enabled() andalso SMP)).
72
73do_start(SMP) ->
74    Path = lib_dir(),
75    erl_ddll:start(),
76    load_driver(Path),
77    PortOrPorts = open_drv_port(SMP),
78    {ok, PortOrPorts}.
79
80
81lib_dir() ->
82    case code:priv_dir(megaco) of
83	{error, Reason} ->
84	    throw({error, {priv_dir, Reason}});
85	P when is_list(P) ->
86	    P ++ "/lib"
87    end.
88
89
90load_driver(Path) ->
91    case erl_ddll:load_driver(Path, drv_name()) of
92	ok ->
93	    ok;
94	{error, Reason} ->
95	    case (catch erl_ddll:format_error(Reason)) of
96		FormatReason when is_list(FormatReason) ->
97		    throw({error, {load_driver, FormatReason}});
98		_ ->
99		    throw({error, {load_driver, Reason}})
100	    end
101    end.
102
103
104open_drv_port(true) ->
105    open_drv_ports(?NUM_SCHED(), []);
106open_drv_port(_) ->
107    open_drv_port().
108
109open_drv_ports(0, Acc) ->
110    list_to_tuple(Acc);
111open_drv_ports(N, Acc) when is_integer(N) andalso (N > 0) ->
112    Port = open_drv_port(),
113    open_drv_ports(N-1, [Port | Acc]).
114
115open_drv_port() ->
116    case (catch erlang:open_port({spawn, drv_name()}, [binary])) of
117	Port when is_port(Port) ->
118	    Port;
119	{'EXIT', Reason} ->
120	    erl_ddll:unload_driver(drv_name()),
121	    throw({error, {open_port, Reason}})
122    end.
123
124drv_name() ->
125    case erlang:system_info(threads) of
126	true ->
127	    "megaco_flex_scanner_drv_mt";
128	false ->
129	    "megaco_flex_scanner_drv"
130    end.
131
132
133%%----------------------------------------------------------------------
134%% Stop the flex scanner
135%%----------------------------------------------------------------------
136
137stop(Port) when is_port(Port) ->
138    erlang:port_close(Port),
139    erl_ddll:unload_driver(drv_name()),
140    stopped;
141stop(Ports) when is_tuple(Ports) ->
142    stop(tuple_to_list(Ports));
143stop(Ports) when is_list(Ports) ->
144    lists:foreach(fun(Port) ->  erlang:port_close(Port) end, Ports),
145    erl_ddll:unload_driver(drv_name()),
146    stopped.
147
148
149%%----------------------------------------------------------------------
150%% Scan a message
151%%----------------------------------------------------------------------
152
153scan(Binary, Port) when is_port(Port) ->
154    do_scan(Binary, Port);
155scan(Binary, Ports) when is_tuple(Ports) ->
156%%     p("scan -> entry with"
157%%       "~n   Ports: ~p", [Ports]),
158    do_scan(Binary, select_port(Ports)).
159
160do_scan(Binary, Port) ->
161%%     p("do_scan -> entry with"
162%%       "~n   Port: ~p", [Port]),
163    case erlang:port_control(Port, $s, Binary) of
164	[] ->
165	    receive
166		{tokens, Tokens, LatestLine} ->
167%% 		    p("do_scan -> OK with:"
168%% 		      "~n   length(Tokens): ~p"
169%% 		      "~n   LatestLine:     ~p", [length(Tokens), LatestLine]),
170		    Vsn = version(Tokens),
171		    {ok, Tokens, Vsn, LatestLine}
172	    after 5000 ->
173%%  		    p("do_scan -> ERROR", []),
174		    {error, "Driver term send failure", 1}
175	    end;
176	Reason ->
177%% 	    p("do_scan -> port control failed: "
178%% 	      "~n   Reason: ~p", [Reason]),
179	    {error, Reason, 1}
180    end.
181
182select_port(Ports) ->
183    SchedId = ?SCHED_ID(),
184    %% lists:nth(SchedId, Ports).
185    element(SchedId, Ports).
186
187version([]) ->
188    99; % Let the parser deal with this
189version([{'SafeChars',_,"!/1"}|_]) ->
190    1;
191version([{'SafeChars',_,"megaco/1"}|_]) ->
192    1;
193version([{'SafeChars',_,"!/2"}|_]) ->
194    2;
195version([{'SafeChars',_,"megaco/2"}|_]) ->
196    2;
197version([{'SafeChars',_,"!/3"}|_]) ->
198    3;
199version([{'SafeChars',_,"megaco/3"}|_]) ->
200    3;
201version([{'SafeChars',_,[$!, $/ | Vstr]}|_]) ->
202    guess_version(Vstr);
203version([{'SafeChars',_,[$m, $e, $g, $a, $c, $o, $/ | Vstr]}|_]) ->
204    guess_version(Vstr);
205version([_|T]) ->
206    version(T).
207
208
209guess_version([C]) when (48 =< C) and (C =< 57) ->
210    C-48;
211guess_version(Str) when is_list(Str) ->
212    case (catch list_to_integer(Str)) of
213	I when is_integer(I) ->
214	    I;
215	_ ->
216	    99 % Let the parser deal with this
217    end;
218guess_version(_) ->
219    99. % Let the parser deal with this
220
221
222%% p(F, A) ->
223%%     io:format("~w [~p,~p] " ++ F ++ "~n", [?MODULE, self(), ?SCHED_ID() | A]).
224