Harald Welte | f98b8a7 | 2012-02-21 00:20:49 +0100 | [diff] [blame] | 1 | -module(map_ss_server). |
| 2 | -author('Harald Welte <laforge@gnumonks.org>'). |
| 3 | |
| 4 | -include_lib("TCAP/include/tcap.hrl"). |
| 5 | |
| 6 | -behaviour(gen_server). |
| 7 | |
| 8 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). |
| 9 | |
| 10 | -export([start_link/3, bind_ac/3, unbind_ac/3, dump/0]). |
| 11 | |
| 12 | -record(state, { |
| 13 | supervisor, |
| 14 | tcap_pid, |
| 15 | as_tbl, |
| 16 | dlg_tbl |
| 17 | }). |
| 18 | |
| 19 | -record(ass_record, { |
| 20 | ac, |
| 21 | module |
| 22 | }). |
| 23 | |
| 24 | -record(dlg_record, { |
| 25 | dialogue_id, |
| 26 | pid |
| 27 | }). |
| 28 | |
| 29 | |
| 30 | % client side |
| 31 | |
| 32 | bind_ac(Srv, AcName, Module) when is_list(AcName) -> |
| 33 | bind_ac(Srv, list_to_tuple(AcName), Module); |
| 34 | bind_ac(Srv, AcName, Module) when is_tuple(AcName) -> |
| 35 | gen_server:call(Srv, {bind_ac, AcName, Module}). |
| 36 | |
| 37 | unbind_ac(Srv, AcName, Module) when is_list(AcName) -> |
| 38 | unbind_ac(Srv, list_to_tuple(AcName), Module); |
| 39 | unbind_ac(Srv, AcName, Module) when is_tuple(AcName) -> |
| 40 | gen_server:call(Srv, {unbind_ac, AcName, Module}). |
| 41 | |
| 42 | dump() -> |
| 43 | fixme. |
| 44 | |
| 45 | |
| 46 | % gen_fsm callbacks |
| 47 | |
| 48 | start_link(Supervisor, Ssn, TCO) when is_integer(Ssn) -> |
| 49 | ProcName = list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn)), |
| 50 | gen_server:start_link({local, ProcName}, ?MODULE, [Supervisor, Ssn, TCO], []). |
| 51 | |
| 52 | init([Supervisor, Ssn, TCO]) -> |
| 53 | AssTbl = ets:new(list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn)), |
| 54 | [ordered_set, named_table, {keypos, #ass_record.ac}]), |
| 55 | DlgTbl = ets:new(list_to_atom(?MODULE ++ "_" ++ integer_to_list(Ssn) ++ "_dlg"), |
| 56 | [ordered_set, named_table, {keypos, #dlg_record.dialogue_id}]), |
| 57 | {ok, #state{supervisor = Supervisor, tcap_pid = TCO, as_tbl = AssTbl, dlg_tbl = DlgTbl}}. |
| 58 | |
| 59 | |
| 60 | handle_call({bind_ac, Ac, Module}, _From, LoopDat) -> |
| 61 | NewRec = #ass_record{ac = Ac, module = Module}, |
| 62 | case ets:insert_new(LoopDat#state.as_tbl, NewRec) of |
| 63 | false -> |
| 64 | {reply, {error, ets_insert}, LoopDat}; |
| 65 | _ -> |
| 66 | {reply, ok, LoopDat} |
| 67 | end; |
| 68 | |
| 69 | handle_call({unbind_ac, Ac, Module}, _From, LoopDat) -> |
| 70 | DelRec = #ass_record{ac = Ac, module = Module}, |
| 71 | ets:delete_object(LoopDat#state.as_tbl, DelRec), |
| 72 | {reply, ok, LoopDat}. |
| 73 | |
| 74 | handle_cast(W={'TC',_,_,_}, LoopDat) -> |
| 75 | handle_tcap(W, LoopDat); |
| 76 | handle_cast(Info, LoopDat) -> |
| 77 | error_logger:error_report(["unknown handle_cast", |
| 78 | {module, ?MODULE}, {info, Info}, |
| 79 | {state, LoopDat}]), |
| 80 | {noreply, LoopDat}. |
| 81 | |
| 82 | handle_info(Info, LoopDat) -> |
| 83 | error_logger:error_report(["unknown handle_info", |
| 84 | {module, ?MODULE}, {info, Info}, |
| 85 | {state, LoopDat}]), |
| 86 | {noreply, LoopDat}. |
| 87 | |
| 88 | terminate(Reason, _LoopDat) -> |
| 89 | io:format("terminating ~p with reason ~p~n", [self(), Reason]), |
| 90 | ok. |
| 91 | |
| 92 | code_change(_OldVsn, State, _Extra) -> |
| 93 | {ok, State}. |
| 94 | |
| 95 | % server side |
| 96 | handle_tcap({'TC','BEGIN',indication, |
| 97 | I=#'TC-BEGIN'{appContextName = Ac, dialogueID = DlgId}}, LoopDat) -> |
| 98 | case dlg_pid_for_id(DlgId, LoopDat) of |
| 99 | {ok, _Pid} -> |
| 100 | error_logger:error_report(["TC-BEGIN for existing Dialogue", |
| 101 | {dialogue_id, DlgId}]), |
| 102 | Abrt = gen_abort(I, applicationContextNotSupported), |
| 103 | tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}); |
| 104 | {error, _} -> |
| 105 | case mod_for_ac(Ac, LoopDat) of |
| 106 | {ok, Module} -> |
| 107 | Args = [LoopDat#state.tcap_pid, Ac, DlgId, Module], |
| 108 | ChildName = list_to_atom("dlg_" ++ integer_to_list(DlgId)), |
| 109 | Mfa = {gen_server, start_link, [map_dlg_server, Args, []]}, |
| 110 | ChildSpec = {ChildName, Mfa, temporary, 1000, worker, [map_dlg_server, Module]}, |
| 111 | {ok, Pid} = supervisor:start_child(LoopDat#state.supervisor, ChildSpec), |
| 112 | gen_fsm:send_event(Pid, I), |
| 113 | % FIXME: how to safely remove the Pid in all cases of dialogue termination? |
| 114 | ets:insert(LoopDat#state.dlg_tbl, #dlg_record{dialogue_id=DlgId, pid=Pid}); |
| 115 | {error, _Reason} -> |
| 116 | error_logger:error_report(["TC-BEGIN for non-existing AC", |
| 117 | {application_context, Ac}]), |
| 118 | % send a TC-U-ABORT |
| 119 | Abrt = gen_abort(I, applicationContextNotSupported), |
| 120 | tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) |
| 121 | end |
| 122 | end; |
| 123 | handle_tcap({'TC', What, indication, P}, LoopDat) when |
| 124 | What == 'CONTINUE'; What == 'END'; |
| 125 | What == 'U-ABORT'; What == 'P-ABORT'; |
| 126 | What == 'NOTICE' -> |
| 127 | DlgId = tcap_user:get_dialg_id(P), |
| 128 | % look up the Pid for the specific Dialogue Handler |
| 129 | case dlg_pid_for_id(DlgId, LoopDat) of |
| 130 | {ok, Pid} -> |
| 131 | gen_fsm:send_event(Pid, P); |
| 132 | _ -> |
| 133 | error_logger:error_report(["TC-Dialogue non-existing DialogueID", |
| 134 | {dialogue_id, DlgId}]), |
| 135 | Abrt = gen_abort(P, dialogueRefused), |
| 136 | tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) |
| 137 | end; |
| 138 | handle_tcap({'TC', What, indication, P}, LoopDat) when |
| 139 | What == 'INVOKE'; |
| 140 | What == 'RESULT-L'; What == 'RESULT-NL'; |
| 141 | What == 'U-ERROR'; What == 'L-CANCEL'; |
| 142 | What == 'L-REJECT'; What == 'U-REJECT'; |
| 143 | What == 'TIMER-RESET' -> |
| 144 | DlgId = tcap_user:get_dialg_id(P), |
| 145 | % look up the Pid for the specific Dialogue Handler |
| 146 | case dlg_pid_for_id(DlgId, LoopDat) of |
| 147 | {ok, Pid} -> |
| 148 | gen_fsm:send_event(Pid, P); |
| 149 | _ -> |
| 150 | error_logger:error_report(["TC-Component non-existing DialogueID", |
| 151 | {dialogue_id, DlgId}]), |
| 152 | Abrt = gen_abort(P, dialogueRefused), |
| 153 | tcap_user:send_prim(LoopDat#state.tcap_pid, {'TC','U-ABORT',request,Abrt}) |
| 154 | end. |
| 155 | |
| 156 | mod_for_ac(Ac, LoopDat) -> |
| 157 | case ets:lookup(LoopDat#state.as_tbl, Ac) of |
| 158 | [#ass_record{module = Module}] -> |
| 159 | {ok, Module}; |
| 160 | _ -> |
| 161 | {error, no_such_ac} |
| 162 | end. |
| 163 | |
| 164 | dlg_pid_for_id(DlgId, LoopDat) when is_record(LoopDat, state) -> |
| 165 | case ets:lookup(LoopDat#state.dlg_tbl, DlgId) of |
| 166 | [{DlgId, Pid}] -> |
| 167 | {ok, Pid}; |
| 168 | _ -> |
| 169 | {error, notfound} |
| 170 | end. |
| 171 | |
| 172 | gen_abort(#'TC-BEGIN'{qos = Qos, appContextName = AcName, dialogueID = DlgId}, |
| 173 | Reason) -> |
| 174 | #'TC-U-ABORT'{qos = Qos, appContextName = AcName, dialogueID = DlgId, |
| 175 | abortReason = Reason}. |
| 176 | |
| 177 | gen_reject(#'TC-INVOKE'{dialogueID = DlgId, invokeID = InvId}, Code) -> |
| 178 | #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}; |
| 179 | gen_reject(#'TC-RESULT-L'{dialogueID = DlgId, invokeID = InvId}, Code) -> |
| 180 | #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}; |
| 181 | gen_reject(#'TC-RESULT-NL'{dialogueID = DlgId, invokeID = InvId}, Code) -> |
| 182 | #'TC-U-REJECT'{dialogueID = DlgId, invokeID = InvId, problemCode = Code}. |
| 183 | |
| 184 | |
| 185 | |