| -module(map_dlg_server). |
| -author('Harald Welte <laforge@gnumonks.org>'). |
| |
| -behaviour(gen_server). |
| |
| -include_lib("TCAP/include/tcap.hrl"). |
| |
| -export([init/1, handle_cast/2, code_change/3, handle_call/3, handle_info/2, terminate/2]). |
| |
| -record(state, { |
| supervisor, |
| tcap, |
| dialogue_id, |
| app_ctx, |
| app_ctx_version, |
| app_mod, |
| ssm_tbl |
| }). |
| |
| -record(ssm_rec, { |
| invoke_id, |
| pid |
| }). |
| |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % gen_server exports |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| |
| init([Supervisor, Tcap, AppCtx, DlgId, AppMod]) -> |
| % AC Version is the last digit of the Application Context |
| ACList = tuple_to_list(AppCtx), |
| ACVersion = lists:nth(length(ACList), ACList), |
| Tbl = ets:new(ssn_table, [ordered_set, {keypos, #ssm_rec.invoke_id}]), |
| {ok, #state{tcap = Tcap, app_ctx = AppCtx, app_ctx_version = ACVersion, |
| dialogue_id = DlgId, ssm_tbl = Tbl, app_mod = AppMod, |
| supervisor = Supervisor}}. |
| |
| % Primitives from the TCAP stack (TC-user API) |
| handle_cast({'TC','BEGIN',indication, |
| #'TC-BEGIN'{dialogueID = DlgId, appContextName = AppCtx, |
| userInfo = UserInfo}}, LoopDat) -> |
| % this is basically an Assert |
| AppCtx = LoopDat#state.app_ctx, |
| DlgId = LoopDat#state.dialogue_id, |
| {noreply, LoopDat}; |
| handle_cast({'TC','INVOKE',indication, |
| I=#'TC-INVOKE'{linkedID=asn1_NOVALUE,operation={local, Op}, |
| invokeID=InvokeId}}, LoopDat) -> |
| % Invoke with no Linked ID |
| #state{app_ctx = AppCtx, app_mod = Mod} = LoopDat, |
| case Mod:get_ssm_mod(AppCtx, Op) of |
| {ok, Module} -> |
| Ssn = 2342, % FIXME |
| Name = list_to_atom("map_ssn_" ++ integer_to_list(Ssn) ++ "_" |
| ++ integer_to_list(LoopDat#state.dialogue_id) + "_" |
| ++ integer_to_list(InvokeId)), |
| Opts = [self()], |
| Mfa = {gen_fsm, start_link, [Name, Module, Opts, [{debug, [trace]}]]}, |
| ChildSpec = {Name, Mfa, temporary, 1000, worker, [Module]}, |
| {ok, Pid} = supervisor:start_child(LoopDat#state.supervisor, ChildSpec), |
| ets:insert(LoopDat#state.ssm_tbl, #ssm_rec{invoke_id = InvokeId, pid = Pid}), |
| decode_to_pid(I, Pid); |
| _ -> |
| error_logger:error_report(["TC-INVOKE for unknown Operation", |
| {operation, Op}]), |
| % FIXME |
| ok |
| end, |
| {noreply, LoopDat}; |
| handle_cast({'TC','INVOKE',indication, |
| I=#'TC-INVOKE'{linkedID = LinkedId}}, LoopDat) -> |
| % INVOKE linked to exisiting other Invoke |
| case get_ssm_by_id(LinkedId, LoopDat) of |
| {ok, Pid} -> |
| decode_to_pid(I, Pid); |
| _ -> |
| error_logger:error_report(["Linked TC-INVOKE for unknown Invoke", |
| {linked_id, LinkedId}]), |
| % FIXME |
| ok |
| end, |
| {noreply, LoopDat}; |
| handle_cast({'TC','END',indication,P}, LoopDat) -> |
| % TCAP has told us that the dialogue has ended. We simply terminate, |
| % and as the SSMs are linked to us, they will all terminate, too. |
| {stop, normal, LoopDat}; |
| handle_cast({'TC',_What,indication,P}, LoopDat) -> |
| InvokeId = tcap_user:get_invoke_id(P), |
| case get_ssm_by_id(InvokeId, LoopDat) of |
| {ok, Pid} -> |
| decode_to_pid(P, Pid); |
| _ -> |
| error_logger:error_report(["TC Primitive for unknown Invoke", |
| {prim, P}, {invoke_id, InvokeId}]), |
| % FIXME |
| ok |
| end, |
| {noreply, LoopDat}; |
| |
| % Primitives from the SSMs |
| handle_cast({result_l, InvokeId, LocalOp, Rec}, LoopDat) -> |
| case map_comp:encode_map_comp(LocalOp, result, Rec) of |
| {ok, MapEnc} -> |
| R = #'TC-RESULT-L'{dialogueID = LoopDat#state.dialogue_id, |
| invokeID = InvokeId, parameters = MapEnc}, |
| tcap_user:send_prim(LoopDat#state.tcap, {'TC','RESULT-L', request, R}), |
| ets:delete_object(LoopDat#state.ssm_tbl, InvokeId); |
| _ -> |
| error_logger:error_report(["SSM primitive couldn't be encoded", |
| {operation, LocalOp}, |
| {ssm_record, Rec}]), |
| ok |
| end, |
| {noreply, LoopDat}; |
| handle_cast({result_nl, InvokeId, LocalOp, Rec}, LoopDat) when is_record(Rec, 'TC-RESULT-NL') -> |
| case map_comp:encode_map_comp(LocalOp, result, Rec) of |
| {ok, MapEnc} -> |
| R = #'TC-RESULT-NL'{dialogueID = LoopDat#state.dialogue_id, |
| invokeID = InvokeId, parameters = MapEnc}, |
| tcap_user:send_prim(LoopDat#state.tcap, {'TC','RESULT-NL', request, R}); |
| _ -> |
| error_logger:error_report(["SSM primitive couldn't be encoded", |
| {operation, LocalOp}, |
| {ssm_record, Rec}]), |
| ok |
| end, |
| {noreply, LoopDat}; |
| handle_cast({u_error, InvokeId, Error, Rec}, LoopDat) -> |
| case map_comp:encode_map_err(Error, Rec) of |
| {ok, MapEnc} -> |
| E = #'TC-U-ERROR'{dialogueID = LoopDat#state.dialogue_id, |
| invokeID = InvokeId, error = Error, |
| parameters = MapEnc}, |
| tcap_user:send_prim(LoopDat#state.tcap, {'TC','U-ERROR', request, E}); |
| _ -> |
| error_logger:error_report(["SSM U-ERROR couldn't be encoded", |
| {error_nr, Error}, |
| {ssm_record, Rec}]), |
| ok |
| end, |
| {noreply, LoopDat}; |
| handle_cast({'end'}, LoopDat) -> |
| % User SSM has requested the dialogue to END, we issue a corresponding |
| % TC-END.req to TCAP - and as the SSMs are linked to us, they will all |
| % terminate, too. |
| E = #'TC-END'{dialogueID = LoopDat#state.dialogue_id, |
| appContextName = LoopDat#state.app_ctx}, |
| tcap_user:send_prim(LoopDat#state.tcap, {'TC','END',request,E}), |
| {stop, normal, LoopDat}; |
| handle_cast({continue}, LoopDat) -> |
| C = #'TC-CONTINUE'{dialogueID = LoopDat#state.dialogue_id, |
| appContextName = LoopDat#state.app_ctx}, |
| tcap_user:send_prim(LoopDat#state.tcap, {'TC','CONTINUE',request,C}), |
| {next_state, LoopDat}. |
| |
| handle_call(What, From, LoopDat) -> |
| error_logger:error_report(["Unknown call", {from, From}, {data, What}]), |
| {noreply, LoopDat}. |
| |
| handle_info(What, LoopDat) -> |
| error_logger:error_report(["Unknown info", {data, What}]), |
| {noreply, LoopDat}. |
| |
| code_change(_OldVsn, LoopDat, _Extra) -> |
| {ok, LoopDat}. |
| |
| terminate(_Reason, LoopDat) -> |
| ets:delete(LoopDat#state.ssm_tbl), |
| ok. |
| |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % intenral functions |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| |
| decode_to_pid(I, Pid) -> |
| % FIXME: catch exceptions here... |
| I2 = map_comp:decode_tcap_prim(I), |
| I3 = map_helper:postproc(I2, post), |
| to_pid(I3, Pid). |
| |
| to_pid(#'TC-INVOKE'{operation = {local, LocalOp}, parameters = Params}, Pid) -> |
| gen_fsm:send_event(Pid, {invoke, LocalOp, Params}); |
| to_pid(#'TC-RESULT-L'{operation = {local, LocalOp}, parameters = Params}, Pid) -> |
| gen_fsm:send_event(Pid, {result_l, LocalOp, Params}); |
| to_pid(#'TC-RESULT-NL'{operation = {local, LocalOp}, parameters = Params}, Pid) -> |
| gen_fsm:send_event(Pid, {result_nl, LocalOp, Params}); |
| to_pid(#'TC-U-ERROR'{error = {local, Err}, parameters = Params}, Pid) -> |
| gen_fsm:send_event(Pid, {u_error, Err, Params}). |
| |
| get_ssm_mod_by_op(Op, #state{app_ctx = AppCtx, app_mod = Mod}) -> |
| Mod:get_ssm_mod(AppCtx, Op). |
| |
| get_ssm_by_id(Id, LoopDat) -> |
| case ets:lookup(LoopDat#state.ssm_tbl, Id) of |
| [#ssm_rec{pid = Pid}] -> |
| {ok, Pid}; |
| _ -> |
| {error, notfound} |
| end. |