% ITU-T Q.76x ISUPcoding / decoding % (C) 2011 by Harald Welte % % 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 https://www.erlang.org (or a modified version of these % libraries), containing parts covered by the terms of the Erlang Public % License (https://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(isup_codec). -author('Harald Welte '). -include("isup.hrl"). -export([parse_isup_msg/1, encode_isup_msg/1, parse_isup_party/2, encode_isup_party/1, gen_party_number/3]). -compile(export_all). -compile({parse_transform, exprecs}). -export_records([party_number, isup_msg]). parse_isup_party(<<>>, OddEven, DigitList) -> % in case of odd number of digits, we need to cut the last case OddEven of 1 -> lists:sublist(DigitList, length(DigitList)-1); 0 -> DigitList end; parse_isup_party(BcdBin, OddEven, DigitList) -> <> = BcdBin, NewDigits = [First, Second], parse_isup_party(Remain, OddEven, DigitList ++ NewDigits). parse_isup_party(BinBcd, OddEven) when is_binary(BinBcd) -> parse_isup_party(BinBcd, OddEven, []). % parse a single option parse_isup_opt(OptType = ?ISUP_PAR_CALLED_P_NUM, _OptLen, Content) -> % C.3.7 Called Party Number <> = Content, PhoneNum = parse_isup_party(Remain, OddEven), {OptType, #party_number{nature_of_addr_ind = Nature, internal_net_num = Inn, numbering_plan = NumPlan, phone_number = PhoneNum}}; parse_isup_opt(OptType = ?ISUP_PAR_CALLING_P_NUM, _OptLen, Content) -> % C.3.8 Calling Party Number <> = Content, PhoneNum = parse_isup_party(Remain, OddEven), {OptType, #party_number{nature_of_addr_ind = Nature, number_incompl_ind = Ni, numbering_plan = NumPlan, present_restrict = PresRestr, screening_ind = Screen, phone_number = PhoneNum}}; parse_isup_opt(OptType = ?ISUP_PAR_CONNECTED_NUM, _OptLen, Content) -> % C.3.14 Connected Number <> = Content, PhoneNum = parse_isup_party(Remain, OddEven), {OptType, #party_number{nature_of_addr_ind = Nature, numbering_plan = NumPlan, present_restrict = PresRestr, screening_ind = Screen, phone_number = PhoneNum}}; parse_isup_opt(OptType = ?ISUP_PAR_SUBSEQ_NUM, _OptLen, Content) -> % C.3.32 Subsequent Number <> = Content, PhoneNum = parse_isup_party(Remain, OddEven), {OptType, #party_number{phone_number = PhoneNum}}; parse_isup_opt(OptType, OptLen, Content) -> {OptType, {OptLen, Content}}. % parse a Binary into a list of options parse_isup_opts(<<>>, OptList) -> % empty list OptList; parse_isup_opts(<<0>>, OptList) -> % end of options OptList; parse_isup_opts(OptBin, OptList) when is_binary(OptBin) -> <> = OptBin, NewOpt = parse_isup_opt(OptType, OptLen, Content), parse_isup_opts(Remain, OptList ++ [NewOpt]). parse_isup_opts(OptBin) -> parse_isup_opts(OptBin, []). % Parse options preceeded by 1 byte OptPtr parse_isup_opts_ptr(OptBinPtr) -> OptPtr = binary:at(OptBinPtr, 0), case OptPtr of 0 -> []; _ -> OptBin = binary:part(OptBinPtr, OptPtr, byte_size(OptBinPtr)-OptPtr), parse_isup_opts(OptBin, []) end. % References to 'Tabe C-xxx' are to Annex C of Q.767 % Default case: no fixed and no variable parts, only options % ANM, RLC, FOT parse_isup_msgt(M, Bin) when M == ?ISUP_MSGT_ANM; M == ?ISUP_MSGT_RLC; M == ?ISUP_MSGT_FOT -> parse_isup_opts_ptr(Bin); % Table C-5 Address complete parse_isup_msgt(?ISUP_MSGT_ACM, Bin) -> <> = Bin, BciOpt = {backward_call_ind, BackCallInd}, Opts = parse_isup_opts_ptr(Remain), [BciOpt|Opts]; % Table C-7 Call progress parse_isup_msgt(?ISUP_MSGT_CPG, Bin) -> <> = Bin, BciOpt = {event_info, EventInf}, Opts = parse_isup_opts_ptr(Remain), [BciOpt|Opts]; % Table C-9 Circuit group reset acknowledgement parse_isup_msgt(?ISUP_MSGT_GRA, Bin) -> % V: Range and status <> = Bin, RangStsLen = binary:at(Bin, PtrVar), RangeStatus = binary:part(Bin, PtrVar+1, RangStsLen), RangeStsTuple = {?ISUP_PAR_RANGE_AND_STATUS, {RangStsLen, RangeStatus}}, [RangeStsTuple]; % Table C-11 Connect parse_isup_msgt(?ISUP_MSGT_CON, Bin) -> <> = Bin, BciOpt = {backward_call_ind, BackCallInd}, Opts = parse_isup_opts_ptr(Remain), [BciOpt|Opts]; % Table C-12 Continuity parse_isup_msgt(?ISUP_MSGT_COT, Bin) -> <> = Bin, [{continuity_ind, ContInd}]; % Table C-16 Initial address parse_isup_msgt(?ISUP_MSGT_IAM, Bin) -> <> = Bin, FixedOpts = [{conn_ind_nature, CINat}, {fw_call_ind, FwCallInd}, {calling_cat, CallingCat}, {transm_medium_req, TransmReq}], <> = VarAndOpt, % V: Called Party Number CalledPartyLen = binary:at(VarAndOpt, PtrVar), CalledParty = binary:part(VarAndOpt, PtrVar+1, CalledPartyLen), VarOpts = [parse_isup_opt(?ISUP_PAR_CALLED_P_NUM, CalledPartyLen, CalledParty)], % Optional part case PtrOpt of 0 -> Opts = []; _ -> Remain = binary:part(VarAndOpt, 1 + PtrOpt, byte_size(VarAndOpt)-(1+PtrOpt)), Opts = parse_isup_opts(Remain) end, FixedOpts ++ VarOpts ++ Opts; % Table C-17 Release % Table 26/Q.763: Confusion parse_isup_msgt(M, VarAndOpt) when M == ?ISUP_MSGT_REL; M == ?ISUP_MSGT_CFN -> <> = VarAndOpt, % V: Cause indicators CauseIndLen = binary:at(VarAndOpt, PtrVar), CauseInd = binary:part(VarAndOpt, PtrVar+1, CauseIndLen), VarOpts = [{?ISUP_PAR_CAUSE_IND, {CauseIndLen, CauseInd}}], case PtrOpt of 0 -> Opts = []; _ -> Remain = binary:part(VarAndOpt, 1 + PtrOpt, byte_size(VarAndOpt)-(1+PtrOpt)), Opts = parse_isup_opts(Remain) end, VarOpts ++ Opts; % Table C-19 Subsequent address parse_isup_msgt(?ISUP_MSGT_SAM, VarAndOpt) -> <> = VarAndOpt, % V: Subsequent number SubseqNumLen = binary:at(VarAndOpt, PtrVar), SubsetNum = binary:part(VarAndOpt, PtrVar+1, SubseqNumLen), VarOpts = [{?ISUP_PAR_SUBSEQ_NUM, {SubseqNumLen, SubsetNum}}], Remain = binary:part(VarAndOpt, 1 + PtrOpt, byte_size(VarAndOpt)-(1+PtrOpt)), Opts = parse_isup_opts(Remain), VarOpts ++ Opts; % Table C-21 Suspend, Resume parse_isup_msgt(Msgt, Bin) when Msgt == ?ISUP_MSGT_RES; Msgt == ?ISUP_MSGT_SUS -> <> = Bin, FixedOpts = [{susp_res_ind, SuspResInd}], Opts = parse_isup_opts_ptr(Remain), FixedOpts ++ Opts; % Table C-23 parse_isup_msgt(M, <<>>) when M == ?ISUP_MSGT_BLO; M == ?ISUP_MSGT_BLA; M == ?ISUP_MSGT_CCR; M == ?ISUP_MSGT_RSC; M == ?ISUP_MSGT_UBL; M == ?ISUP_MSGT_UBA -> []; % Table 39/Q.763 messages for national use, fixed length 1 byte msgtype parse_isup_msgt(M, <<>>) when M == ?ISUP_MSGT_LPA; M == ?ISUP_MSGT_OLM; M == ?ISUP_MSGT_UCIC -> []; % Table C-25 parse_isup_msgt(M, Bin) when M == ?ISUP_MSGT_CGB; M == ?ISUP_MSGT_CGBA; M == ?ISUP_MSGT_CGU; M == ?ISUP_MSGT_CGUA -> <> = Bin, FixedOpts = [{cg_supv_msgt, CGMsgt}], % V: Range and status RangStsLen = binary:at(VarBin, PtrVar-1), RangeStatus = binary:part(VarBin, PtrVar, RangStsLen), VarOpts = [{?ISUP_PAR_RANGE_AND_STATUS, {RangStsLen, RangeStatus}}], FixedOpts ++ VarOpts; % Table C-26 Circuit group reset parse_isup_msgt(?ISUP_MSGT_GRS, Bin) -> <> = Bin, % V: Range without status RangeLen = binary:at(Bin, PtrVar), Range = binary:part(Bin, PtrVar+1, RangeLen), [{?ISUP_PAR_RANGE_AND_STATUS, {RangeLen, Range}}]. parse_isup_msg(DataBin) when is_binary(DataBin) -> <> = DataBin, Opts = parse_isup_msgt(MsgType, Remain), #isup_msg{cic = Cic, msg_type = MsgType, parameters = Opts}. % encode a phone number from a list of digits into the BCD binary sequence encode_isup_party(BcdInt) when is_integer(BcdInt) -> BcdList = osmo_util:int2digit_list(BcdInt), encode_isup_party(BcdList); encode_isup_party(BcdList) when is_list(BcdList) -> encode_isup_party(BcdList, <<>>, length(BcdList)). encode_isup_party([], Bin, NumDigits) -> case NumDigits rem 2 of 1 -> {Bin, 1}; 0 -> {Bin, 0} end; encode_isup_party([First,Second|BcdList], Bin, NumDigits) -> encode_isup_party(BcdList, <>, NumDigits); encode_isup_party([Last], Bin, NumDigits) -> encode_isup_party([], <>, NumDigits). % encode a single option encode_isup_par(?ISUP_PAR_CALLED_P_NUM, #party_number{nature_of_addr_ind = Nature, internal_net_num = Inn, numbering_plan = NumPlan, phone_number= PhoneNum}) -> % C.3.7 Called Party Number {PhoneBin, OddEven} = encode_isup_party(PhoneNum), <>; encode_isup_par(?ISUP_PAR_CALLING_P_NUM, #party_number{nature_of_addr_ind = Nature, number_incompl_ind = Ni, numbering_plan = NumPlan, present_restrict = PresRestr, screening_ind = Screen, phone_number= PhoneNum}) -> % C.3.8 Calling Party Number {PhoneBin, OddEven} = encode_isup_party(PhoneNum), <>; encode_isup_par(?ISUP_PAR_CONNECTED_NUM, #party_number{nature_of_addr_ind = Nature, numbering_plan = NumPlan, present_restrict = PresRestr, screening_ind = Screen, phone_number = PhoneNum}) -> % C.3.14 Connected Number {PhoneBin, OddEven} = encode_isup_party(PhoneNum), <>; encode_isup_par(?ISUP_PAR_SUBSEQ_NUM, #party_number{phone_number = PhoneNum}) -> % C.3.32 Subsequent Number {PhoneBin, OddEven} = encode_isup_party(PhoneNum), <>; encode_isup_par(Atom, _More) when is_atom(Atom) -> <<>>; encode_isup_par(OptNum, {OptLen, Binary}) when is_binary(Binary), is_integer(OptNum), is_integer(OptLen) -> Binary. % encode a single OPTIONAL parameter (TLV type), skip all others encode_isup_optpar(ParNum, _ParBody) when is_atom(ParNum) -> <<>>; encode_isup_optpar(ParNum, ParBody) -> ParBin = encode_isup_par(ParNum, ParBody), ParLen = byte_size(ParBin), <>. % recursive function to encode all optional parameters encode_isup_opts([], OutBin) -> % terminate with end-of-options, but only if we have options case OutBin of <<>> -> OutBin; _ -> <> end; encode_isup_opts([Opt|OptPropList], OutBin) -> {OptType, OptBody} = Opt, OptBin = encode_isup_optpar(OptType, OptBody), encode_isup_opts(OptPropList, <>). encode_isup_opts(OptPropList) -> encode_isup_opts(OptPropList, <<>>). encode_isup_hdr(#isup_msg{msg_type = MsgType, cic = Cic}) -> <>. % Default case: no fixed and no variable parts, only options % ANM, RLC, FOT encode_isup_msgt(M, #isup_msg{parameters = Params}) when M == ?ISUP_MSGT_ANM; M == ?ISUP_MSGT_RLC; M == ?ISUP_MSGT_FOT -> OptBin = encode_isup_opts(Params), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = 1 end, <>; % Table C-5 Address complete encode_isup_msgt(?ISUP_MSGT_ACM, #isup_msg{parameters = Params}) -> BackCallInd = proplists:get_value(backward_call_ind, Params), OptBin = encode_isup_opts(Params), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = 1 end, <>; % Table C-7 Call progress encode_isup_msgt(?ISUP_MSGT_CPG, #isup_msg{parameters = Params}) -> EventInf = proplists:get_value(event_info, Params), OptBin = encode_isup_opts(Params), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = 1 end, <>; % Table C-9 Circuit group reset acknowledgement encode_isup_msgt(?ISUP_MSGT_GRA, #isup_msg{parameters = Params}) -> % V: Range and status {RangStsLen, RangeStatus} = proplists:get_value(?ISUP_PAR_RANGE_AND_STATUS, Params), <<1:8, RangStsLen:8, RangeStatus/binary>>; % Table C-11 Connect encode_isup_msgt(?ISUP_MSGT_CON, #isup_msg{parameters = Params}) -> BackCallInd = proplists:get_value(backward_call_ind, Params), OptBin = encode_isup_opts(Params), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = 1 end, <>; % Table C-12 Continuity encode_isup_msgt(?ISUP_MSGT_COT, #isup_msg{parameters = Params}) -> ContInd = proplists:get_value(continuity_ind, Params), <>; % Table C-16 Initial address encode_isup_msgt(?ISUP_MSGT_IAM, #isup_msg{parameters = Params}) -> % Fixed part CINat = proplists:get_value(conn_ind_nature, Params), FwCallInd = proplists:get_value(fw_call_ind, Params), CallingCat = proplists:get_value(calling_cat, Params), TransmReq = proplists:get_value(transm_medium_req, Params), PtrVar = 2, % one byte behind the PtrOpt FixedBin = <>, % V: Called Party Number CalledParty = encode_isup_par(?ISUP_PAR_CALLED_P_NUM, proplists:get_value(?ISUP_PAR_CALLED_P_NUM, Params)), CalledPartyLen = byte_size(CalledParty), % Optional part Params2 = proplists:delete(?ISUP_PAR_CALLED_P_NUM, Params), OptBin = encode_isup_opts(Params2), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = CalledPartyLen + 1 + 1 % 1 byte length, 1 byte start offset end, <>; % Table C-17 Release encode_isup_msgt(Msgt, #isup_msg{parameters = Params}) when Msgt == ?ISUP_MSGT_REL; Msgt == ?ISUP_MSGT_CFN -> PtrVar = 2, % one byte behind the PtrOpt % V: Cause indicators CauseInd = encode_isup_par(?ISUP_PAR_CAUSE_IND, proplists:get_value(?ISUP_PAR_CAUSE_IND, Params)), CauseIndLen = byte_size(CauseInd), % Optional Part Params2 = proplists:delete(?ISUP_PAR_CAUSE_IND, Params), OptBin = encode_isup_opts(Params2), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = CauseIndLen + 1 + 1 % 1 byte length, 1 byte start offset end, <>; % Table C-19 Subsequent address encode_isup_msgt(?ISUP_MSGT_SAM, #isup_msg{parameters = Params}) -> PtrVar = 2, % one byte behind the PtrOpt % V: Subsequent number SubseqNum = encode_isup_par(?ISUP_PAR_SUBSEQ_NUM, proplists:get_value(?ISUP_PAR_SUBSEQ_NUM, Params)), SubseqNumLen = byte_size(SubseqNum), % Optional Part Params2 = proplists:delete(?ISUP_PAR_SUBSEQ_NUM, Params), OptBin = encode_isup_opts(Params2), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = SubseqNumLen + 1 + 1 % 1 byte length, 1 byte start offset end, <>; % Table C-21 Suspend, Resume encode_isup_msgt(Msgt, #isup_msg{parameters = Params}) when Msgt == ?ISUP_MSGT_RES; Msgt == ?ISUP_MSGT_SUS -> SuspResInd = proplists:get_value(susp_res_ind, Params), OptBin = encode_isup_opts(Params), case OptBin of <<>> -> PtrOpt = 0; _ -> PtrOpt = 1 end, <>; % Table C-23 encode_isup_msgt(M, #isup_msg{}) when M == ?ISUP_MSGT_BLO; M == ?ISUP_MSGT_BLA; M == ?ISUP_MSGT_CCR; M == ?ISUP_MSGT_RSC; M == ?ISUP_MSGT_UBL; M == ?ISUP_MSGT_UBA -> <<>>; % Table 39/Q.763 (national use) encode_isup_msgt(M, #isup_msg{}) when M == ?ISUP_MSGT_LPA; M == ?ISUP_MSGT_OLM; M == ?ISUP_MSGT_UCIC -> <<>>; % Table C-25 encode_isup_msgt(M, #isup_msg{parameters = Params}) when M == ?ISUP_MSGT_CGB; M == ?ISUP_MSGT_CGBA; M == ?ISUP_MSGT_CGU; M == ?ISUP_MSGT_CGUA -> PtrVar = 1, % one byte behind the PtrVar CGMsgt = proplists:get_value(cg_supv_msgt, Params), % V: Range and status {RangStsLen, RangeStatus} = proplists:get_value(?ISUP_PAR_RANGE_AND_STATUS, Params), <>; % Table C-26 Circuit group reset encode_isup_msgt(?ISUP_MSGT_GRS, #isup_msg{parameters = Params}) -> PtrVar = 1, % one byte behind the PtrVar {RangeLen, Range} = proplists:get_value(?ISUP_PAR_RANGE_AND_STATUS, Params), % V: Range without status <>. encode_isup_msg(Msg = #isup_msg{msg_type = MsgType}) -> HdrBin = encode_isup_hdr(Msg), Remain = encode_isup_msgt(MsgType, Msg), <>. listify(L) when is_list(L) -> L; listify(L) when is_integer(L) -> osmo_util:int2digit_list(L). encode_nature(international) -> ?ISUP_ADDR_NAT_INTERNATIONAL; encode_nature(national) -> ?ISUP_ADDR_NAT_NATIONAL; encode_nature(subscriber) -> ?ISUP_ADDR_NAT_SUBSCRIBER; encode_nature(Int) when is_integer(Int) -> Int. encode_numplan(isdn) -> 1; encode_numplan(telephony) -> 1; encode_numplan(data) -> 3; encode_numplan(telex) -> 4; encode_numplan(Int) when is_integer(Int) -> Int. gen_party_number(NAI, NumPlan, Number) -> #party_number{nature_of_addr_ind = encode_nature(NAI), numbering_plan = encode_numplan(NumPlan), phone_number = listify(Number)}.