signerl/TCAP/src/ITU/tcap_tco_server.erl

761 lines
29 KiB
Erlang

%%% $Id: tcap_tco_server.erl,v 1.7 2005/08/04 09:33:17 vances Exp $
%%%---------------------------------------------------------------------
%%% @copyright 2004-2005 Motivity Telecom, 2010-2011 Harald Welte
%%% @author Vance Shipley <vances@motivity.ca>, Harald Welte <laforge@gnumonks.org>
%%% @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 Coordinator (TCO) functional block within the
%%% transaction sub-layer of ITU TCAP.
%%%
%%% <p>This module implements the transaction coordinator (TCO)
%%% functional block. Adaptations to specific SCCP layer
%%% and TC-User implementations may be implemented as callback
%%% modules behaving to this behaviour module. This module behaves
%%% to <tt>gen_server</tt>.</p>
%%%
%%% <h2>Usage</h2>
%%% <p>The callback module should be implemented as a gen_server
%%% behaviour but with a <tt>tcap_tco_server</tt> behaviour
%%% module attribute:
%%% <pre>
%%% -behaviour(tcap_tco_server).</pre></p>
%%%
%%% <p>The call back module handles the SCCP -&gt; TCAP primitives
%%% directly, performs any reformatting required, and returns the
%%% standard primitives to the <tt>tcap_tco_server</tt> handler. A
%%% very simple example is the
%%% <a href="http://www.motivity.ca/sccp"><tt>sccp</tt></a>
%%% application which implements the SCCP SAP as a pid() which sends
%%% and receives messages in the primitive format. In our callback
%%% module we create a <tt>handle_info/2</tt> clause which matches
%%% the primitives:</p>
%%%
%%% <p><pre>handle_info({'N', _, indication, _} = Primitive, State) -&gt;
%%% {primitive, Primitive, State}.</pre>
%%% As the example above illustrates the <tt>tcap_tco_server</tt>
%%% behaviour extends the allowed return values to accept the direct
%%% return of a received service primitive.</p>
%%%
%%% <p>The <tt>handle_cast/2</tt> function may be used in the same
%%% way.</p>
%%%
%%% <h2><a name="calbacks">Callback Functions</a></h2>
%%%
%%% <p>In addition to the <tt>gen_server</tt> callbacks the following
%%% callback functions are used.</p>
%%%
%%% <h3><a name="send_primitive-2">send_primitive/2</a></h3>
%%%
%%% <p><tt>send_primitive(Primitive, State) -&gt; void()</tt>
%%% <ul><li><tt>Primitive = {'N', 'UNITDATA', request, UdataParams}</tt></li>
%%% <li><tt>UdataParams = #'N-UNITDATA'{}</tt></li>
%%% </ul>
%%% The TCO will call this function when it has a service primitive
%%% to deliver to the SCCP layer.</p>
%%%
%%% <h3><a name="start_user-2">start_user/2</a></h3>
%%%
%%% <p><tt>start_user(CSL, DialogueID, State) -&gt; pid()</tt>
%%% <ul><li><tt>CSL = {DHA, CCO}</tt></li>
%%% <li><tt>DHA = pid()</tt></li>
%%% <li><tt>CCO = pid()</tt></li>
%%% <li><tt>DialogueID = tid()</tt></li>
%%% </ul>
%%% This function is called by a dialogue handler (DHA) to initialize
%%% a local TC-User for a dialogue begun by a remote TC-User.</p>
%%% <p><tt>CSL</tt> is the component sublayer identifier which
%%% contains the pids of the dialogue handler and component coordinator.</p>
%%% <p>Returns the pid of the TC-User process whcih will handle the
%%% new dialogue.</p>
%%%
%%% <h3><a name="start_transaction-1">start_transaction/2</a></h3>
%%%
%%% <p><tt>start_transaction(TransactionID, State) -&gt; StartFunc</tt>
%%% <ul><li><tt>TransactionID = tid()</tt></li>
%%% <li><tt>State = term()</tt></li>
%%% <li><tt>StartFunc = {M,F,A}</tt></li>
%%% <li><tt>M = F = atom()</tt></li>
%%% <li><tt>A = [term()]</tt></li>
%%% </ul>
%%% The callback module may optionally export this function
%%% to overide the default method used to start a transaction
%%% state machine (TSM).</p>
%%% <p>StartFunc defines the function call used to start the TSM
%%% process. It should be a module-function-arguments tuple
%%% <tt>{M,F,A}</tt> used as <tt>apply(M,F,A)</tt>.</p>
%%% <p>The start function must create and link to the child process,
%%% and should return <tt>{ok, Child}</tt> where <tt>Child</tt> is
%%% the pid of the child process.</p>
%%% <p>See the description of StartFunc in the supervisor module.</p>
%%%
%%% <h3><a name="start_dialogue-1">start_dialogue/1</a></h3>
%%%
%%% <p><tt>start_dialogue(DialogueID, State) -&gt; StartFunc</tt>
%%% <ul><li><tt>DialogueID = tid()</tt></li>
%%% <li><tt>State = term()</tt></li>
%%% <li><tt>StartFunc = {M,F,A}</tt></li>
%%% <li><tt>M = F = atom()</tt></li>
%%% <li><tt>A = [term()]</tt></li>
%%% </ul>
%%% The callback module may optionally export this function
%%% to overide the default method used to start a dialogue
%%% handler (DHA).</p>
%%% <p>StartFunc defines the function call used to start the DHA
%%% process. It should be a module-function-arguments tuple
%%% <tt>{M,F,A}</tt> used as <tt>apply(M,F,A)</tt>.</p>
%%% <p>The start function must create and link to the child process,
%%% and should return <tt>{ok, Child}</tt> where <tt>Child</tt> is
%%% the pid of the child process.</p>
%%% <p>See the description of StartFunc in the supervisor module.</p>
%%%
%%% @end
%%%
%%% @reference ITU-T Q.774 (06/97) Annex A Transaction capabilities SDLs
%%%
-module(tcap_tco_server).
-copyright('Copyright (c) 2003-2005 Motivity Telecom Inc.').
-author('vances@motivity.ca').
-vsn('$Revision: 1.7 $').
-behaviour(gen_server).
% export the gen_server interface
-export([start/4, start/5, start_link/3, start_link/4,
call/2, call/3, multi_call/2, multi_call/3, multi_call/4,
cast/2, abcast/2, abcast/3, reply/2, enter_loop/4, enter_loop/5]).
% export the gen_server call backs
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2]).
% behaviour modules must export this function
-export([behaviour_info/1]).
% api for other modules
-export([new_tid/0]).
%% define what callbacks users must export
%%
%% @hidden
behaviour_info(callbacks) ->
gen_server:behaviour_info(callbacks)
% add the tcap_tco_server required callbacks
++ [{send_primitive, 2}, {start_transaction, 2}, {start_dialogue, 2}];
behaviour_info(Other) ->
gen_server:behaviour_info(Other).
-include("TCAPMessages.hrl").
%-include("TR.hrl").
-include("tcap.hrl").
-include("sccp.hrl").
-record(state, {supervisor, module, ext_state}).
%%----------------------------------------------------------------------
%% The gen_server call backs
%%----------------------------------------------------------------------
%% @hidden
init([Sup, Module, Args]) when is_list(Args) ->
process_flag(trap_exit, true),
case Module:init(Args) of
{ok, ExtState} ->
NewState = #state{supervisor = Sup, module = Module, ext_state = ExtState},
{ok, NewState};
{ok, ExtState, Timeout} ->
NewState = #state{supervisor = Sup, module = Module, ext_state = ExtState},
{ok, NewState, Timeout};
{stop, Reason} ->
{stop, Reason};
ignore ->
ignore;
Other ->
Other
end.
%% @hidden
%%
% assign a new dialogue ID
handle_call(dialogueID, From, State) ->
{reply, new_tid(), State};
% shutdown the server
handle_call(stop, _From, State) ->
{stop, shutdown, ok, State};
handle_call({local_new_trans, OTID}, {Usap, Ref}, State) ->
% Create a Transaction State Machine (TSM)
ChildName = list_to_atom("tcap_trans_sup_" ++ integer_to_list(OTID)),
StartFunc = get_start(out_transaction, [OTID, Usap] , State),
ChildSpec = {ChildName, StartFunc, temporary, 1000, worker, [tcap_tsm_fsm]},
Ret = supervisor:start_child(State#state.supervisor, ChildSpec),
{reply, Ret, State};
% unknown request
handle_call(Request, From, State) ->
Module = State#state.module,
case Module:handle_call(Request, From, State#state.ext_state) of
{reply, Reply, ExtState} ->
{reply, Reply, State#state{ext_state = ExtState}};
{reply, Reply, ExtState, Timeout} ->
{reply, Reply, State#state{ext_state = ExtState}, Timeout};
{noreply, ExtState} ->
{noreply, State#state{ext_state = ExtState}};
{noreply, ExtState, Timeout} ->
{noreply, State#state{ext_state = ExtState}, Timeout};
{stop, Reason, Reply, ExtState} ->
{stop, Reason, Reply, State#state{ext_state = ExtState}};
{stop, Reason, ExtState} ->
{stop, Reason, State#state{ext_state = ExtState}};
Other ->
Other
end.
%% @spec (Request, State) -> Result
%% Info = term()
%% State = term()
%% Result = {noreply, NewState} | {noreply, NewState, Timeout}
%% | {stop, Reason, NewState}
%% | {primitive, Primitive, NewState}
%% NewState = term()
%% Timeout = int() | infinity
%% Reason = term()
%% Primitive = {'N', 'UNITDATA', indication, SccpParams} |
%% {'N', 'NOTICE', indication, SccpParams}
%%
%% @doc Receive a message sent with <tt>gen_server:cast/2</tt>.
%%
%% <p>A user callback module may return an SCCP service primitive
%% to TCO for processing with the return value
%% <tt>{primitive, Primitive, NewState}</tt>.</p>
%%
%% @see //stdlib/gen_server:handle_cast/2
%%
%% @end
%%
% service primitive indications from the network layer
%
% reference: Figure A.3/Q.774 (sheet 1 of 4)
handle_cast({'N', 'UNITDATA', indication, UdataParams}, State)
when is_record(UdataParams, 'N-UNITDATA') ->
{ok, {Tag, ActRes}} = 'TR':decode('TCMessage', UdataParams#'N-UNITDATA'.userData),
PpRes = {Tag, postproc_tcmessage(ActRes)},
io:format("Decoded TCMessage: ~p~n", [PpRes]),
case PpRes of
{unidirectional, Unidirectional = #'Unidirectional'{}} ->
% Create a Dialogue Handler (DHA)
DialogueID = new_tid(),
StartFunc = get_start(dialogue, DialogueID, State),
ChildSpec = {DialogueID, StartFunc, temporary, 4000, worker, [tcap_dha_fsm]},
{ok, DHA} = supervisor:start_child(State#state.supervisor, ChildSpec),
% TR-UNI indication CSL <- TSL
UserData = #'TR-user-data'{dialoguePortion = Unidirectional#'Unidirectional'.dialoguePortion,
componentPortion = Unidirectional#'Unidirectional'.components},
TrParams = #'TR-UNI'{qos =
destAddress = UdataParams#'N-UNITDATA'.calledAddress,
origAddress = UdataParams#'N-UNITDATA'.callingAddress,
userData = UserData},
gen_fsm:send_event(DHA, {'TR', 'UNI', indication, TrParams}),
{noreply, State};
% {error, Reason} ->
% % Discard received message
% % reference: Figure A.3/Q/774 (sheet 4 of 4) label (3)
% error_logger:error_report(["Syntax error in received N-UNI", {error, Reason},
% {caller, UdataParams#'N-UNITDATA'.callingAddress},
% {called, UdataParams#'N-UNITDATA'.calledAddress}]),
% {noreply, State}
% end;
{'begin', TPDU = #'Begin'{}} ->
% Assign local transaction ID
TransactionID = new_tid(),
StartFunc = get_start(in_transaction, TransactionID, State),
ChildSpec = {TransactionID, StartFunc, temporary, infinity, supervisor, [tcap_tsm_fsm]},
% Is TID = no TID?
% Note: The assignment of the ID above just gets the next available
% value and doesn't ensure that it is not in use (unlikely)
% or that there are enough resources available. The real
% test is in whether the start succeeds.
case supervisor:start_child(State#state.supervisor, ChildSpec) of
{ok, _TransSupPid} ->
% Created a Transaction State Machine (TSM)
case ets:lookup_element(tcap_transaction, TransactionID, 2) of
TSM ->
TsmParams = UdataParams#'N-UNITDATA'{userData = TPDU},
% BEGIN received TSM <- TCO
gen_fsm:send_event(TSM, {'BEGIN', received, TsmParams});
{error, _Reason} ->
error_logger:error_report(["Unable to find TSM that was just started"])
end;
Other ->
error_logger:error_report(["Unable to start TSM", {childspec, ChildSpec}, {error, Other}]),
% TID = no TID
% Build ABORT message (P-Abort Cause = Resource Limitation)
Abort = {abort, #'Abort'{dtid = encode_tid(TPDU#'Begin'.otid),
reason = {'p-abortCause', resourceLimitation}}},
case 'TR':encode('TCMessage', Abort) of
{ok, EncAbort} ->
SccpParams = #'N-UNITDATA'{calledAddress = UdataParams#'N-UNITDATA'.callingAddress,
callingAddress = UdataParams#'N-UNITDATA'.calledAddress,
sequenceControl = false,
returnOption = false, importance = none,
userData = list_to_binary(EncAbort)},
% TR-UNI request TSL -> SCCP
Module = State#state.module,
Module:send_primitive({'N', 'UNITDATA', request, SccpParams}, State#state.ext_state);
{error, Err} ->
error_logger:error_report(["Error generating ASN1", {abort, Abort}, {error, Err}])
end,
error_logger:error_report(["Unable to create TSM for received N-BEGIN",
{caller, UdataParams#'N-UNITDATA'.callingAddress},
{called, UdataParams#'N-UNITDATA'.calledAddress}])
end,
{noreply, State};
% {error, Reason} ->
% TODO
% % is OTID derivable?
% % Build ABORT message with appropraite P-Abort Cause value
% % N-UNITDATA request TSL -> SCCP
% % Discard received message
% % reference: Figure A.3/Q/774 (sheet 4 of 4) label (4)
% error_logger:error_report(["Syntax error in received N-BEGIN", {error, Reason},
% {caller, UdataParams#'N-UNITDATA'.callingAddress},
% {called, UdataParams#'N-UNITDATA'.calledAddress}]),
% {noreply, State}
{continue, TPDU = #'Continue'{dtid = Dtid}} ->
% DTID assigned?
case ets:lookup_element(tcap_transaction, Dtid, 2) of
{error, _Reason} ->
error_logger:error_report(["DTID not found in received N-CONTINUE",
{dtid, Dtid},
{caller, UdataParams#'N-UNITDATA'.callingAddress},
{called, UdataParams#'N-UNITDATA'.calledAddress}]),
% TODO
% Build ABORT message with appropriate P-Abort Cause values
% N-UNITDATA request TSL -> SCCP
% Discard received message
% reference: Figure A.3/Q/774 (sheet 4 of 4) label (4)
{noreply, State};
TSM ->
TsmParams = UdataParams#'N-UNITDATA'{userData = TPDU},
% CONTINUE received TSM <- TCO
gen_fsm:send_event(TSM, {'CONTINUE', received, TsmParams}),
{noreply, State}
end;
% {error, Reason} ->
% TODO
% OTID derivable?
% DTID assigned?
% Build ABORT message with appropraite P-Abort Cause value
% N-UNITDATA request TSL -> SCCP
% Local Abort TSM <- TCO
% Discard received message
% reference: Figure A.3/Q/774 (sheet 4 of 4) label (2)
% error_logger:error_report(["Syntax error in received N-CONTINUE", {error, Reason},
% {caller, UdataParams#'N-UNITDATA'.callingAddress},
% {called, UdataParams#'N-UNITDATA'.calledAddress}]),
% {noreply, State}
% end;
{'end', TPDU = #'End'{dtid = Dtid}} ->
% DTID assigned?
case ets:lookup_element(tcap_transaction, Dtid, 2) of
{error, _Reason} ->
error_logger:error_report(["DTID not found in received N-END",
{dtid, Dtid},
{caller, UdataParams#'N-UNITDATA'.callingAddress},
{called, UdataParams#'N-UNITDATA'.calledAddress}]),
% Discard received message
% reference: Figure A.3/Q/774 (sheet 4 of 4) label (3)
{noreply, State};
TSM ->
TsmParams = UdataParams#'N-UNITDATA'{userData = TPDU},
% END received TSM <- TCO
gen_fsm:send_event(TSM, {'END', received, TsmParams}),
{noreply, State}
end;
% {error, Reason} ->
% TODO
% % DTID assigned?
% % Local Abort TSM <- TCO
% % Discard received message
% % reference: Figure A.3/Q/774 (sheet 4 of 4) label (5)
% error_logger:error_report(["Syntax error in received N-END", {error, Reason},
% {caller, UdataParams#'N-UNITDATA'.callingAddress},
% {called, UdataParams#'N-UNITDATA'.calledAddress}]),
% {noreply, State}
% end;
{abort, TPDU = #'Abort'{}} ->
% DTID assigned?
case catch ets:lookup(tcap_transaction, TPDU#'Abort'.dtid, 2) of
{error, _Reason} ->
error_logger:error_report(["DTID not found in received N-ABORT",
{dtid, TPDU#'Abort'.dtid},
{caller, UdataParams#'N-UNITDATA'.callingAddress},
{called, UdataParams#'N-UNITDATA'.calledAddress}]),
% Discard received message
% reference: Figure A.3/Q/774 (sheet 4 of 4) label (3)
{noreply, State};
TSM ->
TsmParams = UdataParams#'N-UNITDATA'{userData = TPDU},
% Abort received TSM <- TCO
gen_fsm:send_event(TSM, {'ABORT', received, TsmParams}),
{noreply, State}
end;
% {error, Reason} ->
% TODO
% % DTID assigned?
% % Local Abort TSM <- TCO
% % Discard received message
% % reference: Figure A.3/Q/774 (sheet 4 of 4) label (5)
% error_logger:error_report(["Syntax error in received N-ABORT", {error, Reason},
% {caller, UdataParams#'N-UNITDATA'.callingAddress},
% {called, UdataParams#'N-UNITDATA'.calledAddress}]),
% {noreply, State}
% end;
{error, Reason} ->
% TODO
% Message type unknown
% OTID derivable?
% DTID assigned?
% Build ABORT message with appropraite P-Abort Cause value
% N-UNITDATA request TSL -> SCCP
% Local Abort TSM <- TCO
% Discard received message
% reference: Figure A.3/Q/774 (sheet 4 of 4) label (2)
error_logger:error_report(["Unknown TCMessage received", {error, Reason},
{caller, UdataParams#'N-UNITDATA'.callingAddress},
{called, UdataParams#'N-UNITDATA'.calledAddress}]),
{noreply, State}
end;
handle_cast({'N', 'NOTICE', indication, NoticeParams}, State) ->
% Extract the originating transactionID
case 'TR':decode('TCMessage', NoticeParams#'N-NOTICE'.userData) of
{ok, {'begin', TPDU}} ->
case 'TR':decode('Begin', TPDU) of
{ok, Begin} ->
TransactionID = Begin#'Begin'.otid;
_ ->
TransactionID = undefined
end;
{ok, {continue, TPDU}} ->
case 'TR':decode('Continue', TPDU) of
{ok, Continue} ->
TransactionID = Continue#'Continue'.otid;
_ ->
TransactionID = undefined
end;
_ ->
TransactionID = undefined
end,
% TR-NOTICE indication CSL <- TSL
% reference: Figure A.3/Q.774 (sheet 2 of 4)
% The CSL is a null layer for this indication so it becomes
% TC-NOTICE indication TCU <- TSL
% reference: Figure A.5/Q.774 (sheet 7 of 11)
% reference: Figure A.3/Q.774 (sheet 10 of 11)
TcParams = #'TC-NOTICE'{
dialogueID = TransactionID,
origAddress = NoticeParams#'N-NOTICE'.callingAddress,
destAddress = NoticeParams#'N-NOTICE'.calledAddress,
reportCause = NoticeParams#'N-NOTICE'.reason},
% TODO: fixme!!! gen_fsm:send_event(State#state.usap, {'TC', 'NOTICE', indication, TcParams}),
{noreply, State};
%%
%% service primitive requests from the TR-User
%% reference: Figure A.3/Q.774 (sheets 2&3 of 4)
handle_cast({'TR', 'UNI', request, UniParams}, State)
when is_record(UniParams, 'TR-UNI') ->
% Assemble TR-portion of UNI message
{SequenceControl, ReturnOption, Importance} = UniParams#'TR-UNI'.qos,
DialoguePortion = (UniParams#'TR-UNI'.userData)#'TR-user-data'.dialoguePortion,
ComponentPortion = (UniParams#'TR-UNI'.userData)#'TR-user-data'.componentPortion,
case 'TR':encode('TCMessage', {unidirectional, #'Unidirectional'{
dialoguePortion = DialoguePortion, components = ComponentPortion}}) of
{ok, TPDU} ->
TpduBin = iolist_to_binary(TPDU),
SccpParams = #'N-UNITDATA'{calledAddress = UniParams#'TR-UNI'.destAddress,
callingAddress = UniParams#'TR-UNI'.origAddress,
sequenceControl = SequenceControl, returnOption = ReturnOption,
importance = Importance, userData = TpduBin},
Module = State#state.module,
Module:send_primitive({'N', 'UNITDATA', request, SccpParams}, State#state.ext_state),
{noreply, State};
{error, Err} ->
error_logger:error_report(["Error generating ASN1", {error, Err},
{dialogue_portion, DialoguePortion},
{components, ComponentPortion}]),
{noreply, State}
end;
handle_cast({'TR', 'BEGIN', request, BeginParams}, State)
when is_record(BeginParams, 'TR-BEGIN') ->
% Create a Transaction State Machine (TSM)
TransactionID = BeginParams#'TR-BEGIN'.transactionID,
TSM = ets:lookup_element(tcap_transaction, TransactionID, 2),
gen_fsm:send_event(TSM, {'BEGIN', transaction, BeginParams}),
{noreply, State};
handle_cast({'TR', 'CONTINUE', request, ContParams}, State)
when is_record(ContParams, 'TR-CONTINUE') ->
TransactionID = ContParams#'TR-CONTINUE'.transactionID,
TSM = ets:lookup_element(tcap_transaction, TransactionID, 2),
gen_fsm:send_event(TSM, {'CONTINUE', transaction, ContParams}),
{noreply, State};
handle_cast({'TR', 'END', request, EndParams}, State)
when is_record(EndParams, 'TR-END') ->
TransactionID = EndParams#'TR-END'.transactionID,
TSM = ets:lookup_element(tcap_transaction, TransactionID, 2),
gen_fsm:send_event(TSM, {'END', transaction, EndParams}),
{noreply, State};
handle_cast({'TR', 'U-ABORT', request, AbortParams}, State)
when is_record(AbortParams, 'TR-U-ABORT') ->
TransactionID = AbortParams#'TR-U-ABORT'.transactionID,
TSM = ets:lookup_element(tcap_transaction, TransactionID, 2),
gen_fsm:send_event(TSM, {'ABORT', transaction, AbortParams}),
{noreply, State};
%
% The TSM sends us a message as it's last action so
% we can remove the supervisor child specification
%
handle_cast({'tsm-stopped', SupRef}, State) ->
supervisor:delete_child(State#state.supervisor, SupRef),
% reference: Figure A.3/Q/774 (sheet 2 of 4)
{noreply, State};
% unrecognized request
handle_cast(Request, State) ->
Module = State#state.module,
case Module:handle_cast(Request, State#state.ext_state) of
{noreply, ExtState} ->
{noreply, State#state{ext_state = ExtState}};
{noreply, ExtState, Timeout} ->
{noreply, State#state{ext_state = ExtState}, Timeout};
{primitive, Primitive, ExtState} ->
handle_cast(Primitive, State#state{ext_state = ExtState});
{stop, Reason, ExtState} ->
{stop, Reason, State#state{ext_state = ExtState}};
Other ->
Other
end.
%% @spec (Info, State) -> Result
%% Info = timeout | term()
%% State = term()
%% Result = {noreply, NewState} | {noreply, NewState, Timeout}
%% | {stop, Reason, NewState}
%% | {primitive, Primitive, NewState}
%% NewState = term()
%% Timeout = int() | infinity
%% Reason = term()
%% Primitive = {'N', 'UNITDATA', indication, SccpParams} |
%% {'N', 'NOTICE', indication, SccpParams}
%%
%% @doc Receive a message sent with '!'.
%%
%% <p>A user callback module may return an SCCP service primitive
%% to TCO for processing with the return value
%% <tt>{primitive, Primitive, NewState}</tt>.</p>
%%
%% @see //stdlib/gen_server:handle_info/2
%%
handle_info({'EXIT', _Pid, Reason}, State) ->
{stop, Reason, State};
handle_info(Info, State) ->
Module = State#state.module,
case Module:handle_info(Info, State#state.ext_state) of
{noreply, ExtState} ->
{noreply, State#state{ext_state = ExtState}};
{noreply, ExtState, Timeout} ->
{noreply, State#state{ext_state = ExtState}, Timeout};
{primitive, Primitive, ExtState} ->
handle_cast(Primitive, State#state{ext_state = ExtState});
{stop, Reason, ExtState} ->
{stop, Reason, State#state{ext_state = ExtState}};
Other ->
Other
end.
%% @hidden
terminate(Reason, State) ->
Module = State#state.module,
Module:terminate(Reason, State#state.ext_state).
%% @hidden
code_change(OldVersion, statename, State, Extra) ->
Module = State#state.module,
case Module:code_change(OldVersion, State#state.ext_state, Extra) of
{ok, ExtState} ->
{ok, State#state{ext_state = ExtState}};
Other ->
Other
end.
%%----------------------------------------------------------------------
%% internal functions
%%----------------------------------------------------------------------
%% @hidden
%%
%% get the next originating transaction id from the global counter
%%
%% NOTE: we are simply assuming that when the counter rolls over the last
%% transaction to have this ID is long gone (4.2 billion IDs)
%%
%% reference: Figure A.3 bis/Q.774
new_tid() ->
ets:update_counter(tcap_transaction, transactionID, {2, 1, 16#ffffffff, 0}).
get_start(dialogue, DialogueID, State) ->
Module = State#state.module,
case erlang:function_exported(Module, start_dialogue, 1) of
true ->
Module:start_dialogue(DialogueID, State#state.ext_state);
false ->
StartUserFun = fun(CSL) -> Module:start_user(CSL, DialogueID, State#state.ext_state) end,
StartArgs = [DialogueID, self(), StartUserFun],
{gen_fsm, start_link, [tcap_dha_fsm, StartArgs, []]}
end;
get_start(in_transaction, TransactionID, State) ->
Module = State#state.module,
case erlang:function_exported(Module, start_transaction, 1) of
true ->
Module:start_transaction(TransactionID, State#state.ext_state);
false ->
SendFun = fun(P) -> Module:send_primitive(P, State#state.ext_state) end,
StartDHA = get_start(dialogue, TransactionID, State),
StartArgs = [TransactionID, SendFun, StartDHA],
{gen_fsm, start_link, [tcap_tsm_fsm, StartArgs, []]}
end;
get_start(out_transaction, [TransactionID, Usap], State) when is_record(State, state) ->
#state{module = Module, supervisor = Sup} = State,
case erlang:function_exported(Module, start_transaction, 1) of
true ->
Module:start_transaction(TransactionID, State#state.ext_state);
false ->
SendFun = fun(P) -> Module:send_primitive(P, State#state.ext_state) end,
StartDHA = get_start(dialogue, TransactionID, State),
% FIXME: use StartDHA and pass it into transaction_sup->tsm_fsm
StartArgs = [SendFun, Usap, TransactionID, self()],
{supervisor, start_link, [tcap_transaction_sup, StartArgs]}
end.
%%----------------------------------------------------------------------
%% The gen_server API functions
%%----------------------------------------------------------------------
%% @hidden
start(Module, SupRef, Args, Options) ->
gen_server:start(?MODULE, [SupRef, Module, Args], Options).
%% @hidden
start(ServerRef, SupRef, Module, Args, Options) ->
gen_server:start(ServerRef, ?MODULE, [SupRef, Module, Args], Options).
%% @hidden
start_link(Module, Args, Options) ->
gen_fsm:start_link(?MODULE, [Module, Args], Options).
%% @hidden
start_link(ServerRef, Module, Args, Options) ->
gen_fsm:start_link(ServerRef, ?MODULE, [Module, Args], Options).
%% @hidden
call(ServerRef, Request) ->
gen_server:call(ServerRef, Request).
%% @hidden
call(ServerRef, Request, Timeout) ->
gen_server:call(ServerRef, Request, Timeout).
%% @hidden
multi_call(Name, Request) ->
gen_server:multi_call(Name, Request).
%% @hidden
multi_call(Nodes, Name, Request) ->
gen_server:multi_call(Nodes, Name, Request).
%% @hidden
multi_call(Nodes, Name, Request, Timeout) ->
gen_server:multi_call(Nodes, Name, Request, Timeout).
%% @hidden
cast(ServerRef, Request) ->
gen_server:cast(ServerRef, Request).
%% @hidden
abcast(Name, Request) ->
gen_server:abcast(Name, Request).
%% @hidden
abcast(Nodes, Name, Request) ->
gen_server:abcast(Nodes, Name, Request).
%% @hidden
reply(Client, Reply) ->
gen_server:reply(Client, Reply).
%% @hidden
enter_loop(Module, Options, State, ServerName, Timeout) ->
gen_server:enter_loop(Module, Options, State, ServerName, Timeout).
%% @hidden
enter_loop(Module, Options, State, Timeout) ->
gen_server:enter_loop(Module, Options, State, Timeout).
% enter_loop(Module, Options, State, ServerName) ->
% gen_server:enter_loop(Module, Options, State, ServerName).
% convert a TID from the four-octet binary/list form (OCTET STRING) to unsigned int
decode_tid(Bin) when is_binary(Bin) ->
binary:decode_unsigned(Bin);
decode_tid(List) when is_list(List) ->
decode_tid(list_to_binary(List)).
encode_tid(In) when is_integer(In) ->
<<In:32/big>>;
encode_tid(In) when is_list(In) ->
list_to_binary(In);
encode_tid(In) when is_binary(In) ->
In.
postproc_tcmessage(C=#'Continue'{otid = Otid, dtid = Dtid}) ->
C#'Continue'{otid = decode_tid(Otid), dtid = decode_tid(Dtid)};
postproc_tcmessage(E=#'End'{dtid = Dtid}) ->
E#'End'{dtid = decode_tid(Dtid)};
postproc_tcmessage(B=#'Begin'{otid = Otid}) ->
B#'Begin'{otid = decode_tid(Otid)}.