%%% $Id: tcap_tsm_fsm.erl,v 1.3 2005/08/04 09:33:17 vances Exp $ %%%--------------------------------------------------------------------- %%% @copyright 2004-2005 Motivity Telecom, 2010-2011 Harald Welte %%% @author Vance Shipley , Harald Welte %%% @end %%% %%% Copyright (c) 2004-2005, Motivity Telecom %%% Copyright (c) 2010-2011, Harald Welte %%% %%% All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% - Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% - Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% - Neither the name of Motivity Telecom nor the names of its %%% contributors may be used to endorse or promote products derived %%% from this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS %%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT %%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR %%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT %%% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT %%% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, %%% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY %%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT %%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE %%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %%% %%%--------------------------------------------------------------------- %%% %%% @doc Transaction State Machine (TCM) functional block within the %%% transaction sub-layer of ITU TCAP. %%% %%% @reference ITU-T Q.774 (06/97) Annex A Transaction capabilities SDLs %%% %%% @private %%% -module(tcap_tsm_fsm). -copyright('Copyright (c) 2004-2005 Motivity Telecom Inc., 2010-2011 Harald Welte'). -author('vances@motivity.ca, laforge@gnumonks.org'). -vsn('$Revision: 1.3 $'). -behaviour(gen_fsm). %% call backs needed for gen_fsm behaviour -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). %% transaction_fsm state callbacks -export([idle/2, initiation_sent/2, initiation_received/2, active/2]). %% record definitions for TR-User primitives -include("tcap.hrl"). %% record definitions for N-User primitives -include("sccp.hrl"). %% record definitions for TCAP messages -include("TCAPMessages.hrl"). %% the transaction_fsm state data -record(state, {nsap, usap, tco, dha_sup, supref, localTID, remoteTID, local_address, remote_address}). %%---------------------------------------------------------------------- %% The gen_fsm call backs %%---------------------------------------------------------------------- %% initialize the server init([NsapFun, USAP, TID, SupRef, TCO]) -> %% store our process identifier in the global transaction ID table io:format("Inserting {~p, ~p} into tcap_transactions~n", [TID, self()]), ets:insert(tcap_transaction, {TID, self()}), process_flag(trap_exit, true), {ok, idle, #state{nsap = NsapFun, usap = USAP, localTID = TID, supref = SupRef, tco = TCO}}. %%% %%% idle state handler %%% %% started by remote %% reference: Figure A.4/Q.774 (sheet 1 of 5) idle({'BEGIN', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> %% Store remote address and remote TID NewState = State#state{remote_address = SccpParms#'N-UNITDATA'.callingAddress, remoteTID = (SccpParms#'N-UNITDATA'.userData)#'Begin'.otid}, Begin = SccpParms#'N-UNITDATA'.userData, QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, UserData = #'TR-user-data'{dialoguePortion = Begin#'Begin'.dialoguePortion, componentPortion = Begin#'Begin'.components}, TrParms = #'TR-BEGIN'{qos = QOS, destAddress = SccpParms#'N-UNITDATA'.calledAddress, origAddress = SccpParms#'N-UNITDATA'.callingAddress, transactionID = State#state.localTID, userData = UserData}, %% TR-BEGIN CSL <- TSL gen_fsm:send_event(resolve_dha(NewState), {'TR', 'BEGIN', indication, TrParms}), {next_state, initiation_received, NewState}; %% started by TR-User %% reference: Figure A.4/Q.774 (sheet 1 of 5) idle({'BEGIN', transaction, BeginParms}, State) when is_record(BeginParms, 'TR-BEGIN') -> %% Store local address %% NOTE - This may be provided by TC-user or be implicitly associated with %% the access point at which the N-UNITDATA primitive is issued. NewState = State#state{local_address = BeginParms#'TR-BEGIN'.origAddress}, TrUserData = process_undefined (BeginParms#'TR-BEGIN'.userData), DialoguePortion = TrUserData#'TR-user-data'.dialoguePortion, ComponentPortion = TrUserData#'TR-user-data'.componentPortion, Otid = State#state.localTID, Begin = #'Begin'{otid = <>, dialoguePortion = DialoguePortion, components = ComponentPortion}, %% Assemble TR-portion of BEGIN message io:format("Trying to encode ~p~n", [Begin]), {ok, TPDU} = 'TR':encode('TCMessage', {'begin', Begin}), {SequenceControl, ReturnOption} = qos_from_tr_prim(BeginParms), SccpParms = #'N-UNITDATA'{calledAddress = BeginParms#'TR-BEGIN'.destAddress, callingAddress = BeginParms#'TR-BEGIN'.origAddress, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {next_state, initiation_sent, NewState}. %%% %%% initiation_received state handler %%% %%% reference: Figure A.4/Q.774 (sheet 2 of 5) %% Continue from TR-User initiation_received({'CONTINUE', transaction, ContParms}, State) when is_record(ContParms, 'TR-CONTINUE') -> %% Store new local address if it is provided by User case ContParms#'TR-CONTINUE'.origAddress of undefined -> NewState = State; NewAddress -> NewState = State#state{local_address = NewAddress} end, TrUserData = process_undefined(ContParms#'TR-CONTINUE'.userData), DialoguePortion = TrUserData#'TR-user-data'.dialoguePortion, ComponentPortion = TrUserData#'TR-user-data'.componentPortion, Otid = State#state.localTID, Dtid = State#state.remoteTID, Continue = #'Continue'{otid = <>, dtid = <>, dialoguePortion = DialoguePortion, components = ComponentPortion}, %% Assemble TR-portion of CONTINUE message {ok, TPDU} = 'TR':encode('TCMessage', {continue, Continue}), {SequenceControl, ReturnOption} = qos_from_tr_prim(ContParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = NewState#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {next_state, active, NewState}; %% End from TR-User (prearranged) initiation_received({'END', transaction, EndParms}, State) when is_record(EndParms, 'TR-END'), EndParms#'TR-END'.termination == prearranged -> {stop, normal, State}; %% End from TR-User (not prearranged) initiation_received({'END', transaction, EndParms}, State) when is_record(EndParms, 'TR-END') -> TrUserData = process_undefined(EndParms#'TR-END'.userData), DialoguePortion = TrUserData#'TR-user-data'.dialoguePortion, ComponentPortion = TrUserData#'TR-user-data'.componentPortion, End = #'End'{dialoguePortion = DialoguePortion, components = ComponentPortion}, %% Assemble TR-portion of END message {ok, TPDU} = 'TR':encode('TCMessage', {'end', End}), {SequenceControl, ReturnOption} = qos_from_tr_prim(EndParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = State#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {stop, normal, State}; %% Abort from TR-User initiation_received({'ABORT', transaction, AbortParms}, State) when is_record(AbortParms, 'TR-U-ABORT') -> TrUserData = process_undefined(AbortParms#'TR-U-ABORT'.userData), Cause = TrUserData#'TR-user-data'.dialoguePortion, Abort = #'Abort'{reason = {'u-abortCause', Cause}}, %% Assemble TR-portion of ABORT message {ok, TPDU} = 'TR':encode('TCMessage', {abort, Abort}), {SequenceControl, ReturnOption} = qos_from_tr_prim(AbortParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = State#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {stop, normal, State}. %%% %%% initiation_sent state handler %%% %%% reference: Figure A.4/Q.774 (sheet 2 of 5) %% Continue from remote initiation_sent({'CONTINUE', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> %% Store remote address and remote TID Continue = SccpParms#'N-UNITDATA'.userData, OTID = Continue#'Continue'.otid, NewState = State#state{ remote_address = SccpParms#'N-UNITDATA'.callingAddress, remoteTID = OTID}, QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, UserData = #'TR-user-data'{dialoguePortion = Continue#'Continue'.dialoguePortion, componentPortion = Continue#'Continue'.components}, TrParms = #'TR-CONTINUE'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, gen_fsm:send_event(resolve_dha(NewState), {'TR', 'CONTINUE', indication, TrParms}), {next_state, active, NewState}; %% End from remote initiation_sent({'END', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, End = SccpParms#'N-UNITDATA'.userData, UserData = #'TR-user-data'{dialoguePortion = End#'End'.dialoguePortion, componentPortion = End#'End'.components}, TrParms = #'TR-END'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, gen_fsm:send_event(resolve_dha(State), {'TR', 'END', indication, TrParms}), {stop, normal, State}; %% Abort from remote initiation_sent({'ABORT', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, Abort = SccpParms#'N-UNITDATA'.userData, %% TR-U-ABORT? case Abort#'Abort'.reason of {'p-abortCause', Cause} -> TrParms = #'TR-P-ABORT'{qos = QOS, transactionID = State#state.localTID, pAbort = Cause}, gen_fsm:send_event(resolve_dha(State), {'TR', 'P-ABORT', indication, TrParms}); {'u-abortCause', Cause} -> UserData = #'TR-user-data'{dialoguePortion = Cause}, TrParms = #'TR-U-ABORT'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, gen_fsm:send_event(resolve_dha(State), {'TR', 'U-ABORT', indication, TrParms}) end, {stop, normal, State}; %% Local Abort initiation_sent({'local-abort', received, Cause}, State) -> TrParms = #'TR-P-ABORT'{pAbort = Cause}, gen_fsm:send_event(resolve_dha(State), {'TR', 'P-ABORT', indication, TrParms}), {stop, normal, State}; %% End from TR-User initiation_sent({'END', transaction, EndParms}, State) when is_record(EndParms, 'TR-END') -> {stop, normal, State}; %% Abort from TR-User initiation_sent({'ABORT', transaction, AbortParms}, State) when is_record(AbortParms, 'TR-U-ABORT') -> %% Purely local action {stop, normal, State}. %%% %%% active state handler %%% %%% reference: Figure A.4/Q.774 (sheet 2 of 5) %% Continue received from remote active({'CONTINUE', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> Continue = SccpParms#'N-UNITDATA'.userData, QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, UserData = #'TR-user-data'{dialoguePortion = Continue#'Continue'.dialoguePortion, componentPortion = Continue#'Continue'.components}, TrParms = #'TR-CONTINUE'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, %% TR-CONTINUE indication CSL <- TSL gen_fsm:send_event(resolve_dha(State), {'TR', 'CONTINUE', indication, TrParms}), {next_state, active, State}; %% Continue from TR-User active({'CONTINUE', transaction, ContParms}, State) when is_record(ContParms, 'TR-CONTINUE') -> TrUserData = process_undefined(ContParms#'TR-CONTINUE'.userData), DialoguePortion = TrUserData#'TR-user-data'.dialoguePortion, ComponentPortion = TrUserData#'TR-user-data'.componentPortion, Otid = State#state.localTID, Dtid = State#state.remoteTID, io:format("OTID ~p, DTID ~p~n", [Otid, Dtid]), Continue = #'Continue'{otid = <>, dtid = <>, dialoguePortion = DialoguePortion, components = ComponentPortion}, %% Assemble TR-portion of CONTINUE message {ok, TPDU} = 'TR':encode('TCMessage', {continue, Continue}), {SequenceControl, ReturnOption} = qos_from_tr_prim(ContParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = State#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {next_state, active, State}; %% End from remote active({'END', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, End = SccpParms#'N-UNITDATA'.userData, UserData = #'TR-user-data'{dialoguePortion = End#'End'.dialoguePortion, componentPortion = End#'End'.components}, TrParms = #'TR-END'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, %% TR-END indication CSL <- TSL gen_fsm:send_event(resolve_dha(State), {'TR', 'END', indication, TrParms}), {stop, normal, State}; %% End from TR-User (prearranged) active({'END', transaction, EndParms}, State) when is_record(EndParms, 'TR-END'), EndParms#'TR-END'.termination == prearranged -> {stop, normal, State}; %% End from TR-User (not prearranged) active({'END', transaction, EndParms}, State) when is_record(EndParms, 'TR-END') -> TrUserData = process_undefined(EndParms#'TR-END'.userData), DialoguePortion = TrUserData#'TR-user-data'.dialoguePortion, ComponentPortion = TrUserData#'TR-user-data'.componentPortion, End = #'End'{dialoguePortion = DialoguePortion, components = ComponentPortion}, %% Assemble TR-portion of END message {ok, TPDU} = 'TR':encode('TCMessage', {'end', End}), {SequenceControl, ReturnOption} = qos_from_tr_prim(EndParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = State#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {stop, normal, State}; %% Abort received from remote active({'ABORT', received, SccpParms}, State) when is_record(SccpParms, 'N-UNITDATA') -> QOS = {SccpParms#'N-UNITDATA'.sequenceControl, SccpParms#'N-UNITDATA'.returnOption}, Abort = SccpParms#'N-UNITDATA'.userData, %% TR-U-ABORT? case Abort#'Abort'.reason of {'p-abortCause', Cause} -> % No TrParms = #'TR-P-ABORT'{qos = QOS, transactionID = State#state.localTID, pAbort = Cause}, %% TR-P-ABORT indication CSL <- TSL gen_fsm:send_event(resolve_dha(State), {'TR', 'P-ABORT', indication, TrParms}); {'u-abortCause', Cause} -> % Yes UserData = #'TR-user-data'{dialoguePortion = Cause}, TrParms = #'TR-U-ABORT'{qos = QOS, transactionID = State#state.localTID, userData = UserData}, %% TR-U-ABORT indication CSL <- TSL gen_fsm:send_event(resolve_dha(State), {'TR', 'U-ABORT', indication, TrParms}) end, {stop, normal, State}; %% Local Abort active({'local-abort', received, Cause}, State) -> TrParms = #'TR-P-ABORT'{qos = {false, false}, transactionID = State#state.localTID, pAbort= Cause}, %% TR-P-ABORT indication CSL <- TSL gen_fsm:send_event(resolve_dha(State), {'TR', 'P-ABORT', indication, TrParms}), {stop, normal, State}; %% Abort from TR-User active({'ABORT', transaction, AbortParms}, State) when is_record(AbortParms, 'TR-U-ABORT') -> TrUserData = process_undefined(AbortParms#'TR-U-ABORT'.userData), Cause = TrUserData#'TR-user-data'.dialoguePortion, Abort = #'Abort'{reason = {'u-abortCause', Cause}}, %% Assemble TR-portion of ABORT message {ok, TPDU} = 'TR':encode('TCMessage', {abort, Abort}), {SequenceControl, ReturnOption} = qos_from_tr_prim(AbortParms), SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address, callingAddress = State#state.local_address, sequenceControl = SequenceControl, returnOption = ReturnOption, importance = none, userData = TPDU}, %% N-UNITDATA request TSL -> SCCP send_to_nsap(State, {'N', 'UNITDATA', request, SccpParms}), {stop, normal, State}. %% handle an event sent using gen_fsm:send_all_state_event/2 handle_event(Event, StateName, State) -> error_logger:format("transaction_fsm (~w) received unexpected message: ~w~n", [Event]), {next_state, StateName, State}. %% handle an event sent using gen_fsm:sync_send_all_state_event/2,3 handle_sync_event(Event, _From, StateName, State) -> error_logger:format("transaction_fsm (~w) received unexpected message: ~w~n", [Event]), {next_state, StateName, State}. %% handle any other message handle_info(Info, StateName, State) -> error_logger:format("transaction_fsm (~w) received unexpected message: ~w~n", [Info]), {next_state, StateName, State}. %% handle a shutdown request terminate(_Reason, _StateName, State) -> io:format("Deleting {~p, ~p} from tcap_transactions~n", [ State#state.localTID, self()]), ets:delete(tcap_transaction, State#state.localTID), %% signal TCO that we are stopping gen_server:cast(State#state.tco, {'tsm-stopped', State#state.supref}). %% handle updating state data due to a code replacement code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. %% internal helper function to send a primitive through the NSAP send_to_nsap(State, P) when is_record(State, state) -> SendFun = State#state.nsap, SendFun(P). process_undefined(U = #'TR-user-data'{dialoguePortion = undefined}) -> U#'TR-user-data'{dialoguePortion = asn1_NOVALUE}; process_undefined(U = #'TR-user-data'{}) -> U. resolve_dha(DlgId) when is_integer(DlgId) -> [{DlgId, DHA}] = ets:lookup(tcap_dha, DlgId), DHA; resolve_dha(#state{localTID = TID}) -> resolve_dha(TID). % QoS from TR-* primitive to {SequenceControl, ReturnOpt} qos_from_tr_qos(undefined) -> {false, true}; qos_from_tr_qos({Seq, Ret}) -> {Seq, Ret}. qos_from_tr_prim(#'TR-CONTINUE'{qos=Qos}) -> qos_from_tr_qos(Qos); qos_from_tr_prim(#'TR-BEGIN'{qos=Qos}) -> qos_from_tr_qos(Qos); qos_from_tr_prim(#'TR-END'{qos=Qos}) -> qos_from_tr_qos(Qos); qos_from_tr_prim(#'TR-U-ABORT'{qos=Qos}) -> qos_from_tr_qos(Qos).