diff --git a/apps/dsm/DSMCall.cpp b/apps/dsm/DSMCall.cpp index 0580e4bc..a1e9e562 100644 --- a/apps/dsm/DSMCall.cpp +++ b/apps/dsm/DSMCall.cpp @@ -67,10 +67,16 @@ DSMCall::~DSMCall() /** returns whether var exists && var==value*/ bool DSMCall::checkVar(const string& var_name, const string& var_val) { map::iterator it = var.find(var_name); - if ((it != var.end()) && (it->second == var_val)) - return true; + return (it != var.end()) && (it->second == var_val); +} - return false; +/** returns whether params, param exists && param==value*/ +bool checkParam(const string& par_name, const string& par_val, map* params) { + if (NULL == params) + return false; + + map::iterator it = params->find(par_name); + return (it != params->end()) && (it->second == par_val); } void DSMCall::onInvite(const AmSipRequest& req) { @@ -134,7 +140,7 @@ void DSMCall::onOutgoingInvite(const string& headers) { } } -void DSMCall::onRinging(const AmSipReply& reply) { + void DSMCall::onRinging(const AmSipReply& reply) { map params; params["code"] = int2str(reply.code); params["reason"] = reply.reason; @@ -247,7 +253,71 @@ void DSMCall::onCancel() { } } +void DSMCall::onSipRequest(const AmSipRequest& req) { + + if (checkVar(DSM_ENABLE_REQUEST_EVENTS, DSM_TRUE)) { + map params; + params["method"] = req.method; + params["r_uri"] = req.r_uri; + params["from"] = req.from; + params["to"] = req.to; + params["hdrs"] = req.hdrs; + + params["content_type"] = req.content_type; + params["body"] = req.body; + + params["cseq"] = int2str(req.cseq); + + // pass AmSipRequest for use by mod_dlg + DSMSipRequest* sip_req = new DSMSipRequest(&req); + avar[DSM_AVAR_REQUEST] = AmArg(sip_req); + + engine.runEvent(this, DSMCondition::SipRequest, ¶ms); + + delete sip_req; + avar.erase(DSM_AVAR_REQUEST); + + if (checkParam(DSM_PROCESSED, DSM_TRUE, ¶ms)) { + DBG("DSM script processed SIP request '%s', returning\n", + req.method.c_str()); + return; + } + } + + AmB2BCallerSession::onSipRequest(req); +} + void DSMCall::onSipReply(const AmSipReply& reply, int old_dlg_status) { + + if (checkVar(DSM_ENABLE_REPLY_EVENTS, DSM_TRUE)) { + map params; + params["code"] = int2str(reply.code); + params["reason"] = reply.reason; + params["hdrs"] = reply.hdrs; + params["content_type"] = reply.content_type; + params["body"] = reply.body; + + params["cseq"] = int2str(reply.cseq); + + params["dlg_status"] = int2str(dlg.getStatus()); + params["old_dlg_status"] = int2str(old_dlg_status); + + // pass AmSipReply for use by mod_dlg (? sending ACK?) + DSMSipReply* dsm_reply = new DSMSipReply(&reply); + avar[DSM_AVAR_REPLY] = AmArg(dsm_reply); + + engine.runEvent(this, DSMCondition::SipReply, ¶ms); + + delete dsm_reply; + avar.erase(DSM_AVAR_REPLY); + + if (checkParam(DSM_PROCESSED, DSM_TRUE, ¶ms)) { + DBG("DSM script processed SIP reply '%u %s', returning\n", + reply.code, reply.reason.c_str()); + return; + } + } + AmB2BCallerSession::onSipReply(reply,old_dlg_status); if ((old_dlg_status < AmSipDialog::Connected) && diff --git a/apps/dsm/DSMCall.h b/apps/dsm/DSMCall.h index 1db656ce..a7794d7b 100644 --- a/apps/dsm/DSMCall.h +++ b/apps/dsm/DSMCall.h @@ -82,7 +82,9 @@ public: void onBye(const AmSipRequest& req); void onDtmf(int event, int duration_msec); + void onSipRequest(const AmSipRequest& req); void onSipReply(const AmSipReply& reply, int old_dlg_status); + void process(AmEvent* event); UACAuthCred* getCredentials(); diff --git a/apps/dsm/DSMCoreModule.cpp b/apps/dsm/DSMCoreModule.cpp index 08f8e51b..093b4d6b 100644 --- a/apps/dsm/DSMCoreModule.cpp +++ b/apps/dsm/DSMCoreModule.cpp @@ -2,6 +2,7 @@ * $Id$ * * Copyright (C) 2008 iptego GmbH + * Copyright (C) 2010 Stefan Sayer * * This file is part of SEMS, a free SIP media server. * @@ -169,6 +170,12 @@ DSMCondition* DSMCoreModule::getCondition(const string& from_str) { if (cmd == "B2B.otherBye") return new TestDSMCondition(params, DSMCondition::B2BOtherBye); + if (cmd == "sipRequest") + return new TestDSMCondition(params, DSMCondition::SipRequest); + + if (cmd == "sipReply") + return new TestDSMCondition(params, DSMCondition::SipReply); + return NULL; } @@ -459,12 +466,25 @@ EXEC_ACTION_START(SCLogAllAction) { CONST_ACTION_2P(SCSetAction,'=', false); EXEC_ACTION_START(SCSetAction) { - string var_name = (par1.length() && par1[0] == '$')? - par1.substr(1) : par1; + if (par1.length() && par1[0] == '#') { + // set param + if (NULL != event_params) { + string res = resolveVars(par2, sess, sc_sess, event_params); + (*event_params)[par1.substr(1)] = res; + DBG("set #%s='%s'\n", par1.substr(1).c_str(), res.c_str()); + } else { + DBG("not setting %s (no param set)\n", par1.c_str()); + } + } else { + // set variable + string var_name = (par1.length() && par1[0] == '$')? + par1.substr(1) : par1; - sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params); - DBG("set $%s='%s'\n", - var_name.c_str(), sc_sess->var[var_name].c_str()); + sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params); + + DBG("set $%s='%s'\n", + var_name.c_str(), sc_sess->var[var_name].c_str()); + } } EXEC_ACTION_END; string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess, @@ -511,13 +531,25 @@ string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess, } CONST_ACTION_2P(SCSetSAction,'=', false); EXEC_ACTION_START(SCSetSAction) { - string var_name = (par1.length() && par1[0] == '$')? - par1.substr(1) : par1; + if (par1.length() && par1[0] == '#') { + // set param + if (NULL != event_params) { + string res = replaceParams(par2, sess, sc_sess, event_params); + (*event_params)[par1.substr(1)] = res; + DBG("set #%s='%s'\n", par1.substr(1).c_str(), res.c_str()); + } else { + DBG("not set %s (no param set)\n", par1.c_str()); + } + } else { + // set variable + string var_name = (par1.length() && par1[0] == '$')? + par1.substr(1) : par1; - sc_sess->var[var_name] = replaceParams(par2, sess, sc_sess, event_params); - - DBG("set $%s='%s'\n", - var_name.c_str(), sc_sess->var[var_name].c_str()); + sc_sess->var[var_name] = replaceParams(par2, sess, sc_sess, event_params); + + DBG("set $%s='%s'\n", + var_name.c_str(), sc_sess->var[var_name].c_str()); + } } EXEC_ACTION_END; CONST_ACTION_2P(SCEvalAction,'=', false); diff --git a/apps/dsm/DSMModule.h b/apps/dsm/DSMModule.h index a34c25ce..3b32e0b6 100644 --- a/apps/dsm/DSMModule.h +++ b/apps/dsm/DSMModule.h @@ -53,6 +53,8 @@ class DSMModule { virtual bool onInvite(const AmSipRequest& req, DSMSession* sess) { return true; } }; +typedef map EventParamT; + typedef void* (*SCFactoryCreate)(); #define SCSTR(x) #x diff --git a/apps/dsm/DSMSession.h b/apps/dsm/DSMSession.h index f1ddc829..926398b0 100644 --- a/apps/dsm/DSMSession.h +++ b/apps/dsm/DSMSession.h @@ -40,8 +40,11 @@ using std::vector; using std::map; #include -#define DSM_REPLY_REQUEST "reply_request" // todo: rethink these names -#define DSM_REPLY_REQUEST_FALSE "0" +#define DSM_TRUE "true" +#define DSM_FALSE "false" + +#define DSM_PROCESSED "processed" +#define DSM_CONNECT "connect" #define DSM_CONNECT_SESSION "connect_session" // todo: rethink these names #define DSM_CONNECT_SESSION_FALSE "0" @@ -52,6 +55,13 @@ using std::map; #define DSM_CONNECT_EARLY_SESSION "connect_early_session" // todo: rethink these names #define DSM_CONNECT_EARLY_SESSION_FALSE "0" +#define DSM_ENABLE_REQUEST_EVENTS "enable_request_events" +#define DSM_ENABLE_REPLY_EVENTS "enable_reply_events" + + +#define DSM_AVAR_REQUEST "request" +#define DSM_AVAR_REPLY "reply" + #define DSM_ERRNO_FILE "file" #define DSM_ERRNO_UNKNOWN_ARG "arg" #define DSM_ERRNO_SCRIPT "script" @@ -74,6 +84,9 @@ using std::map; #define CLR_STRERROR \ var["strerror"] = ""; +typedef map VarMapT; +typedef map AVarMapT; + class DSMDisposable; class AmPlaylistItem; @@ -127,10 +140,10 @@ class DSMSession { virtual void releaseOwnership(DSMDisposable* d) = 0; /* holds variables which are accessed by $varname */ - map var; + VarMapT var; - /* holds AmArg variables. todo: merge var with these */ - map avar; + /* holds AmArg variables. todo(?): merge var with these */ + AVarMapT avar; /* result of the last DI call */ AmArg di_res; @@ -164,6 +177,27 @@ class DSMDisposableAudioFile ~DSMDisposableAudioFile() { } }; +class DSMSipRequest +: public ArgObject { + public: + const AmSipRequest* req; + + DSMSipRequest(const AmSipRequest* req) + : req(req) { } + ~DSMSipRequest() { } +}; + +class DSMSipReply +: public ArgObject { + public: + const AmSipReply* reply; + + DSMSipReply(const AmSipReply* reply) + : reply(reply) { } + ~DSMSipReply() { } +}; + + #define DSM_EVENT_ID -10 /** generic event for passing events between DSM sessions */ struct DSMEvent : public AmEvent { diff --git a/apps/dsm/DSMStateEngine.h b/apps/dsm/DSMStateEngine.h index c5751d25..636d6169 100644 --- a/apps/dsm/DSMStateEngine.h +++ b/apps/dsm/DSMStateEngine.h @@ -57,29 +57,33 @@ class DSMCondition public: enum EventType { Any, + Invite, SessionStart, Ringing, EarlySession, FailedCall, - Key, - Timer, - - NoAudio, + SipRequest, + SipReply, Hangup, Hold, UnHold, - XmlrpcResponse, - DSMEvent, - PlaylistSeparator, - B2BOtherReply, B2BOtherBye, + Key, + Timer, + + NoAudio, + PlaylistSeparator, + + DSMEvent, DSMException, + XmlrpcResponse, + JsonRpcResponse, JsonRpcRequest }; diff --git a/apps/dsm/mods/mod_dlg/ModDlg.cpp b/apps/dsm/mods/mod_dlg/ModDlg.cpp index 56ce9015..0f6c10a0 100644 --- a/apps/dsm/mods/mod_dlg/ModDlg.cpp +++ b/apps/dsm/mods/mod_dlg/ModDlg.cpp @@ -43,6 +43,7 @@ SC_EXPORT(MOD_CLS_NAME); MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) { DEF_CMD("dlg.reply", DLGReplyAction); + DEF_CMD("dlg.replyRequest", DLGReplyRequestAction); DEF_CMD("dlg.acceptInvite", DLGAcceptInviteAction); DEF_CMD("dlg.bye", DLGByeAction); DEF_CMD("dlg.connectCalleeRelayed", DLGConnectCalleeRelayedAction); @@ -66,29 +67,52 @@ bool DLGModule::onInvite(const AmSipRequest& req, DSMSession* sess) { return false; \ } -CONST_ACTION_2P(DLGReplyAction, ',', true); -EXEC_ACTION_START(DLGReplyAction) { +// todo: convert errors to exceptions +void replyRequest(DSMSession* sc_sess, AmSession* sess, + EventParamT* event_params, + const string& par1, const string& par2, + const AmSipRequest& req) { string code = resolveVars(par1, sess, sc_sess, event_params); string reason = resolveVars(par2, sess, sc_sess, event_params); unsigned int code_i; if (str2i(code, code_i)) { ERROR("decoding reply code '%s'\n", code.c_str()); sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG); - return false; + return; } if (!sc_sess->last_req.get()) { ERROR("no last request to reply\n"); sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL); sc_sess->SET_STRERROR("no last request to reply"); - return false; + return; } - if (sess->dlg.reply(*sc_sess->last_req.get(), code_i, reason)) { + if (sess->dlg.reply(req, code_i, reason)) { sc_sess->SET_ERRNO(DSM_ERRNO_GENERAL); sc_sess->SET_STRERROR("error sending reply"); } else sc_sess->CLR_ERRNO; +} + +CONST_ACTION_2P(DLGReplyAction, ',', true); +EXEC_ACTION_START(DLGReplyAction) { + replyRequest(sc_sess, sess, event_params, par1, par2, *sc_sess->last_req.get()); +} EXEC_ACTION_END; + +// todo (?) move replyRequest to core module (?) +CONST_ACTION_2P(DLGReplyRequestAction, ',', true); +EXEC_ACTION_START(DLGReplyRequestAction) { + DSMSipRequest* sip_req; + + AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST); + if (it == sc_sess->avar.end() || + !isArgAObject(it->second) || + !(sip_req = dynamic_cast(it->second.asObject()))) { + throw DSMException("dlg", "cause", "no request"); + } + + replyRequest(sc_sess, sess, event_params, par1, par2, *sip_req->req); } EXEC_ACTION_END; CONST_ACTION_2P(DLGAcceptInviteAction, ',', true); diff --git a/apps/dsm/mods/mod_dlg/ModDlg.h b/apps/dsm/mods/mod_dlg/ModDlg.h index 1d5df75b..512e8050 100644 --- a/apps/dsm/mods/mod_dlg/ModDlg.h +++ b/apps/dsm/mods/mod_dlg/ModDlg.h @@ -35,6 +35,7 @@ bool onInvite(const AmSipRequest& req, DSMSession* sess); DECLARE_MODULE_END; DEF_ACTION_2P(DLGReplyAction); +DEF_ACTION_2P(DLGReplyRequestAction); DEF_ACTION_2P(DLGAcceptInviteAction); DEF_ACTION_2P(DLGConnectCalleeRelayedAction); DEF_ACTION_1P(DLGByeAction); diff --git a/apps/dsm/mods/mod_dlg/Readme.mod_dlg.txt b/apps/dsm/mods/mod_dlg/Readme.mod_dlg.txt index 4fa8fbb9..7051f723 100644 --- a/apps/dsm/mods/mod_dlg/Readme.mod_dlg.txt +++ b/apps/dsm/mods/mod_dlg/Readme.mod_dlg.txt @@ -1,13 +1,22 @@ -* mod_dlg saves the initial INVITE to DSMSession::last_req +* mod_dlg saves the initial INVITE to DSMSession::last_req, + this can be processed with dlg.reply(...)/dlg.acceptInvite(...) * set connect_session to 0 with set(connect_session=0) if you want to reply with other than the standard 200 OK to initial INVITE received. +* for processing of other requests, use enable_request_events and replyRequest dlg.reply(code,reason); reply to the request in DSMSession::last_req (usually INVITE, if not yet replied) with code and reason * sets $errno (arg,general) +dlg.replyRequest(code,reason); + request processing in script; use with set($enable_request_events="true"); + reply to any request (in avar[DSM_AVAR_REQUEST]) with code and reason + * sets $errno (arg,general) + * throws exception if request not found (i.e. called from other event than + sipRequest) + dlg.acceptInvite([code, reason]); e.g. dlg.acceptInvite(183, progress); * sets $errno (arg,general) diff --git a/doc/dsm/examples/test_request_events.dsm b/doc/dsm/examples/test_request_events.dsm new file mode 100644 index 00000000..e3fb4f4b --- /dev/null +++ b/doc/dsm/examples/test_request_events.dsm @@ -0,0 +1,24 @@ +-- example for SIP request processing from script + +import(mod_dlg); + +initial state in_call + enter { + -- from now on, get events for SIP requests + set($enable_request_events="true"); + }; + +transition "SIP BYE request" in_call - sipRequest(#method==BYE) / { + dlg.replyRequest(200, "okey bye bye"); + -- set event param "processed" - BYE will not be processed by normal app logic + set(#processed=true); + logAll(1); + stop(false); + } -> end; + +transition "SIP reply" in_call - sipReply / logAll(1) -> lobby; + +-- this is not executed - BYE gets processed above +transition "bye in lobby recvd" in_call - hangup / stop(false) -> end; + +state end; \ No newline at end of file