add missing modules
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 <laforge@gnumonks.org>').
+
+-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 <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.
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 <laforge@gnumonks.org>').
+
+-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}.
+
+
+