% ITU-T Q.71x SCCP Message coding / decoding % (C) 2010 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 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(sccp_codec). -author('Harald Welte '). -include("sccp.hrl"). -export([parse_sccp_msg/1, encode_sccp_msg/1, encode_sccp_msgt/2, is_connectionless/1]). -export([gen_gt_helper/1, gen_addr_helper/2, gen_addr_helper/3]). -compile(export_all). -compile({parse_transform, exprecs}). -export_records([global_title, sccp_addr, sccp_msg]). binarify(In) when is_binary(In) -> In; binarify(In) when is_list(In) -> list_to_binary(In). parse_point_code(BinPC, PCind) when is_binary(BinPC) -> case PCind of 1 -> <> = BinPC; _ -> Remain = BinPC, PointCode = undefined end, {Remain, PointCode}. parse_ssn(BinSSN, SSNind) -> case SSNind of 1 -> <> = BinSSN; _ -> Remain = BinSSN, SSN = undefined end, {Remain, SSN}. enc_is_odd(Enc) -> case Enc of 1 -> 1; _ -> 0 end. parse_gt(BinGT, GTind) -> case GTind of ?SCCP_GTI_NO_GT -> undefined; ?SCCP_GTI_NAT_ONLY -> % Figure 7/Q.713 <> = BinGT, PhoneNum = isup_codec:parse_isup_party(Digits, Odd), #global_title{gti = GTind, nature_of_addr_ind = Nature, phone_number = PhoneNum}; ?SCCP_GTI_TT_ONLY -> % Figure 9/Q.913 <> = BinGT, % Used in national interfaces only, we cannot parse Digits #global_title{gti = GTind, trans_type = TransType, phone_number = Digits}; ?SCCP_GTI_TT_NP_ENC -> % Figure 10/Q.713 <> = BinGT, PhoneNum = isup_codec:parse_isup_party(Digits, enc_is_odd(Enc)), #global_title{gti = GTind, trans_type = TransType, numbering_plan = NumPlan, phone_number = PhoneNum}; ?SCCP_GTI_TT_NP_ENC_NAT -> % Figure 11/Q.713 <> = BinGT, PhoneNum = isup_codec:parse_isup_party(Digits, enc_is_odd(Enc)), #global_title{gti = GTind, trans_type = TransType, numbering_plan = NumPlan, nature_of_addr_ind = Nature, phone_number = PhoneNum}; _ -> BinGT end. % parse SCCP Address parse_sccp_addr(BinAddr) when is_binary(BinAddr) -> <> = BinAddr, {RemainPC, OptPC} = parse_point_code(Remain, PCind), {RemainSSN, OptSSN} = parse_ssn(RemainPC, SSNind), OptGT = parse_gt(RemainSSN, GTind), #sccp_addr{res_nat_use = ResNatUse, route_on_ssn = RoutInd, point_code = OptPC, ssn = OptSSN, global_title = OptGT}. % parse SCCP Optional Part parse_sccp_opt(OptType, _OptLen, Content) -> OptAtom = opt_to_atom(OptType), {OptAtom, Content}. parse_sccp_opts(<<>>, OptList) -> % empty list OptList; parse_sccp_opts(<<0>>, OptList) -> % end of options OptList; parse_sccp_opts(OptBin, OptList) -> <> = OptBin, NewOpt = parse_sccp_opt(OptType, OptLen, Content), parse_sccp_opts(Remain, [NewOpt|OptList]). % Parse incoming SCCP message, one function for every message type parse_sccp_msgt(?SCCP_MSGT_CR, DataBin) -> % first get the fixed part <<_:8, SrcLocalRef:24/big, PCOpt:4, ProtoClass:4, RemainVar/binary >> = DataBin, % variable length fixed part <> = RemainVar, CalledPartyLen = binary:at(RemainVar, PtrVar), CalledParty = binary:part(RemainVar, PtrVar+1, CalledPartyLen), CalledPartyDec = parse_sccp_addr(CalledParty), % optional part OptBin = binary:part(RemainVar, 1 + PtrOpt, byte_size(RemainVar)-(1+PtrOpt)), OptList = parse_sccp_opts(OptBin, []), %OptList = [], % build parsed list of message [{src_local_ref, SrcLocalRef},{protocol_class, {ProtoClass, PCOpt}}, {called_party_addr, CalledPartyDec} | OptList]; parse_sccp_msgt(?SCCP_MSGT_CC, DataBin) -> % first get the fixed part <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big, PCOpt:4, ProtoClass:4, PtrOpt:8, Remain/binary >> = DataBin, % optional part OptBin = binary:part(Remain, PtrOpt-1, byte_size(Remain)-(PtrOpt-1)), OptList = parse_sccp_opts(OptBin, []), % build parsed list of message [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef}, {protocol_class, {ProtoClass, PCOpt}} | OptList]; parse_sccp_msgt(?SCCP_MSGT_CREF, DataBin) -> % first get the fixed part <<_:8, DstLocalRef:24/big, RefusalCause:8, Remain/binary >> = DataBin, % optional part OptList = parse_sccp_opts(Remain, []), % build parsed list of message [{dst_local_ref, DstLocalRef},{refusal_cause, RefusalCause}|OptList]; parse_sccp_msgt(?SCCP_MSGT_RLSD, DataBin) -> <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big, ReleaseCause:8, Remain/binary >> = DataBin, % optional part OptList = parse_sccp_opts(Remain, []), % build parsed list of message [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef},{release_cause, ReleaseCause}|OptList]; parse_sccp_msgt(?SCCP_MSGT_RLC, DataBin) -> <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big>> = DataBin, % build parsed list of message [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef}]; parse_sccp_msgt(?SCCP_MSGT_DT1, DataBin) -> <<_:8, DstLocalRef:24/big, SegmReass:8, DataPtr:8, Remain/binary >> = DataBin, DataLen = binary:at(Remain, DataPtr-1), UserData = binary:part(Remain, DataPtr-1+1, DataLen), % build parsed list of message [{dst_local_ref, DstLocalRef},{segm_reass, SegmReass},{user_data, UserData}]; parse_sccp_msgt(?SCCP_MSGT_DT2, DataBin) -> <<_:8, DstLocalRef:24/big, SeqSegm:16, DataPtr:8, Remain/binary >> = DataBin, DataLen = binary:at(Remain, DataPtr-1), UserData = binary:part(Remain, DataPtr-1+1, DataLen), % build parsed list of message [{dst_local_ref, DstLocalRef},{seq_segm, SeqSegm},{user_data, UserData}]; parse_sccp_msgt(?SCCP_MSGT_AK, DataBin) -> <<_:8, DstLocalRef:24/big, RxSeqnr:8, Credit:8>> = DataBin, [{dst_local_ref, DstLocalRef},{rx_seq_nr, RxSeqnr},{credit, Credit}]; parse_sccp_msgt(?SCCP_MSGT_UDT, DataBin) -> <<_:8, PCOpt:4, ProtoClass:4, CalledPartyPtr:8, CallingPartyPtr:8, DataPtr:8, Remain/binary >> = DataBin, % variable part CalledPartyLen = binary:at(Remain, CalledPartyPtr-3), CalledParty = binary:part(Remain, CalledPartyPtr-3+1, CalledPartyLen), CalledPartyDec = parse_sccp_addr(CalledParty), CallingPartyLen = binary:at(Remain, CallingPartyPtr-2), CallingParty = binary:part(Remain, CallingPartyPtr-2+1, CallingPartyLen), CallingPartyDec = parse_sccp_addr(CallingParty), DataLen = binary:at(Remain, DataPtr-1), UserData = binary:part(Remain, DataPtr-1+1, DataLen), [{protocol_class, {ProtoClass, PCOpt}},{called_party_addr, CalledPartyDec}, {calling_party_addr, CallingPartyDec},{user_data, UserData}]; parse_sccp_msgt(?SCCP_MSGT_UDTS, DataBin) -> <<_:8, ReturnCause:8, CalledPartyPtr:8, CallingPartyPtr:8, DataPtr:8, Remain/binary >> = DataBin, % variable part CalledPartyLen = binary:at(Remain, CalledPartyPtr-3), CalledParty = binary:part(Remain, CalledPartyPtr-3+1, CalledPartyLen), CalledPartyDec = parse_sccp_addr(CalledParty), CallingPartyLen = binary:at(Remain, CallingPartyPtr-2), CallingParty = binary:part(Remain, CallingPartyPtr-2+1, CallingPartyLen), CallingPartyDec = parse_sccp_addr(CallingParty), DataLen = binary:at(Remain, DataPtr-1), UserData = binary:part(Remain, DataPtr-1+1, DataLen), [{return_cause, ReturnCause},{called_party_addr, CalledPartyDec}, {calling_party_addr, CallingPartyDec},{user_data, UserData}]; parse_sccp_msgt(?SCCP_MSGT_ED, DataBin) -> <<_:8, DstLocalRef:24/big, DataPtr:8, Remain/binary>> = DataBin, DataLen = binary:at(Remain, DataPtr-1), UserData = binary:part(Remain, DataPtr-1+1, DataLen), [{dst_local_ref, DstLocalRef}, {user_data, UserData}]; parse_sccp_msgt(?SCCP_MSGT_EA, DataBin) -> <<_:8, DstLocalRef:24/big>> = DataBin, [{dst_local_ref, DstLocalRef}]; parse_sccp_msgt(?SCCP_MSGT_RSR, DataBin) -> <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big, ResetCause:8>> = DataBin, [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef},{reset_cause, ResetCause}]; parse_sccp_msgt(?SCCP_MSGT_RSC, DataBin) -> <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big>> = DataBin, [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef}]; parse_sccp_msgt(?SCCP_MSGT_ERR, DataBin) -> <<_:8, DstLocalRef:24/big, ErrCause:8>> = DataBin, [{dst_local_ref, DstLocalRef},{error_cause, ErrCause}]; parse_sccp_msgt(?SCCP_MSGT_IT, DataBin) -> <<_:8, DstLocalRef:24/big, SrcLocalRef:24/big, PCOpt: 4, ProtoClass:4, SegmSeq:16, Credit:8>> = DataBin, [{dst_local_ref, DstLocalRef},{src_local_ref, SrcLocalRef}, {protocol_class, {ProtoClass, PCOpt}},{seq_segm, SegmSeq},{credit, Credit}]. % FIXME: XUDT/XUDTS, LUDT/LUDTS % process one incoming SCCP message parse_sccp_msg(DataBin) -> MsgType = binary:first(DataBin), Parsed = parse_sccp_msgt(MsgType, DataBin), {ok, #sccp_msg{msg_type = MsgType, parameters = Parsed}}. % Encoding Part gt_enc_by_odd(Odd) -> if Odd == 1 -> 1; true -> 2 end. encode_gt(undefined) -> {?SCCP_GTI_NO_GT, <<>>}; encode_gt(#global_title{gti = GTind, phone_number = PhoneNum, nature_of_addr_ind = Nature, trans_type = TransType, numbering_plan = NumPlan}) -> case GTind of ?SCCP_GTI_NO_GT -> {GTind, <<>>}; ?SCCP_GTI_NAT_ONLY -> % Figure 7/Q.713 {PhoneBin, OddEven} = isup_codec:encode_isup_party(PhoneNum), {GTind, <>}; ?SCCP_GTI_TT_ONLY -> % Figure 9/Q.913 % Used in national interfaces only, we cannot parse Digits {GTind, <>}; ?SCCP_GTI_TT_NP_ENC -> % Figure 10/Q.713 {PhoneBin, OddEven} = isup_codec:encode_isup_party(PhoneNum), Enc = gt_enc_by_odd(OddEven), {GTind, <>}; ?SCCP_GTI_TT_NP_ENC_NAT -> % Figure 11/Q.713 {PhoneBin, OddEven} = isup_codec:encode_isup_party(PhoneNum), Enc = gt_enc_by_odd(OddEven), {GTind, <>} end. encode_pc(undefined) -> {0, <<>>}; encode_pc(PointCode) when is_integer(PointCode) -> {1, <>}; encode_pc(PcRec) -> PcInt = osmo_util:pointcode2int(PcRec), encode_pc(PcInt). encode_ssn(SSN) -> case SSN of undefined -> {0, <<>>}; _ -> {1, <>} end. undef_or_true(Foo) -> case Foo of undefined -> 0; 0 -> 0; _ -> 1 end. encode_sccp_addr(#sccp_addr{res_nat_use = ResNatUse, route_on_ssn = RoutInd, point_code = PointCode, ssn = SSN, global_title = GT}) -> {GTind, GTbin} = encode_gt(GT), {SSNind, SSNbin} = encode_ssn(SSN), {PCind, PCbin} = encode_pc(PointCode), ResNatOut = undef_or_true(ResNatUse), RoutIndOut = undef_or_true(RoutInd), <>. encode_sccp_opt({AddrTag, AddrVal}) when AddrTag == ?SCCP_PNC_CALLED_PARTY_ADDRESS; AddrTag == ?SCCP_PNC_CALLING_PARTY_ADDRESS -> AddrEnc = encode_sccp_addr(AddrVal), AddrLen = byte_size(AddrEnc), <>; encode_sccp_opt({OptInt, DataBin}) when is_binary(DataBin), is_integer(OptInt) -> DataBinLen = byte_size(DataBin), <>; encode_sccp_opt({Opt, DataBin}) when is_atom(Opt) -> OptNum = atom_to_opt(Opt), encode_sccp_opt({OptNum, DataBin}); encode_sccp_opt({Opt, DataInt}) when is_integer(DataInt), DataInt =< 255 -> encode_sccp_opt({Opt, <>}); encode_sccp_opt({Opt, DataList}) when is_list(DataList) -> encode_sccp_opt({Opt, list_to_binary(DataList)}). encode_sccp_opts(OptList, Filter) -> FilteredList = lists:filter(fun({Tag, _Val}) -> proplists:is_defined(opt_to_atom(Tag), Filter) end, OptList), e_sccp_opts(FilteredList, []). e_sccp_opts([], OptEnc) -> % end of options + convert to binary list_to_binary([OptEnc, ?SCCP_PNC_END_OF_OPTIONAL]); e_sccp_opts([CurOpt|OptPropList], OptEnc) -> CurOptEnc = encode_sccp_opt(CurOpt), e_sccp_opts(OptPropList, list_to_binary([OptEnc,CurOptEnc])). encode_sccp_msgt(?SCCP_MSGT_CR, Params) -> SrcLocalRef = proplists:get_value(src_local_ref, Params), {ProtoClass, PCOpt} = proplists:get_value(protocol_class, Params), CalledParty = proplists:get_value(called_party_addr, Params), CalledPartyEnc = encode_sccp_addr(CalledParty), CalledPartyLen = byte_size(CalledPartyEnc), PtrOpt = CalledPartyLen+1+1, OptBin = encode_sccp_opts(Params, [credit, calling_party_addr, user_data, hop_counter, importance]), <>; encode_sccp_msgt(?SCCP_MSGT_CC, Params) -> SrcLocalRef = proplists:get_value(src_local_ref, Params), DstLocalRef = proplists:get_value(dst_local_ref, Params), {ProtoClass, PCOpt} = proplists:get_value(protocol_class, Params), OptBin = encode_sccp_opts(Params, [credit, called_party_addr, user_data, importance]), <>; encode_sccp_msgt(?SCCP_MSGT_CREF, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), RefusalCause = proplists:get_value(refusal_cause, Params), OptBin = encode_sccp_opts(Params, [called_party_addr, user_data, importance]), <>; encode_sccp_msgt(?SCCP_MSGT_RLSD, Params) -> SrcLocalRef = proplists:get_value(src_local_ref, Params), DstLocalRef = proplists:get_value(dst_local_ref, Params), ReleaseCause = proplists:get_value(release_cause, Params), OptBin = encode_sccp_opts(Params, [user_data, importance]), <>; encode_sccp_msgt(?SCCP_MSGT_RLC, Params) -> SrcLocalRef = proplists:get_value(src_local_ref, Params), DstLocalRef = proplists:get_value(dst_local_ref, Params), <>; encode_sccp_msgt(?SCCP_MSGT_DT1, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), SegmReass = proplists:get_value(segm_reass, Params), UserData = binarify(proplists:get_value(user_data, Params)), UserDataLen = byte_size(UserData), <>; encode_sccp_msgt(?SCCP_MSGT_DT2, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), SeqSegm = proplists:get_value(seq_segm, Params), UserData = binarify(proplists:get_value(user_data, Params)), UserDataLen = byte_size(UserData), <>; encode_sccp_msgt(?SCCP_MSGT_AK, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), RxSeqnr = proplists:get_value(rx_seqnr, Params), Credit = proplists:get_value(credit, Params), <>; encode_sccp_msgt(?SCCP_MSGT_UDT, Params) -> {ProtoClass, PCOpt} = proplists:get_value(protocol_class, Params), CalledParty = proplists:get_value(called_party_addr, Params), CalledPartyEnc = encode_sccp_addr(CalledParty), CalledPartyLen = byte_size(CalledPartyEnc), CallingParty = proplists:get_value(calling_party_addr, Params), CallingPartyEnc = encode_sccp_addr(CallingParty), CallingPartyLen = byte_size(CallingPartyEnc), UserData = binarify(proplists:get_value(user_data, Params)), UserDataLen = byte_size(UserData), % variable part CalledPartyPtr = 3, CallingPartyPtr = 2 + (1 + CalledPartyLen), DataPtr = 1 + (1 + CalledPartyLen) + (1 + CallingPartyLen), Remain = <>, <>; encode_sccp_msgt(?SCCP_MSGT_UDTS, Params) -> ReturnCause = proplists:get_value(return_cause, Params), CalledParty = proplists:get_value(called_party_addr, Params), CalledPartyEnc = encode_sccp_addr(CalledParty), CalledPartyLen = byte_size(CalledPartyEnc), CallingParty = proplists:get_value(calling_party_addr, Params), CallingPartyEnc = encode_sccp_addr(CallingParty), CallingPartyLen = byte_size(CallingPartyEnc), UserData = binarify(proplists:get_value(user_data, Params)), UserDataLen = byte_size(UserData), % variable part CalledPartyPtr = 3, CallingPartyPtr = 2 + (1 + CalledPartyLen), DataPtr = 1 + (1 + CalledPartyLen) + (1 + CallingPartyLen), Remain = <>, <>; encode_sccp_msgt(?SCCP_MSGT_ED, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), UserData = binarify(proplists:get_value(user_data, Params)), UserDataLen = byte_size(UserData), DataPtr = 1, <>; encode_sccp_msgt(?SCCP_MSGT_EA, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), <>; encode_sccp_msgt(?SCCP_MSGT_RSR, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), SrcLocalRef = proplists:get_value(src_local_ref, Params), ResetCause = proplists:get_value(reset_cause, Params), <>; encode_sccp_msgt(?SCCP_MSGT_RSC, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), SrcLocalRef = proplists:get_value(src_local_ref, Params), <>; encode_sccp_msgt(?SCCP_MSGT_ERR, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), ErrCause = proplists:get_value(error_cause, Params), <>; encode_sccp_msgt(?SCCP_MSGT_IT, Params) -> DstLocalRef = proplists:get_value(dst_local_ref, Params), SrcLocalRef = proplists:get_value(src_local_ref, Params), {ProtoClass, PCOpt} = proplists:get_value(protocol_class, Params), SegmSeq = proplists:get_value(seq_segm, Params), Credit = proplists:get_value(credit, Params), <>. % FIXME: XUDT/XUDTS, LUDT/LUDTS % encode one sccp message data structure into the on-wire format encode_sccp_msg(#sccp_msg{msg_type = MsgType, parameters = Params}) -> encode_sccp_msgt(MsgType, Params). % is the supplied message type a connectionless message? is_connectionless(#sccp_msg{msg_type = MsgType}) -> is_connectionless(MsgType); is_connectionless(MsgType) -> case MsgType of ?SCCP_MSGT_UDT -> true; ?SCCP_MSGT_UDTS -> true; ?SCCP_MSGT_XUDT -> true; ?SCCP_MSGT_XUDTS -> true; ?SCCP_MSGT_LUDT -> true; ?SCCP_MSGT_LUDTS -> true; _ -> false end. gen_gt_helper(Number) when is_list(Number) -> #global_title{gti=?SCCP_GTI_NAT_ONLY, nature_of_addr_ind=?SCCP_NAI_INTERNATIONAL, phone_number = Number}. gen_addr_helper(Gt, Pc, Ssn) when is_record(Gt, global_title) -> #sccp_addr{point_code=Pc, ssn=Ssn, global_title=Gt}; gen_addr_helper(Number, Pc, Ssn) when is_list(Number) -> Gt = gen_gt_helper(Number), gen_addr_helper(Gt, Pc, Ssn). gen_addr_helper(Gt, Pc) when is_record(Gt, global_title) -> #sccp_addr{point_code=Pc, global_title=Gt}; gen_addr_helper(Number, Pc) when is_list(Number) -> Gt = gen_gt_helper(Number), gen_addr_helper(Gt, Pc). opt_to_atom(Num) -> case Num of ?SCCP_PNC_DESTINATION_LOCAL_REFERENCE -> dst_local_ref; ?SCCP_PNC_SOURCE_LOCAL_REFERENCE -> src_local_ref; ?SCCP_PNC_CALLED_PARTY_ADDRESS -> called_party_addr; ?SCCP_PNC_CALLING_PARTY_ADDRESS -> calling_party_addr; ?SCCP_PNC_PROTOCOL_CLASS -> protocol_class; ?SCCP_PNC_SEGMENTING -> segmenting; ?SCCP_PNC_RECEIVE_SEQ_NUMBER -> rx_seq_number; ?SCCP_PNC_SEQUENCING -> seq_segm; ?SCCP_PNC_CREDIT -> credit; ?SCCP_PNC_RELEASE_CAUSE -> release_cause; ?SCCP_PNC_RETURN_CAUSE -> return_cause; ?SCCP_PNC_RESET_CAUSE -> reset_cause; ?SCCP_PNC_ERROR_CAUSE -> error_cause; ?SCCP_PNC_REFUSAL_CAUSE -> refusal_cause; ?SCCP_PNC_DATA -> user_data; ?SCCP_PNC_SEGMENTATION -> segmentation; ?SCCP_PNC_HOP_COUNTER -> hop_counter; ?SCCP_PNC_IMPORTANCE -> importance; ?SCCP_PNC_LONG_DATA -> long_data; Foo -> Foo end. atom_to_opt(Atom) -> case Atom of dst_local_ref -> ?SCCP_PNC_DESTINATION_LOCAL_REFERENCE; src_local_ref -> ?SCCP_PNC_SOURCE_LOCAL_REFERENCE; called_party_addr -> ?SCCP_PNC_CALLED_PARTY_ADDRESS; calling_party_addr -> ?SCCP_PNC_CALLING_PARTY_ADDRESS; protocol_class -> ?SCCP_PNC_PROTOCOL_CLASS; segmenting -> ?SCCP_PNC_SEGMENTING; rx_seq_number -> ?SCCP_PNC_RECEIVE_SEQ_NUMBER; seq_segm -> ?SCCP_PNC_SEQUENCING; credit -> ?SCCP_PNC_CREDIT; release_cause -> ?SCCP_PNC_RELEASE_CAUSE; return_cause -> ?SCCP_PNC_RETURN_CAUSE; reset_cause -> ?SCCP_PNC_RESET_CAUSE; error_cause -> ?SCCP_PNC_ERROR_CAUSE; refusal_cause -> ?SCCP_PNC_REFUSAL_CAUSE; user_data -> ?SCCP_PNC_DATA; segmentation -> ?SCCP_PNC_SEGMENTATION; hop_counter -> ?SCCP_PNC_HOP_COUNTER; importance -> ?SCCP_PNC_IMPORTANCE; long_data -> ?SCCP_PNC_LONG_DATA; Foo -> Foo end.