forked from erlang/signerl
407 lines
17 KiB
Erlang
407 lines
17 KiB
Erlang
%%% $Id: tcap_tsm_fsm.erl,v 1.3 2005/08/04 09:33:17 vances Exp $
|
|
%%%---------------------------------------------------------------------
|
|
%%% @copyright 2004-2005 Motivity Telecom Inc.
|
|
%%% @author Vance Shipley <vances@motivity.ca> [http://www.motivity.ca]
|
|
%%% @end
|
|
%%%
|
|
%%% Copyright Motivity Telecom Inc. 2004-2005
|
|
%%%
|
|
%%% All rights reserved. No part of this computer program(s) may be
|
|
%%% used, reproduced, stored in any retrieval system, or transmitted,
|
|
%%% in any form or by any means, electronic, mechanical, photocopying,
|
|
%%% recording, or otherwise without prior written permission of
|
|
%%% Motivity Telecom Inc.
|
|
%%%---------------------------------------------------------------------
|
|
%%%
|
|
%%% @doc Transaction State Machine (TCM) functional block within the
|
|
%%% transaction sub-layer of ANSI TCAP.
|
|
%%%
|
|
%%% @reference ANSI T1.114.4 Transaction Capabilities Procedures
|
|
%%% @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.').
|
|
-author('vances@motivity.ca').
|
|
-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, supervisor, supref, localTID, remoteTID,
|
|
local_address, remote_address, dha}).
|
|
|
|
%%----------------------------------------------------------------------
|
|
%% The gen_fsm call backs
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% initialize the server
|
|
init({NSAP, USAP, TID, Supervisor, SupRef, TCO}) ->
|
|
%% store our process identifier in the global transaction ID table
|
|
ets:insert(transaction, {TID, self()}),
|
|
process_flag(trap_exit, true),
|
|
{ok, idle, #state{nsap = NSAP, usap = USAP, localTID = TID, supervisor = Supervisor,
|
|
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},
|
|
{ok, Begin} = 'TR':decode('TCMessage', SccpParms#'N-UNITDATA'.userData),
|
|
%% Start a Dialogue Handler (DHA)
|
|
SupId = list_to_atom("dha_sup_" ++ integer_to_list(State#state.localTID)),
|
|
StartFunc = {supervisor, start_link,
|
|
[dialogue_sup, [{State#state.usap, State#state.localTID, self()}]]},
|
|
ChildSpec = {SupId, StartFunc, permanent, infinity, supervisor, [dialogue_sup]},
|
|
{ok, DHA} = supervisor:start_child(State#state.supervisor, ChildSpec),
|
|
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(DHA, {'TR', 'BEGIN', indication, TrParms}),
|
|
{next_state, initiation_received, NewState#state{dha = DHA}};
|
|
|
|
%% 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},
|
|
DialoguePortion = (BeginParms#'TR-BEGIN'.userData)#'TR-user-data'.dialoguePortion,
|
|
ComponentPortion = (BeginParms#'TR-BEGIN'.userData)#'TR-user-data'.componentPortion,
|
|
Begin = #'Begin'{otid = State#state.localTID, dialoguePortion = DialoguePortion,
|
|
components = ComponentPortion},
|
|
%% Assemble TR-portion of BEGIN message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {'begin', Begin})),
|
|
{SequenceControl, ReturnOption} = BeginParms#'TR-BEGIN'.qos,
|
|
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
|
|
gen_fsm:send_event(NewState#state.nsap, {'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,
|
|
DialoguePortion = (ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.dialoguePortion,
|
|
ComponentPortion = (ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.componentPortion,
|
|
Continue = #'Continue'{otid = State#state.localTID, dialoguePortion = DialoguePortion, components = ComponentPortion},
|
|
%% Assemble TR-portion of CONTINUE message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {continue, Continue})),
|
|
{SequenceControl, ReturnOption} = ContParms#'TR-CONTINUE'.qos,
|
|
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
|
|
gen_fsm:send_event(NewState#state.nsap, {'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') ->
|
|
DialoguePortion = (EndParms#'TR-END'.userData)#'TR-user-data'.dialoguePortion,
|
|
ComponentPortion = (EndParms#'TR-END'.userData)#'TR-user-data'.componentPortion,
|
|
End = #'End'{dialoguePortion = DialoguePortion, components = ComponentPortion},
|
|
%% Assemble TR-portion of END message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {'end', End})),
|
|
{SequenceControl, ReturnOption} = EndParms#'TR-END'.qos,
|
|
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
|
|
gen_fsm:send_event(State#state.nsap, {'N', 'UNITDATA', request, SccpParms}),
|
|
{stop, normal, State};
|
|
|
|
%% Abort from TR-User
|
|
initiation_received({'ABORT', transaction, AbortParms}, State)
|
|
when is_record(AbortParms, 'TR-U-ABORT') ->
|
|
Cause = (AbortParms#'TR-U-ABORT'.userData)#'TR-user-data'.dialoguePortion,
|
|
Abort = #'Abort'{reason = {'u-abortCause', Cause}},
|
|
%% Assemble TR-portion of ABORT message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {abort, Abort})),
|
|
{SequenceControl, ReturnOption} = AbortParms#'TR-U-ABORT'.qos,
|
|
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
|
|
gen_fsm:send_event(State#state.nsap, {'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
|
|
OTID = (SccpParms#'N-UNITDATA'.userData)#'Begin'.otid,
|
|
NewState = State#state{ remote_address
|
|
= SccpParms#'N-UNITDATA'.callingAddress, remoteTID = OTID},
|
|
QOS = {SccpParms#'N-UNITDATA'.sequenceControl,
|
|
SccpParms#'N-UNITDATA'.returnOption},
|
|
{ok, Continue} = 'TR':decode('TCMessage', SccpParms#'N-UNITDATA'.userData),
|
|
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(NewState#state.dha, {'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},
|
|
{ok, End} = 'TR':decode('TCMessage', 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(State#state.dha, {'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},
|
|
{ok, Abort} = 'TR':decode('TCMessage', 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(State#state.dha, {'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(State#state.dha, {'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(State#state.dha, {'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') ->
|
|
QOS = {SccpParms#'N-UNITDATA'.sequenceControl,
|
|
SccpParms#'N-UNITDATA'.returnOption},
|
|
{ok, Continue} = 'TR':decode('TCMessage', SccpParms#'N-UNITDATA'.userData),
|
|
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(State#state.dha, {'TR', 'CONTINUE', indication, TrParms}),
|
|
{next_state, active, State};
|
|
|
|
%% Continue from TR-User
|
|
active({'CONTINUE', transaction, ContParms}, State)
|
|
when is_record(ContParms, 'TR-CONTINUE') ->
|
|
DialoguePortion = (ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.dialoguePortion,
|
|
ComponentPortion = (ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.componentPortion,
|
|
Continue = #'Continue'{dialoguePortion = DialoguePortion, components = ComponentPortion},
|
|
%% Assemble TR-portion of CONTINUE message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {continue, Continue})),
|
|
{SequenceControl, ReturnOption} = ContParms#'TR-CONTINUE'.qos,
|
|
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
|
|
gen_fsm:send_event(State#state.nsap, {'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},
|
|
{ok, End} = 'TR':decode('TCMessage', 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(State#state.dha, {'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') ->
|
|
DialoguePortion = (EndParms#'TR-END'.userData)#'TR-user-data'.dialoguePortion,
|
|
ComponentPortion = (EndParms#'TR-END'.userData)#'TR-user-data'.componentPortion,
|
|
End = #'End'{dialoguePortion = DialoguePortion, components = ComponentPortion},
|
|
%% Assemble TR-portion of END message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {'end', End})),
|
|
{SequenceControl, ReturnOption} = EndParms#'TR-END'.qos,
|
|
SccpParms = #'N-UNITDATA'{calledAddress = State#state.remote_address,
|
|
callingAddress = State#state.local_address,
|
|
sequenceControl = SequenceControl, returnOption = ReturnOption,
|
|
importance = none, userData = TPDU},
|
|
gen_fsm:send_event(State#state.nsap, {'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},
|
|
{ok, Abort} = 'TR':decode('TCMessage', 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(State#state.dha, {'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(State#state.dha, {'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(State#state.dha, {'TR', 'P-ABORT', indication, TrParms}),
|
|
{stop, normal, State};
|
|
|
|
%% Abort from TR-User
|
|
active({'ABORT', transaction, AbortParms}, State)
|
|
when is_record(AbortParms, 'TR-U-ABORT') ->
|
|
Cause = (AbortParms#'TR-U-ABORT'.userData)#'TR-user-data'.dialoguePortion,
|
|
Abort = #'Abort'{reason = {'u-abortCause', Cause}},
|
|
%% Assemble TR-portion of ABORT message
|
|
TPDU = list_to_binary('TR':encode('TCMessage', {abort, Abort})),
|
|
{SequenceControl, ReturnOption} = AbortParms#'TR-U-ABORT'.qos,
|
|
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
|
|
gen_fsm:send_event(State#state.nsap, {'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) ->
|
|
ets:delete(transaction, State#state.localTID),
|
|
%% signal TCO that we are stopping
|
|
gen_server:cast(State#state.supervisor, {'tsm-stopped', State#state.supref}).
|
|
|
|
%% handle updating state data due to a code replacement
|
|
code_change(_OldVsn, StateName, State, _Extra) ->
|
|
{ok, StateName, State}.
|
|
|