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 +% (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 . +% +% 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 "). + +-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 '). + +-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()) + ] + }.