188 lines
6.7 KiB
Erlang
188 lines
6.7 KiB
Erlang
% Osmocom Global Title Translation
|
|
|
|
% (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
|
%
|
|
% All Rights Reserved
|
|
%
|
|
% This program is free software; you can redistribute it and/or modify
|
|
% it under the terms of the GNU Affero General Public License as
|
|
% published by the Free Software Foundation; either version 3 of the
|
|
% License, or (at your option) any later version.
|
|
%
|
|
% This program is distributed in the hope that it will be useful,
|
|
% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
% GNU General Public License for more details.
|
|
%
|
|
% You should have received a copy of the GNU Affero General Public License
|
|
% along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
%
|
|
% Additional Permission under GNU AGPL version 3 section 7:
|
|
%
|
|
% If you modify this Program, or any covered work, by linking or
|
|
% combining it with runtime libraries of Erlang/OTP as released by
|
|
% Ericsson on http://www.erlang.org (or a modified version of these
|
|
% libraries), containing parts covered by the terms of the Erlang Public
|
|
% License (http://www.erlang.org/EPLICENSE), the licensors of this
|
|
% Program grant you additional permission to convey the resulting work
|
|
% without the need to license the runtime libraries of Erlang/OTP under
|
|
% the GNU Affero General Public License. Corresponding Source for a
|
|
% non-source form of such a combination shall include the source code
|
|
% for the parts of the runtime libraries of Erlang/OTP used as well as
|
|
% that of the covered work.
|
|
|
|
-module(osmo_ss7_gtt).
|
|
-author('Harald Welte <laforge@gnumonks.org>').
|
|
|
|
-include("sccp.hrl").
|
|
-include("gtt.hrl").
|
|
|
|
-export([global_title_match/2, apply_gtt_actions/2, execute_gtt/2]).
|
|
|
|
-compile({parse_transform, exprecs}).
|
|
-export_records([gtt_match, gtt_act_repl_digits, gtt_act_repl_num_plan]).
|
|
|
|
% Match a given GT against an ordered list of {match, action} tuples
|
|
global_title_match([], _Gt) ->
|
|
false;
|
|
global_title_match([{Match, Action}|Tail], Gt) when is_record(Gt, global_title) ->
|
|
PhoneNumInt = osmo_util:digit_list2int(Gt#global_title.phone_number),
|
|
if Match#gtt_match.gt_range_from >= PhoneNumInt ->
|
|
% in an ordered list, we can assume that no trailing rules will
|
|
% match
|
|
false;
|
|
true ->
|
|
case single_gt_match(Match, Gt) of
|
|
true ->
|
|
Action;
|
|
_ ->
|
|
% iterate further over the list of GTT rules
|
|
global_title_match(Tail, Gt)
|
|
end
|
|
end;
|
|
% Same as above, but for SCCP Address (i.e. GT + point code and SSN)
|
|
global_title_match([{Match, Action}|Tail], SccpAddr) when
|
|
is_record(SccpAddr, sccp_addr) ->
|
|
Gt = SccpAddr#sccp_addr.global_title,
|
|
PhoneNumInt = osmo_util:digit_list2int(Gt#global_title.phone_number),
|
|
if Match#gtt_match.gt_range_from >= PhoneNumInt ->
|
|
% in an ordered list, we can assume that no trailing rules will
|
|
% match
|
|
false;
|
|
true ->
|
|
case single_gt_match(Match, SccpAddr) of
|
|
true ->
|
|
Action;
|
|
_ ->
|
|
% iterate further over the list of GTT rules
|
|
global_title_match(Tail, SccpAddr)
|
|
end
|
|
end.
|
|
|
|
|
|
% perform matching of a given global title against a single match
|
|
single_gt_match(Match, Gt) when is_record(Match, gtt_match),
|
|
is_record(Gt, global_title) ->
|
|
#gtt_match{gt_range_from = RangeFrom, gt_range_to = RangeTo,
|
|
numbering_plan = NumPlan,
|
|
nature_of_addr_ind = NatureInd} = Match,
|
|
#global_title{phone_number = GtPhoneNum,
|
|
numbering_plan = GtNumPlan,
|
|
nature_of_addr_ind = GtNature} = Gt,
|
|
% build a list of the individual criteria that all have to match
|
|
SubMatchList = [{digits, {RangeFrom, RangeTo}, GtPhoneNum},
|
|
{numbering_plan, NumPlan, GtNumPlan},
|
|
{nature_of_addr_ind, NatureInd, GtNature}],
|
|
gt_sub_match_list(SubMatchList);
|
|
% Same as above, but for SCCP Address (i.e. GT + point code and SSN)
|
|
single_gt_match(Match, SccpAddr) when is_record(Match, gtt_match),
|
|
is_record(SccpAddr, sccp_addr) ->
|
|
#gtt_match{dpc = Dpc, ssn = Ssn} = Match,
|
|
Gt = SccpAddr#sccp_addr.global_title,
|
|
% First match the GT part
|
|
case single_gt_match(Match, Gt) of
|
|
false ->
|
|
false;
|
|
true ->
|
|
% build a list of the individual criteria that all have to match
|
|
SubMatchList = [{dpc, Dpc, SccpAddr#sccp_addr.point_code},
|
|
{ssn, Ssn, SccpAddr#sccp_addr.ssn}],
|
|
gt_sub_match_list(SubMatchList)
|
|
end.
|
|
|
|
% iterate over the list of individual match criteria and call the match function
|
|
gt_sub_match_list([]) ->
|
|
true;
|
|
gt_sub_match_list([{What, MatchPart, GtPart}|SubMatchList]) ->
|
|
case gt_sub_match(What, MatchPart, GtPart) of
|
|
false ->
|
|
false;
|
|
true ->
|
|
gt_sub_match_list(SubMatchList)
|
|
end.
|
|
|
|
% matching of the actual phone number digits
|
|
gt_sub_match(digits, {DigitsFrom, DigitsTo}, GtPart) ->
|
|
PhoneNumInt = osmo_util:digit_list2int(GtPart),
|
|
if
|
|
PhoneNumInt >= DigitsFrom, PhoneNumInt =< DigitsTo -> true;
|
|
true -> false
|
|
end;
|
|
% any match that is not qualified will always match
|
|
gt_sub_match(_What, undefined, _GtPart) ->
|
|
true;
|
|
% remaining default match for all other fields
|
|
gt_sub_match(_What, MatchPart, GtPart) ->
|
|
if
|
|
MatchPart == GtPart -> true;
|
|
true -> false
|
|
end.
|
|
|
|
|
|
% Execute a single action: Replac some digits in the GT
|
|
gtt_action(Gt, Action) when is_record(Gt, global_title),
|
|
is_record(Action, gtt_act_repl_digits) ->
|
|
#gtt_act_repl_digits{replace_digit_start = ReplDigStart,
|
|
replace_digit_end = ReplDigEnd,
|
|
new_digits = NewDigits} = Action,
|
|
GtDigitList = Gt#global_title.phone_number,
|
|
Header = lists:sublist(GtDigitList, 1, ReplDigStart-1),
|
|
Trailer = lists:sublist(GtDigitList, ReplDigEnd+1, length(GtDigitList)),
|
|
Gt#global_title{phone_number = Header ++ NewDigits ++ Trailer};
|
|
|
|
% Execute a single action: Replac the numbering plan in the GT
|
|
gtt_action(Gt, #gtt_act_repl_num_plan{numbering_plan = NewNumPlan})
|
|
when is_record(Gt, global_title) ->
|
|
Gt#global_title{numbering_plan = NewNumPlan};
|
|
|
|
% Execute a single 'generic purpose' action that will call apply/2
|
|
gtt_action(Gt, #gtt_act_apply{funct = Funct, args = Args}) when
|
|
is_record(Gt, global_title) ->
|
|
apply(Funct, Args).
|
|
|
|
|
|
% appliy a list of GTT actions to a Global Title
|
|
apply_gtt_actions(Gt, []) when is_record(Gt, global_title) ->
|
|
Gt;
|
|
apply_gtt_actions(Gt, [Head|List]) when is_record(Gt, global_title) ->
|
|
NewGt = gtt_action(Gt, Head),
|
|
apply_gtt_actions(NewGt, List);
|
|
apply_gtt_actions(Gt, Action) when is_record(Gt, global_title) ->
|
|
gtt_action(Gt, Action).
|
|
|
|
% Execute a complete GTT operation: matching + executing the action
|
|
execute_gtt(Gt, RulesList) when is_record(Gt, global_title),
|
|
is_list(RulesList) ->
|
|
case global_title_match(RulesList, Gt) of
|
|
false ->
|
|
Gt;
|
|
Action ->
|
|
apply_gtt_actions(Gt, Action)
|
|
end;
|
|
% Same as above, but for SCCP Address (i.e. GT + point code and SSN)
|
|
execute_gtt(SccpAddr, RulesList) when is_record(SccpAddr, sccp_addr),
|
|
is_list(RulesList) ->
|
|
Gt = SccpAddr#sccp_addr.global_title,
|
|
NewGt = execute_gtt(Gt, RulesList),
|
|
SccpAddr#sccp_addr{global_title = NewGt}.
|