The DonkeySM: easy application development by state machine definitions

git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1039 8eb893ce-cfd4-0310-b710-fb5ebe64c474
sayer/1.4-spce2.6
Stefan Sayer 18 years ago
parent b38fe059b8
commit c93ea5738f

@ -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 <string>
#include <fstream>
// 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<string> prompts_files =
explode(cfg.getParameter("load_prompts"), ",");
for (vector<string>::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<string> 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<string> diags_names = explode(LoadDiags, ",");
for (vector<string>::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<UACAuthCred*>(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;
}

@ -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 <string>
using std::string;
#include <memory>
/** \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:

@ -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 <dlfcn.h> // dlopen & friends
#include <vector>
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<str.length() && is_wsp(str[pos]))
pos++;
size_t pos1 = pos;
if (is_snt(str[pos])) {
string res = " ";
res[0] = str[pos];
pos++;
return res;
}
char last_chr = ' ';
while (pos1<str.length() && !is_wsp(str[pos1]) && !is_snt(str[pos1])) {
if (str[pos1] == '"') {
pos1++;
while (pos1<str.length() && !((str[pos1] == '"') && (last_chr != '\\'))) {
last_chr = str[pos1];
pos1++;
}
}
if (str[pos1] == '(') {
int lvl = 0;
pos1++;
while (pos1<str.length() && (lvl || (str[pos1] != ')'))) {
if (str[pos1] == '(')
lvl++;
else if (str[pos1] == ')')
lvl--;
if (str[pos1] == '"') {
pos1++;
while (pos1<str.length() && !((str[pos1] == '"') && (last_chr != '\\'))) {
last_chr = str[pos1];
pos1++;
}
}
last_chr = str[pos1];
pos1++;
}
}
pos1++;
}
string res;
if (str[pos] == '"')
res = str.substr(pos+1, pos1-pos-2);
else
res = str.substr(pos, pos1-pos);
pos = pos1;
return res;
}
DSMAction* DSMChartReader::actionFromToken(const string& str) {
for (vector<DSMModule*>::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<DSMModule*>::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<DSMElement*> 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<State*>(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<AttribInitial*>(&(*stack.back()));
if (ai) {
is_initial = true;
stack.pop_back();
delete ai;
}
}
e->addState(*state, is_initial);
delete state;
}
continue;
}
ActionList* al = dynamic_cast<ActionList*>(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<State*>(&(*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<DSMTransition*>(&(*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<DSMConditionList*>(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<DSMTransition*>(&(*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<DSMTransition*>(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;
}

@ -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 <string>
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<DSMAction*> actions;
};
struct DSMConditionList : public DSMElement {
DSMConditionList() { }
vector<DSMCondition*> 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<DSMModule*> mods;
DSMCoreModule core_mod;
public:
DSMChartReader();
~DSMChartReader();
bool decode(DSMStateDiagram* e, const string& chart,
const string& mod_path, DSMElemContainer* owner);
};
#endif

@ -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<string,string>* 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<DSMSession*>(sess); \
if (!sc_sess) { \
ERROR("wrong session type\n"); \
return false; \
}
bool SCPlayPromptAction::execute(AmSession* sess,
DSMCondition::EventType event,
map<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* event_params) {
GET_SCSESSION();
sc_sess->stopRecord();
return false;
}
bool SCClosePlaylistAction::execute(AmSession* sess,
DSMCondition::EventType event,
map<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* 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<string,string>* event_params) {
if (ttype == None || (type != DSMCondition::Any && type != event))
return false;
if (ttype == Always)
return true;
DSMSession* sc_sess = dynamic_cast<DSMSession*>(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<string,string>* 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<string>::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;i<sc_sess->di_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;
}

@ -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 <string>
using std::string;
#include <map>
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<string> params;
bool get_res;
public:
SCDIAction(const string& arg, bool get_res);
bool execute(AmSession* sess,
DSMCondition::EventType event,
map<string,string>* 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<string,string>* event_params);
};
string resolveVars(const string s, AmSession* sess,
DSMSession* sc_sess, map<string,string>* event_params);
#endif

@ -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<AmAudioFile*>::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<string, string> params;
params["key"] = int2str(event);
params["duration"] = int2str(duration_msec);
engine.runEvent(this, DSMCondition::Key, &params);
}
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<AmAudioEvent*>(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<AmPluginEvent*>(event);
if(plugin_event && plugin_event->name == "timer_timeout") {
int timer_id = plugin_event->data.get(0).asInt();
map<string, string> params;
params["id"] = int2str(timer_id);
engine.runEvent(this, DSMCondition::Timer, &params);
}
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;
}
}

@ -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<UACAuthCred> cred;
DSMStateEngine engine;
AmPromptCollection& prompts;
DSMStateDiagramCollection& diags;
string startDiagName;
AmPlaylist playlist;
vector<AmAudioFile*> 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:

@ -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<DSMElement*>::iterator it=
owned_elems.begin(); it != owned_elems.end(); it++)
delete *it;
}
void DSMElemContainer::transferElem(DSMElement* elem) {
owned_elems.push_back(elem);
}

@ -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 <vector>
using std::vector;
class DSMElemContainer {
vector<DSMElement*> owned_elems;
public:
DSMElemContainer();
virtual ~DSMElemContainer();
/** take ownership ever this element (GC) */
void transferElem(DSMElement* elem);
};
#endif

@ -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() {
}

@ -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 <string>
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<string,string>* 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<string,string>* 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<string,string>* event_params); \
}; \
#define CONST_TwoParAction(CL_name, sep, optional) \
CL_name::CL_name(const string& arg) { \
vector<string> 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

@ -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() {
}

@ -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 <AmArg.h>
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
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<string, string> var;
/* result of the last DI call */
AmArg di_res;
};
#endif

@ -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 <fstream>
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 <DSMStateDiagram>::iterator it =
diags.begin(); it != diags.end(); it++)
e->addDiagram(&(*it));
}

@ -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 <string>
using std::string;
class DSMStateDiagramCollection
: public DSMElemContainer
{
vector <DSMStateDiagram> diags;
public:
DSMStateDiagramCollection();
~DSMStateDiagramCollection();
bool loadFile(const string& filename, const string& name, const string& mod_path);
void addToEngine(DSMStateEngine* e);
};
#endif

@ -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<DSMAction*>::const_iterator it=
state.pre_actions.begin(); it != state.pre_actions.end(); it++) {
DBG(" pre-action '%s'\n", (*it)->name.c_str());
}
for (vector<DSMAction*>::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<DSMCondition*>::const_iterator it=
trans.precond.begin(); it != trans.precond.end(); it++) {
DBG(" DSMCondition '%s'\n", (*it)->name.c_str());
}
for (vector<DSMAction*>::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<State>::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<DSMAction*>::iterator from,
vector<DSMAction*>::iterator to,
AmSession* sess, DSMCondition::EventType event,
map<string,string>* event_params, bool& is_consumed) {
// DBG("running %zd actions\n", to - from);
for (vector<DSMAction*>::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<string,string>* event_params) {
if ((type != Any) && (event != type))
return false;
if (!event_params)
return true;
for (map<string,string>::iterator it=params.begin();
it!=params.end(); it++) {
map<string,string>::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<string,string>* event_params) {
if (!current || !current_diag)
return;
bool is_consumed = true;
do {
is_consumed = true;
for (vector<DSMTransition>::iterator tr = current->transitions.begin();
tr != current->transitions.end();tr++) {
DBG("checking transition '%s'\n", tr->name.c_str());
vector<DSMCondition*>::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<string,string>* 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<string,string>* event_params) {
for (vector<DSMStateDiagram*>::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(){
}

@ -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 <map>
using std::map;
#include <vector>
using std::vector;
#include <string>
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<string, string> params;
virtual bool match(AmSession* sess, DSMCondition::EventType event,
map<string,string>* 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<string,string>* event_params) = 0;
/** @return state engine modification */
virtual SEAction getSEAction(string& param) { return None; }
};
class DSMTransition;
class State
: public DSMElement {
public:
State();
~State();
vector<DSMAction*> pre_actions;
vector<DSMAction*> post_actions;
vector<DSMTransition> transitions;
};
class DSMTransition
: public DSMElement {
public:
DSMTransition();
~DSMTransition();
vector<DSMCondition*> precond;
vector<DSMAction*> actions;
string from_state;
string to_state;
};
class DSMStateDiagram {
vector<State> 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<DSMStateDiagram*> diags;
vector<pair<DSMStateDiagram*, State*> > stack;
bool callDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event,
map<string,string>* event_params);
bool jumpDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event,
map<string,string>* event_params);
bool returnDiag(AmSession* sess);
bool runactions(vector<DSMAction*>::iterator from,
vector<DSMAction*>::iterator to,
AmSession* sess, DSMCondition::EventType event,
map<string,string>* 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<string,string>* event_params);
};
#endif

@ -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

@ -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<string, string> 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)

@ -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(<send bye>)
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)

@ -0,0 +1,5 @@
initial state START
enter {
log(3, $pin);
returnFSM();
};

@ -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;

@ -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;

@ -0,0 +1,9 @@
-- this is a commend
-- this is a comment
-- this is a comment
initial state START
enter {
playPrompt(ok_pin);
};

@ -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;

@ -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)

@ -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

@ -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

@ -0,0 +1,3 @@
1=../apps/webconference/wav/1.wav
2=../apps/webconference/wav/2.wav
3=../apps/webconference/wav/3.wav

@ -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 );
> // }
> // }

@ -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).

@ -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;

@ -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;

@ -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

@ -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 <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#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<DSMSession*>(sess); \
if (!sc_sess) { \
ERROR("wrong session type\n"); \
return false; \
}
bool FileExistsCondition::match(AmSession* sess, DSMCondition::EventType event,
map<string,string>* 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<string,string>* 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;
}

@ -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<string,string>* event_params);
};
DEF_SCStrArgAction(SCMkDirAction);
#endif
Loading…
Cancel
Save