From f98b8a744eda7cf4a2ca4a287f24ded633513358 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 21 Feb 2012 00:20:49 +0100 Subject: add missing modules --- src/map_app_sup.erl | 19 +++++ src/map_dlg_server.erl | 199 +++++++++++++++++++++++++++++++++++++++++++++++++ src/map_ss_server.erl | 185 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 403 insertions(+) create mode 100644 src/map_app_sup.erl create mode 100644 src/map_dlg_server.erl create mode 100644 src/map_ss_server.erl diff --git a/src/map_app_sup.erl b/src/map_app_sup.erl new file mode 100644 index 0000000..c0961f7 --- /dev/null +++ b/src/map_app_sup.erl @@ -0,0 +1,19 @@ +-module(map_app_sup). +-author('Harald Welte '). + +-behaviour(supervisor). + +-export([init/1, start_link/3]). + +init([{Mod, Args, Opts}, Ssn, AppMod]) -> + TcapFunc = {tcap_sap_sup, start_link, [Mod, Args, Opts]}, + TcapSpec = {tcap, TcapFunc, permanent, 4000, worker, [tcap_sap_sup, Mod]}, + % FIXME: this should go... + TcoName = list_to_atom("tcap_tco_ssn_" ++ integer_to_list(Ssn)), + SssFunc = {gen_server, start_link, [map_ss_server, [Ssn, TcoName], []]}, + SssSpec = {sss, SssFunc, permanent, 4000, worker, [map_ss_server]}, + {ok, {{one_for_one, 1, 1}, [TcapSpec, SssSpec]}}. + + +start_link(MAO, Ssn, AppMod) -> + supervisor:start_link(?MODULE, [MAO, Ssn, AppMod]). diff --git a/src/map_dlg_server.erl b/src/map_dlg_server.erl new file mode 100644 index 0000000..08b0fe4 --- /dev/null +++ b/src/map_dlg_server.erl @@ -0,0 +1,199 @@ +-module(map_dlg_server). +-author('Harald Welte '). + +-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. diff --git a/src/map_ss_server.erl b/src/map_ss_server.erl new file mode 100644 index 0000000..005cd8b --- /dev/null +++ b/src/map_ss_server.erl @@ -0,0 +1,185 @@ +-module(map_ss_server). +-author('Harald Welte '). + +-include_lib("TCAP/include/tcap.hrl"). + +-behaviour(gen_server). + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-export([start_link/3, bind_ac/3, unbind_ac/3, dump/0]). + +-record(state, { + supervisor, + tcap_pid, + as_tbl, + dlg_tbl +}). + +-record(ass_record, { + ac, + module +}). + +-record(dlg_record, { + dialogue_id, + pid +}). + + +% client side + +bind_ac(Srv, AcName, Module) when is_list(AcName) -> + bind_ac(Srv, list_to_tuple(AcName), Module); +bind_ac(Srv, AcName, Module) when is_tuple(AcName) -> + gen_server:call(Srv, {bind_ac, AcName, Module}). + +unbind_ac(Srv, AcName, Module) when is_list(AcName) -> + unbind_ac(Srv, list_to_tuple(AcName), Module); +unbind_ac(Srv, AcName, Module) when is_tuple(AcName) -> + gen_server:call(Srv, {unbind_ac, AcName, Module}). + +dump() -> + fixme. + + +% gen_fsm callbacks + +start_link(Supervisor, Ssn, TCO) when is_integer(Ssn) -> + ProcName = list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn)), + gen_server:start_link({local, ProcName}, ?MODULE, [Supervisor, Ssn, TCO], []). + +init([Supervisor, Ssn, TCO]) -> + AssTbl = ets:new(list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn)), + [ordered_set, named_table, {keypos, #ass_record.ac}]), + DlgTbl = ets:new(list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn) ++ "_dlg"), + [ordered_set, named_table, {keypos, #dlg_record.dialogue_id}]), + {ok, #state{supervisor = Supervisor, tcap_pid = TCO, as_tbl = AssTbl, dlg_tbl = DlgTbl}}. + + +handle_call({bind_ac, Ac, Module}, _From, LoopDat) -> + NewRec = #ass_record{ac = Ac, module = Module}, + case ets:insert_new(LoopDat#state.as_tbl, NewRec) of + false -> + {reply, {error, ets_insert}, LoopDat}; + _ -> + {reply, ok, LoopDat} + end; + +handle_call({unbind_ac, Ac, Module}, _From, LoopDat) -> + DelRec = #ass_record{ac = Ac, module = Module}, + ets:delete_object(LoopDat#state.as_tbl, DelRec), + {reply, ok, LoopDat}. + +handle_cast(W={'TC',_,_,_}, LoopDat) -> + handle_tcap(W, LoopDat); +handle_cast(Info, LoopDat) -> + error_logger:error_report(["unknown handle_cast", + {module, ?MODULE}, {info, Info}, + {state, LoopDat}]), + {noreply, LoopDat}. + +handle_info(Info, LoopDat) -> + error_logger:error_report(["unknown handle_info", + {module, ?MODULE}, {info, Info}, + {state, LoopDat}]), + {noreply, LoopDat}. + +terminate(Reason, _LoopDat) -> + io:format("terminating ~p with reason ~p~n", [self(), Reason]), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +% server side +handle_tcap({'TC','BEGIN',indication, + I=#'TC-BEGIN'{appContextName = Ac, dialogueID = DlgId}}, LoopDat) -> + case dlg_pid_for_id(DlgId, LoopDat) of + {ok, _Pid} -> + error_logger:error_report(["TC-BEGIN for existing Dialogue", + {dialogue_id, DlgId}]), + Abrt = gen_abort(I, applicationContextNotSupported), + tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}); + {error, _} -> + case mod_for_ac(Ac, LoopDat) of + {ok, Module} -> + Args = [LoopDat#state.tcap_pid, Ac, DlgId, Module], + ChildName = list_to_atom("dlg_" ++ integer_to_list(DlgId)), + Mfa = {gen_server, start_link, [map_dlg_server, Args, []]}, + ChildSpec = {ChildName, Mfa, temporary, 1000, worker, [map_dlg_server, Module]}, + {ok, Pid} = supervisor:start_child(LoopDat#state.supervisor, ChildSpec), + gen_fsm:send_event(Pid, I), + % FIXME: how to safely remove the Pid in all cases of dialogue termination? + ets:insert(LoopDat#state.dlg_tbl, #dlg_record{dialogue_id=DlgId, pid=Pid}); + {error, _Reason} -> + error_logger:error_report(["TC-BEGIN for non-existing AC", + {application_context, Ac}]), + % send a TC-U-ABORT + Abrt = gen_abort(I, applicationContextNotSupported), + tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) + end + end; +handle_tcap({'TC', What, indication, P}, LoopDat) when + What == 'CONTINUE'; What == 'END'; + What == 'U-ABORT'; What == 'P-ABORT'; + What == 'NOTICE' -> + DlgId = tcap_user:get_dialg_id(P), + % look up the Pid for the specific Dialogue Handler + case dlg_pid_for_id(DlgId, LoopDat) of + {ok, Pid} -> + gen_fsm:send_event(Pid, P); + _ -> + error_logger:error_report(["TC-Dialogue non-existing DialogueID", + {dialogue_id, DlgId}]), + Abrt = gen_abort(P, dialogueRefused), + tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) + end; +handle_tcap({'TC', What, indication, P}, LoopDat) when + What == 'INVOKE'; + What == 'RESULT-L'; What == 'RESULT-NL'; + What == 'U-ERROR'; What == 'L-CANCEL'; + What == 'L-REJECT'; What == 'U-REJECT'; + What == 'TIMER-RESET' -> + DlgId = tcap_user:get_dialg_id(P), + % look up the Pid for the specific Dialogue Handler + case dlg_pid_for_id(DlgId, LoopDat) of + {ok, Pid} -> + gen_fsm:send_event(Pid, P); + _ -> + error_logger:error_report(["TC-Component non-existing DialogueID", + {dialogue_id, DlgId}]), + Abrt = gen_abort(P, dialogueRefused), + tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) + end. + +mod_for_ac(Ac, LoopDat) -> + case ets:lookup(LoopDat#state.as_tbl, Ac) of + [#ass_record{module = Module}] -> + {ok, Module}; + _ -> + {error, no_such_ac} + end. + +dlg_pid_for_id(DlgId, LoopDat) when is_record(LoopDat, state) -> + case ets:lookup(LoopDat#state.dlg_tbl, DlgId) of + [{DlgId, Pid}] -> + {ok, Pid}; + _ -> + {error, notfound} + end. + +gen_abort(#'TC-BEGIN'{qos = Qos, appContextName = AcName, dialogueID = DlgId}, + Reason) -> + #'TC-U-ABORT'{qos = Qos, appContextName = AcName, dialogueID = DlgId, + abortReason = Reason}. + +gen_reject(#'TC-INVOKE'{dialogueID = DlgId, invokeID = InvId}, Code) -> + #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}; +gen_reject(#'TC-RESULT-L'{dialogueID = DlgId, invokeID = InvId}, Code) -> + #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}; +gen_reject(#'TC-RESULT-NL'{dialogueID = DlgId, invokeID = InvId}, Code) -> + #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}. + + + -- cgit v1.2.3