signerl/TCAP/src/ITU/tcap_dha_fsm.erl

916 lines
39 KiB
Erlang

%%% $Id: tcap_dha_fsm.erl,v 1.3 2005/08/04 09:33:17 vances Exp $
%%%---------------------------------------------------------------------
%%% @copyright 2004-2005 Motivity Telecom, 2010-2012 Harald Welte
%%% @author Vance Shipley <vances@motivity.ca>, Harald Welte <laforge@gnumonks.org>
%%% @end
%%%
%%% Copyright (c) 2004-2005, Motivity Telecom
%%% Copyright (c) 2010-2012, Harald Welte <laforge@gnumonks.org>
%%%
%%% 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 Dialogue Handler (DHA) functional block within the component
%%% sub-layer of ITU TCAP.
%%%
%%% @reference ITU-T Q.774 (06/97) Annex A Transaction capabilities SDLs
%%%
%%% @private
%%%
-module(tcap_dha_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_info/3, handle_event/3, handle_sync_event/4,
terminate/3, code_change/4]).
%% transaction_fsm state callbacks
-export([idle/2, wait_for_uni_components/2, wait_for_begin_components/2,
initiation_received/2, wait_cont_components_ir/2,
wait_cont_components_active/2, wait_for_end_components/2,
initiation_sent/2, active/2]).
-export([get_cco_pid/1]).
%% 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").
-include("UnidialoguePDUs.hrl").
-include("DialoguePDUs.hrl").
%% the dialogue_fsm state data
-record(state, {usap, tco, supid, cco, otid, did, parms, appContextMode}).
%%----------------------------------------------------------------------
%% The gen_fsm call backs
%%----------------------------------------------------------------------
%% Start the Dialogue Handler (DHA) process
%% reference: Figure A.5/Q.774 (sheet 1 of 11)
init({USAP, DialogueID, TCO}) ->
init({USAP, DialogueID, TCO, undefined});
init({USAP, DialogueID, TCO, SupId}) ->
ets:insert(tcap_dha, {DialogueID, self()}),
CCO = list_to_atom("tcap_cco_" ++ integer_to_list(DialogueID)),
process_flag(trap_exit, true),
{ok, idle, #state{usap = USAP, did = DialogueID,
tco = TCO, supid = SupId, cco = CCO}}.
%% reference: Figure A.5/Q.774 (sheet 1 of 11)
%%% TC-UNI request from TCU
idle({'TC', 'UNI', request, UniParms}, State)
when is_record(UniParms, 'TC-UNI') ->
%% Dialogue info included?
case UniParms#'TC-UNI'.userInfo of
undefined ->
DialoguePortion = undefined;
UserInfo when is_binary(UserInfo) ->
%% Build AUDT apdu
DialoguePortion = 'UnidialoguePDUs':encode('AUDT-apdu',
#'AUDT-apdu'{'application-context-name' = UniParms#'TC-UNI'.appContextName,
'user-information' = UserInfo})
end,
TrParms = #'TR-UNI'{qos = UniParms#'TC-UNI'.qos,
destAddress = UniParms#'TC-UNI'.destAddress,
origAddress = UniParms#'TC-UNI'.origAddress,
userData = #'TR-user-data'{dialoguePortion = dialogue_ext(DialoguePortion)}},
NewState = State#state{parms = TrParms},
%% Request components to CHA
gen_server:cast(NewState#state.cco, 'request-components'),
%% Process components
{next_state, wait_for_uni_components, NewState};
%% reference: Figure A.5/Q.774 (sheet 1 of 11)
%%% TC-BEGIN request from TCU
idle({'TC', 'BEGIN', request, BeginParms}, State)
when is_record(BeginParms, 'TC-BEGIN') ->
%% Dialogue info included?
case BeginParms#'TC-BEGIN'.appContextName of
undefined ->
DialoguePortion = undefined;
ACtx ->
UserInfo = osmo_util:asn_val(BeginParms#'TC-BEGIN'.userInfo),
%% Set protocol version = 1
%% Build AARQ apdu
{ok, DialoguePortion} = 'DialoguePDUs':encode('AARQ-apdu',
#'AARQ-apdu'{'protocol-version' = [version1],
'application-context-name' = ACtx,
'user-information' = UserInfo})
end,
TrParms = #'TR-BEGIN'{qos = BeginParms#'TC-BEGIN'.qos,
destAddress = BeginParms#'TC-BEGIN'.destAddress,
origAddress = BeginParms#'TC-BEGIN'.origAddress,
transactionID = BeginParms#'TC-BEGIN'.dialogueID,
userData = #'TR-user-data'{dialoguePortion = dialogue_ext(DialoguePortion)}},
NewState = State#state{parms = TrParms,
%% Set application context mode
appContextMode = BeginParms#'TC-BEGIN'.appContextName},
%% Request components to CHA
gen_server:cast(NewState#state.cco, 'request-components'),
%% Process components
{next_state, wait_for_begin_components, NewState};
%% reference: Figure A.5/Q.774 (sheet 2 of 11)
%%% TR-UNI indication from TSL
idle({'TR', 'UNI', indication, UniParms}, State) when is_record(UniParms, 'TR-UNI') ->
%% Extract dialogue portion
case extract_uni_dialogue_portion(UniParms#'TR-UNI'.userData) of
incorrect_dialogue_portion -> %% Dialogue portion correct? (no)
%% Discard components
{stop, normal, State};
no_version1 -> %% Is version 1 supported? (no)
%% Discard components
{stop, normal, State};
TcParms when is_record(TcParms, 'TC-UNI') ->
if
is_record(UniParms#'TR-UNI'.userData, 'TR-user-data'),
(UniParms#'TR-UNI'.userData)#'TR-user-data'.componentPortion /= asn1_NOVALUE ->
case 'TC':decode('Components', (UniParms#'TR-UNI'.userData)#'TR-user-data'.componentPortion) of
{ok, [] = Components} -> ComponentsPresent = false;
{ok, Components} -> ComponentsPresent = true
end;
true ->
Components = undefined,
ComponentsPresent = false
end,
%% Assign dialogue ID
DialogueID = tcap_tco_server:new_tid(),
NewTcParms = TcParms#'TC-UNI'{qos = UniParms#'TR-UNI'.qos,
destAddress = UniParms#'TR-UNI'.destAddress,
origAddress = UniParms#'TR-UNI'.origAddress,
dialogueID = DialogueID,
componentsPresent = ComponentsPresent},
NewState = State#state{did = DialogueID, parms = UniParms},
%% Components to CHA
case ComponentsPresent of
true ->
gen_server:cast(NewState#state.cco, {components, Components});
false ->
ok % should never happen
end,
%% TC-UNI indication to TCU
gen_fsm:send_event(NewState#state.usap, {'TC', 'UNI', indication, NewTcParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState}
end;
%% reference: Figure A.5/Q.774 (sheet 3 of 11)
%%% TR-BEGIN indication from TSL
idle({'TR', 'BEGIN', indication, BeginParms}, State) when is_record(BeginParms, 'TR-BEGIN') ->
%% Extract dialogue portion
case extract_begin_dialogue_portion(BeginParms#'TR-BEGIN'.userData) of
incorrect_dialogue_portion -> %% Dialogue portion correct? (no)
%% Build ABORT apdu
ABRT = 'DialoguePDUs':encode('ABRT-apdu', #'ABRT-apdu'{'abort-source' = 'dialogue-service-provider'}),
%% Discard components
%% TR-U-ABORT request to TSL
TrParms = BeginParms#'TR-BEGIN'{userData = #'TR-user-data'{dialoguePortion = dialogue_ext(ABRT)}},
NewState = State#state{otid = BeginParms#'TR-BEGIN'.transactionID, parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'U-ABORT', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
{stop, normal, NewState};
no_version1 -> %% Is version 1 supported? (no)
DialoguePortion = (BeginParms#'TR-BEGIN'.userData)#'TR-user-data'.dialoguePortion,
%% Build AARE apdu
AARE = 'DialoguePDUs':encode('AARE-apdu', #'AARE-apdu'{
'protocol-version' = [version1],
'application-context-name' = DialoguePortion#'AARQ-apdu'.'application-context-name',
result = 'reject-permanent',
'result-source-diagnostic' = {'dialogue-service-provider', 'no-common-dialogue-portion'}}),
%% Discard components
%% TR-P-ABORT request to TSL
TrParms = {transactionID = BeginParms#'TR-P-ABORT'.transactionID, pAbort = AARE},
NewState = State#state{otid = BeginParms#'TR-BEGIN'.transactionID,
appContextMode = DialoguePortion#'AARQ-apdu'.'application-context-name',
parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'P-ABORT', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
{stop, normal, NewState};
TcParms when is_record(TcParms, 'TC-BEGIN') ->
if
is_record(BeginParms#'TR-BEGIN'.userData, 'TR-user-data'),
(BeginParms#'TR-BEGIN'.userData)#'TR-user-data'.componentPortion /= asn1_NOVALUE ->
case 'TC':decode('Components', (BeginParms#'TR-BEGIN'.userData)#'TR-user-data'.componentPortion) of
{ok, [] = Components} -> ComponentsPresent = false;
{ok, Components} -> ComponentsPresent = true
end;
true ->
Components = undefined,
ComponentsPresent = false
end,
%% Assign dialogue ID
DialogueID = tcap_tco_server:new_tid(),
NewTcParms = TcParms#'TC-BEGIN'{qos = BeginParms#'TR-BEGIN'.qos,
destAddress = BeginParms#'TR-BEGIN'.destAddress,
origAddress = BeginParms#'TR-BEGIN'.origAddress,
dialogueID = DialogueID,
componentsPresent = ComponentsPresent},
NewState = State#state{otid = BeginParms#'TR-BEGIN'.transactionID, did = DialogueID,
parms = BeginParms, appContextMode = TcParms#'TC-BEGIN'.appContextName},
%% TC-BEGIN indication to TCU
gen_fsm:send_event(NewState#state.usap, {'TC', 'BEGIN', indication, NewTcParms}),
%% Any components?
case ComponentsPresent of
true ->
%% Components to CHA
gen_server:cast(NewState#state.cco, {components, Components});
false ->
ok
end,
{next_state, initiation_received, NewState}
end.
%% reference: Figure A.5/Q.774 (sheet 5 of 11)
%%% TC-CONTINUE request from TCU
initiation_received({'TC', 'CONTINUE', request, ContParms}, State) when is_record(ContParms, 'TC-CONTINUE') ->
%% Dialogue info included?
case ContParms#'TC-CONTINUE'.userInfo of
UserInfo when is_binary(UserInfo) ->
AARE = #'AARE-apdu'{'protocol-version' = [version1],
'application-context-name' = ContParms#'TC-CONTINUE'.appContextName,
result = accepted,
'result-source-diagnostic' = {'dialogue-service-user', null},
'user-information' = UserInfo},
{ok, DlgPor} = 'DialoguePDUs':encode('AARE-apdu', AARE),
DialoguePortion = dialogue_ext(DlgPor);
undefined ->
DialoguePortion = asn1_NOVALUE
end,
TrParms = #'TR-CONTINUE'{qos = ContParms#'TC-CONTINUE'.qos,
origAddress = ContParms#'TR-CONTINUE'.origAddress,
transactionID = State#state.otid,
userData = #'TR-user-data'{dialoguePortion = dialogue_ext(DialoguePortion)}},
NewState = State#state{parms = TrParms},
{next_state, wait_cont_components_ir, NewState};
%% reference: Figure A.5/Q.774 (sheet 5 of 11)
%%% TC-END request from TCU
initiation_received({'TC', 'END', request, EndParms}, State) when is_record(EndParms, 'TC-END') ->
%% Prearranged end?
case EndParms#'TC-END'.termination of
prearranged ->
%% TR-END request to TSL
TrParms = #'TR-END'{qos = EndParms#'TC-END'.qos,
transactionID = State#state.otid,
termination = EndParms#'TC-END'.termination},
NewState = State#state{parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'END', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState};
basic ->
%% Dialogue info included?
case EndParms#'TC-END'.userInfo of
UserInfo when is_list(UserInfo) ->
AARE = #'AARE-apdu'{'protocol-version' = [version1],
'application-context-name' = EndParms#'TC-END'.appContextName,
result = accepted,
'result-source-diagnostic' = {'dialogue-service-user', null},
'user-information' = UserInfo},
{ok, DlgPor} = 'DialoguePDUs':encode('AARE-apdu', AARE),
DialoguePortion = dialogue_ext(DlgPor);
undefined ->
DialoguePortion = asn1_NOVALUE
end,
TrParms = #'TR-END'{qos = EndParms#'TC-END'.qos,
transactionID = State#state.otid,
termination = EndParms#'TC-END'.termination,
userData = #'TR-user-data'{dialoguePortion = DialoguePortion}},
NewState = State#state{parms = TrParms},
%% Request components to CHA
gen_server:cast(NewState#state.cco, 'request-components'),
%% Process components
{next_state, wait_for_end_components, NewState}
end;
%% reference: Figure A.5/Q.774 (sheet 6 of 11)
%%% TC-U-ABORT request from TCU
initiation_received({'TC', 'U-ABORT', request, AbortParms}, State) when is_record(AbortParms, 'TC-U-ABORT'),
(AbortParms#'TC-U-ABORT'.abortReason == applicationContextNotSupported)
or (AbortParms#'TC-U-ABORT'.abortReason == dialogueRefused)
or (AbortParms#'TC-U-ABORT'.abortReason == userSpecified) ->
case State#state.appContextMode of
%% Is application context mode set? (no)
undefined ->
UserData = #'TR-user-data'{};
%% Abort reason present and = AC-name not supported OR dialogue refused?
_AppContextName when AbortParms#'TC-U-ABORT'.abortReason == applicationContextNotSupported ->
%% Set protocol version = 1
%% Build AARE-pdu (rejected)
AARE = 'DialoguePDUs':encode('AARE-apdu',
#'AARE-apdu'{'protocol-version' = [version1],
'application-context-name' = AbortParms#'TC-U-ABORT'.appContextName,
result = 'reject-permanent',
'result-source-diagnostic' = {'dialogue-service-user', 'application-context-name-not-supported'}}),
UserData = #'TR-user-data'{dialoguePortion = dialogue_ext(AARE)};
_AppContextName when AbortParms#'TC-U-ABORT'.abortReason == dialogueRefused ->
%% Set protocol version = 1
%% Build AARE-pdu (rejected)
AARE = 'DialoguePDUs':encode('AARE-apdu',
#'AARE-apdu'{'protocol-version' = [version1],
'application-context-name' = AbortParms#'TC-U-ABORT'.appContextName,
result = 'reject-permanent',
'result-source-diagnostic' = {'dialogue-service-user', null}}),
UserData = #'TR-user-data'{dialoguePortion = dialogue_ext(AARE)};
_AppContextName when AbortParms#'TC-U-ABORT'.abortReason == userSpecified ->
%% Build ABRT-apdu (abort source = dialogue-service-user)
ABRT = 'DialoguePDUs':encode('ABRT-apdu',
#'ABRT-apdu'{'abort-source' = 'dialogue-service-user',
'user-information' = AbortParms#'TC-U-ABORT'.userInfo}),
UserData = #'TR-user-data'{dialoguePortion = dialogue_ext(ABRT)}
end,
%% TR-U-ABORT request to TSL
TrParms = #'TR-U-ABORT'{qos = AbortParms#'TC-U-ABORT'.qos,
transactionID = State#state.otid,
userData = UserData},
NewState = State#state{parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'U-ABORT', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState}.
%% reference: Figure A.5/Q.774 (sheet 7 of 11)
%%% TC-END request from TCU
initiation_sent({'TC', 'END', request, EndParms}, State) when is_record(EndParms, 'TC-END'),
EndParms#'TC-END'.termination == prearranged -> % termination must be prearranged
%% TR-END request to TSL
TrParms = #'TR-END'{qos = EndParms#'TC-END'.qos,
transactionID = State#state.otid,
termination = EndParms#'TC-END'.termination},
NewState = State#state{parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'END', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
{stop, normal, NewState};
%% reference: Figure A.5/Q.774 (sheet 7 of 11)
%%% TC-U-ABORT request from TCU (local action)
initiation_sent({'TC', 'U-ABORT', request, AbortParms}, State) when is_record(AbortParms, 'TC-U-ABORT') ->
%% TR-U-ABORT request to TSL
TrParms = #'TR-U-ABORT'{qos = AbortParms#'TC-U-ABORT'.qos, transactionID = State#state.otid},
NewState = State#state{parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'U-ABORT', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
{stop, normal, NewState};
%% reference: Figure A.5/Q.774 (sheet 7 of 11)
%%% TR-END indication from TSL
initiation_sent({'TR', 'END', indication, EndParms}, State) when is_record(EndParms, 'TR-END') ->
if
is_record(EndParms#'TR-END'.userData, 'TR-user-data'),
(EndParms#'TR-END'.userData)#'TR-user-data'.componentPortion /= asn1_NOVALUE ->
case 'TC':decode('Components', (EndParms#'TR-END'.userData)#'TR-user-data'.componentPortion) of
{ok, [] = Components} -> ComponentsPresent = false;
{ok, Components} -> ComponentsPresent = true
end;
true ->
Components = undefined,
ComponentsPresent = false
end,
%% Dialogue portion included?
%% AC Mode set?
%% Extract dialogue portion
%% Dialogue portion correct?
case extract_dialogue_portion(EndParms#'TR-END'.userData, State#state.appContextMode) of
abort ->
%% Discard components
%% TC-P-ABORT indication to TCU
TcParms = #'TC-P-ABORT'{qos = EndParms#'TR-END'.qos,
dialogueID = State#state.did,
pAbort = abnormalDialogue},
NewState = State#state{parms = EndParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'P-ABORT', indication, TcParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState};
AARE ->
%% TC-END indication to TCU
TcParms = #'TC-END'{qos = EndParms#'TR-END'.qos,
dialogueID = State#state.did,
appContextName = State#state.appContextMode,
componentsPresent = ComponentsPresent,
userInfo = AARE,
termination = EndParms#'TR-END'.termination},
NewState = State#state{parms = EndParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'END', indication, TcParms}),
%% Any components?
case ComponentsPresent of
true ->
%% Components to CHA
gen_server:cast(NewState#state.cco, {components, Components});
false ->
ok
end,
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState}
end;
%% reference: Figure A.5/Q.774 (sheet 7 of 11)
%% NOTE: currently the TCO short circuits this function and sends directly to TCU
initiation_sent({'TR', 'NOTICE', indication, NoticeParms}, State) when is_record(NoticeParms, 'TR-NOTICE') ->
%% TC-NOTICE indication to TCU
TcParms = #'TC-NOTICE'{dialogueID = State#state.did,
origAddress = NoticeParms#'TR-NOTICE'.origAddress,
destAddress = NoticeParms#'TR-NOTICE'.destAddress,
reportCause = NoticeParms#'TR-NOTICE'.reportCause},
NewState = State#state{parms = NoticeParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'NOTICE', indication, TcParms}),
{next_state, initiation_sent, NewState};
%% reference: Figure A.5/Q.774 (sheet 8 of 11)
%% TR-CONTINUE indication from TSL
initiation_sent({'TR', 'CONTINUE', indication, ContParms}, State) when is_record(ContParms, 'TR-CONTINUE') ->
if
is_record(ContParms#'TR-CONTINUE'.userData, 'TR-user-data'),
(ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.componentPortion /= asn1_NOVALUE ->
case 'TC':decode('Components', (ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.componentPortion) of
{ok, [] = Components} -> ComponentsPresent = false;
{ok, Components} -> ComponentsPresent = true
end;
true ->
Components = undefined,
ComponentsPresent = false
end,
%% Dialogue portion included?
%% AC Mode set?
%% Extract dialogue portion
%% Dialogue portion correct?
io:format("Components: ~p\n", [Components]),
io:format("Dialogue: ~p\n", [(ContParms#'TR-CONTINUE'.userData)#'TR-user-data'.dialoguePortion]),
case extract_dialogue_portion(ContParms#'TR-CONTINUE'.userData, State#state.appContextMode) of
abort ->
%% Discard components
%% TC-P-ABORT indication to TCU
TcParms = #'TC-P-ABORT'{qos = ContParms#'TR-CONTINUE'.qos,
dialogueID = State#state.did,
pAbort = abnormalDialogue},
NewState = State#state{parms = ContParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'P-ABORT', indication, TcParms}),
%% Build ABRT apdu
ABRT = 'DialoguePDUs':encode('ABRT-apdu',
#'ABRT-apdu'{'abort-source' = 'dialogue-service-provider'}),
UserData = #'TR-user-data'{dialoguePortion = dialogue_ext(ABRT)},
%% TR-U-ABORT request to TSL
TrParms = #'TR-U-ABORT'{qos = ContParms#'TC-U-ABORT'.qos,
transactionID = NewState#state.otid, userData = UserData},
LastState = State#state{parms = ContParms},
gen_server:cast(LastState#state.tco, {'TR', 'U-ABORT', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(LastState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, LastState};
AARE ->
%% TC-CONTINUE indication to TCU
TcParms = #'TC-CONTINUE'{qos = ContParms#'TR-CONTINUE'.qos,
origAddress = ContParms#'TR-CONTINUE'.origAddress,
appContextName = State#state.appContextMode,
dialogueID = State#state.did,
userInfo = AARE,
componentsPresent = ComponentsPresent},
NewState = State#state{parms = ContParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'CONTINUE', indication, TcParms}),
%% Any components?
case ComponentsPresent of
true ->
%% Components to CHA
gen_server:cast(NewState#state.cco, {components, Components});
false ->
ok
end,
{next_state, active, NewState}
end;
%% reference: Figure A.5/Q.774 (sheet 8 of 11)
%% TR-U-ABORT indication from TSL
initiation_sent({'TR', 'U-ABORT', indication, AbortParms}, State) when is_record(AbortParms, 'TR-U-ABORT') ->
case catch begin
if
%% Is AC mode set? (no) Is Dialogue portion present? (no)
State#state.appContextMode == undefined and (not is_record(AbortParms#'TR-U-ABORT'.userData, 'TR-user-data')
or (AbortParms#'TR-U-ABORT'.userData)#'TR-user-data'.dialoguePortion == undefined) ->
throw(#'TC-U-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos, dialogueID = State#state.did});
%% Is AC mode set? (no) Is Dialogue portion present? (yes)
State#state.appContextMode == undefined, is_record(AbortParms#'TR-U-ABORT'.userData, 'TR-user-data'),
AbortParms#'TR-U-ABORT'.userData /= undefined ->
throw(#'TC-P-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did, pAbort = abnormalDialogue});
%% Is User Data included in primitive? (no)
not is_record(AbortParms#'TR-U-ABORT'.userData, 'TR-user-data');
(AbortParms#'TR-U-ABORT'.userData)#'TR-user-data'.dialoguePortion == undefined ->
throw(#'TC-P-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did, pAbort = abnormalDialogue});
true -> ok
end,
%% Is PDU type = ABRT or AARE (rejected)?
case 'DialoguePDUs':decode('DialoguePDU', (AbortParms#'TR-U-ABORT'.userData)#'TR-user-data'.dialoguePortion) of
%% Is abstract syntax = dialogue-PDU AS? (no)
{dialoguePDU, APDU} when is_record(APDU, 'AARE-apdu'),
APDU#'AARE-apdu'.'application-context-name' /= State#state.appContextMode ->
#'TC-P-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
pAbort = abnormalDialogue};
%% Is Abort source = user? (yes)
{dialoguePDU, APDU} when is_record(APDU, 'ABRT-apdu'),
element(1, APDU#'ABRT-apdu'.'abort-source') == 'dialogue-service-user' ->
#'TC-U-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
abortReason = userSpecific,
userInfo = APDU#'ABRT-apdu'.'user-information'};
%% Is Associate source = user? (yes)
{dialoguePDU, APDU} when is_record(APDU, 'AARE-apdu'), APDU#'AARE-apdu'.'result-source-diagnostic'
== {'dialogue-service-user', 'application-context-name-not-supported'},
APDU#'AARE-apdu'.result == 'reject-permanent' ->
#'TC-U-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
abortReason = applicationContextNotSupported,
appContextName = APDU#'AARE-apdu'.'application-context-name',
userInfo = APDU#'AARE-apdu'.'user-information'};
{dialoguePDU, APDU} when is_record(APDU, 'AARE-apdu'),
element(1, APDU#'AARE-apdu'.'result-source-diagnostic') == 'dialogue-service-user',
APDU#'AARE-apdu'.result == 'reject-permanent' ->
#'TC-U-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
abortReason = dialogueRefused,
appContextName = APDU#'AARE-apdu'.'application-context-name',
userInfo = APDU#'AARE-apdu'.'user-information'};
%% Is AARE (no common dialogue portion)?
{dialoguePDU, APDU} when is_record(APDU, 'AARE-apdu'),
APDU#'AARE-apdu'.'result-source-diagnostic' == {'dialogue-service-provider', 'no-common-dialogue-portion'} ->
#'TC-P-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
pAbort = noCommonDialoguePortion};
_ ->
#'TC-P-ABORT'{qos = AbortParms#'TR-U-ABORT'.qos,
dialogueID = State#state.did,
pAbort = abnormalDialogue}
end
end of
TcParms when is_record(TcParms, 'TC-U-ABORT') ->
NewState = State#state{parms = AbortParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'U-ABORT', indication, TcParms});
TcParms when is_record(TcParms, 'TC-P-ABORT') ->
NewState = State#state{parms = AbortParms},
gen_fsm:send_event(NewState#state.usap, {'TC', 'P-ABORT', indication, TcParms})
end,
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState};
%% reference: Figure A.5/Q.774 (sheet 8 of 11)
%% TR-P-ABORT indication from TSL
initiation_sent({'TR', 'P-ABORT', indication, AbortParms}, State) when is_record(AbortParms, 'TR-P-ABORT') ->
TcParms = #'TC-P-ABORT'{qos = AbortParms#'TR-P-ABORT'.qos,
dialogueID = State#state.did,
pAbort = AbortParms#'TR-P-ABORT'.pAbort},
NewState = State#state{parms = AbortParms},
%% TC-P-ABORT indication to TCU
gen_fsm:send_event(NewState#state.usap, {'TC', 'P-ABORT', indication, TcParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState}.
%% reference: Figure A.5/Q.774 (sheet 9 of 11)
%% TC-CONTINUE request from TCU
active({'TC', 'CONTINUE', request, ContParms}, State) when is_record(ContParms, 'TC-CONTINUE') ->
TrParms = #'TR-CONTINUE'{qos = ContParms#'TC-CONTINUE'.qos,
origAddress = ContParms#'TC-CONTINUE'.origAddress,
transactionID = ContParms#'TC-CONTINUE'.dialogueID,
userData = #'TR-user-data'{dialoguePortion = ContParms#'TC-CONTINUE'.userInfo}},
NewState = State#state{parms = TrParms},
%% Request component to CHA
gen_server:cast(NewState#state.cco, 'request-components'),
%% Process components
{next_state, wait_cont_components_active, NewState};
%% reference: Figure A.5/Q.774 (sheet 9 of 11)
%% TC-END request from TCU
active({'TC', 'END', request, EndParms}, State) when is_record(EndParms, 'TC-END') ->
%% Prearranged end?
case EndParms#'TC-END'.termination of
prearranged ->
%% TR-END request to TSL
TrParms = #'TR-END'{qos = EndParms#'TC-END'.qos,
transactionID = State#state.otid,
termination = EndParms#'TC-END'.termination},
NewState = State#state{parms = TrParms},
gen_server:cast(NewState#state.tco, {'TR', 'END', request, TrParms}),
%% Dialogue terminated to CHA
gen_server:cast(NewState#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, NewState};
basic ->
%% Request component to CHA
gen_server:cast(State#state.cco, 'request-components'),
%% Process components
{next_state, wait_for_end_components, State}
end;
%% reference: Figuer A.5/Q774 (sheet 10 of 11)
%% TR-END indication from TSL
active({'TR', 'END', indication, EndParms}, State) when is_record(EndParms, 'TR-END') ->
UserData = EndParms#'TR-END'.userData,
if
UserData#'TR-user-data'.dialoguePortion /= asn1_NOVALUE ->
% discard components
% TC-P-ABORT.ind to TCU
ok;
true ->
ComponentPortion = UserData#'TR-user-data'.componentPortion,
if
ComponentPortion /= asn1_NOVALUE ->
case 'TC':decode('Components', ComponentPortion) of
{ok, [] = Components} -> ComponentsPresent = false;
{ok, Components} -> ComponentsPresent = true
end;
true ->
Components = undefined,
ComponentsPresent = false
end,
%% TC-END indication to TCU
TcParms = #'TC-END'{qos = EndParms#'TR-END'.qos,
dialogueID = State#state.did,
appContextName = State#state.appContextMode,
componentsPresent = ComponentsPresent,
termination = EndParms#'TR-END'.termination},
NewState = State#state{parms = EndParms},
%% Components To CHA
gen_fsm:send_event(NewState#state.usap, {'TC', 'END', indication, TcParms}),
case ComponentsPresent of
true ->
gen_server:cast(State#state.cco, {components, Components});
_ ->
ok
end
end,
%% Dialogue terminated to CHA
gen_server:cast(State#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, State}.
%% reference: Figure A.5 bis/Q.774
%% reference: Figure A.5/Q.774 (sheet 2 of 11)
wait_for_uni_components('no-component', State) ->
wait_for_uni_components1(State);
wait_for_uni_components({'requested-components', Components}, State) ->
%% Assemble component portion
{ok, ComponentPortion} = 'TC':encode('Components', Components),
%% Assemble TSL user data
UserData = (State#state.parms)#'TR-UNI'.userData,
NewUserData = UserData#'TR-user-data'{componentPortion = ComponentPortion},
TrParms = (State#state.parms)#'TR-UNI'{userData = NewUserData},
wait_for_uni_components1(State#state{parms = TrParms}).
wait_for_uni_components1(State) ->
%% TR-UNI request to TSL
gen_server:cast(State#state.tco, {'TR', 'UNI', request, State#state.parms}),
%% Dialogue terminated to CHA
gen_server:cast(State#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, State}.
%% reference: Figure A.5 bis/Q.774
%% reference: Figure A.5/Q.774 (sheet 2 of 11)
wait_for_begin_components('no-component', State) ->
wait_for_begin_components1(State);
wait_for_begin_components({'requested-components', Components}, State) ->
%% Assemble component portion
{ok, ComponentPortion} = 'TC':encode('Components', Components),
%% Assemble TSL user data
UserData = (State#state.parms)#'TR-BEGIN'.userData,
NewUserData = UserData#'TR-user-data'{componentPortion = ComponentPortion},
TrParms = (State#state.parms)#'TR-BEGIN'{userData = NewUserData},
wait_for_begin_components1(State#state{parms = TrParms}).
wait_for_begin_components1(State) ->
%% We don't Assign local transaction ID, as we simply re-use the DialougeID!
TrParms = State#state.parms,
%% TR-BEGIN request to TSL
gen_server:cast(State#state.tco, {'TR', 'BEGIN', request, TrParms}),
{next_state, initiation_sent, State#state{parms = TrParms}}.
%% reference: Figure A.5 bis/Q.774
%% reference: Figure A.5/Q.774 (sheet 5 of 11)
wait_cont_components_ir('no-component', State) ->
wait_cont_components_ir1(State);
wait_cont_components_ir({'requested-components', Components}, State) ->
%% Assemble component portion
{ok, ComponentPortion} = 'TC':encode('Components', Components),
%% Assemble TSL user data
UserData = (State#state.parms)#'TR-CONTINUE'.userData,
NewUserData = UserData#'TR-user-data'{componentPortion = ComponentPortion},
TrParms = (State#state.parms)#'TR-CONTINUE'{userData = NewUserData},
wait_cont_components_ir1(State#state{parms = TrParms}).
wait_cont_components_ir1(State) ->
%% TR-CONTINUE request to TSL
gen_server:cast(State#state.tco, {'TR', 'CONTINUE', request, State#state.parms}),
{next_state, initiation_sent, State}.
%% reference: Figure A.5 bis/Q.774
%% reference: Figure A.5/Q.774 (sheet 9 of 11)
wait_cont_components_active('no-component', State) ->
wait_cont_components_active1(State);
wait_cont_components_active({'requested-components', Components}, State) ->
%% Assemble component portion
{ok, ComponentPortion} = 'TC':encode('Components', Components),
%% Assemble TSL user data
UserData = (State#state.parms)#'TR-CONTINUE'.userData,
NewUserData = UserData#'TR-user-data'{componentPortion = ComponentPortion},
TrParms = (State#state.parms)#'TR-CONTINUE'{userData = NewUserData},
wait_cont_components_active1(State#state{parms = TrParms}).
wait_cont_components_active1(State) ->
%% TR-CONTINUE request to TSL
gen_server:cast(State#state.tco, {'TR', 'CONTINUE', request, State#state.parms}),
{next_state, active, State}.
%% reference: Figure A.5 bis/Q.774
%% reference: Figure A.5/Q.774 (sheet 5 of 11)
%% reference: Figure A.5/Q.774 (sheet 9 of 11)
wait_for_end_components('no-component', State) ->
wait_for_end_components1(State);
wait_for_end_components({'requested-components', Components}, State) ->
%% Assemble component portion
{ok, ComponentPortion} = 'TC':encode('Components', Components),
%% Assemble TSL user data
UserData = (State#state.parms)#'TR-END'.userData,
NewUserData = UserData#'TR-user-data'{componentPortion = ComponentPortion},
TrParms = (State#state.parms)#'TR-END'{userData = NewUserData},
wait_for_end_components1(State#state{parms = TrParms}).
wait_for_end_components1(State) ->
%% TR-END request to TSL
gen_server:cast(State#state.tco, {'TR', 'END', request, State#state.parms}),
%% Dialogue terminated to CHA
gen_server:cast(State#state.cco, 'dialogue-terminated'),
%% Free dialogue ID
{stop, normal, State}.
%% Dialogue portion included? (yes)
extract_uni_dialogue_portion(UserData) when is_record(UserData, 'TR-user-data'),
UserData#'TR-user-data'.dialoguePortion /= undefined ->
%% Dialogue portion correct?
case 'UnidialoguePDUs':decode('UnidialoguePDU', UserData#'TR-user-data'.dialoguePortion) of
{unidialoguePDU, AUDT} when is_record(AUDT, 'AUDT-apdu') ->
%% Is version 1 supported?
case lists:member(version1, AUDT#'AUDT-apdu'.'protocol-version') of
true ->
#'TC-UNI'{appContextName = AUDT#'AUDT-apdu'.'application-context-name',
userInfo = AUDT#'AUDT-apdu'.'user-information'};
false ->
no_version1
end;
_ ->
incorrect_dialogue_portion
end;
%% Dialogue portion included? (no)
extract_uni_dialogue_portion(_DialoguePortion) ->
#'TC-UNI'{}.
%% Dialogue portion included? (yes)
extract_begin_dialogue_portion(UserData) when is_record(UserData, 'TR-user-data'),
UserData#'TR-user-data'.dialoguePortion /= undefined ->
%% Extract dialogue portion
%{'EXTERNAL', {syntax,{0,0,17,773,1,1,1}}, _, DlgPDU} = UserData#'TR-user-data'.dialoguePortion,
% some implementations seem to be broken and not send the 'symtax' part?!?
{'EXTERNAL', _, _, DlgPDU} = UserData#'TR-user-data'.dialoguePortion,
case 'DialoguePDUs':decode('DialoguePDU', DlgPDU) of
{ok, {dialogueRequest, AARQ}} when is_record(AARQ, 'AARQ-apdu') ->
%% Is version 1 supported?
case lists:member(version1, AARQ#'AARQ-apdu'.'protocol-version') of
true ->
%% Set application context mode
#'TC-BEGIN'{appContextName = AARQ#'AARQ-apdu'.'application-context-name',
userInfo = AARQ#'AARQ-apdu'.'user-information'};
false ->
no_version1
end;
_ ->
incorrect_dialogue_portion
end;
%% Dialogue portion included? (no)
extract_begin_dialogue_portion(_DialoguePortion) ->
#'TC-BEGIN'{}.
% if AC is undefined and dialogue portion present -> abort
extract_dialogue_portion(UserData, undefined) when is_record(UserData, 'TR-user-data') and
(UserData#'TR-user-data'.dialoguePortion /= undefined) and
(UserData#'TR-user-data'.dialoguePortion /= asn1_NOVALUE) ->
%% Dialogue portion included? (yes) AC mode set? (no)
abort;
% if dialogue portion is not present but App context name is set -> abort
extract_dialogue_portion(UserData, _AppContextName) when not is_record(UserData, 'TR-user-data') or
(UserData#'TR-user-data'.dialoguePortion == undefined) or
(UserData#'TR-user-data'.dialoguePortion == asn1_NOVALUE) ->
%% Dialogue portion included? (no) AC mode set? (yes)
abort;
% if dialogue portion is present and AppContext name is set -> decode dialogue and proceed
extract_dialogue_portion(UserData, _AppContextName) when is_record(UserData, 'TR-user-data') and
(UserData#'TR-user-data'.dialoguePortion /= undefined) and
(UserData#'TR-user-data'.dialoguePortion /= asn1_NOVALUE) ->
%% Extract dialogue portion
%{'EXTERNAL', {syntax,{0,0,17,773,1,1,1}}, _, DlgPDU} = UserData#'TR-user-data'.dialoguePortion,
% some implementations seem to be broken and not send the 'symtax' part?!?
{'EXTERNAL', _, _, DlgPDU} = UserData#'TR-user-data'.dialoguePortion,
case 'DialoguePDUs':decode('DialoguePDU', DlgPDU) of
{ok, {dialogueResponse, AARE}} when is_record(AARE, 'AARE-apdu') ->
AARE; %% Dialogue portion correct? (yes)
_ ->
abort %% Dialogue portion correct? (no)
end.
%% handle any other message
handle_info(Info, StateName, State) ->
error_logger:format("dialogue_fsm (~w) received unexpected message: ~w~n", [Info]),
{next_state, StateName, State}.
%% handle an event sent using gen:fsm_send_all_state_event/2
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%% handle an event sent using gen_fsm:sync_send_all_state_event/2,3
handle_sync_event(get_cco_pid, From, StateName, StateData) ->
CCO = StateData#state.cco,
{reply, CCO, StateName, StateData};
handle_sync_event(_Event, _From, StateName, StateData) ->
{next_state, StateName, StateData}.
%% handle a shutdown request
terminate(_Reason, _StateName, State) when State#state.supid == undefined ->
%% we were started by TSM, no worries
ets:delete(tcap_dha, State#state.did),
ok;
terminate(_Reason, _StateName, State) ->
%% signal TCO so he can reap the ChildSpec of our supervisor
ets:delete(tcap_dha, State#state.did),
gen_server:cast(State#state.tco, {'dha-stopped', State#state.supid}).
%% handle updating state data due to a code replacement
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
% front-end function called by tcap_user to get CCO for given DHA
get_cco_pid(DHA) ->
gen_fsm:sync_send_all_state_event(DHA, get_cco_pid).
% Wrap encoded DialoguePortion in EXTERNAL ASN.1 data type
dialogue_ext(undefined) ->
asn1_NOVALUE;
dialogue_ext(asn1_NOVALUE) ->
asn1_NOVALUE;
dialogue_ext(DlgEnc) ->
#'EXTERNAL'{'direct-reference' = {0,0,17,773,1,1,1},
'indirect-reference' = asn1_NOVALUE,
'encoding' = {'single-ASN1-type', DlgEnc}}.