Imported Upstream version 1.6.0~20150612~a2d214df36

upstream upstream/1.6.0_20150612_a2d214df36
Victor Seva 10 years ago
parent 6a96ffdbbc
commit 2bf464e5cb

@ -24,12 +24,13 @@ RELEASE := $(REL_VERSION)$(EXTRAVERSION)
endif
APP_NAME ?= sems
SYSTEM_SAMPLECLOCK_RATE ?= 32000LL
CPPFLAGS += -D_DEBUG \
-D_THREAD_SAFE -D_REENTRANT \
-DSEMS_VERSION='"$(RELEASE)"' -DARCH='"$(ARCH)"'\
-DOS='"$(OS)"' \
-DSYSTEM_SAMPLECLOCK_RATE=32000LL \
-DSYSTEM_SAMPLECLOCK_RATE=$(SYSTEM_SAMPLECLOCK_RATE) \
-DSEMS_APP_NAME='"$(APP_NAME)"'
# -DMAX_RTP_SESSIONS=8192 \
# -DSUPPORT_IPV6 \

@ -25,7 +25,7 @@ Introduction:
complex VoIP services can be realized completely in SEMS.
SEMS supports all important patent free codecs out of the
box (g711u, g711a, GSM06.10, speex, G.726, L16 and iLBC).
box (g711u, g711a, GSM06.10, speex, G.726, L16, OPUS, iLBC etc).
There is a wrapper for the IPP G.729 codec implementation
available. Integrating other codecs in SEMS is very simple
(patented or not).
@ -48,12 +48,25 @@ License:
program is released under the GPL with the additional exemption
that compiling, linking, and/or using OpenSSL is allowed.
For a license to use SEMS under non-GPL terms, please contact
FRAFOS GmbH at info@frafos.com .
See doc/COPYING for details.
Applications:
The following applications are shipped with SEMS :
Back-to-back User Agent
* sbc flexible SBC application, supports
- identity change
- header manipulation (filter etc)
- (multihomed) RTP relay, NAT handling, transcoding
- SIP authentication
- Session timer, call timer, prepaid
etc
Announcements (Prompts, Ringbacktones, Pre-call-prompts):
* announcement plays an announcement
@ -103,15 +116,6 @@ Applications:
* pin_collect collect a PIN, optionally verify it, and transfer
the call into a conference
Back-to-back User Agent
* sbc flexible SBC application, supports
- identity change
- header manipulation (filter etc)
- (multihomed) RTP relay
- SIP authentication
- Session timer, call timer, prepaid
etc
App development
* dsm DSM state machine scripting (use this)
@ -215,12 +219,13 @@ How to get started with SEMS:
applications, see the explanation of 'application' parameter in
sems.conf.
Creating packages on debian (ubuntu, ...), here for wheezy:
Creating packages on debian (ubuntu, ...), here for jessie:
install debian package build tools:
$ sudo apt-get install debhelper devscripts
install dependencies:
install dependencies (those below or let dpkg-buildpackage below tell you
which ones):
$ sudo apt-get install g++ make libspandsp-dev flite-dev libspeex-dev \
libssl-dev python-dev python-sip-dev openssl libev-dev \
libmysql++-dev libevent-dev libxml2-dev libcurl4-openssl-dev
@ -228,9 +233,9 @@ Creating packages on debian (ubuntu, ...), here for wheezy:
get the source:
$ wget ftp.iptel.org/pub/sems/sems-x.y.z.tar.gz ; tar xzvf sems-x.y.z.tar.gz
or, for git master:
$ git clone git://git.sip-router.org/sems
$ git clone https://github.com/sems-server/sems.git
$ cd sems-x.y.z ; ln -s pkg/deb/debian .
$ cd sems-x.y.z ; ln -s pkg/deb/jessie ./debian
set version in changelog if not correct
$ dch -v x.y.z "SEMS x.y.z release"
@ -297,14 +302,19 @@ Authors:
B. Oldenburg
Balint Kovacs
Bogdan Pintea
Carsten Bock
Greger Viken Teigre
Grzegorz Stanislawski
Helmut Kuper
Jeremy A
Jiri Kuthan
Joe Stusick
Jose-Luis Millan
Juha Heinanen
Matthew Williams
Michael Furmur
Ovidiu Sas
Pavel Kasparek
Peter Lemenkov
Peter Loeppky
Richard Newman
@ -312,9 +322,11 @@ Authors:
Rui Jin Zheng
Tom van der Geer
Ulrich Abend
Vaclav Kubart
Victor Seva
(if you feel you should be on this list, please mail to stefan.sayer@gmail.com)
Special thanks to IPTEGO GmbH, iptelorg GmbH and TelTech Systems Inc. for
Special thanks to FRAFOS GmbH, sipwise GmbH, IPTEGO GmbH, iptelorg GmbH and TelTech Systems Inc. for
sponsoring development of SEMS.
Contributions:

@ -168,7 +168,7 @@ int ConferenceFactory::onLoad()
/* Get default audio from MySQL */
string mysql_server, mysql_user, mysql_passwd, mysql_db;
string mysql_server, mysql_user, mysql_passwd, mysql_db, mysql_ca_cert;
mysql_server = cfg.getParameter("mysql_server");
if (mysql_server.empty()) {
@ -192,6 +192,8 @@ int ConferenceFactory::onLoad()
mysql_db = "sems";
}
mysql_ca_cert = cfg.getParameter("mysql_ca_cert");
try {
#ifdef VERSION2
@ -199,6 +201,10 @@ int ConferenceFactory::onLoad()
#else
Connection.set_option(new mysqlpp::ReconnectOption(true));
#endif
if (!mysql_ca_cert.empty())
Connection.set_option(
new mysqlpp::SslOption(0, 0, mysql_ca_cert.c_str(), "",
"DHE-RSA-AES256-SHA"));
Connection.connect(mysql_db.c_str(), mysql_server.c_str(),
mysql_user.c_str(), mysql_passwd.c_str());
if (!Connection) {

@ -32,6 +32,7 @@
#include "DSM.h"
#include "AmConferenceStatus.h"
#include "AmAdvancedAudio.h"
#include "AmRingTone.h"
#include "AmSipSubscription.h"
#include "../apps/jsonrpc/JsonRPCEvents.h" // todo!
@ -446,6 +447,9 @@ void DSMCall::onSystemEvent(AmSystemEvent* ev) {
}
void DSMCall::onBeforeDestroy() {
map<string, string> params;
engine.runEvent(this, this, DSMCondition::BeforeDestroy, &params);
engine.onBeforeDestroy(this, this);
}
@ -685,6 +689,17 @@ void DSMCall::playSilence(unsigned int length, bool front) {
CLR_ERRNO;
}
void DSMCall::playRingtone(int length, int on, int off, int f, int f2, bool front) {
AmRingTone* af = new AmRingTone(length, on, off, f, f2);
if (front)
playlist.addToPlayListFront(new AmPlaylistItem(af, NULL));
else
playlist.addToPlaylist(new AmPlaylistItem(af, NULL));
audiofiles.push_back(af);
CLR_ERRNO;
}
void DSMCall::recordFile(const string& name) {
if (rec_file)
stopRecord();

@ -120,6 +120,7 @@ public:
void addToPlaylist(AmPlaylistItem* item, bool front = false);
void playFile(const string& name, bool loop, bool front=false);
void playSilence(unsigned int length, bool front=false);
void playRingtone(int length, int on, int off, int f, int f2, bool front);
void recordFile(const string& name);
unsigned int getRecordLength();
unsigned int getRecordDataSize();

@ -62,6 +62,7 @@ DSMAction* DSMCoreModule::getAction(const string& from_str) {
DEF_CMD("playFileFront", SCPlayFileFrontAction);
DEF_CMD("playSilence", SCPlaySilenceAction);
DEF_CMD("playSilenceFront", SCPlaySilenceFrontAction);
DEF_CMD("playRingtone", SCPlayRingtoneAction);
DEF_CMD("recordFile", SCRecordFileAction);
DEF_CMD("stopRecord", SCStopRecordAction);
DEF_CMD("getRecordLength", SCGetRecordLengthAction);
@ -96,9 +97,16 @@ DSMAction* DSMCoreModule::getAction(const string& from_str) {
DEF_CMD("substr", SCSubStrAction);
DEF_CMD("inc", SCIncAction);
DEF_CMD("log", SCLogAction);
DEF_CMD("logs", SCLogsAction);
DEF_CMD("dbg", SCDbgAction);
DEF_CMD("info", SCInfoAction);
DEF_CMD("warn", SCWarnAction);
DEF_CMD("error", SCErrorAction);
DEF_CMD("clear", SCClearAction);
DEF_CMD("clearStruct", SCClearStructAction);
DEF_CMD("clearArray", SCClearArrayAction);
DEF_CMD("size", SCSizeAction);
DEF_CMD("arrayIndex", SCArrayIndexAction);
DEF_CMD("logVars", SCLogVarsAction);
DEF_CMD("logParams", SCLogParamsAction);
DEF_CMD("logSelects", SCLogSelectsAction);
@ -239,6 +247,9 @@ DSMCondition* DSMCoreModule::getCondition(const string& from_str) {
if (cmd == "start")
return new TestDSMCondition(params, DSMCondition::Start);
if (cmd == "beforeDestroy")
return new TestDSMCondition(params, DSMCondition::BeforeDestroy);
if (cmd == "reload")
return new TestDSMCondition(params, DSMCondition::Reload);
@ -364,6 +375,39 @@ EXEC_ACTION_START(SCPlaySilenceAction) {
sc_sess->playSilence(length);
} EXEC_ACTION_END;
CONST_ACTION_2P(SCPlayRingtoneAction, ',', false);
EXEC_ACTION_START(SCPlayRingtoneAction) {
int length = 0, on=0, off=0, f=0, f2=0;
string varname = par1;
if (varname.length() && varname[0]=='$')
varname = varname.substr(1);
string front = resolveVars(par2, sess, sc_sess, event_params);
#define GET_VAR_INT(var_str, var_name) \
it = sc_sess->var.find(varname+"." var_str); \
if (it != sc_sess->var.end()) { \
if (!str2int(it->second, var_name)) { \
throw DSMException("core", "cause", "cannot parse number"); \
} \
}
VarMapT::iterator
GET_VAR_INT("length", length);
GET_VAR_INT("on", on);
GET_VAR_INT("off", off);
GET_VAR_INT("f", f);
GET_VAR_INT("f2", f2);
#undef GET_VAR_INT
DBG("Playing ringtone with length %d, on %d, off %d, f %d, f2 %d, front %s\n",
length, on, off, f, f2, front.c_str());
sc_sess->playRingtone(length, on, off, f, f2, front=="true");
} EXEC_ACTION_END;
EXEC_ACTION_START(SCPlaySilenceFrontAction) {
int length;
string length_str = resolveVars(arg, sess, sc_sess, event_params);
@ -569,6 +613,37 @@ EXEC_ACTION_START(SCLogAction) {
l_line.c_str());
} EXEC_ACTION_END;
CONST_ACTION_2P(SCLogsAction, ',', false);
EXEC_ACTION_START(SCLogsAction) {
unsigned int lvl;
if (str2i(resolveVars(par1, sess, sc_sess, event_params), lvl)) {
ERROR("unknown log level '%s'\n", par1.c_str());
EXEC_ACTION_STOP;
}
string l_line = replaceParams(par2, sess, sc_sess, event_params);
_LOG((int)lvl, "FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;
EXEC_ACTION_START(SCDbgAction) {
string l_line = replaceParams(arg, sess, sc_sess, event_params);
DBG("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;
EXEC_ACTION_START(SCInfoAction) {
string l_line = replaceParams(arg, sess, sc_sess, event_params);
INFO("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;
EXEC_ACTION_START(SCWarnAction) {
string l_line = replaceParams(arg, sess, sc_sess, event_params);
WARN("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;
EXEC_ACTION_START(SCErrorAction) {
string l_line = replaceParams(arg, sess, sc_sess, event_params);
ERROR("FSM: '%s'\n", l_line.c_str());
} EXEC_ACTION_END;
void log_vars(const string& l_arg, AmSession* sess,
DSMSession* sc_sess, map<string,string>* event_params) {
unsigned int lvl;
@ -684,6 +759,10 @@ string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess,
repl_pos = rstart+1;
if (rstart == string::npos)
break;
if (rstart && (res.length() > rstart) && (res[rstart]==res[repl_pos])) {
res.erase(rstart, 1);
continue;
}
if (rstart && res[rstart-1] == '\\') // escaped
continue;
size_t rend;
@ -711,23 +790,36 @@ string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess,
// todo: simply use resolveVars (?)
switch(res[rstart]) {
case '$': {
if (sc_sess->var.find(keyname) == sc_sess->var.end())
res.erase(rstart, rend-rstart);
else
res.replace(rstart, rend-rstart, sc_sess->var[keyname]);
if (sc_sess->var.find(keyname) == sc_sess->var.end()) {
res.erase(rstart, rend-rstart);
if (repl_pos) repl_pos--; // repl_pos was after $
} else {
res.replace(rstart, rend-rstart, sc_sess->var[keyname]);
if (sc_sess->var[keyname].size())
repl_pos+=sc_sess->var[keyname].size()-1; // skip after new string
}
} break;
case '#':
if (NULL!=event_params) {
if (event_params->find(keyname) != event_params->end())
if (event_params->find(keyname) != event_params->end()) {
res.replace(rstart, rend-rstart, (*event_params)[keyname]);
else
res.erase(rstart, rend-rstart);
repl_pos+=(*event_params)[keyname].size()-1; // skip after new string
} else {
res.erase(rstart, rend-rstart);
if (repl_pos) repl_pos--; // repl_pos was after #
}
} break;
case '@': {
// todo: optimize
res.replace(rstart, rend-rstart,
resolveVars("@"+keyname, sess, sc_sess, event_params));
// todo: optimize
string n = resolveVars("@"+keyname, sess, sc_sess, event_params);
res.replace(rstart, rend-rstart, n);
if (n.size())
repl_pos+=n.size()-1; // skip after new string
// rstart rend
// bla@(varname)uuuu
// r
// r
// bla12345huuuu
} break;
default: break;
}
@ -819,10 +911,10 @@ EXEC_ACTION_START(SCClearAction) {
sc_sess->var.erase(var_name);
} EXEC_ACTION_END;
EXEC_ACTION_START(SCClearArrayAction) {
EXEC_ACTION_START(SCClearStructAction) {
string varprefix = (arg.length() && arg[0] == '$')?
arg.substr(1) : arg;
DBG("clear variable array '%s.*'\n", varprefix.c_str());
DBG("clear variable struct '%s.*'\n", varprefix.c_str());
varprefix+=".";
@ -838,6 +930,27 @@ EXEC_ACTION_START(SCClearArrayAction) {
} EXEC_ACTION_END;
EXEC_ACTION_START(SCClearArrayAction) {
string varprefix = (arg.length() && arg[0] == '$')?
arg.substr(1) : arg;
DBG("clear variable array '%s[*'\n", varprefix.c_str());
varprefix+="[";
VarMapT::iterator lb = sc_sess->var.lower_bound(varprefix);
while (lb != sc_sess->var.end()) {
if ((lb->first.length() < varprefix.length()) ||
strncmp(lb->first.c_str(), varprefix.c_str(),varprefix.length()))
break;
// fixme: check whether it's really an array index
VarMapT::iterator lb_d = lb;
lb++;
sc_sess->var.erase(lb_d);
}
} EXEC_ACTION_END;
CONST_ACTION_2P(SCSizeAction, ',', false);
EXEC_ACTION_START(SCSizeAction) {
string array_name = par1;
@ -863,6 +976,35 @@ EXEC_ACTION_START(SCSizeAction) {
DBG("set $%s=%s\n", dst_name.c_str(), res.c_str());
} EXEC_ACTION_END;
CONST_ACTION_2P(SCArrayIndexAction, ',', false);
EXEC_ACTION_START(SCArrayIndexAction) {
string array_name = par1;
if (array_name.length() && array_name[0]=='$')
array_name.erase(0,1);
string val = resolveVars(par2, sess, sc_sess, event_params);
unsigned int i = 0;
bool found = false;
while (true) {
VarMapT::iterator lb = sc_sess->var.find(array_name+"["+int2str(i)+"]");
if (lb == sc_sess->var.end())
break;
if (val == lb->second) {
found = true;
break;
}
i++;
}
string res = found ? int2str(i) : "nil";
if (par2[0]=='$') {
sc_sess->var[par2.substr(1)+".index"] = res;
DBG("set %s=%s\n", (par2+".index").c_str(), res.c_str());
} else {
sc_sess->var["index"] = res;
DBG("set $index=%s\n", res.c_str());
}
} EXEC_ACTION_END;
CONST_ACTION_2P(SCAppendAction,',', false);
EXEC_ACTION_START(SCAppendAction) {
@ -1200,7 +1342,7 @@ EXEC_ACTION_START(SCDIAction) {
p.erase(0, 8);
AmArg var_struct;
string varprefix = p+".";
bool has_vars = false;
//bool has_vars = false;
map<string, string>::iterator lb = sc_sess->var.lower_bound(varprefix);
while (lb != sc_sess->var.end()) {
if ((lb->first.length() < varprefix.length()) ||
@ -1214,7 +1356,7 @@ EXEC_ACTION_START(SCDIAction) {
string2argarray(varname, lb->second, var_struct);
lb++;
has_vars = true;
//has_vars = true;
}
di_args.push(var_struct);
} else if (p.length() > 7 &&

@ -92,12 +92,19 @@ DEF_ACTION_2P(SCAppendAction);
DEF_ACTION_2P(SCSubStrAction);
DEF_ACTION_1P(SCIncAction);
DEF_ACTION_1P(SCClearAction);
DEF_ACTION_1P(SCClearStructAction);
DEF_ACTION_1P(SCClearArrayAction);
DEF_ACTION_2P(SCSizeAction);
DEF_ACTION_2P(SCArrayIndexAction);
DEF_ACTION_2P(SCSetTimerAction);
DEF_ACTION_1P(SCRemoveTimerAction);
DEF_ACTION_1P(SCRemoveTimersAction);
DEF_ACTION_2P(SCLogAction);
DEF_ACTION_2P(SCLogsAction);
DEF_ACTION_1P(SCDbgAction);
DEF_ACTION_1P(SCInfoAction);
DEF_ACTION_1P(SCWarnAction);
DEF_ACTION_1P(SCErrorAction);
DEF_ACTION_1P(SCLogVarsAction);
DEF_ACTION_1P(SCLogParamsAction);
DEF_ACTION_1P(SCLogSelectsAction);
@ -111,6 +118,7 @@ DEF_ACTION_1P(SCPlaySilenceAction);
DEF_ACTION_1P(SCPlaySilenceFrontAction);
DEF_ACTION_2P(SCPostEventAction);
DEF_ACTION_1P(SCRelayB2BEventAction);
DEF_ACTION_2P(SCPlayRingtoneAction);
DEF_ACTION_2P(SCB2BConnectCalleeAction);
DEF_ACTION_1P(SCB2BTerminateOtherLegAction);
@ -163,4 +171,8 @@ class TestDSMCondition
map<string,string>* event_params);
};
/** return string q with variables/params/selects replaced */
string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess,
map<string,string>* event_params);
#endif

@ -99,6 +99,18 @@ using std::map;
#define CLR_STRERROR \
var["strerror"] = "";
#define SET_ERROR(s, errno, errstr) \
do { \
s->SET_ERRNO(errno); \
s->SET_STRERROR(errstr); \
} while (0)
#define CLR_ERROR(s) \
do { \
s->CLR_ERRNO; \
s->CLR_STRERROR; \
} while (0)
typedef map<string, string> VarMapT;
typedef map<string, AmArg> AVarMapT;
@ -114,6 +126,8 @@ class DSMSession {
virtual void playPrompt(const string& name, bool loop = false, bool front = false) = 0;
virtual void playFile(const string& name, bool loop, bool front = false) = 0;
virtual void playSilence(unsigned int length, bool front = false) = 0;
virtual void playRingtone(int length, int on, int off, int f, int f2, bool front) = 0;
virtual void recordFile(const string& name) = 0;
virtual unsigned int getRecordLength() = 0;
virtual unsigned int getRecordDataSize() = 0;

@ -50,11 +50,11 @@ bool DSMStateDiagramCollection::readFile(const string& filename, const string& n
while (ifs.good() && !ifs.eof()) {
string r;
getline(ifs, r);
// skip comments
// skip comments: lines starting with -- or #
size_t fpos = r.find_first_not_of(" \t");
if (fpos != string::npos) {
if (r.length() > fpos+1 &&
r.substr(fpos, 2) == "--")
if ((r.length() > fpos+1 && r.substr(fpos, 2) == "--") ||
((r.length() >= fpos+1 && r.substr(fpos, 1) == "#" && r.substr(fpos, 8) != "#include") ))
continue;
if (r.length() > fpos+1 &&

@ -37,6 +37,79 @@
#include "DSM.h" // for DSMFactory::MonitoringFullCallgraph
const char* DSMCondition::type2str(EventType event) {
#define rt(e) case e: return #e;
switch (event) {
rt(Any);
rt(Start);
rt(Invite);
rt(SessionStart);
rt(Ringing);
rt(EarlySession);
rt(FailedCall);
rt(SipRequest);
rt(SipReply);
rt(BeforeDestroy);
rt(Hangup);
rt(Hold);
rt(UnHold);
rt(B2BOtherRequest);
rt(B2BOtherReply);
rt(B2BOtherBye);
rt(SessionTimeout);
rt(RtpTimeout);
rt(RemoteDisappeared);
rt(Key);
rt(Timer);
rt(NoAudio);
rt(PlaylistSeparator);
rt(DSMEvent);
rt(B2BEvent);
rt(DSMException);
rt(XmlrpcResponse);
rt(JsonRpcResponse);
rt(JsonRpcRequest);
rt(Startup);
rt(Reload);
rt(System);
rt(SIPSubscription);
rt(RTPTimeout);
// SBC related
rt(LegStateChange);
rt(BLegRefused);
rt(PutOnHold);
rt(ResumeHeld);
rt(CreateHoldRequest);
rt(HandleHoldReply);
rt(RelayInit);
rt(RelayInitUAC);
rt(RelayInitUAS);
rt(RelayFinalize);
rt(RelayOnSipRequest);
rt(RelayOnSipReply);
rt(RelayOnB2BRequest);
rt(RelayOnB2BReply);
default: return "<unknown>";
#undef rt
};
}
DSMStateDiagram::DSMStateDiagram(const string& name)
: name(name) {
}
@ -526,7 +599,9 @@ void DSMStateEngine::runEvent(AmSession* sess, DSMSession* sc_sess,
map<string,string> exception_params;
bool is_exception = run_exception;
DBG("o v DSM processing event, current state '%s' v\n", current->name.c_str());
DBG("o v DSM current state '%s', processing '%s' event v\n",
current->name.c_str(), DSMCondition::type2str(event));
bool is_consumed = true;
do {
try {

@ -68,6 +68,7 @@ class DSMCondition
FailedCall,
SipRequest,
SipReply,
BeforeDestroy,
Hangup,
Hold,
@ -128,6 +129,8 @@ class DSMCondition
#endif
};
static const char* type2str(EventType event);
bool invert;
DSMCondition() : invert(false) { }

@ -193,6 +193,7 @@ void SystemDSM::_func { \
NOT_IMPLEMENTED(playPrompt(const string& name, bool loop, bool front));
NOT_IMPLEMENTED(playFile(const string& name, bool loop, bool front));
NOT_IMPLEMENTED(playSilence(unsigned int length, bool front));
NOT_IMPLEMENTED(playRingtone(int length, int on, int off, int f, int f2, bool front));
NOT_IMPLEMENTED(recordFile(const string& name));
NOT_IMPLEMENTED_UINT(getRecordLength());
NOT_IMPLEMENTED_UINT(getRecordDataSize());

@ -58,6 +58,7 @@ class SystemDSM
void playPrompt(const string& name, bool loop = false, bool front = false);
void playFile(const string& name, bool loop, bool front = false);
void playSilence(unsigned int length, bool front = false);
void playRingtone(int length, int on, int off, int f, int f2, bool front);
void recordFile(const string& name);
unsigned int getRecordLength();
unsigned int getRecordDataSize();

@ -49,7 +49,9 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("conference.teeleave", ConfTeeLeaveAction);
DEF_CMD("conference.setupMixIn", ConfSetupMixInAction);
DEF_CMD("conference.playMixIn", ConfPlayMixInAction);
DEF_CMD("conference.playMixIn", ConfPlayMixInAction);
DEF_CMD("conference.playMixInList", ConfPlayMixInListAction);
DEF_CMD("conference.flushMixInList", ConfFlushMixInListAction);
} MOD_ACTIONEXPORT_END;
@ -175,7 +177,8 @@ EXEC_ACTION_START(ConfJoinAction) {
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
}
} EXEC_ACTION_END;
// get conference channel or other object (mixer etc)
template<class T>
static T* getDSMConfChannel(DSMSession* sc_sess, const char* key_name) {
if (sc_sess->avar.find(key_name) == sc_sess->avar.end()) {
@ -399,3 +402,67 @@ EXEC_ACTION_START(ConfPlayMixInAction) {
m->mixin(af);
} EXEC_ACTION_END;
CONST_ACTION_2P(ConfPlayMixInListAction, ',', true);
EXEC_ACTION_START(ConfPlayMixInListAction) {
string filename = resolveVars(par1, sess, sc_sess, event_params);
bool loop = resolveVars(par2, sess, sc_sess, event_params) == "true";
bool has_playlist = true;
// get playlist
DSMDisposableT<AmPlaylist >* l_obj =
getDSMConfChannel<DSMDisposableT<AmPlaylist> >(sc_sess, CONF_AKEY_MIXLIST);
if (NULL == l_obj) {
// playlist newly setup
AmPlaylist* pl = new AmPlaylist(NULL); // no event receiver - no 'clear' event
l_obj = new DSMDisposableT<AmPlaylist >(pl);
AmArg c_arg;
c_arg.setBorrowedPointer(l_obj);
sc_sess->avar[CONF_AKEY_MIXLIST] = c_arg;
// add to garbage collector
sc_sess->transferOwnership(l_obj);
has_playlist = false;
}
AmPlaylist* l = l_obj->get();
DSMDisposableAudioFile* af = new DSMDisposableAudioFile();
if(af->open(filename,AmAudioFile::Read)) {
ERROR("audio file '%s' could not be opened for reading.\n",
filename.c_str());
delete af;
throw DSMException("file", "path", filename);
}
sc_sess->transferOwnership(af);
af->loop.set(loop);
DBG("adding file '%s' to mixin playlist\n", filename.c_str());
l->addToPlaylist(new AmPlaylistItem(af, NULL));
if (!has_playlist) {
// get mixin mixer
DSMDisposableT<AmAudioMixIn >* m_obj =
getDSMConfChannel<DSMDisposableT<AmAudioMixIn > >(sc_sess, CONF_AKEY_MIXER);
if (NULL == m_obj) {
throw DSMException("conference", "cause", "mixer not setup!\n");
}
AmAudioMixIn* m = m_obj->get();
// play from list
m->mixin(l);
}
} EXEC_ACTION_END;
EXEC_ACTION_START(ConfFlushMixInListAction) {
// get playlist
DSMDisposableT<AmPlaylist >* l_obj =
getDSMConfChannel<DSMDisposableT<AmPlaylist> >(sc_sess, CONF_AKEY_MIXLIST);
if (NULL == l_obj) {
DBG("no mix list present - not flushing list\n");
EXEC_ACTION_STOP;
}
AmPlaylist* l = l_obj->get();
l->flush();
DBG("flushed mixInList\n");
} EXEC_ACTION_END;

@ -39,6 +39,7 @@ DECLARE_MODULE(MOD_CLS_NAME);
#define CONF_AKEY_CHANNEL "conf.chan"
#define CONF_AKEY_DEF_TEECHANNEL "conf.teechan"
#define CONF_AKEY_MIXER "conf.mixer"
#define CONF_AKEY_MIXLIST "conf.mixlist"
/** holds a conference channel */
class DSMConfChannel
@ -94,5 +95,6 @@ DEF_ACTION_1P(ConfTeeLeaveAction);
DEF_ACTION_2P(ConfSetupMixInAction);
DEF_ACTION_1P(ConfPlayMixInAction);
DEF_ACTION_2P(ConfPlayMixInListAction);
DEF_ACTION_1P(ConfFlushMixInListAction);
#endif

@ -47,7 +47,8 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("groups.join", GroupsJoinAction);
DEF_CMD("groups.leave", GroupsLeaveAction);
DEF_CMD("groups.leaveAll", GroupsLeaveAllAction);
// DEF_CMD("groups.get", GroupsGetAction);
DEF_CMD("groups.get", GroupsGetAction);
DEF_CMD("groups.getSize", GroupsGetSizeAction);
// DEF_CMD("groups.getMembers", GroupsGetMembersAction);
DEF_CMD("groups.postEvent", GroupsPostEventAction);
@ -125,6 +126,43 @@ EXEC_ACTION_START(GroupsLeaveAllAction) {
GroupsModule::leave_all_groups(ltag);
} EXEC_ACTION_END;
CONST_ACTION_2P(GroupsGetAction, '=', false);
EXEC_ACTION_START(GroupsGetAction) {
string var = par1;
if (var.size() && var[0]=='$') var.erase(0,1);
string groupname = resolveVars(par2, sess, sc_sess, event_params);
GroupsModule::groups_mut.lock();
GroupMap::iterator grp = GroupsModule::groups.find(groupname);
int i=0;
if (grp != GroupsModule::groups.end()) {
for (set<string>::iterator it =
grp->second.begin(); it != grp->second.end(); it++) {
sc_sess->var[var+"["+int2str(i)+"]"] = *it;
i++;
}
}
GroupsModule::groups_mut.unlock();
DBG("get %d group members of '%s' in $%s[]\n", i, groupname.c_str(), var.c_str());
} EXEC_ACTION_END;
CONST_ACTION_2P(GroupsGetSizeAction, '=', false);
EXEC_ACTION_START(GroupsGetSizeAction) {
string var = par1;
if (var.size() && var[0]=='$') var.erase(0,1);
string groupname = resolveVars(par2, sess, sc_sess, event_params);
DBG("posting event to group '%s'\n", groupname.c_str());
GroupsModule::groups_mut.lock();
int size = 0;
GroupMap::iterator grp = GroupsModule::groups.find(groupname);
if (grp != GroupsModule::groups.end()) {
size = grp->second.size();
}
GroupsModule::groups_mut.unlock();
sc_sess->var[var] = int2str(size);
DBG("get group '%s' size $%s=%d\n", groupname.c_str(), var.c_str(), size);
} EXEC_ACTION_END;
CONST_ACTION_2P(GroupsPostEventAction, ',', true);
EXEC_ACTION_START(GroupsPostEventAction) {
string groupname = resolveVars(par1, sess, sc_sess, event_params);
@ -144,7 +182,7 @@ EXEC_ACTION_START(GroupsPostEventAction) {
DBG("posting event to group '%s'\n", groupname.c_str());
GroupsModule::groups_mut.unlock();
GroupsModule::groups_mut.lock();
GroupMap::iterator grp = GroupsModule::groups.find(groupname);
bool posted = false;
if (grp != GroupsModule::groups.end()) {

@ -60,7 +60,8 @@ DECLARE_MODULE_END;
DEF_ACTION_1P(GroupsJoinAction);
DEF_ACTION_1P(GroupsLeaveAction);
DEF_ACTION_1P(GroupsLeaveAllAction);
/* DEF_ACTION_1P(GroupsGetAction); */
DEF_ACTION_2P(GroupsGetAction);
DEF_ACTION_2P(GroupsGetSizeAction);
/* DEF_ACTION_1P(GroupsGetMembersAction); */
DEF_ACTION_2P(GroupsPostEventAction);

@ -0,0 +1,37 @@
#ifndef _DBTypes_h_
#define _DBTypes_h_
#include "AmArg.h"
#include "AmConfigReader.h"
#include <map>
#include <list>
using std::map;
using std::list;
struct DBIdxType
{
string key;
string value;
DBIdxType() {}
DBIdxType(const string& key, const string& value)
: key(key), value(value)
{}
};
typedef map<string,AmArg> DBMapType;
typedef list<DBIdxType> DBIdxList;
#define DB_E_OK 0
#define DB_E_CONNECTION -1
#define DB_E_WRITE -2
#define DB_E_READ -3
enum RestoreResult {
SUCCESS,
FAILURE, // an error
NOT_FOUND // object not found in the storage
};
#endif

@ -0,0 +1,194 @@
#include "DRedisConnection.h"
#include "jsonArg.h"
#include "AmUtils.h"
#include <errno.h>
DRedisConfig::DRedisConfig(const string& host, unsigned int port,
bool unix_socket, bool full_logging,
bool use_transactions, int connect_timeout)
: host(host), port(port),
unix_socket(unix_socket),
full_logging(full_logging),
use_transactions(use_transactions)
{
tv_timeout.tv_sec = connect_timeout / 1000;
tv_timeout.tv_usec = (connect_timeout - 1000 * tv_timeout.tv_sec) * 1000;
}
DRedisConnection::DRedisConnection(const DRedisConfig& cfg)
: redis_context(NULL), cfg(cfg)
{
}
DRedisConnection::~DRedisConnection()
{
disconnect();
}
void DRedisConnection::disconnect()
{
if(redis_context) {
DBG("disconnecting from Redis...");
redisFree(redis_context);
redis_context = NULL;
}
}
bool DRedisConnection::connect()
{
if(redis_context)
return true;
if(!cfg.unix_socket) {
DBG("connecting to REDIS at %s:%u\n", cfg.host.c_str(), cfg.port);
redis_context = redisConnectWithTimeout((char*)cfg.host.c_str(),
cfg.port, cfg.tv_timeout);
}
else {
DBG("connecting to REDIS at %s\n", cfg.host.c_str());
redis_context = redisConnectUnixWithTimeout(cfg.host.c_str(),
cfg.tv_timeout);
}
if (redis_context->err) {
ERROR("REDIS Connection error: %s\n", redis_context->errstr);
disconnect();
return false;
}
return true;
}
int DRedisConnection::handle_redis_reply(redisReply *reply, const char* _cmd) {
if (!reply) {
switch (redis_context->err) {
case REDIS_ERR_IO:
ERROR("I/O error: %s (%s)\n", redis_context->errstr,_cmd);
disconnect();
return DB_E_CONNECTION;
case REDIS_ERR_EOF: // silently reconnect
case REDIS_ERR_OTHER:
disconnect();
return DB_E_CONNECTION;
case REDIS_ERR_PROTOCOL:
ERROR("REDIS Protocol error detected\n");
disconnect();
return DB_E_CONNECTION;
}
}
switch (reply->type) {
case REDIS_REPLY_ERROR:
ERROR("REDIS %s ERROR: %s\n", _cmd, reply->str);
return DB_E_WRITE;
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
if (reply->len>=0) {
if (cfg.full_logging) {
DBG("REDIS %s: str: %.*s\n", _cmd, reply->len, reply->str);
}
} break;
case REDIS_REPLY_INTEGER:
if (cfg.full_logging) {
DBG("REDIS %s: int: %lld\n", _cmd, reply->integer);
} break;
case REDIS_REPLY_ARRAY: {
if (cfg.full_logging) {
DBG("REDIS %s: array START\n", _cmd);
};
for (size_t i=0;i<reply->elements;i++) {
switch(reply->element[i]->type) {
case REDIS_REPLY_ERROR: ERROR("REDIS %s ERROR: %.*s\n",
_cmd, reply->element[i]->len,
reply->element[i]->str);
return DB_E_WRITE;
case REDIS_REPLY_INTEGER:
if (cfg.full_logging) {
DBG("REDIS %s: %lld\n", _cmd, reply->element[i]->integer);
} break;
case REDIS_REPLY_NIL:
if (cfg.full_logging) {
DBG("REDIS %s: nil\n", _cmd);
} break;
case REDIS_REPLY_ARRAY:
if (cfg.full_logging) {
DBG("REDIS : %zd elements\n", reply->elements);
} break;
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
if (cfg.full_logging) {
if (reply->element[i]->len >= 0) {
DBG("REDIS %s: %.*s\n", _cmd,
reply->element[i]->len, reply->element[i]->str);
}
}
break;
default:
ERROR("unknown REDIS reply %d to %s!",reply->element[i]->type, _cmd); break;
}
}
if (cfg.full_logging) {
DBG("REDIS %s: array END\n", _cmd);
};
}; break;
default: ERROR("unknown REDIS reply %d to %s!", reply->type, _cmd); break;
}
if (cfg.full_logging) {
DBG("REDIS cmd %s executed successfully\n", _cmd);
}
return DB_E_OK;
}
#define RETURN_READ_ERROR \
freeReplyObject(reply); \
return DB_E_READ;
int DRedisConnection::exec_cmd(const char* cmd, redisReply*& reply) {
if(!redis_context) {
ERROR("REDIS cmd '%s': not connected",cmd);
return DB_E_CONNECTION;
}
reply = NULL;
reply = (redisReply *)redisCommand(redis_context, cmd);
int ret = handle_redis_reply(reply, cmd);
if (ret != DB_E_OK)
return ret;
DBG("successfully executed redis cmd\n");
return DB_E_OK;
}
int DRedisConnection::append_cmd(const char* cmd) {
if(!redis_context) {
ERROR("REDIS append cmd '%s': not connected",cmd);
return DB_E_CONNECTION;
}
return redisAppendCommand(redis_context, cmd) == REDIS_OK ?
DB_E_OK : DB_E_CONNECTION;
}
int DRedisConnection::get_reply(redisReply*& reply) {
if(!redis_context) {
ERROR("REDIS get_reply: not connected");
return DB_E_CONNECTION;
}
redisGetReply(redis_context, (void**)&reply);
int ret = handle_redis_reply(reply, "<pipelined>");
return ret;
}

@ -0,0 +1,50 @@
#ifndef _DRedisConnection_h_
#define _DRedisConnection_h_
#include "hiredis/hiredis.h"
#include "DBTypes.h"
#define DEFAULT_REDIS_HOST "127.0.0.1"
#define DEFAULT_REDIS_PORT 6379
#define DEFAULT_REDIS_CONNECT_TIMEOUT 500
struct DRedisConfig
{
string host;
unsigned int port;
bool unix_socket;
bool full_logging;
bool use_transactions;
struct timeval tv_timeout;
DRedisConfig(const string& host = DEFAULT_REDIS_HOST,
unsigned int port = DEFAULT_REDIS_PORT,
bool unix_socket = false,
bool full_logging = false,
bool use_transactions = false,
int connect_timeout = DEFAULT_REDIS_CONNECT_TIMEOUT);
};
class DRedisConnection
{
DRedisConfig cfg;
redisContext* redis_context;
int handle_redis_reply(redisReply *reply, const char* _cmd);
public:
DRedisConnection(const DRedisConfig& cfg);
~DRedisConnection();
bool connect();
void disconnect();
bool connected() { return redis_context != NULL; }
int exec_cmd(const char* cmd, redisReply*& reply);
int append_cmd(const char* cmd);
int get_reply(redisReply*& reply);
};
#endif

@ -0,0 +1,10 @@
plug_in_name = mod_redis
DSMPATH ?= ../..
module_ldflags = -lhiredis
module_cflags = -DMOD_NAME=\"$(plug_in_name)\" -I$(DSMPATH)
COREPATH ?=$(DSMPATH)/../../core
lib_full_name = $(DSMPATH)/mods/lib/$(lib_name)
include $(DSMPATH)/mods/Makefile.dsm_module

@ -0,0 +1,369 @@
/*
* Copyright (C) 2014 Stefan Sayer
*
* 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. This program is released under
* the GPL with the additional exemption that compiling, linking,
* and/or using OpenSSL is allowed.
*
* 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 "ModRedis.h"
#include "log.h"
#include "AmUtils.h"
#include "DSMSession.h"
#include "AmSession.h"
#include "AmPlaylist.h"
#include "DSMCoreModule.h"
#include <stdio.h>
#include <fstream>
SC_EXPORT(DSMRedisModule);
DSMRedisModule::DSMRedisModule() {
}
DSMRedisModule::~DSMRedisModule() {
}
DSMAction* DSMRedisModule::getAction(const string& from_str) {
string cmd;
string params;
splitCmd(from_str, cmd, params);
DEF_CMD("redis.connect", DSMRedisConnectAction);
DEF_CMD("redis.disconnect", DSMRedisDisconnectAction);
DEF_CMD("redis.execCommand", DSMRedisExecCommandAction);
DEF_CMD("redis.appendCommand", DSMRedisAppendCommandAction);
DEF_CMD("redis.getReply", DSMRedisGetReplyAction);
return NULL;
}
DSMCondition* DSMRedisModule::getCondition(const string& from_str) {
// string cmd;
// string params;
// splitCmd(from_str, cmd, params);
// if (cmd == "redis.hasResult") {
// return new MyHasResultCondition(params, false);
// }
// if (cmd == "redis.connected") {
// return new MyConnectedCondition(params, true);
// }
return NULL;
}
DSMRedisResult::~DSMRedisResult() {
if (NULL != result) {
freeReplyObject(result);
}
}
void DSMRedisResult::release() {
result = NULL;
}
DSMRedisConnection* getRedisDSMSessionConnection(DSMSession* sc_sess) {
if (sc_sess->avar.find(REDIS_AKEY_CONNECTION) == sc_sess->avar.end()) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "No connection to redis database");
return NULL;
}
AmObject* ao = NULL; DSMRedisConnection* res = NULL;
try {
if (!isArgAObject(sc_sess->avar[REDIS_AKEY_CONNECTION])) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "No connection to redis database (not AmObject)");
return NULL;
}
ao = sc_sess->avar[REDIS_AKEY_CONNECTION].asObject();
} catch (...){
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "No connection to redis database (not AmObject)");
return NULL;
}
if (NULL == ao || NULL == (res = dynamic_cast<DSMRedisConnection*>(ao))) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "No connection to database (not a RedisConnection)");
return NULL;
}
return res;
}
DSMRedisConnection* getConnectedRedisDSMSessionConnection(DSMSession* sc_sess) {
DSMRedisConnection* res = getRedisDSMSessionConnection(sc_sess);
if (!res || res->connected() || res->connect())
return res;
return NULL;
}
DSMRedisResult* getRedisDSMResult(DSMSession* sc_sess) {
if (sc_sess->avar.find(REDIS_AKEY_RESULT) == sc_sess->avar.end()) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_NORESULT, "No result available");
return NULL;
}
AmObject* ao = NULL; DSMRedisResult* res = NULL;
try {
assertArgAObject(sc_sess->avar[REDIS_AKEY_RESULT]);
ao = sc_sess->avar[REDIS_AKEY_RESULT].asObject();
} catch (...){
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_NORESULT, "Result object has wrong type");
return NULL;
}
if (NULL == ao || NULL == (res = dynamic_cast<DSMRedisResult*>(ao))) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_NORESULT, "Result object has wrong type");
return NULL;
}
return res;
}
string replaceQueryParams(const string& q, DSMSession* sc_sess,
map<string,string>* event_params) {
string res = q;
size_t repl_pos = 0;
while (repl_pos<res.length()) {
size_t rstart = res.find_first_of("$#", repl_pos);
repl_pos = rstart+1;
if (rstart == string::npos)
break;
if (rstart && res[rstart-1] == '\\') // escaped
continue;
size_t rend = res.find_first_of(" ,()$#\t;'\"", rstart+1);
if (rend==string::npos)
rend = res.length();
switch(res[rstart]) {
case '$':
res.replace(rstart, rend-rstart,
sc_sess->var[res.substr(rstart+1, rend-rstart-1)]); break;
case '#':
if (NULL!=event_params) {
res.replace(rstart, rend-rstart,
(*event_params)[res.substr(rstart+1, rend-rstart-1)]); break;
}
default: break;
}
}
return res;
}
string skip_till(string& s, string sep) {
size_t pos = s.find_first_of(sep);
if (pos == string::npos) {
string res = s;
s.clear();
return res;
} else {
string res = s.substr(0, pos);
if (s.length()>pos)
s = s.substr(pos+1);
else
s.clear();
return res;
}
}
EXEC_ACTION_START(DSMRedisConnectAction) {
string f_arg = resolveVars(arg, sess, sc_sess, event_params);
string db_url = f_arg.length()?f_arg:sc_sess->var["config.redis_db_url"];
if (db_url.empty() || db_url.length() < 11 || db_url.substr(0, 8) != "redis://") {
ERROR("missing correct db_url config or connect parameter - must start with redis://\n");
SET_ERROR(sc_sess, DSM_ERRNO_UNKNOWN_ARG,
"missing correct db_url config or connect parameter - must start with redis://\n");
EXEC_ACTION_STOP;
}
db_url = db_url.substr(8);
// split url - tcp:host;param=value or unix:host;param=value
string db_proto = skip_till(db_url, ":");
if (db_proto != "unix" && db_proto != "tcp") {
ERROR("missing correct redis_db_url config or connect parameter - must start with unix or tcp protocol\n");
SET_ERROR(sc_sess, DSM_ERRNO_UNKNOWN_ARG,
"missing correct db_url config or connect parameter - must start with unix or tcp protocol\n");
EXEC_ACTION_STOP;
}
bool unix_socket = db_proto=="unix";
string db_host = skip_till(db_url, ":;");
if (db_host.empty()) {
ERROR("missing correct redis_db_url config or connect parameter - host must be non-empty\n");
SET_ERROR(sc_sess, DSM_ERRNO_UNKNOWN_ARG,
"missing correct db_url config or connect parameter - host must be non-empty\n");
EXEC_ACTION_STOP;
}
unsigned int redis_port = DEFAULT_REDIS_PORT;
bool full_logging = false;
bool use_transactions = false;
int connect_timeout = DEFAULT_REDIS_CONNECT_TIMEOUT;
while (!db_url.empty()) {
string param = skip_till(db_url, ";");
vector<string> p = explode(param, "=");
if (p.size() != 2) {
ERROR("missing correct redis_db_url config or connect parameter - "
"parameter '%s' must be param=value\n", param.c_str());
SET_ERROR(sc_sess, DSM_ERRNO_UNKNOWN_ARG,
"missing correct db_url config or connect parameter - parameter must be param=value\n");
EXEC_ACTION_STOP;
}
if (p[0] == "full_logging") {
full_logging = p[1] == "true";
} else if (p[0] == "use_transactions") {
use_transactions = p[1] == "true";
} else if (p[0] == "connect_timeout"){
str2int(p[1], connect_timeout);
} else if (p[0] == "port"){
str2i(p[1], redis_port);
} else {
ERROR("unknown redis_db_url config or connect parameter - "
"parameter '%s'\n", p[0].c_str());
SET_ERROR(sc_sess, DSM_ERRNO_UNKNOWN_ARG, "missing correct db_url config or connect parameter\n");
EXEC_ACTION_STOP;
}
}
DSMRedisConnection* conn =
new DSMRedisConnection(db_host, redis_port, unix_socket, full_logging,
use_transactions, connect_timeout);
if (!conn->connect()) {
delete conn;
ERROR("Could not connect to redis DB\n");
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "Could not connect to redis DB");
EXEC_ACTION_STOP;
}
// save connection for later use
AmArg c_arg;
c_arg.setBorrowedPointer(conn);
sc_sess->avar[REDIS_AKEY_CONNECTION] = c_arg;
// for garbage collection
sc_sess->transferOwnership(conn);
CLR_ERROR(sc_sess);
} EXEC_ACTION_END;
EXEC_ACTION_START(DSMRedisDisconnectAction) {
DSMRedisConnection* conn = getRedisDSMSessionConnection(sc_sess);
if (NULL == conn) {
EXEC_ACTION_STOP;
}
conn->disconnect();
// connection object might be reused - but its safer to create a new one
sc_sess->releaseOwnership(conn);
delete conn;
sc_sess->avar.erase(REDIS_AKEY_CONNECTION);
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
void decodeRedisResult(VarMapT& dst, const string& varname, redisReply* reply) {
if (!reply)
return;
switch (reply->type) {
case REDIS_REPLY_STRING: dst[varname] = string(reply->str, reply->len); break;
case REDIS_REPLY_INTEGER: dst[varname] = int2str((int)reply->integer); break; // todo: long long?
case REDIS_REPLY_NIL: dst[varname] = "nil"; break;
case REDIS_REPLY_STATUS: dst[varname] = string(reply->str, reply->len); break;
case REDIS_REPLY_ERROR: ERROR("decoding REDIS reply - ERROR type"); break;
case REDIS_REPLY_ARRAY: {
for (size_t i=0;i<reply->elements;i++) {
decodeRedisResult(dst, varname+"["+int2str((unsigned int)i)+"]", reply->element[i]);
}
} break;
}
}
void handleResult(DSMSession* sc_sess, int res, redisReply* reply, const string& resultvar) {
switch (res) {
case DB_E_OK: {
decodeRedisResult(sc_sess->var, resultvar, reply);
freeReplyObject(reply);
} break;
case DB_E_CONNECTION: SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "REDIS connection error"); return;
case DB_E_WRITE: SET_ERROR(sc_sess, DSM_ERRNO_REDIS_WRITE, "REDIS write error"); return;
case DB_E_READ: SET_ERROR(sc_sess, DSM_ERRNO_REDIS_READ, "REDIS read error"); return;
default: SET_ERROR(sc_sess, DSM_ERRNO_REDIS_UNKNOWN, "REDIS unknown error"); return;
}
}
CONST_ACTION_2P(DSMRedisExecCommandAction, '=', false);
EXEC_ACTION_START(DSMRedisExecCommandAction) {
string resultvar = par1;
if (resultvar.length() && resultvar[0]=='$') resultvar=resultvar.substr(1);
string cmd = replaceParams(par2, sess, sc_sess, event_params);
DBG("executing redis command $%s='%s'\n", resultvar.c_str(), cmd.c_str());
DSMRedisConnection* conn = getConnectedRedisDSMSessionConnection(sc_sess);
if (!conn) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "Not connected to REDIS\n");
EXEC_ACTION_STOP;
}
redisReply* reply;
int res = conn->exec_cmd(cmd.c_str(), reply);
handleResult(sc_sess, res, reply, resultvar);
} EXEC_ACTION_END;
EXEC_ACTION_START(DSMRedisAppendCommandAction) {
string cmd = replaceParams(arg, sess, sc_sess, event_params);
DBG("appending redis command '%s' - from '%s'\n", cmd.c_str(), arg.c_str());
DSMRedisConnection* conn = getConnectedRedisDSMSessionConnection(sc_sess);
if (!conn) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "Not connected to REDIS\n");
EXEC_ACTION_STOP;
}
if (conn->append_cmd(cmd.c_str()) == DB_E_OK) {
CLR_ERROR(sc_sess);
} else {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "Error appending command - no memory?\n");
}
} EXEC_ACTION_END;
EXEC_ACTION_START(DSMRedisGetReplyAction) {
string resultvar = arg;
if (resultvar.length() && resultvar[0]=='$') resultvar=resultvar.substr(1);
DBG("getting result for redis command in $%s\n", resultvar.c_str());
DSMRedisConnection* conn = getConnectedRedisDSMSessionConnection(sc_sess);
if (!conn) {
SET_ERROR(sc_sess, DSM_ERRNO_REDIS_CONNECTION, "Not connected to REDIS\n");
EXEC_ACTION_STOP;
}
redisReply* reply;
int res = conn->get_reply(reply);
handleResult(sc_sess, res, reply, resultvar);
} EXEC_ACTION_END;

@ -0,0 +1,87 @@
/*
* Copyright (C) 2014 Stefan Sayer
*
* 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. This program is released under
* the GPL with the additional exemption that compiling, linking,
* and/or using OpenSSL is allowed.
*
* 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_REDIS_H
#define _MOD_REDIS_H
#include "DSMModule.h"
#include "DSMSession.h"
#include "DRedisConnection.h"
#define REDIS_AKEY_CONNECTION "db_redis.con"
#define REDIS_AKEY_RESULT "db_redis.res"
#define DSM_ERRNO_REDIS_CONNECTION "connection"
#define DSM_ERRNO_REDIS_WRITE "write"
#define DSM_ERRNO_REDIS_READ "read"
#define DSM_ERRNO_REDIS_QUERY "query"
#define DSM_ERRNO_REDIS_NORESULT "result"
#define DSM_ERRNO_REDIS_NOROW "result"
#define DSM_ERRNO_REDIS_NOCOLUMN "result"
#define DSM_ERRNO_REDIS_UNKNOWN "unknown"
class DSMRedisModule
: public DSMModule {
public:
DSMRedisModule();
~DSMRedisModule();
DSMAction* getAction(const string& from_str);
DSMCondition* getCondition(const string& from_str);
};
class DSMRedisConnection
: public DRedisConnection,
public AmObject,
public DSMDisposable
{
public:
DSMRedisConnection(const string& host, unsigned int port,
bool unix_socket, bool full_logging, bool use_transactions, int connect_timeout)
: DRedisConnection(DRedisConfig(host, port, unix_socket, full_logging, use_transactions, connect_timeout))
{ }
~DSMRedisConnection() { }
};
class DSMRedisResult
: public AmObject,
public DSMDisposable
{
redisReply* result;
public:
DSMRedisResult(redisReply* result) : result(result) { }
~DSMRedisResult();
void release();
};
DEF_ACTION_1P(DSMRedisConnectAction);
DEF_ACTION_1P(DSMRedisDisconnectAction);
DEF_ACTION_2P(DSMRedisExecCommandAction);
DEF_ACTION_1P(DSMRedisAppendCommandAction);
DEF_ACTION_1P(DSMRedisGetReplyAction);
#endif

@ -61,6 +61,13 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("sbc.streamsSetReceiving", MODSBCRtpStreamsSetReceiving);
DEF_CMD("sbc.clearExtLocalTag", MODSBCClearExtLocalTag);
DEF_CMD("sbc.setExtLocalTag", MODSBCSetExtLocalTag);
DEF_CMD("sbc.setLastReq", MODSBCSetLastReq);
DEF_CMD("sbc.testSDPConnectionAddress", MODSBCtestSDPConnectionAddress);
} MOD_ACTIONEXPORT_END;
MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) {
@ -582,3 +589,85 @@ EXEC_ACTION_START(MODSBCRtpStreamsSetReceiving) {
b2b_media->setReceiving(p_a, p_b);
} EXEC_ACTION_END;
EXEC_ACTION_START(MODSBCClearExtLocalTag) {
DBG("clearing externally used local tag for call leg [%s/%p]\n",
sess->getLocalTag().c_str(), sess);
sess->dlg->setExtLocalTag("");
} EXEC_ACTION_END;
EXEC_ACTION_START(MODSBCSetExtLocalTag) {
string new_tag = resolveVars(arg, sess, sc_sess, event_params);
DBG("setting externally used local tag for call leg [%s/%p] to '%s'\n",
sess->getLocalTag().c_str(), sess, new_tag.c_str());
sess->dlg->setExtLocalTag(new_tag);
} EXEC_ACTION_END;
EXEC_ACTION_START(MODSBCSetLastReq) {
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REQUEST);
if (it == sc_sess->avar.end()) {
ERROR("Could not find "DSM_AVAR_REQUEST" avar for request");
EXEC_ACTION_STOP;
}
if (NULL == it->second.asObject()) {
ERROR("Could not find "DSM_AVAR_REQUEST" avar as pointer");
EXEC_ACTION_STOP;
}
AmSipRequest* r = dynamic_cast<AmSipRequest*>(it->second.asObject());
if (NULL == r) {
ERROR("Could not find "DSM_AVAR_REQUEST" avar as request");
EXEC_ACTION_STOP;
}
sc_sess->last_req.reset(new AmSipRequest(*r));
} EXEC_ACTION_END;
EXEC_ACTION_START(MODSBCtestSDPConnectionAddress) {
vector<string> check_adrs = explode(resolveVars(arg, sess, sc_sess, event_params), ",");
AVarMapT::iterator it = sc_sess->avar.find(DSM_AVAR_REPLY);
if (it == sc_sess->avar.end()) {
ERROR("Could not find "DSM_AVAR_REPLY" avar for reply");
EXEC_ACTION_STOP;
}
if (NULL == it->second.asObject()) {
ERROR("Could not find "DSM_AVAR_REPLY" avar as pointer");
EXEC_ACTION_STOP;
}
DSMSipReply* r = dynamic_cast<DSMSipReply*>(it->second.asObject());
if (NULL == r) {
ERROR("Could not find "DSM_AVAR_REPLY" avar as reply");
EXEC_ACTION_STOP;
}
const AmMimeBody* sdp_body = r->reply->body.hasContentType(SIP_APPLICATION_SDP);
if (!sdp_body) {
ERROR("No SDP in reply\n");
EXEC_ACTION_STOP;
}
AmSdp parser_sdp;
if (parser_sdp.parse((const char*)sdp_body->getPayload())) {
ERROR("error parsing SDP '%s'\n", sdp_body->getPayload());
EXEC_ACTION_STOP;
}
bool found = false;
for (vector<string>::iterator it = check_adrs.begin(); it != check_adrs.end(); it++) {
if (*it == parser_sdp.conn.address) {
DBG("found address!\n");
found = true;
break;
}
}
sc_sess->var["match_connection_addr"] = found ? "true":"false";
DBG("set: match_connection_addr = '%s'\n", sc_sess->var["match_connection_addr"].c_str());
} EXEC_ACTION_END;

@ -62,4 +62,11 @@ DEF_ACTION_1P(MODSBCRemoveFromMediaProcessor);
DEF_ACTION_2P(MODSBCRtpStreamsSetReceiving);
DEF_ACTION_1P(MODSBCClearExtLocalTag);
DEF_ACTION_1P(MODSBCSetExtLocalTag);
DEF_ACTION_1P(MODSBCSetLastReq);
DEF_ACTION_1P(MODSBCtestSDPConnectionAddress);
#endif

@ -41,6 +41,11 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("utils.playCountRight", SCUPlayCountRightAction);
DEF_CMD("utils.playCountLeft", SCUPlayCountLeftAction);
DEF_CMD("utils.getCountRight", SCUGetCountRightAction);
DEF_CMD("utils.getCountLeft", SCUGetCountLeftAction);
DEF_CMD("utils.getCountRightNoSuffix", SCUGetCountRightNoSuffixAction);
DEF_CMD("utils.getCountLeftNoSuffix", SCUGetCountLeftNoSuffixAction);
DEF_CMD("utils.getNewId", SCGetNewIdAction);
DEF_CMD("utils.spell", SCUSpellAction);
DEF_CMD("utils.rand", SCURandomAction);
@ -55,7 +60,53 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
} MOD_ACTIONEXPORT_END;
MOD_CONDITIONEXPORT_NONE(MOD_CLS_NAME);
MOD_CONDITIONEXPORT_BEGIN(MOD_CLS_NAME) {
if (cmd == "utils.isInList") {
return new IsInListCondition(params, false);
}
} MOD_CONDITIONEXPORT_END;
vector<string> utils_get_count_files(DSMSession* sc_sess, unsigned int cnt,
const string& basedir, const string& suffix, bool right) {
vector<string> res;
if (cnt <= 20) {
res.push_back(basedir+int2str(cnt)+suffix);
return res;
}
for (int i=9;i>1;i--) {
div_t num = div(cnt, (int)pow(10.,i));
if (num.quot) {
res.push_back(basedir+int2str(int(num.quot * pow(10.,i)))+suffix);
}
cnt = num.rem;
}
if (!cnt)
return res;
if ((cnt <= 20) || (!(cnt%10))) {
res.push_back(basedir+int2str(cnt)+suffix);
return res;
}
div_t num = div(cnt, 10);
if (right) {
// language has single digits before 10s
res.push_back(basedir+int2str(num.quot * 10)+suffix);
res.push_back(basedir+("x"+int2str(num.rem))+suffix);
} else {
// language has single digits before 10s
res.push_back(basedir+("x"+int2str(num.rem))+suffix);
res.push_back(basedir+int2str(num.quot * 10)+suffix);
}
return res;
}
bool utils_play_count(DSMSession* sc_sess, unsigned int cnt,
const string& basedir, const string& suffix, bool right) {
@ -131,6 +182,107 @@ EXEC_ACTION_START(SCUPlayCountLeftAction) {
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
CONST_ACTION_2P(SCUGetCountRightAction, ',', true);
EXEC_ACTION_START(SCUGetCountRightAction) {
string cnt_s = resolveVars(par1, sess, sc_sess, event_params);
string basedir = resolveVars(par2, sess, sc_sess, event_params);
unsigned int cnt = 0;
if (str2i(cnt_s,cnt)) {
ERROR("could not parse count '%s'\n", cnt_s.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
sc_sess->SET_STRERROR("could not parse count '"+cnt_s+"'\n");
return false;
}
vector<string> filenames = utils_get_count_files(sc_sess, cnt, basedir, ".wav", true);
cnt=0;
for (vector<string>::iterator it=filenames.begin();it!=filenames.end();it++) {
sc_sess->var["count_file["+int2str(cnt)+"]"]=*it;
cnt++;
}
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
CONST_ACTION_2P(SCUGetCountLeftAction, ',', true);
EXEC_ACTION_START(SCUGetCountLeftAction) {
string cnt_s = resolveVars(par1, sess, sc_sess, event_params);
string basedir = resolveVars(par2, sess, sc_sess, event_params);
unsigned int cnt = 0;
if (str2i(cnt_s,cnt)) {
ERROR("could not parse count '%s'\n", cnt_s.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
sc_sess->SET_STRERROR("could not parse count '"+cnt_s+"'\n");
return false;
}
vector<string> filenames = utils_get_count_files(sc_sess, cnt, basedir, ".wav", false);
cnt=0;
for (vector<string>::iterator it=filenames.begin();it!=filenames.end();it++) {
sc_sess->var["count_file["+int2str(cnt)+"]"]=*it;
cnt++;
}
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
CONST_ACTION_2P(SCUGetCountRightNoSuffixAction, ',', true);
EXEC_ACTION_START(SCUGetCountRightNoSuffixAction) {
string cnt_s = resolveVars(par1, sess, sc_sess, event_params);
string basedir = resolveVars(par2, sess, sc_sess, event_params);
unsigned int cnt = 0;
if (str2i(cnt_s,cnt)) {
ERROR("could not parse count '%s'\n", cnt_s.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
sc_sess->SET_STRERROR("could not parse count '"+cnt_s+"'\n");
return false;
}
vector<string> filenames = utils_get_count_files(sc_sess, cnt, basedir, "", true);
cnt=0;
for (vector<string>::iterator it=filenames.begin();it!=filenames.end();it++) {
sc_sess->var["count_file["+int2str(cnt)+"]"]=*it;
cnt++;
}
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
CONST_ACTION_2P(SCUGetCountLeftNoSuffixAction, ',', true);
EXEC_ACTION_START(SCUGetCountLeftNoSuffixAction) {
string cnt_s = resolveVars(par1, sess, sc_sess, event_params);
string basedir = resolveVars(par2, sess, sc_sess, event_params);
unsigned int cnt = 0;
if (str2i(cnt_s,cnt)) {
ERROR("could not parse count '%s'\n", cnt_s.c_str());
sc_sess->SET_ERRNO(DSM_ERRNO_UNKNOWN_ARG);
sc_sess->SET_STRERROR("could not parse count '"+cnt_s+"'\n");
return false;
}
vector<string> filenames = utils_get_count_files(sc_sess, cnt, basedir, "", false);
cnt=0;
for (vector<string>::iterator it=filenames.begin();it!=filenames.end();it++) {
sc_sess->var["count_file["+int2str(cnt)+"]"]=*it;
cnt++;
}
sc_sess->CLR_ERRNO;
} EXEC_ACTION_END;
EXEC_ACTION_START(SCGetNewIdAction) {
string d = resolveVars(arg, sess, sc_sess, event_params);
sc_sess->var[d]=AmSession::getNewId();
@ -308,3 +460,26 @@ EXEC_ACTION_START(SCUPlayRingToneAction) {
sc_sess->transferOwnership(rt);
} EXEC_ACTION_END;
CONST_CONDITION_2P(IsInListCondition, ',', false);
MATCH_CONDITION_START(IsInListCondition) {
string key = resolveVars(par1, sess, sc_sess, event_params);
string cslist = resolveVars(par2, sess, sc_sess, event_params);
DBG("checking whether '%s' is in list '%s'\n", key.c_str(), cslist.c_str());
bool res = false;
vector<string> items = explode(cslist, ",");
for (vector<string>::iterator it=items.begin(); it != items.end(); it++) {
if (key == trim(*it, " \t")) {
res = true;
break;
}
}
DBG("key %sfound\n", res?"":" not");
if (inv) {
return !res;
} else {
return res;
}
} MATCH_CONDITION_END;

@ -35,8 +35,15 @@
DECLARE_MODULE(MOD_CLS_NAME);
DEF_CONDITION_2P(IsInListCondition);
DEF_ACTION_2P(SCUPlayCountRightAction);
DEF_ACTION_2P(SCUPlayCountLeftAction);
DEF_ACTION_2P(SCUGetCountRightAction);
DEF_ACTION_2P(SCUGetCountLeftAction);
DEF_ACTION_2P(SCUGetCountRightNoSuffixAction);
DEF_ACTION_2P(SCUGetCountLeftNoSuffixAction);
DEF_ACTION_1P(SCGetNewIdAction);
DEF_ACTION_2P(SCUSpellAction);
DEF_ACTION_2P(SCURandomAction);

@ -42,6 +42,8 @@ int MOD_CLS_NAME::preload() {
initGenericErrorDefaultFunc(&handler);
handler = (xmlGenericErrorFunc)xml_err_func;
xmlSetGenericErrorFunc(NULL, &xml_err_func);
xmlKeepBlanksDefault(0);
xmlIndentTreeOutput = 1; // doesn't seem to have effect :/
return 0;
}
@ -51,6 +53,11 @@ MOD_ACTIONEXPORT_BEGIN(MOD_CLS_NAME) {
DEF_CMD("xml.evalXPath", MODXMLEvalXPathAction);
DEF_CMD("xml.XPathResultCount", MODXMLXPathResultNodeCount);
DEF_CMD("xml.getXPathResult", MODXMLgetXPathResult);
DEF_CMD("xml.printXPathResult", MODXMLprintXPathResult);
DEF_CMD("xml.updateXPathResult", MODXMLupdateXPathResult);
DEF_CMD("xml.docDump", MODXMLdocDump);
DEF_CMD("xml.setLoglevel", MODXMLSetLogLevelAction);
@ -263,6 +270,264 @@ EXEC_ACTION_START(MODXMLXPathResultNodeCount) {
} EXEC_ACTION_END;
CONST_ACTION_2P(MODXMLgetXPathResult, '=', false);
EXEC_ACTION_START(MODXMLgetXPathResult) {
string cnt_var = par1;
string xpath_res_var = resolveVars(par2, sess, sc_sess, event_params);
if (cnt_var.size() && cnt_var[0]=='$') {
cnt_var.erase(0,1);
}
ModXmlXPathObj* xpath_obj =
getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
if (NULL == xpath_obj){
DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
sc_sess->var[cnt_var] = "0";
EXEC_ACTION_STOP;
}
vector<string> res;
if (NULL == xpath_obj->xpathObj->nodesetval){
res.push_back(string());
} else {
xmlNodeSetPtr nodes = xpath_obj->xpathObj->nodesetval;
xmlNodePtr cur;
for (int i=0;i<xpath_obj->xpathObj->nodesetval->nodeNr;i++) {
if(nodes->nodeTab[i]->type == XML_NAMESPACE_DECL) {
xmlNsPtr ns;
ns = (xmlNsPtr)nodes->nodeTab[i];
cur = (xmlNodePtr)ns->next;
res.push_back(string(string((const char*) ns->prefix)+"="+string((const char*) ns->href)));
} else if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
cur = nodes->nodeTab[i];
xmlChar* c = xmlNodeGetContent(cur);
res.push_back(c ? string((const char*)c) : string());
} else {
cur = nodes->nodeTab[i];
res.push_back(string((const char*) cur->name)+"\": type "+int2str(cur->type));
}
}
}
if (res.size() == 1) {
sc_sess->var[cnt_var] = res[0];
DBG("set $%s='%s'\n", cnt_var.c_str(), res[0].c_str());
} else {
unsigned int p =0;
for (vector<string>::iterator it = res.begin(); it!= res.end(); it++) {
sc_sess->var[cnt_var+"["+int2str(p)+"]"] = *it;
DBG("set $%s='%s'\n", (cnt_var+"["+int2str(p)+"]").c_str(), it->c_str());
p++;
}
}
} EXEC_ACTION_END;
CONST_ACTION_2P(MODXMLprintXPathResult, '=', false);
EXEC_ACTION_START(MODXMLprintXPathResult) {
string cnt_var = par1;
string xpath_res_var = resolveVars(par2, sess, sc_sess, event_params);
if (cnt_var.size() && cnt_var[0]=='$') {
cnt_var.erase(0,1);
}
ModXmlXPathObj* xpath_obj =
getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
if (NULL == xpath_obj){
DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
sc_sess->var[cnt_var] = "0";
EXEC_ACTION_STOP;
}
string& res = sc_sess->var[cnt_var];
if (NULL == xpath_obj->xpathObj->nodesetval){
res = "";
} else {
xmlNodeSetPtr nodes = xpath_obj->xpathObj->nodesetval;
xmlNodePtr cur;
for (int i=0;i<xpath_obj->xpathObj->nodesetval->nodeNr;i++) {
if(nodes->nodeTab[i]->type == XML_NAMESPACE_DECL) {
xmlNsPtr ns;
ns = (xmlNsPtr)nodes->nodeTab[i];
cur = (xmlNodePtr)ns->next;
if(cur->ns) {
res += "namespace \""+string((const char*) ns->prefix)+"\"=\""+string((const char*) ns->href)+"\" for node "+
string((const char*) cur->ns->href)+":"+string((const char*) cur->name)+"\n";
} else {
res += "namespace \""+ string((const char*) ns->prefix) +"\"=\""+ string((const char*) ns->href) +"\" for node "+
string((const char*) cur->name)+"\n";
}
} else if(nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
cur = nodes->nodeTab[i];
if(cur->ns) {
xmlChar* c = xmlNodeGetContent(cur);
res += "element node \""+string((const char*) cur->ns->href)+":"+string((const char*) cur->name)+"\" content: \""+
(c?string((const char*)c):string("NULL"))+ "\"\n";
} else {
xmlChar* c = xmlNodeGetContent(cur);
res += "element node \""+string((const char*) cur->name)+"\" content: \""+ (c?string((const char*)c):string("NULL"))+"\n";
}
} else {
cur = nodes->nodeTab[i];
res += "node \""+string((const char*) cur->name)+"\": type "+int2str(cur->type)+"\n";
}
}
}
DBG("set $%s='%s'\n", cnt_var.c_str(), res.c_str());
} EXEC_ACTION_END;
/**
modified from http://www.xmlsoft.org/examples/xpath2.c (MIT license)
* update_xpath_nodes:
* @nodes: the nodes set.
* @value: the new value for the node(s)
*
* Prints the @nodes content to @output.
*/
static void
update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar* value, int index) {
int size;
int i;
assert(value);
size = (nodes) ? nodes->nodeNr : 0;
if (index < 0) {
// update all
/*
* NOTE: the nodes are processed in reverse order, i.e. reverse document
* order because xmlNodeSetContent can actually free up descendant
* of the node and such nodes may have been selected too ! Handling
* in reverse order ensure that descendant are accessed first, before
* they get removed. Mixing XPath and modifications on a tree must be
* done carefully !
*/
for(i = size - 1; i >= 0; i--) {
if (NULL == nodes->nodeTab[i])
continue;
xmlNodeSetContent(nodes->nodeTab[i], value);
/*
* All the elements returned by an XPath query are pointers to
* elements from the tree *except* namespace nodes where the XPath
* semantic is different from the implementation in libxml2 tree.
* As a result when a returned node set is freed when
* xmlXPathFreeObject() is called, that routine must check the
* element type. But node from the returned set may have been removed
* by xmlNodeSetContent() resulting in access to freed data.
* This can be exercised by running
* valgrind xpath2 test3.xml '//discarded' discarded
* There is 2 ways around it:
* - make a copy of the pointers to the nodes from the result set
* then call xmlXPathFreeObject() and then modify the nodes
* or
* - remove the reference to the modified nodes from the node set
* as they are processed, if they are not namespace nodes.
*/
if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL)
nodes->nodeTab[i] = NULL;
}
} else {
if (index >= size) {
ERROR("trying to update XML node %d, size is %d\n", index, size);
return;
}
if (NULL == nodes->nodeTab[index]) {
ERROR("trying to update XML node %d which is NULL\n", index);
}
xmlNodeSetContent(nodes->nodeTab[index], value);
/*
* All the elements returned by an XPath query are pointers to
* elements from the tree *except* namespace nodes where the XPath
* semantic is different from the implementation in libxml2 tree.
* As a result when a returned node set is freed when
* xmlXPathFreeObject() is called, that routine must check the
* element type. But node from the returned set may have been removed
* by xmlNodeSetContent() resulting in access to freed data.
* This can be exercised by running
* valgrind xpath2 test3.xml '//discarded' discarded
* There is 2 ways around it:
* - make a copy of the pointers to the nodes from the result set
* then call xmlXPathFreeObject() and then modify the nodes
* or
* - remove the reference to the modified nodes from the node set
* as they are processed, if they are not namespace nodes.
*/
if (nodes->nodeTab[index]->type != XML_NAMESPACE_DECL)
nodes->nodeTab[index] = NULL;
}
}
CONST_ACTION_2P(MODXMLupdateXPathResult, '=', false);
EXEC_ACTION_START(MODXMLupdateXPathResult) {
string xpath_res_var = resolveVars(par1, sess, sc_sess, event_params);
string value = resolveVars(par2, sess, sc_sess, event_params);
// support index
int index = -1;
if (xpath_res_var.size()>2 && xpath_res_var[xpath_res_var.size()-1]==']') {
size_t p = xpath_res_var.rfind('[');
if (p != string::npos) {
str2int(xpath_res_var.substr(p+1, xpath_res_var.size()-p-2), index);
xpath_res_var.erase(p);
}
}
DBG("index %d, var '%s'\n", index, xpath_res_var.c_str());
ModXmlXPathObj* xpath_obj =
getXMLElemFromVariable<ModXmlXPathObj>(sc_sess, xpath_res_var);
if (NULL == xpath_obj){
DBG("no xpath result found in '%s'\n", xpath_res_var.c_str());
EXEC_ACTION_STOP;
}
// todo: call xmlEncodeSpecialChars with doc
update_xpath_nodes(xpath_obj->xpathObj->nodesetval, (const xmlChar*) value.c_str(), index);
} EXEC_ACTION_END;
CONST_ACTION_2P(MODXMLdocDump, '=', false);
EXEC_ACTION_START(MODXMLdocDump) {
string res_var = par1;
string xml_doc_var = resolveVars(par2, sess, sc_sess, event_params);
if (res_var.size() && res_var[0]=='$') {
res_var.erase(0,1);
}
ModXmlDoc* xml_doc = getXMLElemFromVariable<ModXmlDoc>(sc_sess, xml_doc_var);
if (NULL == xml_doc) {
DBG("XML document not found t variable '%s'\n", xml_doc_var.c_str());
sc_sess->var[res_var] = "";
EXEC_ACTION_STOP;
}
xmlChar* mem;
int size;
xmlDocDumpFormatMemory(xml_doc->doc, &mem, &size, /* indent=*/1);
sc_sess->var[res_var] = string((const char*)mem, size);
xmlFree(mem);
DBG("set $%s to XML of size %d\n", res_var.c_str(), size);
} EXEC_ACTION_END;
EXEC_ACTION_START(MODXMLSetLogLevelAction) {
string xml_log_level_s = resolveVars(arg, sess, sc_sess, event_params);
if (xml_log_level_s == "error")

@ -47,6 +47,10 @@ DEF_ACTION_2P(MODXMLParseSIPMsgBodyAction);
DEF_ACTION_2P(MODXMLParseAction);
DEF_ACTION_2P(MODXMLEvalXPathAction);
DEF_ACTION_2P(MODXMLXPathResultNodeCount);
DEF_ACTION_2P(MODXMLgetXPathResult);
DEF_ACTION_2P(MODXMLprintXPathResult);
DEF_ACTION_2P(MODXMLupdateXPathResult);
DEF_ACTION_2P(MODXMLdocDump);
DEF_ACTION_1P(MODXMLSetLogLevelAction);
class ModXmlDoc

@ -167,7 +167,7 @@ int EarlyAnnounceFactory::onLoad()
/* Get default audio from MySQL */
string mysql_server, mysql_user, mysql_passwd, mysql_db;
string mysql_server, mysql_user, mysql_passwd, mysql_db, mysql_ca_cert;
mysql_server = cfg.getParameter("mysql_server");
if (mysql_server.empty()) {
@ -191,6 +191,8 @@ int EarlyAnnounceFactory::onLoad()
mysql_db = "sems";
}
mysql_ca_cert = cfg.getParameter("mysql_ca_cert");
AnnounceApplication = cfg.getParameter("application");
if (AnnounceApplication.empty()) {
AnnounceApplication = MOD_NAME;
@ -213,6 +215,10 @@ int EarlyAnnounceFactory::onLoad()
#else
Connection.set_option(new mysqlpp::ReconnectOption(true));
#endif
if (!mysql_ca_cert.empty())
Connection.set_option(
new mysqlpp::SslOption(0, 0, mysql_ca_cert.c_str(), "",
"DHE-RSA-AES256-SHA"));
Connection.connect(mysql_db.c_str(), mysql_server.c_str(),
mysql_user.c_str(), mysql_passwd.c_str());
if (!Connection) {

@ -0,0 +1,71 @@
# Skeleton of a call center style application that answers, plays beep_file
# to caller and then tries attendants on callee_list in round robin fashion
# as long as one of them replies
# Author Juha Heinanen <jh@tutpro.com>
import time
from log import *
from ivr import *
beep_file = "/var/lib/sems/audio/general/beep_snd.wav"
callee_list = ['sip:foo@test.tutpro.com', 'sip:test@test.tutpro.com']
beeping = 1
connecting = 2
connected = 3
class IvrDialog(IvrDialogBase):
def onSessionStart(self):
self.callee_list = callee_list
self.callee_index = 0
self.setNoRelayonly()
self.state = beeping
self.audio_msg = IvrAudioFile()
self.audio_msg.open(beep_file, AUDIO_READ)
self.enqueue(self.audio_msg, None)
def onBye(self):
self.stopSession()
def onEmptyQueue(self):
if self.state == beeping:
self.state = connecting
self.connectTry()
return
return
def onOtherReply(self, code, reason):
debug('call_center: got reply: ' + str(code) + ' ' + reason)
if self.state == connecting:
if code < 200:
return
if code >= 200 and code < 300:
self.flush()
self.disconnectMedia()
self.setRelayonly()
self.state = connected
debug('call_center: connected to ' + self.callee_uri)
return
if code >= 300:
time.sleep(3)
self.connectTry()
return
else:
return
def connectTry(self):
self.callee_uri = self.callee_list[self.callee_index]
self.callee_index = (self.callee_index + 1) % 2
debug('call_center: trying to connectCallee ' + self.callee_uri)
self.connectCallee(self.callee_uri, self.callee_uri)

@ -94,6 +94,16 @@ extern "C" {
return PyString_FromString(res.c_str());
}
static PyObject* ivr_getHeaders(PyObject*, PyObject* args)
{
char* headers;
char* header_name;
if(!PyArg_ParseTuple(args,"ss",&headers,&header_name))
return NULL;
string res = getHeader(headers,header_name);
return PyString_FromString(res.c_str());
}
static PyObject* ivr_ignoreSigchld(PyObject*, PyObject* args)
{
@ -135,6 +145,7 @@ extern "C" {
static PyMethodDef ivr_methods[] = {
{"log", (PyCFunction)ivr_log, METH_VARARGS,"Log a message using Sems' logging system"},
{"getHeader", (PyCFunction)ivr_getHeader, METH_VARARGS,"Python getHeader wrapper"},
{"getHeaders", (PyCFunction)ivr_getHeader, METH_VARARGS,"Python getHeaders wrapper"},
{"createThread", (PyCFunction)ivr_createThread, METH_VARARGS, "Create another interpreter thread"},
{"setIgnoreSigchld", (PyCFunction)ivr_ignoreSigchld, METH_VARARGS, "ignore SIGCHLD signal"},
{NULL} /* Sentinel */

@ -688,6 +688,7 @@ void CallLeg::onB2BReconnect(ReconnectLegEvent* ev)
// release old signaling and media session
clear_other();
clearRtpReceiverRelay();
relayed_req.clear();
// check if we aren't processing INVITE now (BLF ringing call pickup)
AmSipRequest *invite = dlg->getUASPendingInv();
@ -800,6 +801,8 @@ void CallLeg::disconnect(bool hold_remote, bool preserve_media_session)
clear_other();
set_sip_relay_only(false); // we can't relay once disconnected
est_invite_cseq = 0; // attempt to invalidate though 0 is valid value
relayed_req.clear(); // do not forward anything back any more
if (!hold_remote || isOnHold()) updateCallStatus(Disconnected);
else {

@ -1016,9 +1016,9 @@ bool _RegisterCache::saveSingleContact(RegisterCacheCtx& ctx,
AliasEntry alias_entry;
if(findAliasEntry(alias_map.begin()->first,alias_entry) &&
(now.tv_sec > alias_entry.ua_expire)) {
(now.tv_sec < alias_entry.ua_expire)) {
unsigned int exp = now.tv_sec - alias_entry.ua_expire;
unsigned int exp = alias_entry.ua_expire - now.tv_sec;
contact_hdr = SIP_HDR_COLSP(SIP_HDR_CONTACT)
+ alias_entry.contact_uri + ";expires="
+ int2str(exp) + CRLF;

@ -192,7 +192,7 @@ int RegisterDialog::fixUacContacts(const AmSipRequest& req)
}
// use existing 'expires' param if == 0 or greater than min value
if(contact_expires && (contact_expires < min_reg_expire)) {
if(contact_expires && (contact_expires < (long int)min_reg_expire)) {
// else use min value
contact_it->params["expires"] = int2str(min_reg_expire);
}
@ -214,7 +214,7 @@ int RegisterDialog::fixUacContacts(const AmSipRequest& req)
struct timeval now;
gettimeofday(&now,NULL);
if(max_ua_expire && (contact_expires > max_ua_expire))
if(max_ua_expire && (contact_expires > (long int)max_ua_expire))
contact_expires = max_ua_expire;
DBG("min_reg_expire = %u", min_reg_expire);

@ -244,8 +244,8 @@ int SBCFactory::onLoad()
return -1;
}
// TODO: add config param for the number of threads
subnot_processor.addThreads(1);
subnot_processor.addThreads(cfg.getParameterInt("out_of_dialog_threads",
DEFAULT_OOD_THREADS));
RegisterCache::instance()->start();
return 0;

@ -42,6 +42,8 @@ class SBCCallLeg;
using std::string;
#define DEFAULT_OOD_THREADS 1
#define SBC_TIMER_ID_CALL_TIMERS_START 10
#define SBC_TIMER_ID_CALL_TIMERS_END 99

@ -66,7 +66,7 @@ static const SdpPayload *findPayload(const std::vector<SdpPayload>& payloads, co
for (vector<SdpPayload>::const_iterator p = payloads.begin(); p != payloads.end(); ++p) {
// fix for clients using non-standard names for static payload type (SPA504g: G729a)
if (transport == TP_RTPAVP && payload.payload_type < 20) {
if (transport == TP_RTPAVP && payload.payload_type >= 0 && payload.payload_type < 20) {
if (payload.payload_type != p->payload_type) continue;
}
else {
@ -707,7 +707,9 @@ void SBCCallLeg::onDtmf(int event, int duration)
}
AmB2BMedia *ms = getMediaSession();
if(ms) {
// Don't send the DTMF to the other leg unless we are transcoding
if (ms && getRtpRelayMode() == RTP_Transcoding) {
DBG("sending DTMF (%i;%i)\n", event, duration);
ms->sendDtmf(!a_leg,event,duration);
}
@ -1440,9 +1442,10 @@ int SBCCallLeg::filterSdp(AmMimeBody &body, const string &method)
bool prefer_existing_codecs = call_profile.codec_prefs.preferExistingCodecs(a_leg);
bool needs_normalization =
call_profile.codec_prefs.shouldOrderPayloads(a_leg) ||
call_profile.transcoder.isActive() ||
!call_profile.sdpfilter.empty();
call_profile.codec_prefs.shouldOrderPayloads(a_leg) ||
call_profile.transcoder.isActive() ||
!call_profile.sdpfilter.empty() ||
!call_profile.aleg_sdpfilter.empty();
if (needs_normalization) {
normalizeSDP(sdp, false, ""); // anonymization is done in the other leg to use correct IP address
@ -1495,10 +1498,16 @@ int SBCCallLeg::filterSdp(AmMimeBody &body, const string &method)
// => So we wouldn't try to avoid filtering out transcoder codecs what would
// just complicate things.
if (call_profile.sdpfilter.size()) {
res = filterSDP(sdp, call_profile.sdpfilter);
// figure out appropriate SDP filter instance (A leg, or common one)
vector<FilterEntry>& sdpfilter = call_profile.sdpfilter;
if (!a_leg && call_profile.have_aleg_sdpfilter)
sdpfilter = call_profile.aleg_sdpfilter;
if (sdpfilter.size()) {
res = filterSDP(sdp, sdpfilter);
changed = true;
}
if (call_profile.sdpalinesfilter.size()) {
// filter SDP "a=lines"
filterSDPalines(sdp, call_profile.sdpalinesfilter);

@ -185,6 +185,12 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
if (!readFilter(cfg, "sdp_filter", "sdpfilter_list", sdpfilter, true))
return false;
have_aleg_sdpfilter = cfg.hasParameter("aleg_sdp_filter");
if (have_aleg_sdpfilter) {
if (!readFilter(cfg, "aleg_sdp_filter", "aleg_sdpfilter_list", aleg_sdpfilter, true))
return false;
}
if (!readFilter(cfg, "media_filter", "mediafilter_list", mediafilter, true))
return false;
@ -461,6 +467,16 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
sdpfilter.size()?"en":"dis", filter_type.c_str(), filter_elems,
anonymize_sdp?"":"not ");
if (have_aleg_sdpfilter) {
filter_type = aleg_sdpfilter.size() ?
FilterType2String(aleg_sdpfilter.back().filter_type) : "disabled";
filter_elems = aleg_sdpfilter.size() ? aleg_sdpfilter.back().filter_list.size() : 0;
INFO("SBC: separate A-leg SDP filter is %sabled, %s, %zd items in list\n",
sdpfilter.size()?"en":"dis", filter_type.c_str(), filter_elems);
} else {
INFO("SBC: separate A-leg SDP filter is disabled (same SDP filter for both legs)\n");
}
filter_type = sdpalinesfilter.size() ?
FilterType2String(sdpalinesfilter.back().filter_type) : "disabled";
filter_elems = sdpalinesfilter.size() ? sdpalinesfilter.back().filter_list.size() : 0;
@ -607,6 +623,7 @@ bool SBCCallProfile::operator==(const SBCCallProfile& rhs) const {
//messagefilter_list == rhs.messagefilter_list &&
//sdpfilter_enabled == rhs.sdpfilter_enabled &&
sdpfilter == rhs.sdpfilter &&
aleg_sdpfilter == rhs.aleg_sdpfilter &&
mediafilter == rhs.mediafilter &&
sst_enabled == rhs.sst_enabled &&
sst_aleg_enabled == rhs.sst_aleg_enabled &&
@ -1368,11 +1385,11 @@ static bool read(const std::string &src, vector<SdpPayload> &codecs)
for (vector<string>::iterator it=elems.begin(); it != elems.end(); ++it) {
SdpPayload p;
if (!readPayload(p, *it)) return false;
if (!readPayload(p, trim(*it, " "))) return false;
int payload_id = plugin->getDynPayload(p.encoding_name, p.clock_rate, 0);
amci_payload_t* payload = plugin->payload(payload_id);
if(!payload) {
ERROR("Ignoring unknown payload found in call profile: %s/%i\n",
ERROR("Ignoring unknown payload found in call profile: '%s/%i'\n",
p.encoding_name.c_str(), p.clock_rate);
}
else {

@ -155,6 +155,8 @@ struct SBCCallProfile
bool anonymize_sdp;
vector<FilterEntry> sdpfilter;
vector<FilterEntry> aleg_sdpfilter;
bool have_aleg_sdpfilter;
vector<FilterEntry> sdpalinesfilter;
vector<FilterEntry> mediafilter;
@ -355,7 +357,8 @@ struct SBCCallProfile
rtprelay_bw_limit_rate(-1),
rtprelay_bw_limit_peak(-1),
outbound_interface_value(-1),
contact_hiding(false),
have_aleg_sdpfilter(false),
contact_hiding(false),
reg_caching(false),
log_rtp(false),
log_sip(false),

@ -250,6 +250,12 @@ void SimpleRelayDialog::onB2BReply(const AmSipReply& reply)
relayReply(reply);
}
void SimpleRelayDialog::onSendRequest(AmSipRequest& req, int& flags)
{
if(auth_h.get())
auth_h->onSendRequest(req,flags);
}
void SimpleRelayDialog::onSipRequest(const AmSipRequest& req)
{
for (list<CCModuleInfo>::iterator i = cc_ext.begin();
@ -273,6 +279,25 @@ void SimpleRelayDialog::onSipReply(const AmSipRequest& req,
const AmSipReply& reply,
AmBasicSipDialog::Status old_dlg_status)
{
unsigned int cseq_before = cseq;
if(auth_h.get() && auth_h->onSipReply(req,reply,old_dlg_status)) {
if (cseq_before != cseq) {
DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; "
"updating relayed_req map\n", reply.cseq, cseq_before);
RelayMap::iterator t = relayed_reqs.find(reply.cseq);
if (t != relayed_reqs.end()) {
relayed_reqs[cseq_before] = t->second;
relayed_reqs.erase(t);
DBG("updated relayed_req (UAC trans): CSeq %u -> %u\n",
reply.cseq, cseq_before);
}
return;
}
}
for (list<CCModuleInfo>::iterator i = cc_ext.begin();
i != cc_ext.end(); ++i) {
i->module->onSipReply(req, reply, old_dlg_status, i->user_data);
@ -390,6 +415,46 @@ int SimpleRelayDialog::initUAC(const AmSipRequest& req,
if(!cp.bleg_dlg_contact_params.empty())
setContactParams(cp.bleg_dlg_contact_params);
if(cp.auth_enabled) {
// adding auth handler
AmDynInvokeFactory* uac_auth_f =
AmPlugIn::instance()->getFactory4Di("uac_auth");
if (NULL == uac_auth_f) {
ERROR("uac_auth module not loaded. uac auth NOT enabled.\n");
} else {
AmDynInvoke* uac_auth_i = uac_auth_f->getInstance();
// get a sessionEventHandler from uac_auth
AmArg di_args,ret;
AmArg cred_h,dlg_ctrl;
CredentialHolder* c = (CredentialHolder*)this;
cred_h.setBorrowedPointer(c);
DialogControl* cc = (DialogControl*)this;
dlg_ctrl.setBorrowedPointer(cc);
di_args.push(cred_h);
di_args.push(dlg_ctrl);
auth_cred.reset(new UACAuthCred(cp.auth_credentials));
uac_auth_i->invoke("getHandler", di_args, ret);
if (!ret.size()) {
ERROR("Can not add auth handler to new registration!\n");
} else {
AmObject* p = ret.get(0).asObject();
if (p != NULL) {
AmSessionEventHandler* h = dynamic_cast<AmSessionEventHandler*>(p);
if (h != NULL) {
auth_h.reset(h);
DBG("uac auth enabled for callee session.\n");
}
}
}
}
}
return 0;
}

@ -35,6 +35,8 @@
#include "SBC.h"
#include "ExtendedCCInterface.h"
#include "ampi/UACAuthAPI.h"
#include <map>
#include <list>
using std::map;
@ -43,7 +45,9 @@ class SimpleRelayDialog
: public AmBasicSipDialog,
public AmBasicSipEventHandler,
public AmEventQueue,
public AmEventHandler
public AmEventHandler,
public DialogControl,
public CredentialHolder
{
atomic_ref_cnt* parent_obj;
string other_dlg;
@ -64,6 +68,13 @@ class SimpleRelayDialog
};
std::list<CCModuleInfo> cc_ext;
// auth support
std::auto_ptr<UACAuthCred> auth_cred;
std::auto_ptr<AmSessionEventHandler> auth_h;
UACAuthCred* getCredentials() { return auth_cred.get(); }
AmBasicSipDialog* getDlg() { return this; }
// relay methods
int relayRequest(const AmSipRequest& req);
int relayReply(const AmSipReply& reply);
@ -132,6 +143,7 @@ public:
virtual bool terminated() { return finished; }
// AmBasicSipEventHandler interface
void onSendRequest(AmSipRequest& req, int& flags);
void onSipRequest(const AmSipRequest& req);
void onSipReply(const AmSipRequest& req,
const AmSipReply& reply,

@ -33,6 +33,7 @@
#include "SBCDSMParams.h"
#include "AmAdvancedAudio.h"
#include "AmRingTone.h"
#include <algorithm>
@ -621,6 +622,17 @@ void SBCDSMInstance::playSilence(unsigned int length, bool front) {
CLR_ERRNO;
}
void SBCDSMInstance::playRingtone(int length, int on, int off, int f, int f2, bool front) {
AmRingTone* af = new AmRingTone(length, on, off, f, f2);
if (front)
getPlaylist()->addToPlayListFront(new AmPlaylistItem(af, NULL));
else
getPlaylist()->addToPlaylist(new AmPlaylistItem(af, NULL));
audiofiles.push_back(af);
CLR_ERRNO;
}
NOT_IMPLEMENTED(recordFile(const string& name));
NOT_IMPLEMENTED_UINT(getRecordLength());
NOT_IMPLEMENTED_UINT(getRecordDataSize());

@ -95,6 +95,7 @@ class SBCDSMInstance
void playPrompt(const string& name, bool loop = false, bool front = false);
void playFile(const string& name, bool loop, bool front = false);
void playSilence(unsigned int length, bool front = false);
void playRingtone(int length, int on, int off, int f, int f2, bool front);
void recordFile(const string& name);
unsigned int getRecordLength();
unsigned int getRecordDataSize();

@ -327,6 +327,7 @@ void SyslogCDR::end(const string& ltag, SBCCallProfile* call_profile,
SBCVarMapIteratorT var_it = call_profile->cc_vars.find(varname);
if (var_it == call_profile->cc_vars.end()) {
DBG("unknown variable '%s' in cdr_format\n", it->c_str());
cdr+=csv_quote(string("")) + ",";
} else {
AmArg* v = &var_it->second;
if (!prop.empty()) {

@ -35,6 +35,9 @@ active_profile=transparent
# Default: no
#core_options_handling=yes
# How many threads to use for processing out-of-dialog messages, default: 1
# out_of_dialog_threads=4
## RFC4028 Session Timer
# default configuration - can be overridden by call profiles

@ -1,6 +1,8 @@
COREPATH_TOOLS ?= ../../../core
COREPATH?=$(COREPATH_TOOLS)
include $(COREPATH_TOOLS)/../Makefile.defs
sbc_scripts = $(wildcard sems-sbc-*)
all: install_tools

@ -10,7 +10,8 @@
WCCCallStats::WCCCallStats(const string& stats_dir)
: total(0),
failed(0),
seconds(0)
seconds(0),
write_cnt(0)
{
if (stats_dir.empty())
filename = "";

@ -3,7 +3,10 @@ plug_in_name = webconference
module_ldflags =
module_cflags =
extra_install = $(plug_in_name)_audio
extra_install = $(plug_in_name)_audio install_tools
COREPATH ?=../../core
include $(COREPATH)/plug-in/Makefile.app_module
install_tools:
-@$(MAKE) -C tools/ install

@ -98,6 +98,20 @@ void ConferenceRoom::newParticipant(const string& localtag,
const string& participant_id) {
gettimeofday(&last_access_time, NULL);
if (!participant_id.empty()) {
// search for participant with id and localtag empty
for (list<ConferenceRoomParticipant>::iterator it=participants.begin();
it != participants.end(); it++) {
if (it->participant_id == participant_id && it->localtag.empty()) {
DBG("found invited participant with ID '%s'\n", participant_id.c_str());
it->localtag = localtag;
it->number = number;
return;
}
}
}
participants.push_back(ConferenceRoomParticipant());
participants.back().localtag = localtag;
participants.back().number = number;
@ -105,15 +119,22 @@ void ConferenceRoom::newParticipant(const string& localtag,
}
bool ConferenceRoom::hasParticipant(const string& localtag) {
bool res = false;
for (list<ConferenceRoomParticipant>::iterator it =participants.begin();
it != participants.end();it++)
if (it->localtag == localtag) {
res = true;
break;
}
return res;
for (list<ConferenceRoomParticipant>::iterator it =
participants.begin(); it != participants.end();it++) {
if (it->localtag == localtag)
return true;
}
return false;
}
bool ConferenceRoom::hasInvitedParticipant(const string& participant_id) {
for (list<ConferenceRoomParticipant>::iterator it =
participants.begin(); it != participants.end();it++) {
if (it->participant_id == participant_id)
return true;
}
return false;
}
void ConferenceRoom::setMuted(const string& localtag, int mute) {

@ -80,6 +80,9 @@ struct ConferenceRoom {
bool hasParticipant(const string& localtag);
/** @returns whether participant is listed (and maybe not joined) */
bool hasInvitedParticipant(const string& participant_id);
void setMuted(const string& localtag, int mute);
bool expired(const struct timeval& now);

@ -79,6 +79,9 @@ unsigned int WebConferenceFactory::LonelyUserTimer = 0;
string WebConferenceFactory::participant_id_paramname; // default: param not used
string WebConferenceFactory::participant_id_hdr = "X-ParticipantID"; // default header
bool WebConferenceFactory::room_pin_split = false;
unsigned int WebConferenceFactory::room_pin_split_pos = 0;
int WebConferenceFactory::onLoad()
{
return getInstance()->load();
@ -157,6 +160,17 @@ int WebConferenceFactory::load()
direct_room_strip);
}
string room_pin_split_s = cfg.getParameter("room_pin_split");
if (!room_pin_split_s.empty()) {
if (str2i(room_pin_split_s, room_pin_split_pos)) {
ERROR("room_pin_split in webconference config not readable\n");
return -1;
}
room_pin_split = true;
} else {
room_pin_split = false;
}
string feedback_filename = cfg.getParameter("feedback_file");
if (!feedback_filename.empty()) {
feedback_file.open(feedback_filename.c_str(), std::ios::out|std::ios::app);
@ -255,11 +269,19 @@ int WebConferenceFactory::load()
return 0;
}
bool WebConferenceFactory::isValidConference(const string& conf_id) {
bool WebConferenceFactory::isValidConference(const string& conf_id, const string& participant_id) {
if (!PrivateRoomsMode)
return true;
bool res = false;
rooms_mut.lock();
bool res = rooms.find(conf_id) != rooms.end();
map<string, ConferenceRoom>::iterator room = rooms.find(conf_id);
if (room != rooms.end()) {
if (!participant_id.size() || room->second.hasInvitedParticipant(participant_id)) {
DBG("room '%s', participant_id '%s' -> valid\n", conf_id.c_str(), participant_id.c_str());
res = true;
}
}
rooms_mut.unlock();
return res;
}
@ -267,11 +289,25 @@ bool WebConferenceFactory::isValidConference(const string& conf_id) {
bool WebConferenceFactory::newParticipant(const string& conf_id,
const string& localtag,
const string& number,
const string& participant_id) {
const string& participant_id,
bool check_exisiting) {
rooms_mut.lock();
if (PrivateRoomsMode && rooms.find(conf_id) == rooms.end()) {
rooms_mut.unlock();
return false;
if (PrivateRoomsMode) {
map<string, ConferenceRoom>::iterator room = rooms.find(conf_id);
if (room == rooms.end()) {
rooms_mut.unlock();
return false;
}
DBG("found conference room '%s'\n", conf_id.c_str());
if (check_exisiting && room_pin_split && !room->second.hasInvitedParticipant(participant_id)) {
DBG("participant with ID '%s' not listed in invited participants for '%s'\n",
participant_id.c_str(), conf_id.c_str());
rooms_mut.unlock();
return false;
}
}
rooms[conf_id].newParticipant(localtag, number, participant_id);
@ -375,22 +411,34 @@ AmSession* WebConferenceFactory::onInvite(const AmSipRequest& req, const string&
if (!session_timer_f->onInvite(req, cfg))
return NULL;
}
WebConferenceDialog* w;
if (use_direct_room && !regexec(&direct_room_re, req.user.c_str(), 0,0,0)) {
string room = req.user;
if (room.length() > direct_room_strip)
room = room.substr(direct_room_strip);
DBG("direct room access match. connecting to room '%s'\n",
room.c_str());
w = new WebConferenceDialog(prompts, getInstance(), room);
w->setUri(getAccessUri(room));
map<string,string>::const_iterator r_it = app_params.find("room");
map<string,string>::const_iterator enter_room_it = app_params.find("enter_room");
if (enter_room_it != app_params.end() && enter_room_it->second=="true") {
// enter the room
DBG("creating new Webconference call, room name to be entered via keypad\n");
w = new WebConferenceDialog(prompts, getInstance(), NULL);
} else if (r_it != app_params.end()) {
// use provided room name
string room = r_it->second;
DBG("creating new Webconference call, room name '%s'\n", room.c_str());
w = new WebConferenceDialog(prompts, getInstance(), room);
w->setUri(getAccessUri(room));
} else if (use_direct_room && !regexec(&direct_room_re, req.user.c_str(), 0,0,0)) {
// regegex match
string room = req.user;
if (room.length() > direct_room_strip)
room = room.substr(direct_room_strip);
DBG("direct room access match. connecting to room '%s'\n", room.c_str());
w = new WebConferenceDialog(prompts, getInstance(), room);
w->setUri(getAccessUri(room));
} else {
// enter the room
w = new WebConferenceDialog(prompts, getInstance(), NULL);
}
setupSessionTimer(w);
return w;
}
@ -433,6 +481,10 @@ void WebConferenceFactory::invoke(const string& method,
args.assertArrayFmt("ss");
roomDelete(args, ret);
ret.push(getServerInfoString().c_str());
} else if(method == "addParticipant"){
args.assertArrayFmt("sss"); // conf_id, participant_id, number
roomAddParticipant(args, ret);
ret.push(getServerInfoString().c_str());
} else if(method == "dialout"){
args.assertArrayFmt("sssssss");
dialout(args, ret);
@ -662,6 +714,20 @@ void WebConferenceFactory::closeExpiredRooms() {
}
}
void WebConferenceFactory::roomAddParticipant(const AmArg& args, AmArg& ret) {
string room = args.get(0).asCStr();
string participant_id = args.get(1).asCStr();
string number = args.get(2).asCStr();
if (newParticipant(room, /* ltag = */ "", number, participant_id,
/* check_exisiting = */ false)) {
ret.push(200);
ret.push("OK");
} else {
ret.push(400);
ret.push("Failed"); // no info here
}
}
void WebConferenceFactory::dialout(const AmArg& args, AmArg& ret) {
string room = args.get(0).asCStr();
string adminpin = args.get(1).asCStr();

@ -140,6 +140,9 @@ public:
static unsigned int LonelyUserTimer;
static bool room_pin_split;
static unsigned int room_pin_split_pos;
// P-App-Param parameter to get participant ID from
static string participant_id_paramname;
// if participant_id_paramname not configured:
@ -154,11 +157,12 @@ public:
AmArg& session_params);
int onLoad();
bool isValidConference(const string& conf_id);
bool isValidConference(const string& conf_id, const string& participant_id);
bool newParticipant(const string& conf_id,
const string& localtag,
const string& number,
const string& participant_id);
const string& participant_id,
bool check_exisiting=true);
void updateStatus(const string& conf_id,
const string& localtag,
ConferenceRoomParticipant::ParticipantStatus status,
@ -182,6 +186,7 @@ public:
void roomInfo(const AmArg& args, AmArg& ret);
void roomDelete(const AmArg& args, AmArg& ret);
void changeRoomAdminpin(const AmArg& args, AmArg& ret);
void roomAddParticipant(const AmArg& args, AmArg& ret);
void dialout(const AmArg& args, AmArg& ret);
void kickout(const AmArg& args, AmArg& ret);

@ -151,6 +151,26 @@ void WebConferenceDialog::onSessionStart() {
prompts.addToPlaylist(ENTER_PIN, (long)this, play_list);
} else {
DBG("########## direct connect conference '%s' #########\n", conf_id.c_str());
string orig_pin_str = conf_id;
pin_str = conf_id;
if (WebConferenceFactory::room_pin_split) {
if (pin_str.length() <= WebConferenceFactory::room_pin_split_pos) {
DBG("short conference room/pin combination ('%s', want at least %d)\n",
pin_str.c_str(), WebConferenceFactory::room_pin_split_pos);
setInOut(&play_list,&play_list);
play_list.flush();
prompts.addToPlaylist(WRONG_PIN, (long)this, play_list);
pin_str ="";
return;
}
participant_id = pin_str.substr(WebConferenceFactory::room_pin_split_pos);
conf_id = pin_str.substr(0, WebConferenceFactory::room_pin_split_pos);
DBG("split entered pin into room '%s' and PIN '%s'\n", conf_id.c_str(), participant_id.c_str());
}
if (!factory->newParticipant(conf_id, getLocalTag(), dlg->getRemoteParty(),
participant_id)) {
DBG("inexisting conference room '%s\n", conf_id.c_str());
@ -363,7 +383,7 @@ void WebConferenceDialog::process(AmEvent* ev)
if (!factory->newParticipant(pin_str, getLocalTag(), dlg->getRemoteParty(),
participant_id)) {
DBG("inexisting conference room '%s'\n", pin_str.c_str());
DBG("inexisting conference room '%s' or pin wrong\n", pin_str.c_str());
state = PlayErrorFinish;
setInOut(&play_list,&play_list);
prompts.addToPlaylist(WRONG_PIN_BYE, (long)this, play_list);
@ -430,25 +450,51 @@ void WebConferenceDialog::onDtmf(int event, int duration) {
play_list.flush();
} else if (event==10 || event==11) {
// pound and star key
if (!pin_str.length() || !factory->isValidConference(pin_str)) {
if (!pin_str.length()) {
prompts.addToPlaylist(WRONG_PIN, (long)this, play_list, true);
pin_str.clear();
} else {
state = EnteringConference;
setInOut(NULL, NULL);
play_list.flush();
for (size_t i=0;i<pin_str.length();i++) {
string num = "";
num[0] = pin_str[i];
DBG("adding '%s' to playlist.\n", num.c_str());
return;
}
string orig_pin_str = pin_str;
prompts.addToPlaylist(num, (long)this, play_list);
if (WebConferenceFactory::room_pin_split) {
if (pin_str.length() <= WebConferenceFactory::room_pin_split_pos) {
DBG("short conference room/pin combination ('%s', want at least %d)\n",
pin_str.c_str(), WebConferenceFactory::room_pin_split_pos);
setInOut(&play_list,&play_list);
play_list.flush();
prompts.addToPlaylist(WRONG_PIN, (long)this, play_list);
pin_str ="";
return;
}
setInOut(&play_list,&play_list);
prompts.addToPlaylist(ENTERING_CONFERENCE, (long)this, play_list);
play_list.addToPlaylist(new AmPlaylistItem(&separator, NULL));
participant_id = pin_str.substr(WebConferenceFactory::room_pin_split_pos);
pin_str = pin_str.substr(0, WebConferenceFactory::room_pin_split_pos);
DBG("split entered pin into room '%s' and PIN '%s'\n", pin_str.c_str(), participant_id.c_str());
}
if (!factory->isValidConference(pin_str, WebConferenceFactory::room_pin_split ?
participant_id : "")) {
setInOut(&play_list,&play_list);
play_list.flush();
prompts.addToPlaylist(WRONG_PIN, (long)this, play_list);
pin_str ="";
return;
}
state = EnteringConference;
setInOut(NULL, NULL);
play_list.flush();
for (size_t i=0;i<orig_pin_str.length();i++) {
string num = "";
num[0] = orig_pin_str[i];
DBG("adding '%s' to playlist.\n", num.c_str());
prompts.addToPlaylist(num, (long)this, play_list);
}
setInOut(&play_list,&play_list);
prompts.addToPlaylist(ENTERING_CONFERENCE, (long)this, play_list);
play_list.addToPlaylist(new AmPlaylistItem(&separator, NULL));
}
}
}

@ -29,6 +29,13 @@ star_finishes_pin=yes
# the conference room, without asking for the PIN.
#
direct_room_re=000777.*
# room_pin_split=n - split PIN/room mode, if set split pin in room
# and pin at position n, makes sense together with private
# rooms mode
#
#room_pin_split=4
#
# direct_room_strip specifies the number of digits to strip
# from the user part of the request uri to get the conference

@ -0,0 +1,18 @@
COREPATH_TOOLS ?= ../../../core
COREPATH ?= ../../../core
include $(COREPATH_TOOLS)/../Makefile.defs
wc_scripts = $(wildcard sems-webconference-*)
all: install_tools
install: install_tools
install_tools: $(DESTDIR)$(bin-prefix)/$(bin-dir)
-@for r in $(wc_scripts) ; do \
x=`echo $$r | sed s/sems-/$(APP_NAME)-/g` ; \
echo "installing $$r -> $$x" ; \
$(INSTALL-TOUCH) $(DESTDIR)$(bin-prefix)/$(bin-dir)/$$x ; \
$(INSTALL-BIN) $$r $(DESTDIR)$(bin-prefix)/$(bin-dir)/$$x ; \
done

@ -0,0 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from xmlrpclib import *
if len(sys.argv) != 4:
print "usage: %s <room> <participant_id> <number>" % sys.argv[0]
sys.exit(1)
s = ServerProxy('http://localhost:8090')
print "Active calls: %d" % s.calls()
print s.di('webconference','addParticipant', sys.argv[1], sys.argv[2], sys.argv[3])

@ -0,0 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from xmlrpclib import *
if len(sys.argv) != 2:
print "usage: %s <room name>" % sys.argv[0]
sys.exit(1)
s = ServerProxy('http://localhost:8090')
print "Active calls: %d" % s.calls()
print s.di('webconference','roomCreate', sys.argv[1])

@ -0,0 +1,12 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from xmlrpclib import *
if len(sys.argv) != 3:
print "usage: %s <room> <adminpin>" % sys.argv[0]
sys.exit(1)
s = ServerProxy('http://localhost:8090')
print "Active calls: %d" % s.calls()
print s.di('webconference','roomInfo', sys.argv[1], sys.argv[2])

@ -72,7 +72,6 @@ int XMLRPC2DI::load() {
if (configured) // load only once
return 0;
configured = true;
AmConfigReader cfg;
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
@ -105,7 +104,6 @@ int XMLRPC2DI::load() {
ServerRetryAfter = cfg.getParameterInt("server_retry_after", 10);
DBG("retrying failed server after %u seconds\n", ServerRetryAfter);
string server_timeout = cfg.getParameter("server_timeout");
if (!server_timeout.empty()) {
unsigned int server_timeout_i = 0;
@ -167,6 +165,8 @@ int XMLRPC2DI::load() {
}
server->start();
server->waitUntilStarted();
return 0;
}
@ -337,7 +337,7 @@ XMLRPC2DIServer::XMLRPC2DIServer(unsigned int port,
: AmEventQueue(this),
port(port),
bind_ip(bind_ip),
s(s),
s(s), running(false),
// register method 'calls'
calls_method(s),
// register method 'set_loglevel'
@ -354,8 +354,6 @@ XMLRPC2DIServer::XMLRPC2DIServer(unsigned int port,
getcpsmax_method(s),
getcpslimit_method(s),
setcpslimit_method(s)
{
INFO("XMLRPC Server: enabled builtin method 'calls'\n");
INFO("XMLRPC Server: enabled builtin method 'get_loglevel'\n");
@ -634,19 +632,19 @@ void XMLRPC2DIServer::xmlrpcval2amarg(XmlRpcValue& v, AmArg& a) {
if (v.valid()) {
switch (v.getType()) {
case XmlRpcValue::TypeInt: { /* DBG("X->A INT\n"); */ a = (int)v; } break;
case XmlRpcValue::TypeDouble:{ /* DBG("X->A DBL\n"); */ a = (double)v; } break;
case XmlRpcValue::TypeString:{ /* DBG("X->A STR\n"); */ a = ((string)v).c_str(); } break;
case XmlRpcValue::TypeBoolean : { /* DBG("X->A BOL\n"); */ a = (bool)v; }
case XmlRpcValue::TypeInvalid : { /* DBG("X->A BOL\n"); */ a = AmArg(); }
case XmlRpcValue::TypeDouble:{ /* DBG("X->A DBL\n"); */ a = (double)v; } break;
case XmlRpcValue::TypeString:{ /* DBG("X->A STR\n"); */ a = ((string)v).c_str(); } break;
case XmlRpcValue::TypeBoolean : { /* DBG("X->A BOL\n"); */ a = (bool)v; } break;
case XmlRpcValue::TypeInvalid : { /* DBG("X->A Inv\n"); */ a = AmArg(); } break;
case XmlRpcValue::TypeArray: {
/* DBG("X->A ARR\n"); */
// DBG("X->A ARR\n");
a.assertArray();
xmlrpcval2amargarray(v, a, 0);
} break;
#ifdef XMLRPCPP_SUPPORT_STRUCT_ACCESS
case XmlRpcValue::TypeStruct: {
/* DBG("X->A STR\n"); */
// DBG("X->A STR\n");
a.assertStruct();
const XmlRpc::XmlRpcValue::ValueStruct& xvs =
(XmlRpc::XmlRpcValue::ValueStruct)v;

@ -105,7 +105,7 @@ class XMLRPC2DIServer
unsigned int port;
string bind_ip;
AmSharedVar<bool> running;
AmCondition<bool> running;
XMLRPC2DIServerCallsMethod calls_method;
XMLRPC2DIServerSetLoglevelMethod setloglevel_method;
@ -137,8 +137,12 @@ class XMLRPC2DIServer
void run();
void on_stop();
void waitUntilStarted() { running.wait_for(); }
static void xmlrpcval2amargarray(XmlRpcValue& v, AmArg& a, unsigned int start_index);
static void xmlrpcval2amargarray(XmlRpcValue& v, AmArg& a,
unsigned int start_index);
static void xmlrpcval2amarg(XmlRpcValue& v, AmArg& a);
/** convert all args in a into result*/

@ -2,7 +2,7 @@
CXX = g++
SRC = ./src
SHARED = -shared
CPPFLAGS = -I$(SRC) -fPIC -Wno-deprecated-declarations
CPPFLAGS += -I$(SRC) -fPIC -Wno-deprecated-declarations
DEBUG = -g
OPTIMIZE = -O2
GCCWARN = -Wall #-Wstrict-prototypes

@ -68,7 +68,9 @@ public:
}
};
_AmAppTimer::_AmAppTimer() {
_AmAppTimer::_AmAppTimer()
: direct_timers_mut(true)
{
}
_AmAppTimer::~_AmAppTimer() {
@ -122,9 +124,7 @@ void _AmAppTimer::direct_app_timer_cb(direct_app_timer* t)
delete t;
// finally fire this timer!
direct_timers_mut.unlock();
dt->fire();
return;
}
}
direct_timers_mut.unlock();

@ -471,3 +471,28 @@ string AmArg::print(const AmArg &a) {
}
return "<UNKONWN TYPE>";
}
const int arg2int(const AmArg &a)
{
if (isArgInt(a)) return a.asInt();
if (isArgBool(a)) return a.asBool();
if (isArgCStr(a)) {
int res;
if (!str2int(a.asCStr(), res)) {
throw std::string("can't convert arg to int: " + string(a.asCStr()));
}
return res;
}
throw std::string("can't convert arg to int");
}
string arg2str(const AmArg &a)
{
if (isArgUndef(a)) return "";
if (isArgInt(a)) return int2str(a.asInt());
if (isArgBool(a)) return int2str(a.asBool());
if (isArgCStr(a)) return a.asCStr();
throw std::string("can't convert arg to string");
}

@ -361,11 +361,14 @@ class AmArg
static string print(const AmArg &a);
const char* t2str(int type);
static const char* t2str(int type);
};
// equality
bool operator==(const AmArg& lhs, const AmArg& rhs);
const int arg2int(const AmArg &a);
string arg2str(const AmArg &a);
#endif

@ -42,6 +42,30 @@ AmAudioFileFormat::AmAudioFileFormat(const string& name, int subtype)
channels = p_subtype->channels;
subtype = p_subtype->type;
}
DBG("created AmAudioFileFormat of subtype %i, with rate %u, channels %u\n",
subtype, rate, channels);
}
AmAudioFileFormat::AmAudioFileFormat(const string& name, int subtype, amci_subtype_t* p_subtype)
: name(name), subtype(subtype), p_subtype(p_subtype)
{
codec = getCodec();
if(p_subtype && codec){
rate = p_subtype->sample_rate;
channels = p_subtype->channels;
}
DBG("created AmAudioFileFormat of subtype %i, with rate %u, channels %u\n",
subtype, rate, channels);
}
amci_codec_t* AmAudioFileFormat::getCodec()
{
if(p_subtype && p_subtype->codec_id != codec_id){
codec_id = p_subtype->codec_id;
destroyCodec();
}
return AmAudioFormat::getCodec();
}
void AmAudioFileFormat::setSubtypeId(int subtype_id) {
@ -92,15 +116,16 @@ AmAudioFileFormat* AmAudioFile::fileName2Fmt(const string& name, const string& s
return NULL;
}
int subtype_id = -1;
if (!subtype.empty()) {
subtype_id = AmPlugIn::instance()->subtypeID(iofmt, subtype);
if (subtype_id<0) {
WARN("subtype '%s' for file '%s' not found. Using default subtype\n",
subtype.c_str(), name.c_str());
amci_subtype_t* st = AmPlugIn::instance()->subtype(iofmt, subtype);
if (st!=NULL) {
return new AmAudioFileFormat(iofmt->name, st->type, st);
}
WARN("subtype '%s' for file '%s' not found. Using default subtype\n",
subtype.c_str(), name.c_str());
}
return new AmAudioFileFormat(iofmt->name, subtype_id);
return new AmAudioFileFormat(iofmt->name, -1);
}
@ -376,7 +401,13 @@ int AmAudioFile::read(unsigned int user_ts, unsigned int size)
ret = -2; // eof
} else {
// read from file
s = fread((void*)((unsigned char*)samples),1,s,fp);
int rs = fread((void*)((unsigned char*)samples),1,s,fp);
if (rs != s) {
DBG("marking data size as invalid as we read %d but should read %d", rs, s);
// we read less than we should => data size is probably broken
data_size = -1;
s = rs;
}
ret = (!ferror(fp) ? s : -1);
}
@ -438,7 +469,6 @@ int AmAudioFile::getLength()
if (!data_size || !fmt.get())
return 0;
return
fmt->bytes2samples(data_size)*1000
/ fmt->getRate();
float rate = fmt->getRate() / 1000;
return (int) (fmt->bytes2samples(data_size) / rate);
}

@ -54,9 +54,14 @@ public:
* @param subtype Subtype for the file format (see amci.h).
*/
AmAudioFileFormat(const string& name, int subtype = -1);
/** format with rate & channels, not taken from subtype */
AmAudioFileFormat(const string& name, int subtype, amci_subtype_t* p_subtype);
virtual ~AmAudioFileFormat() { }
virtual amci_codec_t* getCodec();
/** @return Format name. */
string getName() { return name; }
/** @return Format subtype. */

@ -30,7 +30,7 @@
#define IS_ONLY_ONCE (flags & AUDIO_MIXIN_ONCE)
#define IS_IMMEDIATE_START (flags & AUDIO_MIXIN_IMMEDIATE_START)
AmAudioMixIn::AmAudioMixIn(AmAudio* A, AmAudioFile* B,
AmAudioMixIn::AmAudioMixIn(AmAudio* A, AmAudio* B,
unsigned int s, double l,
unsigned int flags)
: A(A),B(B), s(s), l(l),
@ -71,10 +71,14 @@ int AmAudioMixIn::get(unsigned long long system_ts, unsigned char* buffer,
if (res <= 0) { // B empty
res = A->get(system_ts, buffer, output_sample_rate, nb_samples);
mixing = false;
if (IS_ONLY_ONCE)
if (IS_ONLY_ONCE) {
B = NULL;
else
B->rewind();
} else {
AmAudioFile* B_file = dynamic_cast<AmAudioFile*>(B);
if (NULL != B_file) {
B_file->rewind();
}
}
}
B_mut.unlock();
return res;
@ -110,10 +114,14 @@ int AmAudioMixIn::get(unsigned long long system_ts, unsigned char* buffer,
if (len<0) { // B finished
mixing = false;
if (IS_ONLY_ONCE)
if (IS_ONLY_ONCE) {
B = NULL;
else
B->rewind();
} else {
AmAudioFile* B_file = dynamic_cast<AmAudioFile*>(B);
if (NULL != B_file) {
B_file->rewind();
}
}
} else {
for (int i=0; i<(PCM16_B2S(len)); i++) {
pdest[i]+=(short)(((double)mix_buf[i])*l);
@ -135,7 +143,7 @@ int AmAudioMixIn::put(unsigned long long system_ts, unsigned char* buffer,
return -1;
}
void AmAudioMixIn::mixin(AmAudioFile* f) {
void AmAudioMixIn::mixin(AmAudio* f) {
B_mut.lock();
B = f;
mixing = next_start_ts_i = false; /* so that mix in will re-start */

@ -52,7 +52,7 @@
class AmAudioMixIn : public AmAudio {
AmAudio* A;
AmAudioFile* B;
AmAudio* B;
unsigned int s;
double l;
int flags;
@ -68,12 +68,12 @@ class AmAudioMixIn : public AmAudio {
public:
AmAudioMixIn(AmAudio* A, AmAudioFile* B,
AmAudioMixIn(AmAudio* A, AmAudio* B,
unsigned int s, double l,
unsigned int flags = 0);
~AmAudioMixIn();
void mixin(AmAudioFile* f);
void mixin(AmAudio* f);
protected:
// not used

@ -654,8 +654,14 @@ void AmB2BMedia::clearAudio(bool a_leg)
// remove streams from AmRtpReceiver first! (always both?)
i->a.stopStreamProcessing();
i->b.stopStreamProcessing();
if (a_leg) i->a.clear();
else i->b.clear();
if (a_leg) {
i->b.setRelayStream(NULL);
i->a.clear();
}
else {
i->a.setRelayStream(NULL);
i->b.clear();
}
}
for (RelayStreamIterator j = relay_streams.begin(); j != relay_streams.end(); ++j) {

@ -638,9 +638,11 @@ void AmB2BSession::onSessionTimeout() {
}
void AmB2BSession::onRemoteDisappeared(const AmSipReply& reply) {
DBG("remote unreachable, ending other leg\n");
terminateOtherLeg();
AmSession::onRemoteDisappeared(reply);
if (dlg && dlg->getStatus() == AmBasicSipDialog::Connected) {
DBG("%c leg: remote unreachable, ending other leg\n", a_leg?'A':'B');
terminateOtherLeg();
AmSession::onRemoteDisappeared(reply);
}
}
void AmB2BSession::onNoAck(unsigned int cseq)

@ -421,7 +421,12 @@ void AmBasicSipDialog::termUacTrans()
}
}
void AmBasicSipDialog::onRxReply(const AmSipReply& reply)
void AmBasicSipDialog::dropTransactions() {
termUacTrans();
uas_trans.clear();
}
bool AmBasicSipDialog::onRxReplySanity(const AmSipReply& reply)
{
if(ext_local_tag.empty()) {
if(reply.from_tag != local_tag) {
@ -438,6 +443,14 @@ void AmBasicSipDialog::onRxReply(const AmSipReply& reply)
//return;
}
return true;
}
void AmBasicSipDialog::onRxReply(const AmSipReply& reply)
{
if(!onRxReplySanity(reply))
return;
TransMap::iterator t_it = uac_trans.find(reply.cseq);
if(t_it == uac_trans.end()){
ERROR("could not find any transaction matching reply: %s\n",
@ -470,7 +483,8 @@ void AmBasicSipDialog::onRxReply(const AmSipReply& reply)
void AmBasicSipDialog::updateDialogTarget(const AmSipReply& reply)
{
if( (reply.code > 100) && (reply.code < 300) &&
reply.to_uri.length() &&
!reply.to_uri.empty() &&
!reply.to_tag.empty() &&
(remote_uri.empty() ||
(reply.cseq_method.length()==6 &&
((reply.cseq_method == SIP_METH_INVITE) ||

@ -177,6 +177,13 @@ protected:
*/
virtual bool onRxReqStatus(const AmSipRequest& req) { return true; }
/**
* Basic sanity check on received replies
*
* @return true to continue processing, false otherwise
*/
virtual bool onRxReplySanity(const AmSipReply& reply);
/**
* Executed from onRxReply() to allow inherited classes
* to extend the basic behavior (deletes the transaction on final reply).
@ -385,6 +392,8 @@ public:
termUacTrans();
}
virtual void dropTransactions();
/**
* This method should only be used to send responses
* to requests which are not referenced by any dialog.

@ -65,6 +65,7 @@ int AmFileCache::load(const std::string& filename) {
if (fstat(fd, &sbuf) == -1) {
ERROR("cannot stat file '%s'.\n",
name.c_str());
close(fd);
return -2;
}
@ -72,10 +73,12 @@ int AmFileCache::load(const std::string& filename) {
fd, 0)) == (caddr_t)(-1)) {
ERROR("cannot mmap file '%s'.\n",
name.c_str());
close(fd);
return -3;
}
data_size = sbuf.st_size;
close(fd);
return 0;
}

@ -1,17 +1,38 @@
#include "AmConferenceChannel.h"
#include "AmConfig.h"
#include "AmUtils.h"
#include <assert.h>
#include <fstream>
#include <string.h>
AmConferenceChannel::AmConferenceChannel(AmConferenceStatus* status, int channel_id, bool own_channel)
: status(status), channel_id(channel_id), own_channel(own_channel)
AmConferenceChannel::AmConferenceChannel(AmConferenceStatus* status, int channel_id, string channel_tag, bool own_channel)
: status(status), channel_id(channel_id), channel_tag(channel_tag), own_channel(own_channel),
have_in_sr(false), have_out_sr(false), in_file(NULL), out_file(NULL)
{
assert(status);
conf_id = status->getConfID();
if (AmConfig::DumpConferenceStreams) {
in_file_name = AmConfig::DumpConferencePath + "/"+conf_id+"_"+int2str(channel_id)+"_"+long2str((long)this)+"_in.s16";
out_file_name = AmConfig::DumpConferencePath + "/"+conf_id+"_"+int2str(channel_id)+"_"+long2str((long)this)+"_out.s16";
DBG("conf channel opening in_file '%s'\n", in_file_name.c_str());
in_file = new ChannelWritingFile(in_file_name.c_str());
DBG("conf channel opening out_file '%s'\n", out_file_name.c_str());
out_file = new ChannelWritingFile(out_file_name.c_str());
}
}
AmConferenceChannel::~AmConferenceChannel()
{
if(own_channel)
AmConferenceStatus::releaseChannel(conf_id,channel_id);
if (in_file)
in_file->close();
if (out_file)
out_file->close();
}
int AmConferenceChannel::put(unsigned long long system_ts, unsigned char* buffer,
@ -19,6 +40,22 @@ int AmConferenceChannel::put(unsigned long long system_ts, unsigned char* buffer
{
memcpy((unsigned char*)samples,buffer,size);
AmMultiPartyMixer* mixer = status->getMixer();
if (AmConfig::DumpConferenceStreams) {
if (!have_in_sr) {
DBG("writing sample rate of %u to %s\n", input_sample_rate, (in_file_name+".samplerate").c_str());
std::ofstream ofs((in_file_name+".samplerate").c_str());
if (ofs.good()) {
ofs << int2str(input_sample_rate);
ofs.close();
}
have_in_sr = true;
}
if (in_file) {
in_file->write(buffer, size);
}
}
mixer->lock();
size = resampleInput(samples,size,input_sample_rate,
mixer->GetCurrentSampleRate());
@ -40,7 +77,50 @@ int AmConferenceChannel::get(unsigned long long system_ts, unsigned char* buffer
PCM16_S2B(nb_samples * mixer->GetCurrentSampleRate() / output_sample_rate) : 0;
unsigned int mixer_sample_rate = 0;
mixer->GetChannelPacket(channel_id,system_ts,buffer,size,mixer_sample_rate);
if (AmConfig::DumpConferenceStreams) {
if (!have_out_sr) {
DBG("writing mixer sample rate of %u to %s\n", mixer_sample_rate, (out_file_name+".samplerate").c_str());
std::ofstream ofs((out_file_name+".samplerate").c_str());
if (ofs.good()) {
ofs << int2str(mixer_sample_rate);
ofs.close();
}
have_out_sr = true;
}
if (out_file) {
out_file->write(buffer, size);
}
}
size = resampleOutput(buffer,size,mixer_sample_rate,output_sample_rate);
mixer->unlock();
return size;
}
ChannelWritingFile::ChannelWritingFile(const char* path)
: async_file(256*1024) // 256k buffer
{
fp = fopen(path, "w");
if (!fp) {
ERROR("opening file '%s' for writing: '%s'\n", path, strerror(errno));
}
}
ChannelWritingFile::~ChannelWritingFile() {
if (fp)
fclose(fp);
}
int ChannelWritingFile::write_to_file(const void* buf, unsigned int len) {
if (!fp)
return len;
size_t res = fwrite(buf, 1, len, fp);
return !ferror(fp) ? res : -1;
}
void ChannelWritingFile::on_flushed() {
DBG("file on_flushed, deleting self\n");
delete this; // uh!
}

@ -31,6 +31,21 @@
#include "AmAudio.h"
#include "AmConferenceStatus.h"
#include "sip/async_file.h"
class ChannelWritingFile : public async_file
{
FILE* fp;
public:
ChannelWritingFile(const char* path);
~ChannelWritingFile();
int write_to_file(const void* buf, unsigned int len);
void on_flushed();
AmCondition<bool> finished;
};
/**
* \brief one channel of a conference
*
@ -41,9 +56,17 @@ class AmConferenceChannel: public AmAudio
{
bool own_channel;
int channel_id;
string channel_tag;
string conf_id;
AmConferenceStatus* status;
string in_file_name;
string out_file_name;
bool have_in_sr;
bool have_out_sr;
ChannelWritingFile* in_file;
ChannelWritingFile* out_file;
protected:
// Fake implement AmAudio's pure virtual methods
// this avoids to copy the samples locally by implementing only get/put
@ -57,8 +80,8 @@ class AmConferenceChannel: public AmAudio
int input_sample_rate, unsigned int size);
public:
AmConferenceChannel(AmConferenceStatus* status,
int channel_id, bool own_channel);
AmConferenceChannel(AmConferenceStatus* status,
int channel_id, string channel_tag, bool own_channel);
~AmConferenceChannel();

@ -155,7 +155,7 @@ AmConferenceChannel* AmConferenceStatus::getChannel(const string& sess_id, int i
sessions_mut.lock();
std::map<std::string, unsigned int>::iterator it = sessions.find(sess_id);
if(it != sessions.end()){
ch = new AmConferenceChannel(this,it->second,false);
ch = new AmConferenceChannel(this,it->second,sess_id,false);
} else {
if(!sessions.empty()){
int participants = sessions.size()+1;
@ -179,7 +179,7 @@ AmConferenceChannel* AmConferenceStatus::getChannel(const string& sess_id, int i
sessions[sess_id] = ch_id;
channels[ch_id] = si;
ch = new AmConferenceChannel(this,ch_id,true);
ch = new AmConferenceChannel(this,ch_id,sess_id, true);
}
sessions_mut.unlock();

@ -131,6 +131,9 @@ string AmConfig::OptionsTranscoderInStatsHdr; // empty by default
string AmConfig::TranscoderOutStatsHdr; // empty by default
string AmConfig::TranscoderInStatsHdr; // empty by default
bool AmConfig::DumpConferenceStreams = false;
string AmConfig::DumpConferencePath = "/tmp";
Am100rel::State AmConfig::rel100 = Am100rel::REL100_SUPPORTED;
vector <string> AmConfig::CodecOrder;
@ -158,7 +161,8 @@ static int readInterfaces(AmConfigReader& cfg);
AmConfig::IP_interface::IP_interface()
: LocalIP(),
PublicIP()
PublicIP(),
NetIfIdx(0)
{
}
@ -166,7 +170,9 @@ AmConfig::SIP_interface::SIP_interface()
: IP_interface(),
LocalPort(5060),
SigSockOpts(0),
RtpInterface(-1)
RtpInterface(-1),
tcp_connect_timeout(DEFAULT_TCP_CONNECT_TIMEOUT),
tcp_idle_timeout(DEFAULT_TCP_IDLE_TIMEOUT)
{
}
@ -665,6 +671,9 @@ int AmConfig::readConfiguration()
TranscoderOutStatsHdr = cfg.getParameter("transcoder_out_stats_hdr");
TranscoderInStatsHdr = cfg.getParameter("transcoder_in_stats_hdr");
DumpConferenceStreams = cfg.getParameter("dump_conference_streams")=="true";
DumpConferencePath = cfg.getParameter("dump_conference_path");
if (cfg.hasParameter("100rel")) {
string rel100s = cfg.getParameter("100rel");
if (rel100s == "disabled" || rel100s == "off") {
@ -692,8 +701,7 @@ int AmConfig::readConfiguration()
int AmConfig::insert_SIP_interface(const SIP_interface& intf)
{
if(SIP_If_names.find(intf.name) !=
SIP_If_names.end()) {
if(SIP_If_names.find(intf.name) != SIP_If_names.end()) {
if(intf.name != "default") {
ERROR("duplicated interface name '%s'\n",intf.name.c_str());
@ -702,34 +710,36 @@ int AmConfig::insert_SIP_interface(const SIP_interface& intf)
unsigned int idx = SIP_If_names[intf.name];
SIP_Ifs[idx] = intf;
return 0;
}
else {
SIP_Ifs.push_back(intf);
unsigned int idx = SIP_Ifs.size()-1;
SIP_If_names[intf.name] = idx;
if(LocalSIPIP2If.find(intf.LocalIP) ==
LocalSIPIP2If.end()) {
SIP_Ifs.push_back(intf);
unsigned int idx = SIP_Ifs.size()-1;
SIP_If_names[intf.name] = idx;
return 0;
}
LocalSIPIP2If.insert(make_pair(intf.LocalIP,idx));
}
else {
map<string,unsigned short>::iterator it =
LocalSIPIP2If.find(intf.LocalIP);
int AmConfig::insert_SIP_interface_mapping(const SIP_interface& intf) {
unsigned int idx = SIP_If_names[intf.name];
const SIP_interface& old_intf = SIP_Ifs[it->second];
if(intf.LocalPort == old_intf.LocalPort) {
ERROR("duplicated signaling interfaces "
"(%s and %s) detected using %s:%u",
old_intf.name.c_str(),intf.name.c_str(),
intf.LocalIP.c_str(),intf.LocalPort);
string if_local_ip = intf.LocalIP;
return -1;
}
//FIXME: what happens now? shouldn't we insert the interface????
if(LocalSIPIP2If.find(if_local_ip) == LocalSIPIP2If.end()) {
LocalSIPIP2If.insert(make_pair(if_local_ip,idx));
} else {
map<string,unsigned short>::iterator it =
LocalSIPIP2If.find(if_local_ip);
const SIP_interface& old_intf = SIP_Ifs[it->second];
if(intf.LocalPort == old_intf.LocalPort) {
ERROR("duplicated signaling interfaces (%s and %s) detected using %s:%u",
old_intf.name.c_str(), intf.name.c_str(), if_local_ip.c_str(), intf.LocalPort);
return -1;
}
// two interfaces on the sample IP - the one on port 5060 has priority
if (intf.LocalPort == 5060)
LocalSIPIP2If.insert(make_pair(if_local_ip,idx));
}
return 0;
}
@ -1129,6 +1139,9 @@ int AmConfig::finalizeIPConfig()
if(!it->LocalPort)
it->LocalPort = 5060;
if (insert_SIP_interface_mapping(*it)<0)
return -1;
setNetInterface(&(*it));
}

@ -163,6 +163,7 @@ struct AmConfig
static vector<SysIntf> SysIfs;
static int insert_SIP_interface(const SIP_interface& intf);
static int insert_SIP_interface_mapping(const SIP_interface& intf);
static int insert_RTP_interface(const RTP_interface& intf);
static int finalizeIPConfig();
@ -255,6 +256,9 @@ struct AmConfig
* be present in every message leaving server */
static string TranscoderInStatsHdr;
static bool DumpConferenceStreams;
static string DumpConferencePath;
static Am100rel::State rel100;
/** Time of no RTP after which Session is regarded as dead, 0 for no Timeout */

@ -370,14 +370,16 @@ void AmDtmfDetector::reportEvent()
{
m_reportLock.lock();
long duration = (m_lastReportTime.tv_sec - m_startTime.tv_sec) * 1000 +
(m_lastReportTime.tv_usec - m_startTime.tv_usec) / 1000;
m_dtmfSink->postDtmfEvent(new AmDtmfEvent(m_currentEvent, duration));
m_eventPending = false;
m_sipEventReceived = false;
m_rtpEventReceived = false;
m_inbandEventReceived = false;
m_current_eventid_i = false;
if (m_eventPending) {
long duration = (m_lastReportTime.tv_sec - m_startTime.tv_sec) * 1000 +
(m_lastReportTime.tv_usec - m_startTime.tv_usec) / 1000;
m_dtmfSink->postDtmfEvent(new AmDtmfEvent(m_currentEvent, duration));
m_eventPending = false;
m_sipEventReceived = false;
m_rtpEventReceived = false;
m_inbandEventReceived = false;
m_current_eventid_i = false;
}
m_reportLock.unlock();
}

@ -36,6 +36,7 @@
// the internal delay of the mixer (between put and get)
#define MIXER_DELAY_MS 20
#define MAX_BUFFER_STATES 50 // 1 sec max @ 20ms
void DEBUG_MIXER_BUFFER_STATE(const MixerBufferState& mbs, const string& context)
{
@ -274,8 +275,10 @@ AmMultiPartyMixer::findBufferStateForReading(unsigned int sample_rate,
}
}
//DBG("XXDebugMixerXX: Creating buffer state (from GetChannelPacket)");
buffer_state.push_back(MixerBufferState(sample_rate, channelids));
if (buffer_state.size() < MAX_BUFFER_STATES) {
// DBG("XXDebugMixerXX: Creating buffer state (from GetChannelPacket)\n");
buffer_state.push_back(MixerBufferState(sample_rate, channelids));
} // else just reuse the last buffer - conference without a speaker
std::deque<MixerBufferState>::reverse_iterator rit = buffer_state.rbegin();
//DEBUG_MIXER_BUFFER_STATE(*((rit + 1).base()), "returned to PutChannelPacket");
return (rit + 1).base();

@ -307,8 +307,9 @@ int AmOfferAnswer::onRequestOut(AmSipRequest& req)
if (!sdp_body &&
((req.method == SIP_METH_PRACK) ||
(req.method == SIP_METH_ACK))) {
generate_sdp = (state == OA_OfferRecved);
(req.method == SIP_METH_ACK))
&& (state == OA_OfferRecved)) {
generate_sdp = true;
sdp_body = req.body.addPart(SIP_APPLICATION_SDP);
}

@ -116,6 +116,10 @@ AmPlaylist::AmPlaylist(AmEventQueue* q)
}
AmPlaylist::~AmPlaylist() {
flush();
}
void AmPlaylist::addToPlaylist(AmPlaylistItem* item)
{
items_mut.lock();

@ -87,6 +87,7 @@ class AmPlaylist: public AmAudio
public:
AmPlaylist(AmEventQueue* q = NULL);
~AmPlaylist();
bool isEmpty();

@ -448,20 +448,22 @@ amci_subtype_t* AmPlugIn::subtype(amci_inoutfmt_t* iofmt, int subtype)
return 0;
}
int AmPlugIn::subtypeID(amci_inoutfmt_t* iofmt, const string& subtype_name) {
amci_subtype_t* AmPlugIn::subtype(amci_inoutfmt_t* iofmt, const string& subtype_name) {
if(!iofmt)
return -1;
return NULL;
DBG("looking for subtype '%s'\n", subtype_name.c_str());
amci_subtype_t* st = iofmt->subtypes;
if(subtype_name.empty()) // default subtype wanted
return st->type;
return st;
for(;;st++){
if(!st || st->type<0) break;
if(st->name == subtype_name)
return st->type;
if(st->name == subtype_name) {
return st;
}
}
return -1;
return NULL;
}
AmSessionFactory* AmPlugIn::getFactory4App(const string& app_name)

@ -186,11 +186,11 @@ class AmPlugIn : public AmPayloadProvider
amci_subtype_t* subtype(amci_inoutfmt_t* iofmt, int subtype);
/**
* File subtype ID lookup function.
* File subtype lookup function.
* @param subtype_name The subtype's name (e.g. Pcm16).
* @return -1 if failed.
* @return NULL if failed.
*/
int subtypeID(amci_inoutfmt_t* iofmt, const string& subtype_name);
amci_subtype_t* subtype(amci_inoutfmt_t* iofmt, const string& subtype_name);
/**
* Codec lookup function.

@ -11,7 +11,10 @@ AmRingTone::AmRingTone(int length, int on, int off, int f, int f2)
on_period(on),
off_period(off),
freq(f),freq2(f2)
{}
{
if (on_period==0 && off_period==0)
on_period = 1; // sanity
}
AmRingTone::~AmRingTone()
{}

@ -1229,7 +1229,7 @@ void AmRtpStream::setRtpRelayTransparentSSRC(bool transparent) {
}
void AmRtpStream::setRtpRelayFilterRtpDtmf(bool filter) {
DBG("%sabled RTP relay filtering of RTP DTMF (2833 / 3744) for RTP stream instance [%p]\n",
DBG("%sabled RTP relay filtering of RTP DTMF (2833 / 4733) for RTP stream instance [%p]\n",
filter ? "en":"dis", this);
relay_filter_dtmf = filter;
}

@ -100,6 +100,7 @@ inline string transport_p_2_str(int tp)
{
switch(tp){
case TP_RTPAVP: return "RTP/AVP";
case TP_RTPAVPF: return "RTP/AVPF";
case TP_UDP: return "udp";
case TP_RTPSAVP: return "RTP/SAVP";
case TP_RTPSAVPF: return "RTP/SAVPF";
@ -360,7 +361,7 @@ void AmSdp::print(string& body) const
string options;
if (media_it->transport == TP_RTPAVP || media_it->transport == TP_RTPSAVP || media_it->transport == TP_RTPSAVPF || media_it->transport == TP_UDPTLSRTPSAVP || media_it->transport == TP_UDPTLSRTPSAVPF) {
if (media_it->transport == TP_RTPAVP || media_it->transport == TP_RTPAVPF || media_it->transport == TP_RTPSAVP || media_it->transport == TP_RTPSAVPF || media_it->transport == TP_UDPTLSRTPSAVP || media_it->transport == TP_UDPTLSRTPSAVPF) {
for(std::vector<SdpPayload>::const_iterator pl_it = media_it->payloads.begin();
pl_it != media_it->payloads.end(); pl_it++) {
@ -754,11 +755,14 @@ static char* parse_sdp_connection(AmSdp* sdp_msg, char* s, char t)
case ADDR_TYPE:
{
string addr_type(connection_line,3);
connection_line +=4; // fixme
if(addr_type == "IP4"){
string addr_type_uc = addr_type;
std::transform(addr_type_uc.begin(), addr_type_uc.end(), addr_type_uc.begin(), toupper);
connection_line +=4;
if(addr_type_uc == "IP4"){
c.addrType = AT_V4;
state = IP4;
}else if(addr_type == "IP6"){
}else if(addr_type_uc == "IP6"){
c.addrType = AT_V6;
state = IP6;
}else{
@ -891,7 +895,7 @@ static void parse_sdp_media(AmSdp* sdp_msg, char* s)
}
case FMT:
{
if (m.transport == TP_RTPAVP || m.transport == TP_RTPSAVP || m.transport == TP_RTPSAVPF || m.transport == TP_UDPTLSRTPSAVP || m.transport == TP_UDPTLSRTPSAVPF) {
if (m.transport == TP_RTPAVP || m.transport == TP_RTPAVPF || m.transport == TP_RTPSAVP || m.transport == TP_RTPSAVPF || m.transport == TP_UDPTLSRTPSAVP || m.transport == TP_UDPTLSRTPSAVPF) {
if (contains(media_line, line_end, ' ')) {
next = parse_until(media_line, ' ');
string value;
@ -1467,6 +1471,8 @@ static TransProt transport_type(string transport)
if(transport_uc == "RTP/AVP")
return TP_RTPAVP;
else if(transport_uc == "RTP/AVPF")
return TP_RTPAVPF;
else if(transport_uc == "UDP")
return TP_UDP;
else if(transport_uc == "RTP/SAVP")

@ -55,7 +55,7 @@ enum AddressType { AT_NONE=0, AT_V4, AT_V6 };
/** media type */
enum MediaType { MT_NONE=0, MT_AUDIO, MT_VIDEO, MT_APPLICATION, MT_TEXT, MT_MESSAGE, MT_IMAGE };
/** transport protocol */
enum TransProt { TP_NONE=0, TP_RTPAVP, TP_UDP, TP_RTPSAVP, TP_UDPTL, TP_RTPSAVPF, TP_UDPTLSRTPSAVP, TP_UDPTLSRTPSAVPF };
enum TransProt { TP_NONE=0, TP_RTPAVP, TP_RTPAVPF, TP_UDP, TP_RTPSAVP, TP_UDPTL, TP_RTPSAVPF, TP_UDPTLSRTPSAVP, TP_UDPTLSRTPSAVPF };
/** \brief c=... line in SDP*/
struct SdpConnection

@ -1169,6 +1169,11 @@ int AmSession::getRtpInterface()
return rtp_interface;
}
void AmSession::setRtpInterface(int _rtp_interface) {
DBG("setting media interface to %d\n", _rtp_interface);
rtp_interface = _rtp_interface;
}
string AmSession::localMediaIP(int addrType)
{
// sets rtp_interface if not initialized

@ -65,7 +65,7 @@ class AmDtmfEvent;
* The session is identified by Call-ID, From-Tag and To-Tag.
*/
class AmSession :
public AmObject,
public virtual AmObject,
#ifndef SESSION_THREADPOOL
public AmThread,
#endif
@ -610,6 +610,7 @@ public:
virtual void onAfterRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr) {}
int getRtpInterface();
void setRtpInterface(int _rtp_interface);
};
inline AmRtpAudio* AmSession::RTPStream() {

@ -332,6 +332,25 @@ void AmSipDialog::onRequestTxed(const AmSipRequest& req)
}
}
bool AmSipDialog::onRxReplySanity(const AmSipReply& reply)
{
if(!getRemoteTag().empty()
&& reply.to_tag != getRemoteTag()) {
if(status == Early) {
if(reply.code < 200 && !reply.to_tag.empty()) {
return false;// DROP
}
}
else {
// DROP
return false;
}
}
return true;
}
bool AmSipDialog::onRxReplyStatus(const AmSipReply& reply)
{
// rfc3261 12.1
@ -376,6 +395,8 @@ bool AmSipDialog::onRxReplyStatus(const AmSipReply& reply)
case Early:
if(reply.code < 200){
DBG("ignoring provisional reply in Early state");
//DROP!!!
}
else if(reply.code < 300){
setStatus(Connected);
@ -406,6 +427,7 @@ bool AmSipDialog::onRxReplyStatus(const AmSipReply& reply)
// CANCEL rejected
DBG("CANCEL rejected/too late - bye()\n");
setRemoteTag(reply.to_tag);
setStatus(Connected);
bye();
// if BYE could not be sent,
// there is nothing we can do anymore...

@ -68,6 +68,7 @@ protected:
bool onRxReqSanity(const AmSipRequest& req);
bool onRxReqStatus(const AmSipRequest& req);
bool onRxReplySanity(const AmSipReply& reply);
bool onRxReplyStatus(const AmSipReply& reply);

@ -119,8 +119,14 @@ void AmSipDispatcher::handleSipMsg(AmSipRequest &req)
string app_name;
AmSessionFactory* sess_fact = AmPlugIn::instance()->findSessionFactory(req,app_name);
if (sess_fact) {
sess_fact->onOoDRequest(req);
return;
try {
sess_fact->onOoDRequest(req);
return;
} catch (AmSession::Exception& e) {
AmSipDialog::reply_error(req,e.code,e.reason, e.hdrs);
ERROR("%i %s %s\n",e.code,e.reason.c_str(), e.hdrs.c_str());
return;
}
}
if (req.method == SIP_METH_OPTIONS) {

@ -15,6 +15,9 @@ AmSipRequest::AmSipRequest()
string getHeader(const string& hdrs,const string& hdr_name, bool single)
{
if (hdr_name.empty())
return "";
size_t pos1;
size_t pos2;
size_t pos_s;
@ -40,6 +43,12 @@ string getHeader(const string& hdrs,const string& hdr_name,
return getHeader(hdrs, compact_hdr_name, single);
return res;
}
bool hasHeader(const string& hdrs,const string& hdr_name) {
size_t skip = 0, pos1 = 0, pos2 = 0, hdr_start = 0;
return findHeader(hdrs, hdr_name, skip, pos1, pos2, hdr_start);
}
#include "log.h"
bool findHeader(const string& hdrs,const string& hdr_name, const size_t skip,
size_t& pos1, size_t& pos2, size_t& hdr_start)

@ -120,6 +120,9 @@ bool findHeader(const string& hdrs,const string& hdr_name, const size_t skip,
size_t& pos1, size_t& pos2,
size_t& hdr_start);
/** @return whether header hdr_name is in hdrs */
bool hasHeader(const string& hdrs,const string& hdr_name);
bool removeHeader(string& hdrs, const string& hdr_name);
/** add an option tag @param tag to list @param hdr_name */

@ -33,9 +33,18 @@
#include <string>
using std::string;
AmMutex::AmMutex()
AmMutex::AmMutex(bool recursive)
{
pthread_mutex_init(&m,NULL);
if(recursive) {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m, &attr);
}
else {
pthread_mutex_init(&m,NULL);
}
}
AmMutex::~AmMutex()

@ -43,7 +43,7 @@ class AmMutex
pthread_mutex_t m;
public:
AmMutex();
AmMutex(bool recursive = false);
~AmMutex();
void lock();
void unlock();

@ -387,6 +387,8 @@ bool AmUriParser::parse_params(const string& line, int& pos) {
add_param(params, line.substr(p1, p2-p1), line.substr(p2+1, pos-p2 -1));
else
add_param(params, line.substr(p1, p2-p1), line.substr(p2+1, pos-p2));
} else if (st == pS1) {
add_param(params, line.substr(p1, pos-p1), "");
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save