Implement the actual CCO, it was just a skeleton before...

This commit is contained in:
Harald Welte 2011-10-18 20:52:29 +02:00
parent 745bcf4099
commit 59d0c8ab37
1 changed files with 280 additions and 9 deletions

View File

@ -1,10 +1,10 @@
%%% $Id: tcap_cco_server.erl,v 1.3 2005/08/04 09:33:17 vances Exp $
%%%---------------------------------------------------------------------
%%% @copyright 2004-2005 Motivity Telecom
%%% @author Vance Shipley <vances@motivity.ca> [http://www.motivity.ca]
%%% @copyright 2010-2011 Harald Welte
%%% @author Harald Welte <laforge@gnumonks.org>
%%% @end
%%%
%%% Copyright (c) 2004-2005, Motivity Telecom
%%% Copyright (c) 2010-2011, Harald Welte
%%%
%%% All rights reserved.
%%%
@ -43,10 +43,10 @@
%%%
%%% @private
%%%
-module(tcap_cco_server).
-copyright('Copyright (c) 2004-2005 Motivity Telecom Inc.').
-author('vances@motivity.ca').
-copyright('Copyright (c) 2010-2011 Harald Welte').
-author('laforge@gnumonks.org').
-vsn('$Revision: 1.3 $').
-behaviour(gen_server).
@ -55,16 +55,23 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {supervisor, usap, dialogueID}).
-include("tcap.hrl").
-include("TR.hrl").
-include("Remote-Operations-Generic-ROS-PDUs.hrl").
-record(state, {supervisor, usap, dialogueID, components, dha, ism}).
-record(component, {asn_ber, user_prim}).
%%----------------------------------------------------------------------
%% The gen_server call backs
%%----------------------------------------------------------------------
%% initialize the server
init([Supervisor, USAP, DialogueID]) ->
init([Supervisor, USAP, DialogueID, DHA]) ->
process_flag(trap_exit, true),
{ok, #state{supervisor = Supervisor, usap = USAP, dialogueID = DialogueID}}.
{ok, #state{supervisor = Supervisor, usap = USAP, dha = DHA,
dialogueID = DialogueID, components = []}}.
%% shutdown the server
handle_call(stop, _From, State) ->
@ -75,6 +82,94 @@ handle_call(Other, From, State) ->
error_logger:error_report([{unknown_call, Other}, {from, From}]),
{noreply, State}.
% from TCU: TC-INVOKE.req
handle_cast({'TC','INVOKE',request, InvParam}, State)
when is_record(InvParam, 'TC-INVOKE') ->
% assemble INVOKE component
AsnRosRec = uprim_to_asn_rec(InvParam),
Component = #component{user_prim = InvParam, asn_ber = 0}, % FIXME
% mark it as available
NewState = add_components_to_state(State, Component),
% what to do with class and timeout?
{noreply, NewState};
% from TCU: TC-U-CANCEL.req
handle_cast({'TC','U-CANCEL',request, Param}, State)
when is_record(Param, 'TC-U-CANCEL') ->
InvokeId = Param#'TC-U-CANCEL'.invokeID,
OldComps = State#state.components,
% if there are any INV componnents waiting, discard them
NewComps = discard_inv_component(OldComps, InvokeId),
case NewComps of
OldComps ->
% if not, check any active ISM, if yes, terminate ISM
NewISMs = terminate_active_ISM(State#state.ism,
InvokeId),
NewState = State#state{ism = NewISMs};
_ ->
NewState = State#state{components = NewComps}
end,
{noreply, NewState};
% from DHA -> CCO: dialogue-terminated
handle_cast('dialogue-terminated', State) ->
% discard components awaiting transmission
% * automatically released
% if any ISM active, terminate ISM
% * ISMs are linked, they should terminate
% terminate
{stop, dialogue_terminated, State};
% from TCL -> CHA (CCO): TC-RESULT-{L,NL}, U-ERROR
handle_cast({'TC', Req, request, Param}, State) when
Req == 'RESULT-L';
Req == 'RESULT-NL';
Req == 'U-ERROR' ->
% Figure A.6/Q.774 (1 of 4)
% assemble requested component
AsnRosRec = uprim_to_asn_rec(Param),
Component = #component{user_prim = Param, asn_ber = 0}, %FIXME
% mark component available for this dialogue
NewState = add_components_to_state(State, Component),
{noreply, NewState};
% TCL->CHA: TC-U-REJECT.req
handle_cast({'TC','U-REJECT',request, Param}, State)
when is_record(Param, 'TC-U-REJECT') ->
% assemble reject component
AsnRosRec = uprim_to_asn_rec(Param),
Component = #component{user_prim = Param, asn_ber = 0}, %FIXME
% FIXME: if probelm type Result/Error, terminate ISM
% mark component available for this dialogue
NewState = add_components_to_state(State, Component),
{noreply, NewState};
% DHA -> CHA (CCO): Components received
handle_cast({components, Components}, State) ->
% Figure A.6/Q.774 (2 of 4)
process_rx_components(State#state.ism, Components),
{noreply, State};
% ISM -> CCO: Generate REJ component
% DHA -> CHA (CCO)
handle_cast('request-components', State = #state{components=CompIn}) ->
% Figure A.6/Q.774 (4 of 4)
case CompIn of
[] ->
% if no components, signal 'no-components' to DHA
gen_fsm:send_event(State#state.dha, 'no-component'),
NewState = State;
_ ->
% for each component
{CompOut, ISMs} = process_request_components(CompIn, State),
% signal 'requested-components' to DHA
gen_fsm:send_event(State#state.dha,
{'requested-components', CompOut}),
NewState = State#state{ism = State#state.ism ++ ISMs,
components= undefined}
end,
{noreply, NewState};
%% unrecognized casts
handle_cast(Other, State) ->
error_logger:error_report([{unknown_cast, Other}]),
@ -100,3 +195,179 @@ code_change(_, _, _) -> ok.
%%% internal functions
%%%
% add one or multiple components to the list of components
add_component_to_state(State, Component) when is_record(State, state) ->
NewComponents = State#state.components ++ Component,
State#state{components = NewComponents}.
asn_val(Foo) ->
case Foo of
undefined ->
asn1_NOVALIE;
[] ->
asn1_NOVALUE;
Foo ->
Foo
end.
% Figure A.6/Q.774 (4 of 4)
process_request_components(Components, State) when
is_list(Components), is_record(State, state) ->
process_request_components(Components, State, [], []).
process_request_components([], _State, AsnComps, ISMs) ->
{lists:reverse(AsnComps), ISMs};
process_request_components([Head|Tail], State, AsnComps, ISMs) when
is_record(Head, component),
is_record(State, state) ->
#component{asn_ber = Asn, user_prim = Uprim} = Head,
#state{usap = Usap, dialogueID = DialogueId} = State,
case Uprim of
#'TC-INVOKE'{class = Class, timeout = Tout,
invokeID = InvId} ->
% if INVOKE component
% start ISM and store ISM
{ok, ISM} = tcap_ism_fsm:start_link(Usap, DialogueId,
InvId, Class, Tout),
% signal 'operation-sent' to ISM
gen_fsm:sent_event(ISM, 'operation-sent'),
NewISMs = [{InvId, ISM}|ISMs];
_ ->
NewISMs = ISMs
end,
process_request_components(Tail, State, [Asn|AsnComps], NewISMs).
% discard components of type INVOKE for matching InvokeID
discard_inv_component(Components, InvId) when is_list(Components) ->
discard_inv_component(Components, InvId, []).
discard_inv_component([], _InvId, CompOut) ->
lists:reverse(CompOut);
discard_inv_component([Head|Tail], InvId, CompAcc) ->
#component{user_prim = Uprim} = Head,
case Uprim of
#'TC-INVOKE'{invokeID = InvId} ->
CompOut = CompAcc;
_ ->
CompOut = [Head|CompAcc]
end,
discard_inv_component(Tail, InvId, CompOut).
% iterate over list of ISMs, terminate the one with matching InvId
terminate_active_ISM(ISMs, InvId) ->
case lists:keyfind(InvId, 1, ISMs) of
{InvId, ISM} ->
gen_fsm:send_event(ISM, terminate),
lists:keydelete(InvId, 1, ISMs);
false ->
ISMs
end.
% Convert from user-visible primitive records to asn1ct-generated record
uprim_to_asn_rec(Uprim) when is_record(Uprim, 'TC-INVOKE') ->
#'Invoke'{invokeId = asn_val(Uprim#'TC-INVOKE'.invokeID),
linkedId = asn_val(Uprim#'TC-INVOKE'.linkedID),
opcode = asn_val(Uprim#'TC-INVOKE'.operation),
argument = asn_val(Uprim#'TC-INVOKE'.parameters)};
uprim_to_asn_rec(#'TC-RESULT-NL'{invokeID = InvId, operation = Op,
parameters = Params}) ->
ResRes = #'ReturnResult_result'{opcode = asn_val(Op),
result = asn_val(Params)},
#'ReturnResult'{invokeId = asn_val(InvId), result = ResRes};
uprim_to_asn_rec(#'TC-RESULT-L'{invokeID = InvId, operation = Op,
parameters = Params}) ->
ResRes = #'ReturnResult_result'{opcode = asn_val(Op),
result = asn_val(Params)},
#'ReturnResult'{invokeId = asn_val(InvId), result = ResRes};
uprim_to_asn_rec(#'TC-U-ERROR'{invokeID = InvId, error = Error,
parameters = Params}) ->
#'ReturnError'{invokeId = asn_val(InvId),
errcode = asn_val(Error),
parameter = asn_val(Params)};
uprim_to_asn_rec(#'TC-U-REJECT'{invokeID = InvId, problemCode = Pcode}) ->
#'Reject'{invokeId = InvId, problem = Pcode}.
% Convert from asn1ct-generated record to the primitive records
asn_rec_to_uprim({invoke, AsnRec}) when is_record(AsnRec, 'Invoke') ->
#'TC-INVOKE'{invokeID = AsnRec#'Invoke'.invokeId,
linkedID = AsnRec#'Invoke'.linkedId,
operation = AsnRec#'Invoke'.opcode,
parameters = AsnRec#'Invoke'.argument};
asn_rec_to_uprim({returnResultNotLast, AsnRec}) when is_record(AsnRec, 'ReturnResult') ->
#'ReturnResult_result'{opcode = Op, result = Result} = AsnRec#'ReturnResult'.result,
#'TC-RESULT-NL'{invokeID = AsnRec#'ReturnResult'.invokeId,
operation = Op,
parameters = Result};
asn_rec_to_uprim({returnResult, AsnRec}) when is_record(AsnRec, 'ReturnResult') ->
#'ReturnResult_result'{opcode = Op, result = Result} = AsnRec#'ReturnResult'.result,
#'TC-RESULT-L'{invokeID = AsnRec#'ReturnResult'.invokeId,
operation = Op,
parameters = Result};
asn_rec_to_uprim({returnError, AsnRec}) when is_record(AsnRec, 'ReturnError') ->
#'TC-U-ERROR'{invokeID = AsnRec#'ReturnError'.invokeId,
error = AsnRec#'ReturnError'.errcode,
parameters = AsnRec#'ReturnError'.parameter};
asn_rec_to_uprim({reject, AsnRec}) when is_record(AsnRec, 'Reject') ->
#'TC-U-REJECT'{invokeID = AsnRec#'Reject'.invokeId,
problemCode = AsnRec#'Reject'.problem}.
process_rx_components(_ISMs, []) ->
ok;
process_rx_components(ISMs, [Head|Tail]) ->
process_rx_component(ISMs, Head),
process_rx_components(ISMs, Tail).
process_rx_component(ISMs, C={invoke, #'Invoke'{invokeId=InvId}}) ->
{invoke, I} = C,
case I#'Invoke'.linkedId of
asn1_NOVALUE ->
ok;
Linked ->
% check if Linked ISM is in operation sent state
% FIXME
ok
end,
Prim = asn_rec_to_uprim(C),
ISM = lists:keyfind(InvId, 1, ISMs),
gem_fsm:send_event(ISM, Prim);
process_rx_component(ISMs, C={reject, #'Reject'{invokeId=InvId,
problem=Problem}}) ->
ISM = lists:keyfind(InvId, 1, ISMs),
case Problem of
{invoke, _} ->
% FIXME: ISM active (No -> Inform TC-User)
gen_fsm:send_event(ISM, terminate);
_ ->
ok
end,
% FIXME: decide on TC-U-REJECT or TC-R-REJECT
Prim = asn_rec_to_uprim(C),
ISM = lists:keyfind(InvId, 1, ISMs),
gem_fsm:send_event(ISM, Prim);
process_rx_component(ISMs, Comp) ->
% syntax error?
InvId = get_invoke_id_from_comp(Comp),
ISM = lists:keyfind(InvId, 1, ISMs),
% FIXME: ISM active (No -> 6)
Prim = asn_rec_to_uprim(Comp),
gem_fsm:send_event(ISM, Prim).
add_components_to_state(State = #state{components=CompOld}, CompNew) ->
State#state{components = CompOld ++ CompNew}.
% get the invokeId from the given asn-record component tuple.
get_invoke_id_from_comp({invoke,
#'Invoke'{invokeId = InvId}}) ->
InvId;
get_invoke_id_from_comp({returnResult,
#'ReturnResult'{invokeId = InvId}}) ->
InvId;
get_invoke_id_from_comp({returnResultNotLast,
#'ReturnResult'{invokeId = InvId}}) ->
InvId;
get_invoke_id_from_comp({returnError,
#'ReturnError'{invokeId = InvId}}) ->
InvId;
get_invoke_id_from_comp({reject,
#'Reject'{invokeId = InvId}}) ->
InvId.