add new mangle_tt_sri_sm module and associated eunit tests
This module is able to match a SCCP message against a given
Destination-GT prefix and checks if the message contains an
Invoke(SRI-for-SM). If both conditions are true, it alters the TT from
whatever it may currently be to 3.
This is used for SMS special routing cases at the STP.
diff --git a/src/mangle_tt_sri_sm.erl b/src/mangle_tt_sri_sm.erl
new file mode 100644
index 0000000..25db695
--- /dev/null
+++ b/src/mangle_tt_sri_sm.erl
@@ -0,0 +1,186 @@
+% FIXME
+
+% (C) 2012 by Harald Welte <laforge@gnumonks.org>
+% (C) 2012 OnWaves
+%
+% 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(mangle_tt_sri_sm).
+-author("Harald Welte <laforge@gnumonks.org>").
+
+-export([mangle_tt_sri_sm/4]).
+
+-export([gt_match_pfx/2, gt_match_pfx_list/2,
+ isup_party_match_pfx/2, isup_party_match_pfx_list/2]).
+
+-export([get_tcap_components/1, get_tcap_operation/1, get_tcap_operations/1,
+ check_for_tcap_op/3, check_for_invoke_sri_sm/1]).
+
+-include_lib("osmo_map/include/map.hrl").
+-include_lib("osmo_ss7/include/sccp.hrl").
+-include_lib("osmo_ss7/include/isup.hrl").
+
+% high-level function to determine if a Sccp / MAP message contains a Invoke SRI-SM
+check_for_invoke_sri_sm(MapDec) ->
+ check_for_tcap_op(invoke, {local, 45}, MapDec).
+
+
+% check if there's a prefix match between a given GT and prefix
+gt_match_pfx(GT, MatchPfx) when is_record(GT, global_title),
+ is_integer(MatchPfx) ->
+ gt_match_pfx(GT, osmo_util:int2digit_list(MatchPfx));
+gt_match_pfx(GT, MatchPfx) when is_record(GT, global_title),
+ is_list(MatchPfx) ->
+ match_pfx(GT#global_title.phone_number, MatchPfx).
+
+% check if there's a prefix match between a given ISUP party_addr and prefix
+isup_party_match_pfx(Party, MatchPfx) when is_record(Party, party_number),
+ is_integer(MatchPfx) ->
+ isup_party_match_pfx(Party, osmo_util:int2digit_list(MatchPfx));
+isup_party_match_pfx(Party, MatchPfx) when is_record(Party, party_number) ->
+ DigitsIn = Party#party_number.phone_number,
+ match_pfx(DigitsIn, MatchPfx).
+
+match_pfx(DigitsIn, MatchPfx) when is_list(DigitsIn), is_list(MatchPfx) ->
+ MatchPfxLen = length(MatchPfx),
+ Pfx = lists:sublist(DigitsIn, 1, MatchPfxLen),
+ case Pfx of
+ MatchPfx ->
+ true;
+ _ ->
+ false
+ end.
+
+% check if there's a prefix match of Global Titles among a list of prefixes
+gt_match_pfx_list(GT, []) when is_record(GT, global_title) ->
+ false;
+gt_match_pfx_list(GT, [MatchPfx|Tail]) when is_record(GT, global_title) ->
+ case gt_match_pfx(GT, MatchPfx) of
+ true ->
+ true;
+ _ ->
+ gt_match_pfx_list(GT, Tail)
+ end.
+
+% check if there's a prefix match of ISUP Party number among a list of prefixes
+isup_party_match_pfx_list(PN, []) when is_record(PN, party_number) ->
+ false;
+isup_party_match_pfx_list(PN, [MatchPfx|Tail]) when is_record(PN, party_number) ->
+ case isup_party_match_pfx(PN, MatchPfx) of
+ true ->
+ true;
+ _ ->
+ isup_party_match_pfx_list(PN, Tail)
+ end.
+
+% get a list of components from the decoded TCAP+MAP nested record
+get_tcap_components({'begin', Beg}) ->
+ get_tcap_components(Beg);
+get_tcap_components({'end', Beg}) ->
+ get_tcap_components(Beg);
+get_tcap_components({'continue', Beg}) ->
+ get_tcap_components(Beg);
+% map.erl
+get_tcap_components(#'MapSpecificPDUs_begin'{components=Comps}) ->
+ Comps;
+get_tcap_components(#'MapSpecificPDUs_continue'{components=Comps}) ->
+ Comps;
+get_tcap_components(#'MapSpecificPDUs_end'{components=Comps}) ->
+ Comps;
+get_tcap_components(_) ->
+ [].
+
+% get the MAP operation of a specific component
+get_tcap_operation({basicROS, Rec}) ->
+ get_tcap_operation(Rec);
+get_tcap_operation({invoke, Rec}) ->
+ get_tcap_operation(Rec);
+get_tcap_operation({returnResult, Rec}) ->
+ get_tcap_operation(Rec);
+get_tcap_operation({returnResultNotLast, Rec}) ->
+ get_tcap_operation(Rec);
+% map.erl
+get_tcap_operation(#'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke'{opcode=Op}) ->
+ {invoke, Op};
+get_tcap_operation(#'MapSpecificPDUs_continue_components_SEQOF_basicROS_invoke'{opcode=Op}) ->
+ {invoke, Op};
+get_tcap_operation(#'MapSpecificPDUs_end_components_SEQOF_basicROS_invoke'{opcode=Op}) ->
+ {invoke, Op};
+get_tcap_operation(#'MapSpecificPDUs_begin_components_SEQOF_basicROS_returnResult'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_begin_components_SEQOF_basicROS_returnResult_result'.opcode};
+get_tcap_operation(#'MapSpecificPDUs_continue_components_SEQOF_basicROS_returnResult'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_continue_components_SEQOF_basicROS_returnResult_result'.opcode};
+get_tcap_operation(#'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_end_components_SEQOF_basicROS_returnResult_result'.opcode};
+get_tcap_operation(#'MapSpecificPDUs_begin_components_SEQOF_returnResultNotLast'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_begin_components_SEQOF_returnResultNotLast_result'.opcode};
+get_tcap_operation(#'MapSpecificPDUs_continue_components_SEQOF_returnResultNotLast'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_continue_components_SEQOF_returnResultNotLast_result'.opcode};
+get_tcap_operation(#'MapSpecificPDUs_end_components_SEQOF_returnResultNotLast'{result=Res}) ->
+ {returnResult, Res#'MapSpecificPDUs_end_components_SEQOF_returnResultNotLast_result'.opcode}.
+
+% get a list of the MAP operations inside the components of a MAP message
+get_tcap_operations(MapDec) ->
+ Comps = get_tcap_components(MapDec),
+ [get_tcap_operation(X) || X <- Comps].
+
+
+check_for_tcap_op(Comp, Op, SccpDec) when is_record(SccpDec, sccp_msg) ->
+ UserData = proplists:get_value(user_data, SccpDec#sccp_msg.parameters),
+ MapDec = map_codec:parse_tcap_msg(UserData),
+ check_for_tcap_op(Comp, Op, MapDec);
+
+check_for_tcap_op(Comp, Op, MapDec) ->
+ MapOps = get_tcap_operations(MapDec),
+ % check for invoke of SRI-for-SM:
+ lists:member({Comp, Op}, MapOps).
+
+
+mangle_tt_sri_sm(from_msc, _Path, ?SCCP_MSGT_UDT, SccpDec = #sccp_msg{parameters=Opts}) ->
+ CalledParty = proplists:get_value(called_party_addr, Opts),
+ CalledGT = CalledParty#sccp_addr.global_title,
+ {ok, PrefixList} = application:get_env(mgw_nat, mangle_tt_sri_sm_pfx),
+ case gt_match_pfx_list(CalledGT, PrefixList) of
+ true ->
+ case check_for_invoke_sri_sm(SccpDec) of
+ true ->
+ CalledGTNew = CalledGT#global_title{trans_type = 3},
+ CalledPartyNew = CalledParty#sccp_addr{global_title = CalledGTNew},
+ ParamsOut = lists:keyreplace(called_party_addr, 1, Opts,
+ {called_party_addr, CalledPartyNew}),
+ SccpDec#sccp_msg{parameters=ParamsOut};
+ _ ->
+ SccpDec
+ end;
+ _ ->
+ SccpDec
+ end;
+mangle_tt_sri_sm(_, _, _, SccpIn) ->
+ SccpIn.
diff --git a/test/mangle_tt_sri_sm_tests.erl b/test/mangle_tt_sri_sm_tests.erl
new file mode 100644
index 0000000..fef47b8
--- /dev/null
+++ b/test/mangle_tt_sri_sm_tests.erl
@@ -0,0 +1,158 @@
+% Eunit test rig for mangle_tt_sri_sm
+
+-module(mangle_tt_sri_sm_tests).
+-author('Harald Welte <laforge@gnumonks.org>').
+
+-include_lib("eunit/include/eunit.hrl").
+
+-include_lib("osmo_map/include/map.hrl").
+-include_lib("osmo_ss7/include/isup.hrl").
+-include_lib("osmo_ss7/include/sccp.hrl").
+-include_lib("osmo_ss7/include/osmo_util.hrl").
+
+-define(SCCP_MAP_INV_SRI_SM,
+ #sccp_msg{msg_type = 9,
+ parameters = [{protocol_class, {0,0}},
+ {called_party_addr, #sccp_addr{res_nat_use = 0,
+ route_on_ssn = 0,
+ point_code = undefined,
+ ssn = 6,
+ global_title = #global_title{gti = 4,
+ nature_of_addr_ind = 4,
+ trans_type = 0,
+ encoding = undefined,
+ numbering_plan = 1,
+ phone_number = [9,1,3,0,0,0,0,0,0,0,1]}}},
+ {calling_party_addr, #sccp_addr{res_nat_use = 0,
+ route_on_ssn = 0,
+ point_code = undefined,
+ ssn = 8,
+ global_title = #global_title{gti = 4,
+ nature_of_addr_ind = 4,
+ trans_type = 0,
+ encoding = undefined,
+ numbering_plan = 1,
+ phone_number = [9,8,7,0,0,0,0,0,0,1]}}},
+ {user_data,<<98,70,72,4,81,1,13,65,107,30,40,28,6,7,0,17,134,5,1,1,1,160,17,96,15,128,2,7,128,161,9,6,7,4,0,0,1,0,20,2,108,30,161,28,2,1,64,2,1,45,48,20,128,7,145,114,39,67,83,32,249,129,1,1,130,6,145,83,132,9,0,103>>}]
+ }).
+
+-define(MAP_INV_SRI_SM, {'begin',
+ #'MapSpecificPDUs_begin'{
+ otid = [81,1,2,200],
+ dialoguePortion = {'EXTERNAL', {syntax,{0,0,17,773,1,1,1}}, asn1_NOVALUE,
+ [96,15,128,2,7,128,161,9,6,7,4,0,0,1,0,1,3]},
+ components = [{basicROS,
+ {invoke, #'MapSpecificPDUs_begin_components_SEQOF_basicROS_invoke'{
+ invokeId = {present,64},
+ linkedId = asn1_NOVALUE,
+ opcode = {local,45},
+ argument = #'RoutingInfoForSM-Arg'{
+ msisdn = [145,114,39,67,83,32,249],
+ 'sm-RP-PRI' = true,
+ serviceCentreAddress = [145,83,132,9,0,103],
+ _ = asn1_NOVALUE},
+ _ = asn1_NOVALUE}}}],
+ _ = asn1_NOVALUE}}).
+
+% helper functions
+
+make_party_number(Digits) when is_integer(Digits) ->
+ #party_number{phone_number = osmo_util:int2digit_list(Digits)}.
+
+make_gt(Digits) when is_integer(Digits) ->
+ #global_title{phone_number = osmo_util:int2digit_list(Digits)}.
+
+make_sccp_sri_sm_to(CalledPartyNum) ->
+ NumL = osmo_util:int2digit_list(CalledPartyNum),
+ SccpIn = ?SCCP_MAP_INV_SRI_SM,
+ Called = proplists:get_value(called_party_addr, SccpIn#sccp_msg.parameters),
+ Dgt = Called#sccp_addr.global_title,
+ CalledNew = Called#sccp_addr{global_title=Dgt#global_title{phone_number=NumL}},
+ ParamsOut = lists:keyreplace(called_party_addr, 1, SccpIn#sccp_msg.parameters,
+ {called_party_addr, CalledNew}),
+ SccpIn#sccp_msg{parameters=ParamsOut}.
+
+get_dgt_tt(Sccp) when is_record(Sccp, sccp_msg) ->
+ Called = proplists:get_value(called_party_addr, Sccp#sccp_msg.parameters),
+ Dgt = Called#sccp_addr.global_title,
+ Dgt#global_title.trans_type.
+
+
+% actual test cases
+
+tcap_comps() ->
+ {'begin', BeginInvoke} = ?MAP_INV_SRI_SM,
+ Comps = mangle_tt_sri_sm:get_tcap_components(?MAP_INV_SRI_SM),
+ ?assertEqual(BeginInvoke#'MapSpecificPDUs_begin'.components, Comps).
+
+tcap_ops() ->
+ Ops = mangle_tt_sri_sm:get_tcap_operations(?MAP_INV_SRI_SM),
+ ?assertEqual([{invoke,{local,45}}], Ops).
+
+sri_sm() ->
+ % test with decoded MAP as well as SCCP input
+ ?assertEqual(true, mangle_tt_sri_sm:check_for_invoke_sri_sm(?MAP_INV_SRI_SM)),
+ ?assertEqual(true, mangle_tt_sri_sm:check_for_invoke_sri_sm(?SCCP_MAP_INV_SRI_SM)).
+
+isup_pfx_match() ->
+ TrueNum = make_party_number(9101234567),
+ FalseNum = make_party_number(4901234567),
+ % test with integer and list input
+ ?assertEqual(true, mangle_tt_sri_sm:isup_party_match_pfx(TrueNum, 91)),
+ ?assertEqual(true, mangle_tt_sri_sm:isup_party_match_pfx(TrueNum, [9,1])),
+ ?assertEqual(false, mangle_tt_sri_sm:isup_party_match_pfx(FalseNum, 91)),
+ ?assertEqual(false, mangle_tt_sri_sm:isup_party_match_pfx(FalseNum, [9,1])).
+
+gt_pfx_match() ->
+ TrueNum = make_gt(9101234567),
+ FalseNum = make_gt(4901234567),
+ % test with integer and list input
+ ?assertEqual(true, mangle_tt_sri_sm:gt_match_pfx(TrueNum, 91)),
+ ?assertEqual(true, mangle_tt_sri_sm:gt_match_pfx(TrueNum, [9,1])),
+ ?assertEqual(false, mangle_tt_sri_sm:gt_match_pfx(FalseNum, 91)),
+ ?assertEqual(false, mangle_tt_sri_sm:gt_match_pfx(FalseNum, [9,1])).
+
+gt_pfx_list_match() ->
+ TrueNum = make_gt(9101234567),
+ FalseNum = make_gt(4901234567),
+ ?assertEqual(true, mangle_tt_sri_sm:gt_match_pfx_list(TrueNum, [91, 53])),
+ ?assertEqual(true, mangle_tt_sri_sm:gt_match_pfx_list(TrueNum, [53, 91])),
+ ?assertEqual(false, mangle_tt_sri_sm:gt_match_pfx_list(FalseNum, [91, 53])),
+ ?assertEqual(false, mangle_tt_sri_sm:gt_match_pfx_list(FalseNum, [53, 91])).
+
+tt_mangle() ->
+ % test the overall macro-function for mangling the TT in case the DGT matches a
+ % prefix and the message contains an Invoke(SRI-for-SM)
+ Sccp91 = make_sccp_sri_sm_to(9101234567),
+ SccpOut91 = mangle_tt_sri_sm:mangle_tt_sri_sm(from_msc, path, ?SCCP_MSGT_UDT, Sccp91),
+ ?assertEqual(3, get_dgt_tt(SccpOut91)),
+ Sccp43 = make_sccp_sri_sm_to(4301234567),
+ SccpOut43 = mangle_tt_sri_sm:mangle_tt_sri_sm(from_msc, path, ?SCCP_MSGT_UDT, Sccp91),
+ ?assertEqual(3, get_dgt_tt(SccpOut43)),
+ Sccp49 = make_sccp_sri_sm_to(4901234567),
+ SccpOut49 = mangle_tt_sri_sm:mangle_tt_sri_sm(from_msc, path, ?SCCP_MSGT_UDT, Sccp49),
+ ?assertEqual(get_dgt_tt(Sccp49), get_dgt_tt(SccpOut49)).
+
+
+% setup and teardown
+
+setup() ->
+ application:set_env(mgw_nat, mangle_tt_sri_sm_pfx, [ 91, 43 ]).
+
+teardown(_) ->
+ application:unset_env(mgw_nat, mangle_tt_sri_sm_pfx).
+
+mangle_tt_sri_test_() ->
+ {setup,
+ fun setup/0,
+ fun teardown/1,
+ [
+ ?_test(tcap_comps()),
+ ?_test(tcap_ops()),
+ ?_test(sri_sm()),
+ ?_test(isup_pfx_match()),
+ ?_test(gt_pfx_match()),
+ ?_test(gt_pfx_list_match()),
+ ?_test(tt_mangle())
+ ]
+ }.