diff --git a/apps/dsm/DSM.cpp b/apps/dsm/DSM.cpp new file mode 100644 index 00000000..3c17c0c2 --- /dev/null +++ b/apps/dsm/DSM.cpp @@ -0,0 +1,170 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "DSM.h" +#include "AmConfig.h" +#include "AmUtils.h" +#include "AmPlugIn.h" +#include "log.h" +#include "AmConfigReader.h" +#include "DSMDialog.h" + +#include +#include + + +// session creator export +extern "C" void* session_factory_create() { + return DSMFactory::instance(); +} + +DSMFactory* DSMFactory::_instance=0; + +DSMFactory* DSMFactory::instance() +{ + if(_instance == NULL) + _instance = new DSMFactory(MOD_NAME); + return _instance; +} + +string DSMFactory::InboundStartDiag; +string DSMFactory::OutboundStartDiag; + +DSMFactory::DSMFactory(const string& _app_name) + : AmSessionFactory(_app_name), + loaded(false) +{ +} + +int DSMFactory::onLoad() +{ + if (loaded) + return 0; + loaded = true; + + AmConfigReader cfg; + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) + return -1; + + // get application specific global parameters + configureModule(cfg); + + vector prompts_files = + explode(cfg.getParameter("load_prompts"), ","); + for (vector::iterator it= + prompts_files.begin(); it != prompts_files.end(); it++) { + DBG("loading prompts from '%s'\n", it->c_str()); + std::ifstream ifs(it->c_str()); + string s; + while (ifs.good() && !ifs.eof()) { + getline(ifs, s); + if (s.length() && s.find_first_not_of(" \t")!= string::npos && + s[s.find_first_not_of(" \t")] != '#') { + vector p=explode(s, "="); + if (p.size()==2) { + prompts.setPrompt(p[0], p[1], MOD_NAME); + DBG("added prompt '%s' as '%s'\n", + p[0].c_str(), p[1].c_str()); + } + } + } + } + + string DiagPath = cfg.getParameter("diag_path"); + if (DiagPath.length() && DiagPath[DiagPath.length()-1] != '/') + DiagPath += '/'; + + string ModPath = cfg.getParameter("mod_path"); + + string LoadDiags = cfg.getParameter("load_diags"); + vector diags_names = explode(LoadDiags, ","); + for (vector::iterator it= + diags_names.begin(); it != diags_names.end(); it++) { + if (!diags.loadFile(DiagPath+*it+".dsm", *it, ModPath)) { + ERROR("loading %s from %s\n", + it->c_str(), (DiagPath+*it+".dsm").c_str()); + return -1; + } + } + + InboundStartDiag = cfg.getParameter("inbound_start_diag"); + if (InboundStartDiag.empty()) { + INFO("no 'inbound_start_diag' set in config. inbound calls disabled.\n"); + } + OutboundStartDiag = cfg.getParameter("outbound_start_diag"); + if (OutboundStartDiag.empty()) { + INFO("no 'outbound_start_diag' set in config. outbound calls disabled.\n"); + } + + return 0; +} + + +AmSession* DSMFactory::onInvite(const AmSipRequest& req) +{ + if (InboundStartDiag.empty()) { + ERROR("no inbound calls allowed\n"); + throw AmSession::Exception(488, "Not Acceptable Here"); + } + return new DSMDialog(prompts, diags, InboundStartDiag, NULL); +} + +AmSession* DSMFactory::onInvite(const AmSipRequest& req, + AmArg& session_params) +{ + if (OutboundStartDiag.empty()) { + ERROR("no outbound calls allowed\n"); + throw AmSession::Exception(488, "Not Acceptable Here"); + } + + UACAuthCred* cred = NULL; + if (session_params.getType() == AmArg::AObject) { + ArgObject* cred_obj = session_params.asObject(); + if (cred_obj) + cred = dynamic_cast(cred_obj); + } + + AmSession* s = new DSMDialog(prompts, diags, OutboundStartDiag, cred); + + if (NULL == cred) { + WARN("discarding unknown session parameters.\n"); + } else { + AmSessionEventHandlerFactory* uac_auth_f = + AmPlugIn::instance()->getFactory4Seh("uac_auth"); + if (uac_auth_f != NULL) { + DBG("UAC Auth enabled for new DSM session.\n"); + AmSessionEventHandler* h = uac_auth_f->getHandler(s); + if (h != NULL ) + s->addHandler(h); + } else { + ERROR("uac_auth interface not accessible. " + "Load uac_auth for authenticated dialout.\n"); + } + } + + return s; +} + diff --git a/apps/dsm/DSM.h b/apps/dsm/DSM.h new file mode 100644 index 00000000..9fb3e833 --- /dev/null +++ b/apps/dsm/DSM.h @@ -0,0 +1,72 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _DSM_H_ +#define _DSM_H_ + +#include "AmApi.h" + +#include "AmPromptCollection.h" + +#include "DSMStateEngine.h" +#include "DSMStateDiagramCollection.h" +#include "DSMSession.h" + + +#include +using std::string; + +#include + +/** \brief Factory for announcement sessions */ +class DSMFactory + : public AmSessionFactory +{ + AmPromptCollection prompts; + DSMStateDiagramCollection diags; + + static string InboundStartDiag; + static string OutboundStartDiag; + + static DSMFactory* _instance; + DSMFactory(const string& _app_name); + bool loaded; +public: + static DSMFactory* instance(); + + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); + AmSession* onInvite(const AmSipRequest& req, + AmArg& session_params); +}; + +#endif +// Local Variables: +// mode:C++ +// End: + diff --git a/apps/dsm/DSMChartReader.cpp b/apps/dsm/DSMChartReader.cpp new file mode 100644 index 00000000..c0dc4252 --- /dev/null +++ b/apps/dsm/DSMChartReader.cpp @@ -0,0 +1,394 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "DSMChartReader.h" +#include "log.h" + +#include // dlopen & friends + +#include +using std::vector; + +DSMChartReader::DSMChartReader() { +} + +DSMChartReader::~DSMChartReader() { +} + +bool DSMChartReader::is_wsp(const char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +bool DSMChartReader::is_snt(const char c) { + return c== ';' || c == '{' || c == '}'; +} + +string DSMChartReader::getToken(string str, size_t& pos) { + while (pos::iterator it= + mods.begin(); it!= mods.end(); it++) { + DSMAction* a = (*it)->getAction(str); + if (a) return a; + } + + return core_mod.getAction(str); +} + +DSMCondition* DSMChartReader::conditionFromToken(const string& str) { + for (vector::iterator it= + mods.begin(); it!= mods.end(); it++) { + DSMCondition* c=(*it)->getCondition(str); + if (c) return c; + } + return core_mod.getCondition(str); +} + +void splitCmd(const string& from_str, + string& cmd, string& params) { + size_t b_pos = from_str.find('('); + if (b_pos != string::npos) { + cmd = from_str.substr(0, b_pos); + params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1); + } else + cmd = from_str; +} + +bool DSMChartReader::importModule(const string& mod_cmd, const string& mod_path) { + string cmd; + string params; + + splitCmd(mod_cmd, cmd, params); + if (!params.length()) { + ERROR("import needs module name\n"); + return false; + } + + string fname = mod_path; + if (fname.length() &&fname[fname.length()-1]!= '/') + fname+='/'; + fname += params + ".so"; + + void* h_dl = dlopen(fname.c_str(),RTLD_NOW | RTLD_GLOBAL); + if(!h_dl){ + ERROR("import module: %s: %s\n",fname.c_str(),dlerror()); + return false; + } + + SCFactoryCreate fc = NULL; + if ((fc = (SCFactoryCreate)dlsym(h_dl,SC_FACTORY_EXPORT_STR)) == NULL) { + ERROR("invalid SC module '%s'\n", fname.c_str()); + return false; + } + + DSMModule* mod = (DSMModule*)fc(); + if (!mod) { + ERROR("module '%s' did not return functions.\n", + fname.c_str()); + return false; + } + mods.push_back(mod); + DBG("loaded module '%s' from '%s'\n", + params.c_str(), fname.c_str()); + return true; +} + +bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart, + const string& mod_path, DSMElemContainer* owner) { + vector stack; + size_t pos = 0; + while (pos < chart.length()) { + string token = getToken(chart, pos); + if (!token.length()) + continue; + + if (token.length()>6 && token.substr(0, 6) == "import") { + if (!importModule(token, mod_path)) { + ERROR("error loading module in '%s'\n", + token.c_str()); + return false; + } + continue; + } + + if (token == "initial") { + stack.push_back(new AttribInitial()); + continue; + } + + if (token == "state") { + stack.push_back(new State()); + continue; + } + + if (token == "transition") { + stack.push_back(new DSMTransition()); + continue; + } + + if (stack.empty()) { + if (token == ";") + continue; + ERROR("I do not understand '%s'\n", token.c_str()); + return false; + } + + DSMElement* stack_top = &(*stack.back()); + + State* state = dynamic_cast(stack_top); + if (state) { + if (!state->name.length()) { + // DBG("naming state '%s'\n", token.c_str()); + state->name = token; + continue; + } + if (token == "enter") { + stack.push_back(new ActionList(ActionList::AL_enter)); + continue; + } + if (token == "exit") { + stack.push_back(new ActionList(ActionList::AL_exit)); + continue; + } + if (token == ";") { + bool is_initial = false; + stack.pop_back(); + if (!stack.empty()) { + AttribInitial* ai = dynamic_cast(&(*stack.back())); + if (ai) { + is_initial = true; + stack.pop_back(); + delete ai; + } + } + e->addState(*state, is_initial); + delete state; + } + continue; + } + + ActionList* al = dynamic_cast(stack_top); + if (al) { + if (token == ";") { + continue; + } + if (token == "{") { + continue; + } + if ((token == "}") || (token == "->")) { + stack.pop_back(); + if (stack.empty()) { + ERROR("no item for action list\n"); + delete al; + return false; + } + + if (al->al_type == ActionList::AL_enter || + al->al_type == ActionList::AL_exit) { + State* s = dynamic_cast(&(*stack.back())); + if (!s) { + ERROR("no State for action list\n"); + delete al; + return false; + } + if (al->al_type == ActionList::AL_enter) + s->pre_actions = al->actions; + else if (al->al_type == ActionList::AL_exit) + s->post_actions = al->actions; + } else if (al->al_type == ActionList::AL_trans) { + DSMTransition* t = dynamic_cast(&(*stack.back())); + if (!t) { + ERROR("no DSMTransition for action list\n"); + delete al; + return false; + } + t->actions = al->actions; + } else { + ERROR("internal: unknown transition list type\n"); + } + delete al; + continue; + } + + // token is action + // DBG("adding action '%s'\n", token.c_str()); + DSMAction* a = actionFromToken(token); + if (!a) + return false; + owner->transferElem(a); + al->actions.push_back(a); + continue; + } + + DSMConditionList* cl = dynamic_cast(stack_top); + if (cl) { + if (token == ";") + continue; + + if ((token == "{") || (token == "}")) { + // readability + continue; + } + + if ((token == "/") || (token == "->")) { + // end of condition list + stack.pop_back(); + if (stack.empty()) { + ERROR("no transition to apply conditions to\n"); + delete cl; + return false; + } + DSMTransition* tr = dynamic_cast(&(*stack.back())); + if (!tr) { + ERROR("no transition to apply conditions to\n"); + delete cl; + return false; + } + + tr->precond = cl->conditions; + delete cl; + + // start AL_trans action list + if (token == "/") { + stack.push_back(new ActionList(ActionList::AL_trans)); + } + continue; + } + // DBG("new condition: '%s'\n", token.c_str()); + DSMCondition* c = conditionFromToken(token); + if (!c) + return false; + + owner->transferElem(c); + cl->conditions.push_back(c); + continue; + } + + DSMTransition* tr = dynamic_cast(stack_top); + if (tr) { + if (!tr->name.length()) { + tr->name = token; + continue; + } + + if (!tr->from_state.length()) { + tr->from_state = token; + continue; + } + + if (token == "-->") { + continue; + } + + if (token == "->") { + continue; + } + + if (token == "-") { + stack.push_back(new DSMConditionList()); + continue; + } + + if ((token == "-/") || (token == "/")) { + stack.push_back(new ActionList(ActionList::AL_trans)); + continue; + } + + if (token == ";") { + if (!e->addTransition(*tr)) { + delete tr; + return false; + } + delete tr; + + stack.pop_back(); + continue; + } + + if (!tr->to_state.length()) { + tr->to_state = token; + continue; + } + continue; + } + } + return true; +} + diff --git a/apps/dsm/DSMChartReader.h b/apps/dsm/DSMChartReader.h new file mode 100644 index 00000000..af8bc494 --- /dev/null +++ b/apps/dsm/DSMChartReader.h @@ -0,0 +1,96 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DSMChartReader_h_ +#define _DSMChartReader_h_ + +#include "DSMStateEngine.h" +#include "DSMCoreModule.h" +#include "DSMElemContainer.h" + +#include +using std::string; + +class NamedAction : public DSMAction { + public: + NamedAction(const string& m_name) { + name = m_name; + } + void execute(AmSession* sess) { }; +}; + +class AttribInitial : public DSMElement { + public: + AttribInitial() { } +}; + +class AttribName : public DSMElement { + public: + AttribName(const string& m_name) { name = m_name; } +}; + +class ActionList : public DSMElement { + public: + enum AL_type { + AL_enter, + AL_exit, + AL_trans + }; + + AL_type al_type; + + ActionList(AL_type al_type) + : al_type(al_type) { } + + vector actions; +}; + +struct DSMConditionList : public DSMElement { + DSMConditionList() { } + vector conditions; +}; + +class DSMChartReader { + + bool is_wsp(const char c); + bool is_snt(const char c); + + string getToken(string str, size_t& pos); + DSMAction* actionFromToken(const string& str); + DSMCondition* conditionFromToken(const string& str); + + bool importModule(const string& mod_cmd, const string& mod_path); + vector mods; + DSMCoreModule core_mod; + + public: + DSMChartReader(); + ~DSMChartReader(); + bool decode(DSMStateDiagram* e, const string& chart, + const string& mod_path, DSMElemContainer* owner); +}; + +#endif diff --git a/apps/dsm/DSMCoreModule.cpp b/apps/dsm/DSMCoreModule.cpp new file mode 100644 index 00000000..d5e48d0e --- /dev/null +++ b/apps/dsm/DSMCoreModule.cpp @@ -0,0 +1,583 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMCoreModule.h" +#include "DSMSession.h" +#include "AmSession.h" +#include "AmUtils.h" + +DSMCoreModule::DSMCoreModule() { +} + +string trim(string const& str,char const* sepSet) +{ + string::size_type const first = str.find_first_not_of(sepSet); + return ( first==string::npos ) + ? std::string() : + str.substr(first, str.find_last_not_of(sepSet)-first+1); +} + +void DSMCoreModule::splitCmd(const string& from_str, + string& cmd, string& params) { + size_t b_pos = from_str.find('('); + if (b_pos != string::npos) { + cmd = from_str.substr(0, b_pos); + params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1); + } else + cmd = from_str; +} + +DSMAction* DSMCoreModule::getAction(const string& from_str) { + string cmd; + string params; + splitCmd(from_str, cmd, params); + +#define DEF_CMD(cmd_name, class_name) \ + \ + if (cmd == cmd_name) { \ + class_name * a = \ + new class_name(params); \ + a->name = from_str; \ + return a; \ + } + + DEF_CMD("repost", SCRepostAction); + DEF_CMD("jumpFSM", SCJumpFSMAction); + DEF_CMD("callFSM", SCCallFSMAction); + DEF_CMD("returnFSM", SCReturnFSMAction); + + DEF_CMD("stop", SCStopAction); + + DEF_CMD("playPrompt", SCPlayPromptAction); + DEF_CMD("playFile", SCPlayFileAction); + DEF_CMD("recordFile", SCRecordFileAction); + DEF_CMD("stopRecord", SCStopRecordAction); + DEF_CMD("closePlaylist", SCClosePlaylistAction); + + DEF_CMD("set", SCSetAction); + DEF_CMD("append", SCAppendAction); + DEF_CMD("log", SCLogAction); + + DEF_CMD("setTimer", SCSetTimerAction); + + if (cmd == "DI") { + SCDIAction * a = new SCDIAction(params, false); + a->name = from_str; + return a; + } + + if (cmd == "DIgetResult") { + SCDIAction * a = new SCDIAction(params, true); + a->name = from_str; + return a; + } + + ERROR("could not find action named '%s'\n", cmd.c_str()); + return NULL; +} + +DSMCondition* DSMCoreModule::getCondition(const string& from_str) { + string cmd; + string params; + splitCmd(from_str, cmd, params); + + if (cmd == "keyPress") { + DSMCondition* c = new DSMCondition(); + c->name = "key pressed: " + params; + c->type = DSMCondition::Key; + c->params["key"] = params; + return c; + } + + if (cmd == "test") + return new TestDSMCondition(params, DSMCondition::Any); + + if (cmd == "keyTest") + return new TestDSMCondition(params, DSMCondition::Key); + + if (cmd == "timerTest") + return new TestDSMCondition(params, DSMCondition::Timer); + + if (cmd == "noAudioTest") + return new TestDSMCondition(params, DSMCondition::NoAudio); + + if (cmd == "hangup") + return new TestDSMCondition(params, DSMCondition::Hangup); + + ERROR("could not find condition for '%s'\n", cmd.c_str()); + return NULL; +} + + +inline string resolveVars(const string s, AmSession* sess, + DSMSession* sc_sess, map* event_params) { + if (s.length()) { + switch(s[0]) { + case '$': return sc_sess->var[s.substr(1)]; + case '#': + if (event_params) + return (*event_params)[s.substr(1)]; + else + return string(); + case '@': { + string s1 = s.substr(1); + if (s1 == "local_tag") + return sess->getLocalTag(); + else if (s1 == "user") + return sess->dlg.user; + else if (s1 == "domain") + return sess->dlg.domain; + else if (s1 == "remote_tag") + return sess->getRemoteTag(); + else if (s1 == "callid") + return sess->getCallID(); + else if (s1 == "local_uri") + return sess->dlg.local_uri; + else if (s1 == "remote_uri") + return sess->dlg.remote_uri; + else + return string(); + } + default: return trim(s, "\""); + } + } + return s; +} + +#define GET_SCSESSION() \ + DSMSession* sc_sess = dynamic_cast(sess); \ + if (!sc_sess) { \ + ERROR("wrong session type\n"); \ + return false; \ + } + + +bool SCPlayPromptAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + sc_sess->playPrompt(resolveVars(arg, sess, sc_sess, event_params)); + return false; +} + +bool SCPlayFileAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + + bool loop = + resolveVars(par2, sess, sc_sess, event_params) == "true"; + DBG("par1 = '%s', par2 = %s\n", par1.c_str(), par2.c_str()); + sc_sess->playFile(resolveVars(par1, sess, sc_sess, event_params), + loop); + return false; +} + +bool SCRecordFileAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + sc_sess->recordFile(resolveVars(arg, sess, sc_sess, event_params)); + return false; +} + +bool SCStopRecordAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + sc_sess->stopRecord(); + return false; +} + +bool SCClosePlaylistAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + bool notify = + resolveVars(arg, sess, sc_sess, event_params) == "true"; + sc_sess->closePlaylist(notify); + return false; +} + +bool SCStopAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + if (resolveVars(arg, sess, sc_sess, event_params) == "true") { + DBG("sending bye\n"); + sess->dlg.bye(); + } + sess->setStopped(); + return false; +} + +#define DEF_SCModActionExec(clsname) \ + \ + bool clsname::execute(AmSession* sess, \ + DSMCondition::EventType event, \ + map* event_params) { \ + return true; \ + } \ + +DEF_SCModActionExec(SCRepostAction); +DSMAction::SEAction SCRepostAction::getSEAction(string& param) { + return Repost; +} + +DEF_SCModActionExec(SCJumpFSMAction); +DSMAction::SEAction SCJumpFSMAction::getSEAction(string& param) { + param = arg; + return Jump; +} + +DEF_SCModActionExec(SCCallFSMAction); +DSMAction::SEAction SCCallFSMAction::getSEAction(string& param) { + param = arg; + return Call; +} + +DEF_SCModActionExec(SCReturnFSMAction); +DSMAction::SEAction SCReturnFSMAction::getSEAction(string& param) { + return Return; +} + +#undef DEF_SCModActionExec + + +CONST_TwoParAction(SCPlayFileAction, ",", true); + +CONST_TwoParAction(SCLogAction, ",", false); +bool SCLogAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + + unsigned int lvl; + if (str2i(resolveVars(par1, sess, sc_sess, event_params), lvl)) { + ERROR("unknown log level '%s'\n", par1.c_str()); + return false; + } + string l_line = resolveVars(par2, sess, sc_sess, event_params).c_str(); + _LOG((int)lvl, "FSM: %s %s\n", (par2 != l_line)?par2.c_str():"", + l_line.c_str()); + return false; +} + +CONST_TwoParAction(SCSetAction,"=", false); +bool SCSetAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + 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 variable '%s'='%s'\n", + var_name.c_str(), sc_sess->var[var_name].c_str()); + return false; +} + +CONST_TwoParAction(SCAppendAction,",", false); +bool SCAppendAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + + string var_name = (par1.length() && par1[0] == '$')? + par1.substr(1) : par1; + + sc_sess->var[var_name] += resolveVars(par2, sess, sc_sess, event_params); + + DBG("$%s now '%s'\n", + var_name.c_str(), sc_sess->var[var_name].c_str()); + return false; +} + +CONST_TwoParAction(SCSetTimerAction,",", false); +bool SCSetTimerAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + + unsigned int timerid; + if (str2i(resolveVars(par1, sess, sc_sess, event_params), timerid)) { + ERROR("timer id '%s' not decipherable\n", + resolveVars(par1, sess, sc_sess, event_params).c_str()); + return false; + } + + unsigned int timeout; + if (str2i(resolveVars(par2, sess, sc_sess, event_params), timeout)) { + ERROR("timeout value '%s' not decipherable\n", + resolveVars(par2, sess, sc_sess, event_params).c_str()); + return false; + } + + DBG("setting timer %u with timeout %u\n", timerid, timeout); + AmDynInvokeFactory* user_timer_fact = + AmPlugIn::instance()->getFactory4Di("user_timer"); + + if(!user_timer_fact) { + ERROR("load sess_timer module for timers.\n"); + return false; + } + AmDynInvoke* user_timer = user_timer_fact->getInstance(); + if(!user_timer) { + ERROR("load sess_timer module for timers.\n"); + return false; + } + + AmArg di_args,ret; + di_args.push((int)timerid); + di_args.push((int)timeout); // in seconds + di_args.push(sess->getLocalTag().c_str()); + user_timer->invoke("setTimer", di_args, ret); + return false; +} + + +// TODO: replace with real expression matching +TestDSMCondition::TestDSMCondition(const string& expr, DSMCondition::EventType evt) { + + type = evt; + + if (expr.empty()) { + ttype = Always; + return; + } + + ttype = None; + + size_t p = expr.find("=="); + size_t p2; + if (p != string::npos) { + ttype = Eq; p2 = p+2; + } else { + p = expr.find("!="); + if (p != string::npos) { + ttype = Neq; p2 = p+2; + } else { + p = expr.find("<"); + if (p != string::npos) { + ttype = Less; p2 = p+1; + } else { + p = expr.find(">"); + if (p != string::npos) { + ttype = Gt; p2 = p+1; + } else { + ERROR("expression '%s' not understood\n", + expr.c_str()); + return; + } + } + } + } + + lhs = trim(expr.substr(0, p), " "); + rhs = trim(expr.substr(p2,expr.length()-p2+1), " "); + + name = expr; +} + +bool TestDSMCondition::match(AmSession* sess, DSMCondition::EventType event, + map* event_params) { + if (ttype == None || (type != DSMCondition::Any && type != event)) + return false; + + if (ttype == Always) + return true; + + DSMSession* sc_sess = dynamic_cast(sess); + if (!sc_sess) { + ERROR("wrong session type\n"); + return false; + } + + string l; + string r; + if (lhs.length() > 5 && + (lhs.substr(0, 4) == "len(") && lhs[lhs.length()-1] == ')') { + l = int2str(resolveVars(lhs.substr(4, lhs.length()-5), sess, sc_sess, event_params).length()); + } else { + l = resolveVars(lhs, sess, sc_sess, event_params); + } + if (rhs.length() > 5 && + rhs.substr(0, 4) == "len(" && rhs[rhs.length()-1] == ')') { + r = resolveVars(rhs.substr(4, rhs.length()-5), sess, sc_sess, event_params).length(); + } else { + r = resolveVars(rhs, sess, sc_sess, event_params); + } + +// string r = resolveVars(rhs, sess, sc_sess, event_params); + + DBG("test '%s' vs '%s'\n", l.c_str(), r.c_str()); + + switch (ttype) { + case Eq: return l == r; + case Neq: return l != r; + case Less: { + char* endptr = NULL; + long l_i = strtol(l.c_str(), &endptr, 10); + if (endptr && *endptr == '\0') { + long r_i = strtol(r.c_str(), &endptr, 10); + if (endptr && *endptr == '\0') + return l_i < r_i; + } + return l < r; + } + case Gt: { + char* endptr = NULL; + long l_i = strtol(l.c_str(), &endptr, 10); + if (endptr && *endptr == '\0') { + long r_i = strtol(r.c_str(), &endptr, 10); + if (endptr && *endptr == '\0') + return l_i > r_i; + } + return l > r; + } + default: return false; + } +} + + +SCDIAction::SCDIAction(const string& arg, bool get_res) + : get_res(get_res) { + params = explode(arg,","); + if (params.size()<2) { + ERROR("DI needs at least: mod_name, " + "function_name\n"); + return; + } +} + +bool SCDIAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + + if (params.size() < 2) { + ERROR("DI needs at least: mod_name, " + "function_name (in '%s'\n", name.c_str()); + return false; + } + + vector::iterator p_it=params.begin(); + string fact_name = trim(*p_it, " \""); + AmDynInvokeFactory* fact = + AmPlugIn::instance()->getFactory4Di(fact_name); + + if(!fact) { + ERROR("load module for factory '%s'.\n", fact_name.c_str()); + return false; + } + AmDynInvoke* di_inst = fact->getInstance(); + if(!di_inst) { + ERROR("load module for factory '%s'\n", fact_name.c_str()); + return false; + } + p_it++; + + string func_name = trim(*p_it, " \""); + p_it++; + + AmArg di_args; + + while (p_it != params.end()) { + string p = trim(*p_it, " \t"); + if (p.length() && p[0] == '"') { + di_args.push(trim(p,"\"").c_str()); + } else if (p.length() > 5 && + p.substr(0, 5) =="(int)") { + p = resolveVars(p.substr(5), sess, sc_sess, event_params); + char* endptr = NULL; + long p_i = strtol(p.c_str(), &endptr, 10); + if (endptr && *endptr == '\0') { + di_args.push((int)p_i); + } else { + ERROR("converting value '%s' to int\n", + p.c_str()); + return false; + } + } else { + di_args.push(resolveVars(p, sess, sc_sess, event_params).c_str()); + } + p_it++; + } + + sc_sess->di_res.clear(); + DBG("executing DI function '%s'\n", func_name.c_str()); + try { + di_inst->invoke(func_name, di_args, sc_sess->di_res); + } catch (const AmDynInvoke::NotImplemented& ni) { + ERROR("not implemented DI function '%s'\n", + ni.what.c_str()); + return false; + } catch (const AmArg::OutOfBoundsException& oob) { + ERROR("out of bounds in DI call '%s'\n", + name.c_str()); + return false; + } catch (const AmArg::TypeMismatchException& oob) { + ERROR("type mismatch in DI call '%s'\n", + name.c_str()); + return false; + } catch (...) { + ERROR("unexpected Exception in DI call '%s'\n", + name.c_str()); + return false; + } + + if (get_res) { + // rudimentary variables conversion... + if (isArgCStr(sc_sess->di_res)) + sc_sess->var["DI_res"] = sc_sess->di_res.asCStr(); + else if (isArgInt(sc_sess->di_res)) + sc_sess->var["DI_res"] = int2str(sc_sess->di_res.asInt()); + else if (isArgArray(sc_sess->di_res)) { + // copy results to $DI_res0..$DI_resn + for (size_t i=0;idi_res.size();i++) { + switch (sc_sess->di_res.get(i).getType()) { + case AmArg::CStr: { + sc_sess->var["DI_res"+int2str(i)] = + sc_sess->di_res.get(i).asCStr(); + } break; + case AmArg::Int: { + sc_sess->var["DI_res"+int2str(i)] = + int2str(sc_sess->di_res.get(i).asInt()); + } break; + default: { + ERROR("unsupported AmArg return type!"); + } + } + } + } else { + ERROR("unsupported AmArg return type!"); + } + } + return false; +} + + diff --git a/apps/dsm/DSMCoreModule.h b/apps/dsm/DSMCoreModule.h new file mode 100644 index 00000000..d12f764a --- /dev/null +++ b/apps/dsm/DSMCoreModule.h @@ -0,0 +1,105 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DSM_CORE_MODULE_H +#define _DSM_CORE_MODULE_H +#include "DSMModule.h" +#include "DSMStateEngine.h" + +#include +using std::string; +#include +using std::map; + +class AmSession; +class DSMSession; + +class DSMCoreModule +: public DSMModule { + + void splitCmd(const string& from_str, + string& cmd, string& params); + public: + DSMCoreModule(); + + DSMAction* getAction(const string& from_str); + DSMCondition* getCondition(const string& from_str); +}; + +DEF_SCStrArgAction(SCPlayPromptAction); +DEF_SCStrArgAction(SCRecordFileAction); +DEF_SCStrArgAction(SCStopRecordAction); +DEF_SCStrArgAction(SCClosePlaylistAction); +DEF_SCStrArgAction(SCStopAction); + +DEF_SCModSEStrArgAction(SCRepostAction); +DEF_SCModSEStrArgAction(SCJumpFSMAction); +DEF_SCModSEStrArgAction(SCCallFSMAction); +DEF_SCModSEStrArgAction(SCReturnFSMAction); + + +DEF_TwoParAction(SCSetAction); +DEF_TwoParAction(SCAppendAction); +DEF_TwoParAction(SCSetTimerAction); +DEF_TwoParAction(SCLogAction); +DEF_TwoParAction(SCPlayFileAction); + +class SCDIAction +: public DSMAction { + vector params; + bool get_res; + public: + SCDIAction(const string& arg, bool get_res); + bool execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params); +}; + +// TODO: replace with real expression matching +class TestDSMCondition +: public DSMCondition { + enum CondType { + None, + Always, + Eq, + Neq, + Less, + Gt + }; + string lhs; + string rhs; + CondType ttype; + + public: + TestDSMCondition(const string& expr, DSMCondition::EventType e); + bool match(AmSession* sess, DSMCondition::EventType event, + map* event_params); +}; + +string resolveVars(const string s, AmSession* sess, + DSMSession* sc_sess, map* event_params); + +#endif diff --git a/apps/dsm/DSMDialog.cpp b/apps/dsm/DSMDialog.cpp new file mode 100644 index 00000000..1d38f87a --- /dev/null +++ b/apps/dsm/DSMDialog.cpp @@ -0,0 +1,167 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMDialog.h" +#include "AmUtils.h" + +DSMDialog::DSMDialog(AmPromptCollection& prompts, + DSMStateDiagramCollection& diags, + const string& startDiagName, + UACAuthCred* credentials) + : prompts(prompts), diags(diags), startDiagName(startDiagName), + playlist(this), cred(credentials), + rec_file(NULL) +{ + diags.addToEngine(&engine); +} + +DSMDialog::~DSMDialog() +{ + for (vector::iterator it= + audiofiles.begin();it!=audiofiles.end();it++) + delete *it; +} + +void DSMDialog::onSessionStart(const AmSipRequest& req) +{ + DBG("DSMDialog::onSessionStart\n"); + startSession(); +} + +void DSMDialog::onSessionStart(const AmSipReply& rep) +{ + DBG("DSMDialog::onSessionStart (SEMS originator mode)\n"); + startSession(); +} + +void DSMDialog::startSession(){ + engine.init(this, startDiagName); + + setReceiving(true); + + if (!getInput()) + setInput(&playlist); + + setOutput(&playlist); +} + +void DSMDialog::onDtmf(int event, int duration_msec) { + DBG("* Got DTMF key %d duration %d\n", + event, duration_msec); + + map params; + params["key"] = int2str(event); + params["duration"] = int2str(duration_msec); + engine.runEvent(this, DSMCondition::Key, ¶ms); +} + +void DSMDialog::onBye(const AmSipRequest& req) +{ + DBG("onBye\n"); + engine.runEvent(this, DSMCondition::Hangup, NULL); +} + +void DSMDialog::process(AmEvent* event) +{ + + AmAudioEvent* audio_event = dynamic_cast(event); + if(audio_event && + ((audio_event->event_id == AmAudioEvent::cleared) || + (audio_event->event_id == AmAudioEvent::noAudio))){ + // todo: run event + engine.runEvent(this, DSMCondition::NoAudio, NULL); + + return; + } + + AmPluginEvent* plugin_event = dynamic_cast(event); + if(plugin_event && plugin_event->name == "timer_timeout") { + int timer_id = plugin_event->data.get(0).asInt(); + map params; + params["id"] = int2str(timer_id); + engine.runEvent(this, DSMCondition::Timer, ¶ms); + } + + AmSession::process(event); +} + +inline UACAuthCred* DSMDialog::getCredentials() { + return cred.get(); +} + +void DSMDialog::playPrompt(const string& name) { + DBG("playing prompt '%s'\n", name.c_str()); + prompts.addToPlaylist(name, (long)this, playlist); +} + +void DSMDialog::closePlaylist(bool notify) { + DBG("close playlist\n"); + playlist.close(notify); +} + +void DSMDialog::playFile(const string& name, bool loop) { + AmAudioFile* af = new AmAudioFile(); + if(af->open(name,AmAudioFile::Read)) { + ERROR("audio file '%s' could not be opened for reading.\n", + name.c_str()); + delete af; + return; + } + if (loop) + af->loop.set(true); + + playlist.addToPlaylist(new AmPlaylistItem(af, NULL)); + audiofiles.push_back(af); +} + +void DSMDialog::recordFile(const string& name) { + if (rec_file) + stopRecord(); + + DBG("start record to '%s'\n", name.c_str()); + rec_file = new AmAudioFile(); + if(rec_file->open(name,AmAudioFile::Write)) { + ERROR("audio file '%s' could not be opened for recording.\n", + name.c_str()); + delete rec_file; + rec_file = NULL; + return; + } + setInput(rec_file); +} + +void DSMDialog::stopRecord() { + if (rec_file) { + setInput(&playlist); + rec_file->close(); + delete rec_file; + rec_file = NULL; + } else { + WARN("stopRecord: we are not recording\n"); + return; + } +} diff --git a/apps/dsm/DSMDialog.h b/apps/dsm/DSMDialog.h new file mode 100644 index 00000000..feba86b8 --- /dev/null +++ b/apps/dsm/DSMDialog.h @@ -0,0 +1,80 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DSM_DIALOG_H +#define _DSM_DIALOG_H +#include "AmSession.h" +#include "AmPromptCollection.h" + +#include "ampi/UACAuthAPI.h" + +#include "DSMSession.h" +#include "DSMStateEngine.h" +#include "DSMStateDiagramCollection.h" + +class DSMDialog : public AmSession, + public DSMSession, + public CredentialHolder +{ + std::auto_ptr cred; + + DSMStateEngine engine; + AmPromptCollection& prompts; + DSMStateDiagramCollection& diags; + string startDiagName; + AmPlaylist playlist; + + vector audiofiles; + AmAudioFile* rec_file; + +public: + DSMDialog(AmPromptCollection& prompts, + DSMStateDiagramCollection& diags, + const string& startDiagName, + UACAuthCred* credentials = NULL); + ~DSMDialog(); + + void onSessionStart(const AmSipRequest& req); + void onSessionStart(const AmSipReply& rep); + void startSession(); + void onBye(const AmSipRequest& req); + void onDtmf(int event, int duration_msec); + + void process(AmEvent* event); + + UACAuthCred* getCredentials(); + + void playPrompt(const string& name); + void closePlaylist(bool notify); + void playFile(const string& name, bool loop); + void recordFile(const string& name); + void stopRecord(); +}; + +#endif +// Local Variables: +// mode:C++ +// End: diff --git a/apps/dsm/DSMElemContainer.cpp b/apps/dsm/DSMElemContainer.cpp new file mode 100644 index 00000000..cc889a5a --- /dev/null +++ b/apps/dsm/DSMElemContainer.cpp @@ -0,0 +1,42 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMElemContainer.h" +#include "DSMStateEngine.h" + +DSMElemContainer::DSMElemContainer() { +} + +DSMElemContainer::~DSMElemContainer() { + for (vector::iterator it= + owned_elems.begin(); it != owned_elems.end(); it++) + delete *it; +} + +void DSMElemContainer::transferElem(DSMElement* elem) { + owned_elems.push_back(elem); +} diff --git a/apps/dsm/DSMElemContainer.h b/apps/dsm/DSMElemContainer.h new file mode 100644 index 00000000..42853217 --- /dev/null +++ b/apps/dsm/DSMElemContainer.h @@ -0,0 +1,43 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _SC_ELEM_CONTAINER_H +#define _SC_ELEM_CONTAINER_H + +class DSMElement; +#include +using std::vector; + +class DSMElemContainer { + vector owned_elems; + public: + DSMElemContainer(); + virtual ~DSMElemContainer(); + /** take ownership ever this element (GC) */ + void transferElem(DSMElement* elem); + +}; +#endif diff --git a/apps/dsm/DSMModule.cpp b/apps/dsm/DSMModule.cpp new file mode 100644 index 00000000..5e74e0a0 --- /dev/null +++ b/apps/dsm/DSMModule.cpp @@ -0,0 +1,35 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMModule.h" + +DSMModule::DSMModule() { +} + +DSMModule::~DSMModule() { +} + diff --git a/apps/dsm/DSMModule.h b/apps/dsm/DSMModule.h new file mode 100644 index 00000000..8c6a280a --- /dev/null +++ b/apps/dsm/DSMModule.h @@ -0,0 +1,127 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DSM_MODULE_H +#define _DSM_MODULE_H +#include "DSMStateEngine.h" + +#include +using std::string; + +// script modules interface +// factory only: it produces actions and conditions from script statements. +class DSMModule { + + public: + DSMModule(); + virtual ~DSMModule(); + + virtual DSMAction* getAction(const string& from_str) = 0; + virtual DSMCondition* getCondition(const string& from_str) = 0; +}; + +typedef void* (*SCFactoryCreate)(); + +#define SCSTR(x) #x +#define SCXSTR(x) SCSTR(x) + +#define SC_FACTORY_EXPORT sc_factory_create +#define SC_FACTORY_EXPORT_STR SCXSTR(SC_FACTORY_EXPORT) + +#if __GNUC__ < 3 +#define EXPORT_SC_FACTORY(fctname,class_name,args...) \ + extern "C" void* fctname() \ + { \ + return new class_name(##args); \ + } +#else +#define EXPORT_SC_FACTORY(fctname,class_name,...) \ + extern "C" void* fctname() \ + { \ + return new class_name(__VA_ARGS__); \ + } +#endif + +#define SC_EXPORT(class_name) \ + EXPORT_SC_FACTORY(SC_FACTORY_EXPORT,class_name) + + + +class SCStrArgAction +: public DSMAction { + protected: + string arg; + public: + SCStrArgAction(const string& arg) + : arg(arg) { } +}; + +#define DEF_SCStrArgAction(CL_Name) \ + class CL_Name \ + : public SCStrArgAction { \ + public: \ + CL_Name(const string& arg) : SCStrArgAction(arg) { } \ + bool execute(AmSession* sess, \ + DSMCondition::EventType event, \ + map* event_params); \ + }; \ + + +#define DEF_SCModSEStrArgAction(CL_Name) \ + class CL_Name \ + : public SCStrArgAction { \ + public: \ + CL_Name(const string& arg) : SCStrArgAction(arg) { } \ + bool execute(AmSession* sess, \ + DSMCondition::EventType event, \ + map* event_params); \ + SEAction getSEAction(std::string&); \ + }; \ + +#define DEF_TwoParAction(CL_Name) \ + class CL_Name \ + : public DSMAction { \ + string par1; \ + string par2; \ + public: \ + CL_Name(const string& arg); \ + bool execute(AmSession* sess, \ + DSMCondition::EventType event, \ + map* event_params); \ + }; \ + +#define CONST_TwoParAction(CL_name, sep, optional) \ + CL_name::CL_name(const string& arg) { \ + vector args = explode(arg,sep); \ + if (!optional && args.size()!=2) { \ + ERROR("expression '%s' not valid\n", arg.c_str()); \ + return; \ + } \ + par1 = args.size()?trim(args[0], " \t"):""; \ + par2 = args.size()>1?trim(args[1], " \t"):""; \ + } + +#endif diff --git a/apps/dsm/DSMSession.cpp b/apps/dsm/DSMSession.cpp new file mode 100644 index 00000000..d5ec7b79 --- /dev/null +++ b/apps/dsm/DSMSession.cpp @@ -0,0 +1,35 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMSession.h" + +DSMSession::DSMSession() { +} + +DSMSession::~DSMSession() { +} + diff --git a/apps/dsm/DSMSession.h b/apps/dsm/DSMSession.h new file mode 100644 index 00000000..a8c34065 --- /dev/null +++ b/apps/dsm/DSMSession.h @@ -0,0 +1,58 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _DSM_SESSION_H +#define _DSM_SESSION_H + +#include + +#include +using std::string; +#include +using std::vector; +#include +using std::map; + +class DSMSession { + + public: + DSMSession(); + virtual ~DSMSession(); + + virtual void playPrompt(const string& name) = 0; + virtual void playFile(const string& name, bool loop) = 0; + virtual void recordFile(const string& name) = 0; + virtual void stopRecord() = 0; + virtual void closePlaylist(bool notify) = 0; + + /* holds variables which are accessed by $varname */ + map var; + + /* result of the last DI call */ + AmArg di_res; +}; + +#endif diff --git a/apps/dsm/DSMStateDiagramCollection.cpp b/apps/dsm/DSMStateDiagramCollection.cpp new file mode 100644 index 00000000..05440584 --- /dev/null +++ b/apps/dsm/DSMStateDiagramCollection.cpp @@ -0,0 +1,77 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "DSMStateDiagramCollection.h" + +#include "DSMChartReader.h" +#include +using std::ifstream; + +DSMStateDiagramCollection::DSMStateDiagramCollection() { +} + +DSMStateDiagramCollection::~DSMStateDiagramCollection() { +} + +bool DSMStateDiagramCollection::loadFile(const string& filename, const string& name, + const string& mod_path) { + DSMChartReader cr; + + ifstream ifs(filename.c_str()); + if (!ifs.good()) { + ERROR("loading state diagram '%s'\n", + filename.c_str()); + return false; + } + + diags.push_back(DSMStateDiagram(name)); + string s; + while (ifs.good() && !ifs.eof()) { + string r; + getline(ifs, r); + // skip comments + size_t fpos = r.find_first_not_of(" \t"); + if (fpos != string::npos && + r.length() > fpos+1 && + r.substr(fpos, 2) == "--") + continue; + + s += r + "\n"; + } + DBG("dsm text\n------------------\n%s\n------------------\n", s.c_str()); + if (!cr.decode(&diags.back(), s, mod_path, this)) { + ERROR("DonkeySM decode script error!\n"); + return false; + } + return true; +} + +void DSMStateDiagramCollection::addToEngine(DSMStateEngine* e) { + DBG("adding %zd diags to engine\n", diags.size()); + for (vector ::iterator it = + diags.begin(); it != diags.end(); it++) + e->addDiagram(&(*it)); +} diff --git a/apps/dsm/DSMStateDiagramCollection.h b/apps/dsm/DSMStateDiagramCollection.h new file mode 100644 index 00000000..d8d05520 --- /dev/null +++ b/apps/dsm/DSMStateDiagramCollection.h @@ -0,0 +1,48 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _StateDiagramReader_H +#define _StateDiagramReader_H + +#include "DSMStateEngine.h" +#include +using std::string; + + +class DSMStateDiagramCollection +: public DSMElemContainer +{ + vector diags; + + public: + DSMStateDiagramCollection(); + ~DSMStateDiagramCollection(); + + bool loadFile(const string& filename, const string& name, const string& mod_path); + void addToEngine(DSMStateEngine* e); +}; + +#endif diff --git a/apps/dsm/DSMStateEngine.cpp b/apps/dsm/DSMStateEngine.cpp new file mode 100644 index 00000000..69cb85f6 --- /dev/null +++ b/apps/dsm/DSMStateEngine.cpp @@ -0,0 +1,334 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "DSMStateEngine.h" +#include "AmSession.h" +#include "log.h" + + +DSMStateDiagram::DSMStateDiagram(const string& name) + : name(name) { +} + +DSMStateDiagram::~DSMStateDiagram() { +} + +void DSMStateDiagram::addState(const State& state, bool is_initial) { + DBG("adding state '%s'\n", state.name.c_str()); + for (vector::const_iterator it= + state.pre_actions.begin(); it != state.pre_actions.end(); it++) { + DBG(" pre-action '%s'\n", (*it)->name.c_str()); + } + for (vector::const_iterator it= + state.post_actions.begin(); it != state.post_actions.end(); it++) { + DBG(" post-action '%s'\n", (*it)->name.c_str()); + } + + states.push_back(state); + if (is_initial) { + if (!initial_state.empty()) { + ERROR("trying to override initial state '%s' with '%s'\n", + initial_state.c_str(), state.name.c_str()); + } else { + initial_state = state.name; + DBG("set initial state '%s'\n", state.name.c_str()); + } + } +} + +bool DSMStateDiagram::addTransition(const DSMTransition& trans) { + DBG("adding Transition '%s' %s -()-> %s\n", + trans.name.c_str(), trans.from_state.c_str(), trans.to_state.c_str()); + for (vector::const_iterator it= + trans.precond.begin(); it != trans.precond.end(); it++) { + DBG(" DSMCondition '%s'\n", (*it)->name.c_str()); + } + for (vector::const_iterator it= + trans.actions.begin(); it != trans.actions.end(); it++) { + DBG(" Action '%s'\n", (*it)->name.c_str()); + } + + State* source_st = getState(trans.from_state); + if (!source_st) { + ERROR("state '%s' for transition '%s' not found\n", + trans.from_state.c_str(), trans.name.c_str()); + return false; + } + + source_st->transitions.push_back(trans); + return true; +} + +State* DSMStateDiagram::getState(const string& s_name) { + // find target state + for (vector::iterator target_st = + states.begin(); target_st != states.end(); target_st++) { + if (target_st->name == s_name) + return &(*target_st); + } + + return NULL; +} + +State* DSMStateDiagram::getInitialState() { + if (initial_state.empty()) { + ERROR("diag '%s' doesn't have an initial state!\n", + name.c_str()); + return false; + } + return getState(initial_state); +} + + +DSMStateEngine::DSMStateEngine() + : current(NULL) { +} + +DSMStateEngine::~DSMStateEngine() { + +} + +bool DSMStateEngine::runactions(vector::iterator from, + vector::iterator to, + AmSession* sess, DSMCondition::EventType event, + map* event_params, bool& is_consumed) { +// DBG("running %zd actions\n", to - from); + for (vector::iterator it=from; it != to; it++) { + DBG("executing '%s'\n", (*it)->name.c_str()); + if ((*it)->execute(sess, event, event_params)) { + string se_modifier; + switch ((*it)->getSEAction(se_modifier)) { + case DSMAction::Repost: + is_consumed = false; + break; + case DSMAction::Jump: + DBG("jumping %s\n", se_modifier.c_str()); + if (jumpDiag(se_modifier, sess, event, event_params)) { + // is_consumed = false; + return true; + } break; + case DSMAction::Call: + if (callDiag(se_modifier, sess, event, event_params)) { + // is_consumed = false; + return true; + } break; + case DSMAction::Return: + if (returnDiag(sess)) { + //is_consumed = false; + return true; + } break; + default: break; + } + } + } + + return false; +} + +void DSMStateEngine::addDiagram(DSMStateDiagram* diag) { + diags.push_back(diag); +} + +bool DSMStateEngine::init(AmSession* sess, const string& startDiagram) { + + if (!jumpDiag(startDiagram, sess, DSMCondition::Any, NULL)) { + ERROR("initializing with start diag '%s'\n", + startDiagram.c_str()); + return false; + } + + DBG("run null event...\n"); + runEvent(sess, DSMCondition::Any, NULL); + return true; +} + +bool DSMCondition::match(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + + if ((type != Any) && (event != type)) + return false; + + if (!event_params) + return true; + + for (map::iterator it=params.begin(); + it!=params.end(); it++) { + map::iterator val = event_params->find(it->first); + if (val == event_params->end() || val->second != it->second) + return false; + } + + DBG("condition matched: '%s'\n", name.c_str()); + return true; +} + +void DSMStateEngine::runEvent(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + if (!current || !current_diag) + return; + + bool is_consumed = true; + do { + is_consumed = true; + + for (vector::iterator tr = current->transitions.begin(); + tr != current->transitions.end();tr++) { + DBG("checking transition '%s'\n", tr->name.c_str()); + + vector::iterator con=tr->precond.begin(); + while (con!=tr->precond.end()) { + if (!(*con)->match(sess, event, event_params)) + break; + con++; + } + if (con == tr->precond.end()) { + DBG("transition '%s' matched.\n", tr->name.c_str()); + + // matched all preconditions + // find target state + State* target_st = current_diag->getState(tr->to_state); + if (!target_st) { + ERROR("script writer error: transition '%s' from " + "state '%s' to unknown state '%s'\n", + tr->name.c_str(), + current->name.c_str(), + tr->to_state.c_str()); + } + + // run post-actions + if (current->post_actions.size()) { + DBG("running %zd post_actions of state '%s'\n", + current->post_actions.size(), current->name.c_str()); + if (runactions(current->post_actions.begin(), + current->post_actions.end(), + sess, event, event_params, is_consumed)) { + break; + } + } + + // run transition actions + if (tr->actions.size()) { + DBG("running %zd actions of transition '%s'\n", + tr->actions.size(), tr->name.c_str()); + if (runactions(tr->actions.begin(), + tr->actions.end(), + sess, event, event_params, is_consumed)) { + break; + } + } + + // go into new state + DBG("changing to new state '%s'\n", target_st->name.c_str()); + current = target_st; + + // execute pre-actions + if (current->pre_actions.size()) { + DBG("running %zd pre_actions of state '%s'\n", + current->pre_actions.size(), current->name.c_str()); + if (runactions(current->pre_actions.begin(), + current->pre_actions.end(), + sess, event, event_params, is_consumed)) { + break; + } + } + + break; + } + } + } while (!is_consumed); +} + +bool DSMStateEngine::callDiag(const string& diag_name, AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + if (!current || !current_diag) { + ERROR("no current diag to push\n"); + return false; + } + stack.push_back(std::make_pair(current_diag, current)); + return jumpDiag(diag_name, sess, event, event_params); +} + +bool DSMStateEngine::jumpDiag(const string& diag_name, AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + for (vector::iterator it= + diags.begin(); it != diags.end(); it++) { + if ((*it)->getName() == diag_name) { + current_diag = *it; + current = current_diag->getInitialState(); + if (!current) { + ERROR("diag '%s' does not have initial state.\n", + diag_name.c_str()); + return false; + } + + // execute pre-actions + DBG("running %zd pre_actions of init state '%s'\n", + current->pre_actions.size(), current->name.c_str()); + + bool is_finished; + is_finished = true; + runactions(current->pre_actions.begin(), + current->pre_actions.end(), + sess, event, event_params, is_finished); + + return true; + } + } + ERROR("diag '%s' not found.\n", diag_name.c_str()); + return false; +} + +bool DSMStateEngine::returnDiag(AmSession* sess) { + if (stack.empty()) { + ERROR("returning from empty stack\n"); + return false; + } + current_diag = stack.back().first; + current = stack.back().second; + stack.pop_back(); + DBG("returned to diag '%s' state '%s'\n", + current_diag->getName().c_str(), + current->name.c_str()); + + return true; +} + +State::State() { +} + +State::~State() { +} + +DSMTransition::DSMTransition(){ +} + +DSMTransition::~DSMTransition(){ +} diff --git a/apps/dsm/DSMStateEngine.h b/apps/dsm/DSMStateEngine.h new file mode 100644 index 00000000..7be48344 --- /dev/null +++ b/apps/dsm/DSMStateEngine.h @@ -0,0 +1,173 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _STATE_ENGINE_H +#define _STATE_ENGINE_H + +#include "DSMElemContainer.h" + +class AmSession; +#include +using std::map; +#include +using std::vector; +#include +using std::string; + +using std::pair; + +#include "log.h" + +class DSMElement { + public: + DSMElement() { } + virtual ~DSMElement() { } + string name; // documentary only + +}; + +class DSMCondition + : public DSMElement { + public: + enum EventType { + Any, + Key, + Timer, + + NoAudio, + + Hangup, + Hold, + UnHold, + + XmlrpcResponse + }; + + DSMCondition() { } + virtual ~DSMCondition() { } + + EventType type; + map params; + + virtual bool match(AmSession* sess, DSMCondition::EventType event, + map* event_params); +}; + +class DSMAction +: public DSMElement { + public: + /** modifies State Engine operation */ + enum SEAction { + None, // no modification + Repost, // repost current event + Jump, // jump FSM + Call, // call FSM + Return // return from FSM call + }; + + DSMAction() { /* DBG("const action\n"); */ } + virtual ~DSMAction() { /* DBG("dest action\n"); */ } + + /** @return whether state engine is to be modified (via getSEAction) */ + virtual bool execute(AmSession* sess, DSMCondition::EventType event, \ + map* event_params) = 0; + + /** @return state engine modification */ + virtual SEAction getSEAction(string& param) { return None; } +}; + +class DSMTransition; + +class State +: public DSMElement { + public: + State(); + ~State(); + vector pre_actions; + vector post_actions; + + vector transitions; +}; + +class DSMTransition +: public DSMElement { + public: + DSMTransition(); + ~DSMTransition(); + + vector precond; + vector actions; + string from_state; + string to_state; +}; + +class DSMStateDiagram { + vector states; + string name; + string initial_state; + + public: + DSMStateDiagram(const string& name); + ~DSMStateDiagram(); + + State* getInitialState(); + State* getState(const string& s_name); + + void addState(const State& state, bool is_initial = false); + bool addTransition(const DSMTransition& trans); + const string& getName() { return name; } +}; + +class DSMStateEngine { + State* current; + DSMStateDiagram* current_diag; + vector diags; + + vector > stack; + + bool callDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event, + map* event_params); + bool jumpDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event, + map* event_params); + bool returnDiag(AmSession* sess); + bool runactions(vector::iterator from, + vector::iterator to, + AmSession* sess, DSMCondition::EventType event, + map* event_params, bool& is_consumed); + public: + DSMStateEngine(); + ~DSMStateEngine(); + + void addDiagram(DSMStateDiagram* diag); + bool init(AmSession* sess, const string& startDiagram); + + void runEvent(AmSession* sess, + DSMCondition::EventType event, + map* event_params); +}; + + +#endif diff --git a/apps/dsm/Makefile b/apps/dsm/Makefile new file mode 100644 index 00000000..66968e44 --- /dev/null +++ b/apps/dsm/Makefile @@ -0,0 +1,9 @@ +plug_in_name = dsm + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" + +extra_install = $(plug_in_name)_audio + +COREPATH ?=../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/dsm/doc/Readme.dsm.txt b/apps/dsm/doc/Readme.dsm.txt new file mode 100644 index 00000000..7fcc652a --- /dev/null +++ b/apps/dsm/doc/Readme.dsm.txt @@ -0,0 +1,139 @@ + +The DonkeySM - state machines as SEMS applications + +DonkeySM is a state machine interpreter for SEMS. Application +or service logic can comfortably and accurately be defined +as state machine, in a simple textual state machine definition +language, and executed by the dsm module as application in SEMS. + +A DSM consists of states and transitions between the states. +One state is the initial state with which the DSM is started. +The transitions have conditions. If something happens, +i.e. the session processes an event, the transitions are checked +in order. If all conditions match, the transition is executed. +Transitions can contain actions, which are run when the transition +is executed. States can have actions that are executed on entering +the state, and on leaving the state. If a transition is executed, +first the leaving actions of the old state, then the transition +actions, and finally the entering actions of the new state are +executed. + +DSMs can be defined in a hierarchical manner: Another DSM can be +called as sub-DSM, or one can jump to another DSM, ignoring where +we came from. + +A session (call) in the DonkeySM has a set of named (string) variables. +The variables may be used as parameter to most conditions and +actions, by prepending the variable name with a dollar sign. The +parameters of an event (e.g. the key on key press) may be accessed +by prepending the name with a hash. There are also 'selects' with +which a set of dialog properties can be accessed (e.g. @local_tag). + +The DonkeySM can be extended by modules, which add new conditions +and actions to the language. This way, menuing system etc can be +implemented as DSM, while complex logic or processing can efficitently +be implemented in C++. DonkeySM also has built in actions to call +DI methods from other modules. + +It can cache a set of prompts, configured at start, in memory +using PromptCollection. + +A patch for fmsc 1.0.4 from the graphical FSM editor fsme +(http://fsme.sf.net) is available, so DSMs can be defined in +click-n-drag fashion and compiled to SEMS DSM diagrams. + +More info +========= + o doc/dsm_syntax.txt has a quick reference for dsm syntax + o doc/examples/ and lib/ some example DSMs + o mods/ (will) have modules + +Internals +========= +The DSMStateEngine has a set of DSM diagrams which are loaded by +the DSMStateDiagramCollection from text file and interpreted by +the DSMChartReader, a simple stack based tokenizing compiler. + +DSMDialogs, which implement the DSMSession interface (additionally +to being an AmSession), run DSMStateEngine::runEvent for every event +that occurs that should be processed by the engine (e.g. Audio event, +onBye, ...). + +The DSMStateEngine checks every condition of the active state whether +it matches. If all match, the exit actions of the current state, the +transition actions and then the enter actions of the next state are +executed. The DSMCondition::match and DSMAction::execute functions +get the event parameters and the session as parameters, so that they +can operate on variables, implement selects etc. + +The DSMDialog implementation is very simple, it uses a playlist and +has PromptCollection to simply play prompts etc. + +DSMCoreModule is a 'built in' module that implements the basic +conditions (test(), hangup() etc) and actions (set(), playFile(), DI +etc). + +Roadmap +======= + +On the roadmap is the possibility for modules to be the session factory +used for creating the new session. As the DSMSession is mostly an abstract +interface, other session types can easily be implemented, and their +functionality be exposed to the DSM interpreter by custom actions and +conditions that interact with that specific session type. + +Another direction is python/lua/... interpreter module, so that +conditions and actions can be expressed in a more powerful language. + +A set of modules exposing more of the core functionality. + +As the call state representation is nicely encapsulated here, this can +also provide an abstraction layer on which active call replication can +be implemented (rather than re-doing that for every application). + +Q&A +=== + +Why "Donkey" SM? + Thanks Atle for the name: "I dont know why.. but my first thought + when I read good name for FSM interpreter was Donkey. The reason for the + name is that you put alot of things ontop of a donkey, and let it carry + it arround.. and this is the same.. you put loads of stuf ontop.. and it + carries it." + +What is repost()? + If an event should be reevaluated, e.g. by the transitions of another + state or the initial state of another DSM which is called with callFSM/ + jumpFSM, repost() can be called, which signals the interpreter to + evaluate the current event again. + +Is the diagram interpreted or compiled? + DonkeySM reads the DSM script and creates an internal representation + (DSM classes in STL containers). This way it can be executed very + efficiently. + +map var; - Are you crazy? E stands for Express! + yes, right, there would be more efficient ways to implement + that. Anyway, in my experience one mostly has to manipulate and + check strings, and it is just very comfortable to do it this way. + OTOH, if in a normal call there is a transition maybe on average + every 10 seconds, for which 5 conditions are checked, it is not + so much an impact on performance, considering that we are processing + an audio packet every 20 ms. + +You rely too heavily on polymorphism and RTTI - one dynamic_cast for +each condition of each transition is way too heavy! + Sure, but as noted above, there should not be heavy processing done + in the DSM. If you need this, then consider writing your app entirely + in C++. + +SEMS has a dynamically typed type (AmArg), why not use that one for +variables? That would also make DI simpler. + a patch is very welcome, best to semsdev list: semsdev@iptel.org or + the tracker: http://tracker.iptel.org + +some performance numbers? + unfortunately not yet for running DSMs. DSM processing is actually fast: + a (quite simple) 3 state 5 transition DSM compiles in 0.2ms on P-M 2GHz. + So the diagram could actually be read when the call is setup, or DSMs could + load other DSMs (e.g. loadFSM() action) diff --git a/apps/dsm/doc/dsm_syntax.txt b/apps/dsm/doc/dsm_syntax.txt new file mode 100644 index 00000000..a7ec1c0b --- /dev/null +++ b/apps/dsm/doc/dsm_syntax.txt @@ -0,0 +1,102 @@ +============================= +-- comment +import(mod_name); + +[initial] state name + [ enter { + action; + action; + ... + } ] + [ exit { + action; + action; + ... + } ] + ; + +transition name s1 - [ { condition; condition; ... } ] [/ { action; action; ...} ] -> s2; + +============================= +#paramname uses the event parameter 'paramname' (from current event) +$varname uses the variable varname (from session's variable map) +@selectname uses the "select" 'selectname' (from the session's dialog) +============================= +actions: + -- reprocess the current event after transition: + repost() + + -- call/jump/return sub-FSM + jumpFSM(name) + callFSM(name) + returnFSM() + + playPrompt(param) + from promptCollection, e.g. playPrompt("hello"); + playFile(filename) + recordFile(filename) + stopRecord() + closePlaylist + + set($var, value) + e.g. set($var, "text"); set($var, $var2); set($var, #key) + append($var, value) + e.g. append($var, "text"); append($var, #key); + append($var, @select); append($var, $var2); + + log(level, text) + e.g. log(1, $var1) + + setTimer(timer_id, timeout) + e.g. setTimer(1, $timeout) + + DI(factory, function [, params...]) + e.g. DI(factory, function, $var_param, (int)int_param, "$str param", @select_par, ...) + DI(user_timer, setTimer, (int)1, (int)5, @local_tag); + DIgetResult(factory, function, param,...) + saves result from DI call to DI_res or DI_res0, DI_res1, ... + + stop() + e.g. stop(false), stop(true) +============================= +conditions: + + test(#key == 1) + test(#key != 1) + test(#key < 1) + test(#key > 1) + + test($var == 1) + test($var != 1) + test($var < 1) + test($var < 1) + + test(len($var) < len(#key)) + test(len($var) < len(@user)) + + -- like test(expr), but only on key press + keyTest(expr) + timerTest(expr) + noAudioTest(expr) + + keyPress(no) + -- bye received: + hangup + + +============================= +@selects : + local_tag + user + domain + remote_tag + callid + local_uri + remote_uri +============================= +imports: + module imported with import(mod_name); loads mod_name.so in + module load path. modules provide conditions and actions. + modules' actions/conditions-factory is checked first + (modules can override core conditions/actions) + diff --git a/apps/dsm/doc/examples/call/callsub.dsm b/apps/dsm/doc/examples/call/callsub.dsm new file mode 100644 index 00000000..91bbce65 --- /dev/null +++ b/apps/dsm/doc/examples/call/callsub.dsm @@ -0,0 +1,5 @@ +initial state START + enter { + log(3, $pin); + returnFSM(); + }; diff --git a/apps/dsm/doc/examples/call/calltest.dsm b/apps/dsm/doc/examples/call/calltest.dsm new file mode 100644 index 00000000..b2a8e340 --- /dev/null +++ b/apps/dsm/doc/examples/call/calltest.dsm @@ -0,0 +1,22 @@ + +initial state START + enter { + playPrompt(hello); + }; + +state ADDKEY; +state FIN; + +transition "start pin entry" START - / set($pin = "") -> ADDKEY; + +transition "add a key" ADDKEY - keyTest(#key < 10) / + { + closePlaylist(); + append($pin, #key); + playPrompt(#key); + callFSM(callsub) + } -> ADDKEY; + + +transition bye_recvd ADDKEY - hangup() / stop(false) -> FIN; + diff --git a/apps/dsm/doc/examples/pin/main.dsm b/apps/dsm/doc/examples/pin/main.dsm new file mode 100644 index 00000000..f98a8326 --- /dev/null +++ b/apps/dsm/doc/examples/pin/main.dsm @@ -0,0 +1,76 @@ + +initial state START + enter { + playPrompt(hello); + -- test only: should go in onLoad() code, + -- otherwise one connection per call + DI(xmlrpc2di, newConnection, vbs, 127.0.0.1,(int)8302, ""); + }; + +state ADDKEY; +state PLAY_FIN; + +state BYE + enter { + stop(true); + }; + +state FIN + enter { + stop(false); + }; + +transition bye_after_fin PLAY_FIN - noAudioTest(1 == 1) -> BYE; + + +state TESTPINRESULT + enter { + log(3, $validPin) + log(3, $tryAgain) + }; + +transition "start pin entry" START - / set($pin = "") -> ADDKEY; + +transition "add a key" ADDKEY - keyTest(#key < 10) / + { + closePlaylist(); + append($pin, #key); + playPrompt(#key); + } -> ADDKEY; + +transition bye_recvd ADDKEY - hangup() -> FIN; +transition bye_recvd PLAY_FIN - hangup() -> FIN; + +transition check_pin_server ADDKEY - keyTest(#key > 9) / { + closePlaylist(); + playPrompt(entering); + DIgetResult(xmlrpc2di, sendRequestList, vbs, checkPIN, @user, $pin); + set($status= $DI_res0); + set($description= $DI_res1); + set($domt= $DI_res2); + set($imguid= $DI_res3); + set($inid= $DI_res4); + set($vbsSessionId=$DI_res5); + set($validPin= $DI_res6); + set($tryAgain= $DI_res7); + set($participantType=$DI_res8); + set($inProgress= $DI_res9); + set($registered= $DI_res10); + set($startTime= $DI_res11); + repost(); + } -> TESTPINRESULT; + +transition incorrect_retry TESTPINRESULT - + test($validPin == 0); test($tryAgain == 1) + / { + set($pin=""); + playPrompt(retry_pin); + } -> ADDKEY; + +transition incorrect_no_retry TESTPINRESULT - + test($validPin == 0); test($tryAgain == 0) + / { + playPrompt(no_retry_pin); + } -> PLAY_FIN; + +transition correct_pin TESTPINRESULT - test($validPin == 0); / jumpFSM(ok_pin) -> PLAY_FIN; diff --git a/apps/dsm/doc/examples/pin/ok_pin.dsm b/apps/dsm/doc/examples/pin/ok_pin.dsm new file mode 100644 index 00000000..a8e3ea0a --- /dev/null +++ b/apps/dsm/doc/examples/pin/ok_pin.dsm @@ -0,0 +1,9 @@ +-- this is a commend + -- this is a comment + + -- this is a comment + +initial state START + enter { + playPrompt(ok_pin); + }; diff --git a/apps/dsm/doc/examples/test.dsm b/apps/dsm/doc/examples/test.dsm new file mode 100644 index 00000000..3e88ceef --- /dev/null +++ b/apps/dsm/doc/examples/test.dsm @@ -0,0 +1,32 @@ + +initial state START + enter { + playPrompt(hello); + DI(user_timer, setTimer, (int)1, (int)5, @local_tag); + DI(user_timer, setTimer, (int)2, (int)20, @local_tag); + recordFile(/tmp/test_rec.wav); + }; + +state ADDKEY; +state FIN; + +transition "start pin entry" START - / set($pin = "") -> ADDKEY; + +transition "play from timer" ADDKEY - timerTest(#id == 1) / { + playPrompt(hello); + stopRecord(); + closePlaylist(); + playFile("/tmp/test_rec.wav"); + } + -> ADDKEY; + +transition "add a key" ADDKEY - keyTest(#key < 10) / + { + append($pin, #key); + playPrompt(#key); + } -> ADDKEY; + +transition "timeout stop it" ADDKEY - timerTest(#id == 2) / stop(true) -> FIN; + +transition finished ADDKEY - keyTest(#key > 9) / playPrompt(entering) -> FIN; + diff --git a/apps/dsm/doc/todo.txt b/apps/dsm/doc/todo.txt new file mode 100644 index 00000000..e4db05e5 --- /dev/null +++ b/apps/dsm/doc/todo.txt @@ -0,0 +1,10 @@ + o explicit condition ordering (explicit priority) + o checking of FSM consitency (e.g. target state names) + o better error reporting in script compilation (e.g. line number) + o better error handling (exceptions) + o 'not' operator on conditions + o 'or' operator + o provide session init function for modules + o embed python interpreter in module ? + -> py( ... python code ... ) condition and action + o correct en/decode DI args (e.g. arrays) diff --git a/apps/dsm/etc/dsm.conf b/apps/dsm/etc/dsm.conf new file mode 100644 index 00000000..a0d1224c --- /dev/null +++ b/apps/dsm/etc/dsm.conf @@ -0,0 +1,14 @@ + +# diagrams (DSM descriptions) +diag_path=../apps/dsm/lib +load_diags=inbound_call,outbound_call + +# for import(mod_name) +mod_path=lib + +# DSM to start for in/outbound call +inbound_start_diag=inbound_call +outbound_start_diag=outbound_call + +# prompts files (for prompt collection +load_prompts=../apps/dsm/etc/dsm_in_prompts.conf,../apps/dsm/etc/dsm_out_prompts.conf diff --git a/apps/dsm/etc/dsm_in_prompts.conf b/apps/dsm/etc/dsm_in_prompts.conf new file mode 100644 index 00000000..2742332b --- /dev/null +++ b/apps/dsm/etc/dsm_in_prompts.conf @@ -0,0 +1,6 @@ +1=../apps/webconference/wav/1.wav +2=../apps/webconference/wav/2.wav +3=../apps/webconference/wav/3.wav +4=../apps/webconference/wav/4.wav +5=../apps/webconference/wav/5.wav +6=../apps/webconference/wav/6.wav diff --git a/apps/dsm/etc/dsm_out_prompts.conf b/apps/dsm/etc/dsm_out_prompts.conf new file mode 100644 index 00000000..d7b95481 --- /dev/null +++ b/apps/dsm/etc/dsm_out_prompts.conf @@ -0,0 +1,3 @@ +1=../apps/webconference/wav/1.wav +2=../apps/webconference/wav/2.wav +3=../apps/webconference/wav/3.wav diff --git a/apps/dsm/fsmc/fsmc-1.0.4-dsm-00.patch b/apps/dsm/fsmc/fsmc-1.0.4-dsm-00.patch new file mode 100644 index 00000000..5cc9f355 --- /dev/null +++ b/apps/dsm/fsmc/fsmc-1.0.4-dsm-00.patch @@ -0,0 +1,153 @@ +344a345 +> +393a395,397 +> out << "state " << sm.name; +> +> +604a609,715 +> +> QString makeSC( StateMachine& sm, QTextStream& out, const char* header, bool debug ) +> { +> if( sm.name.isEmpty() ) return invalidName; +> if( !sm.state(sm.initialState) ) return invalidInitial; +> +> QStringList states = sm.stateList(); +> if( states.isEmpty() ) return noStates; +> +> QStringList::Iterator it; +> +> for( it = states.begin(); it != states.end(); ++it ) { +> State* s = sm.state( *it ); +> if (sm.initialState == *it) +> out << "initial "; +> +> out << "state " << *it ; +> if (!s->incomeActions.empty()) { +> out << endl << " enter "; +> out << " {" << endl << " "; +> OutputList::Iterator zIt; +> for( zIt = s->incomeActions.begin(); zIt != s->incomeActions.end(); ++zIt ) { +> if (zIt != s->incomeActions.begin()) +> out << endl << " "; +> out << (*zIt).first << "; "; +> } +> out << endl << " }"; +> } +> +> if (!s->outcomeActions.empty()) { +> out << endl << " exit "; +> out << " {" << endl << " "; +> OutputList::Iterator zIt; +> for( zIt = s->outcomeActions.begin(); zIt != s->outcomeActions.end(); ++zIt ) { +> if (zIt != s->outcomeActions.begin()) +> out << endl << " "; +> out << (*zIt).first << "; "; +> } +> out << endl << " }"; +> } +> +> out << ";" << endl << endl; +> +> State::TransitionList::Iterator ti; +> bool first = true; +> +> State::TransitionList tl = s->transitionList(); +> +> for( ti = tl.begin(); ti != tl.end(); ++ti ) { +> +> out << "transition "; +> if ((*ti)->name().contains(' ')) +> out << "\""; +> out << (*ti)->name(); +> if ((*ti)->name().contains(' ')) +> out << "\""; +> +> out << " " << s->name() << " "; +> out << "-"; +> +> // out << "transition " << (*ti)->name() << " " << s->name() << " "; +> // out << "-"; +> +> +> if (!(*ti)->condition.isEmpty()) { +> QString cond = (*ti)->condition; +> +> int i=0; +> while (i < cond.length()) { +> i = cond.find('\n', i); +> if (i < 0) +> break; +> cond = cond.replace(i, 1, "\n "); +> i+=1; +> } +> +> if (cond.contains('\n')) +> out << " {" << endl << " "; +> +> out << " " << cond << " "; +> +> if (cond.contains('\n')) +> out << endl << " " << " }" << endl; +> } +> +> if (!(*ti)->actions.empty()) { +> if (!(*ti)->condition.isEmpty()) +> out << " " ; +> out << "/ "; +> if ((*ti)->actions.size() > 1) +> out << " { "; +> +> OutputList::Iterator zIt; +> for( zIt = (*ti)->actions.begin(); zIt != (*ti)->actions.end(); ++zIt ) +> out << (*zIt).first << "; "; +> +> if ((*ti)->actions.size() > 1) +> out << "} "; +> +> } +> out << "-> " << (*ti)->target << ";" << endl << endl ; +> } +> } +> +> return QString(); +> } +> +775a887,889 +> +> makeSC(sm, out, "", debug); +> return 0; +777,792c891,907 +< if( !impl ) { +< // qDebug( "writing declaration to %s", outputFile ); +< QString error = makeHeader( sm, out, debug ); +< if( !error.isEmpty() ) { +< SHOW_ERROR( error ); +< exit( 1 ); +< } +< } +< else { +< // qDebug( "writing implementation with header %s to %s", headerFile, outputFile ); +< QString error = makeBody( sm, out, headerFile, debug ); +< if( !error.isEmpty() ) { +< SHOW_ERROR( error ); +< exit( 1 ); +< } +< } +--- +> +> // if( !impl ) { +> // // qDebug( "writing declaration to %s", outputFile ); +> // QString error = makeHeader( sm, out, debug ); +> // if( !error.isEmpty() ) { +> // SHOW_ERROR( error ); +> // exit( 1 ); +> // } +> // } +> // else { +> // // qDebug( "writing implementation with header %s to %s", headerFile, outputFile ); +> // QString error = makeBody( sm, out, headerFile, debug ); +> // if( !error.isEmpty() ) { +> // SHOW_ERROR( error ); +> // exit( 1 ); +> // } +> // } diff --git a/apps/dsm/fsmc/readme.txt b/apps/dsm/fsmc/readme.txt new file mode 100644 index 00000000..ec3f20d1 --- /dev/null +++ b/apps/dsm/fsmc/readme.txt @@ -0,0 +1,11 @@ +Note: This is just rather a proof of concept. Improvements are welcome. + +You will need qt3 (not 4) to compile fsme/fsmc. As of july 08, +the current version is 1.0.4. + +DSM Conditions are "Events" in fsme. To make a new state, click on the +empty leaf in the treeview on the left side. Sub-leafs are transitions, +open the tree and click on one to make a new transition. + +It would actually be nice to print the condition name as comment, and +use the event body as condition (the same for actions). diff --git a/apps/dsm/lib/inbound_call.dsm b/apps/dsm/lib/inbound_call.dsm new file mode 100644 index 00000000..3cbd9818 --- /dev/null +++ b/apps/dsm/lib/inbound_call.dsm @@ -0,0 +1,27 @@ +-- this is just a nonsense script +import(mod_sys); + +initial state start; + +transition "secret door" start - test($pin == 155) -> the_secret_room; +transition "another key below 7" start - keyTest(#key < 7) / { append($pin, #key); playPrompt(#key); } -> start; + +transition "everything else" start - keyTest() / log(2, $pin); stop(true) -> end; + +state end; + +state the_secret_room + enter { + recordFile(/tmp/record.wav) + setTimer(1, 10); + -- another way to set a timer: DI(user_timer, setTimer, (int)1, (int)10, @local_tag); + }; +transition replay the_secret_room - timerTest(#id == 1) / { stopRecord(); playFile(/tmp/record.wav); } -> the_replay; + +state the_replay; +transition fin the_replay - noAudioTest() / stop(true) -> end; + +transition "bye recvd" start - hangup() / stop(false) -> end; +transition "bye recvd" the_replay - hangup() / stop(false) -> end; +transition "bye recvd" the_secret_room - hangup() / stop(false) -> end; + diff --git a/apps/dsm/lib/outbound_call.dsm b/apps/dsm/lib/outbound_call.dsm new file mode 100644 index 00000000..6f1fdd1a --- /dev/null +++ b/apps/dsm/lib/outbound_call.dsm @@ -0,0 +1,6 @@ +-- another nonsensical fsm... +initial state start; +transition "just an example" start - / { playPrompt(1); playPrompt(2); playPrompt(3); } -> end; +state end; +transition "stop it" end - noAudioTest() / stop(true) -> end; +transition "bye recvd" end - hangup() / stop(false) -> end; diff --git a/apps/dsm/mods/mod_sys/Makefile b/apps/dsm/mods/mod_sys/Makefile new file mode 100644 index 00000000..56de7d1d --- /dev/null +++ b/apps/dsm/mods/mod_sys/Makefile @@ -0,0 +1,9 @@ +plug_in_name = mod_sys + +DSMPATH ?= ../.. + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" -I$(DSMPATH) + +COREPATH ?=$(DSMPATH)/../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/dsm/mods/mod_sys/ModSys.cpp b/apps/dsm/mods/mod_sys/ModSys.cpp new file mode 100644 index 00000000..192b3721 --- /dev/null +++ b/apps/dsm/mods/mod_sys/ModSys.cpp @@ -0,0 +1,127 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "ModSys.h" +#include "log.h" +#include "AmUtils.h" + +#include "DSMSession.h" +#include "AmSession.h" +#include +#include +#include + +#include "DSMCoreModule.h" + +SC_EXPORT(SCSysModule); + +SCSysModule::SCSysModule() { +} + +SCSysModule::~SCSysModule() { +} + +void splitCmd(const string& from_str, + string& cmd, string& params) { + size_t b_pos = from_str.find('('); + if (b_pos != string::npos) { + cmd = from_str.substr(0, b_pos); + params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1); + } else + cmd = from_str; +} + +DSMAction* SCSysModule::getAction(const string& from_str) { + string cmd; + string params; + splitCmd(from_str, cmd, params); + +#define DEF_CMD(cmd_name, class_name) \ + \ + if (cmd == cmd_name) { \ + class_name * a = \ + new class_name(params); \ + a->name = from_str; \ + return a; \ + } + DEF_CMD("sys.mkdir", SCMkDirAction); + + return NULL; +} + +DSMCondition* SCSysModule::getCondition(const string& from_str) { + string cmd; + string params; + splitCmd(from_str, cmd, params); + + if (cmd == "sys.file_exists") { + return new FileExistsCondition(params, false); + } + + // ahem... missing not? + if (cmd == "sys.file_not_exists") { + return new FileExistsCondition(params, true); + } + + return NULL; +} + +#define GET_SCSESSION() \ + DSMSession* sc_sess = dynamic_cast(sess); \ + if (!sc_sess) { \ + ERROR("wrong session type\n"); \ + return false; \ + } + +bool FileExistsCondition::match(AmSession* sess, DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + DBG("checking file '%s'\n", arg.c_str()); + string fname = resolveVars(arg, sess, sc_sess, event_params); + bool ex = file_exists(fname); + DBG("file '%s' %s\n", fname.c_str(), ex?"exists":"does not exist"); + if (inv) { + DBG("returning %s\n", (!ex)?"true":"false"); + return !ex; + } else { + DBG("returning %s\n", (ex)?"true":"false"); + return ex; + } +} + + +bool SCMkDirAction::execute(AmSession* sess, + DSMCondition::EventType event, + map* event_params) { + GET_SCSESSION(); + string d = resolveVars(arg, sess, sc_sess, event_params); + DBG("mkdir '%s'\n", d.c_str()); + if (mkdir(d.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + ERROR("kmdir failed for '%s': %s\n", + d.c_str(), strerror(errno)); + } + return false; +} diff --git a/apps/dsm/mods/mod_sys/ModSys.h b/apps/dsm/mods/mod_sys/ModSys.h new file mode 100644 index 00000000..442af256 --- /dev/null +++ b/apps/dsm/mods/mod_sys/ModSys.h @@ -0,0 +1,56 @@ +/* + * $Id: $ + * + * Copyright (C) 2008 iptego GmbH + * + * This file is part of SEMS, a free SIP media server. + * + * sems is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * For a license to use the SEMS software under conditions + * other than those described here, or to purchase support for this + * software, please contact iptel.org by e-mail at the following addresses: + * info@iptel.org + * + * SEMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _MOD_SYS_H +#define _MOD_SYS_H +#include "DSMModule.h" + +class SCSysModule +: public DSMModule { + + public: + SCSysModule(); + ~SCSysModule(); + + DSMAction* getAction(const string& from_str); + DSMCondition* getCondition(const string& from_str); +}; + +class FileExistsCondition +: public DSMCondition { + string arg; + bool inv; + + public: + + FileExistsCondition(const string& arg, bool inv) + : arg(arg), inv(inv) { } + bool match(AmSession* sess, DSMCondition::EventType event, + map* event_params); +}; + +DEF_SCStrArgAction(SCMkDirAction); +#endif