diff --git a/Makefile.defs b/Makefile.defs index 0131d769..9a666df3 100644 --- a/Makefile.defs +++ b/Makefile.defs @@ -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 \ diff --git a/README b/README index 7760aeee..f5c16c5f 100644 --- a/README +++ b/README @@ -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: diff --git a/apps/conference/Conference.cpp b/apps/conference/Conference.cpp index 9a90c261..7f9a025a 100644 --- a/apps/conference/Conference.cpp +++ b/apps/conference/Conference.cpp @@ -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) { diff --git a/apps/dsm/DSMCall.cpp b/apps/dsm/DSMCall.cpp index d02f6ff0..886435ca 100644 --- a/apps/dsm/DSMCall.cpp +++ b/apps/dsm/DSMCall.cpp @@ -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 params; + engine.runEvent(this, this, DSMCondition::BeforeDestroy, ¶ms); + 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(); diff --git a/apps/dsm/DSMCall.h b/apps/dsm/DSMCall.h index 70b36de1..312f8708 100644 --- a/apps/dsm/DSMCall.h +++ b/apps/dsm/DSMCall.h @@ -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(); diff --git a/apps/dsm/DSMCoreModule.cpp b/apps/dsm/DSMCoreModule.cpp index 29b58b15..59eb6fdd 100644 --- a/apps/dsm/DSMCoreModule.cpp +++ b/apps/dsm/DSMCoreModule.cpp @@ -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* 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::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 && diff --git a/apps/dsm/DSMCoreModule.h b/apps/dsm/DSMCoreModule.h index 62efe1aa..b02f1d13 100644 --- a/apps/dsm/DSMCoreModule.h +++ b/apps/dsm/DSMCoreModule.h @@ -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* event_params); }; +/** return string q with variables/params/selects replaced */ +string replaceParams(const string& q, AmSession* sess, DSMSession* sc_sess, + map* event_params); + #endif diff --git a/apps/dsm/DSMSession.h b/apps/dsm/DSMSession.h index 4fd007bc..fbc46b17 100644 --- a/apps/dsm/DSMSession.h +++ b/apps/dsm/DSMSession.h @@ -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 VarMapT; typedef map 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; diff --git a/apps/dsm/DSMStateDiagramCollection.cpp b/apps/dsm/DSMStateDiagramCollection.cpp index 79c5ff0d..749d223c 100644 --- a/apps/dsm/DSMStateDiagramCollection.cpp +++ b/apps/dsm/DSMStateDiagramCollection.cpp @@ -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 && diff --git a/apps/dsm/DSMStateEngine.cpp b/apps/dsm/DSMStateEngine.cpp index 3475c047..edbc5f2e 100644 --- a/apps/dsm/DSMStateEngine.cpp +++ b/apps/dsm/DSMStateEngine.cpp @@ -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 ""; +#undef rt + }; +} + DSMStateDiagram::DSMStateDiagram(const string& name) : name(name) { } @@ -526,7 +599,9 @@ void DSMStateEngine::runEvent(AmSession* sess, DSMSession* sc_sess, map 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 { diff --git a/apps/dsm/DSMStateEngine.h b/apps/dsm/DSMStateEngine.h index 6c0b9f1c..44194d72 100644 --- a/apps/dsm/DSMStateEngine.h +++ b/apps/dsm/DSMStateEngine.h @@ -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) { } diff --git a/apps/dsm/SystemDSM.cpp b/apps/dsm/SystemDSM.cpp index 4e950983..42785192 100644 --- a/apps/dsm/SystemDSM.cpp +++ b/apps/dsm/SystemDSM.cpp @@ -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()); diff --git a/apps/dsm/SystemDSM.h b/apps/dsm/SystemDSM.h index 19f444b5..76e8362c 100644 --- a/apps/dsm/SystemDSM.h +++ b/apps/dsm/SystemDSM.h @@ -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(); diff --git a/apps/dsm/mods/mod_conference/ModConference.cpp b/apps/dsm/mods/mod_conference/ModConference.cpp index 07a3e395..38aa6f84 100644 --- a/apps/dsm/mods/mod_conference/ModConference.cpp +++ b/apps/dsm/mods/mod_conference/ModConference.cpp @@ -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 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* l_obj = + getDSMConfChannel >(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(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* m_obj = + getDSMConfChannel >(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* l_obj = + getDSMConfChannel >(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; diff --git a/apps/dsm/mods/mod_conference/ModConference.h b/apps/dsm/mods/mod_conference/ModConference.h index c1e29b20..fa4714fd 100644 --- a/apps/dsm/mods/mod_conference/ModConference.h +++ b/apps/dsm/mods/mod_conference/ModConference.h @@ -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 diff --git a/apps/dsm/mods/mod_groups/ModGroups.cpp b/apps/dsm/mods/mod_groups/ModGroups.cpp index 15a19f14..9af9a794 100644 --- a/apps/dsm/mods/mod_groups/ModGroups.cpp +++ b/apps/dsm/mods/mod_groups/ModGroups.cpp @@ -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::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()) { diff --git a/apps/dsm/mods/mod_groups/ModGroups.h b/apps/dsm/mods/mod_groups/ModGroups.h index 4305e9ae..37c1c0f2 100644 --- a/apps/dsm/mods/mod_groups/ModGroups.h +++ b/apps/dsm/mods/mod_groups/ModGroups.h @@ -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); diff --git a/apps/dsm/mods/mod_redis/DBTypes.h b/apps/dsm/mods/mod_redis/DBTypes.h new file mode 100644 index 00000000..869baad9 --- /dev/null +++ b/apps/dsm/mods/mod_redis/DBTypes.h @@ -0,0 +1,37 @@ +#ifndef _DBTypes_h_ +#define _DBTypes_h_ + +#include "AmArg.h" +#include "AmConfigReader.h" + +#include +#include +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 DBMapType; +typedef list 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 diff --git a/apps/dsm/mods/mod_redis/DRedisConnection.cpp b/apps/dsm/mods/mod_redis/DRedisConnection.cpp new file mode 100644 index 00000000..654ae6c1 --- /dev/null +++ b/apps/dsm/mods/mod_redis/DRedisConnection.cpp @@ -0,0 +1,194 @@ +#include "DRedisConnection.h" +#include "jsonArg.h" +#include "AmUtils.h" + +#include + +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;ielements;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, ""); + return ret; +} + diff --git a/apps/dsm/mods/mod_redis/DRedisConnection.h b/apps/dsm/mods/mod_redis/DRedisConnection.h new file mode 100644 index 00000000..8fe0d363 --- /dev/null +++ b/apps/dsm/mods/mod_redis/DRedisConnection.h @@ -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 diff --git a/apps/dsm/mods/mod_redis/Makefile b/apps/dsm/mods/mod_redis/Makefile new file mode 100644 index 00000000..4c2508b8 --- /dev/null +++ b/apps/dsm/mods/mod_redis/Makefile @@ -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 diff --git a/apps/dsm/mods/mod_redis/ModRedis.cpp b/apps/dsm/mods/mod_redis/ModRedis.cpp new file mode 100644 index 00000000..3f1b77c3 --- /dev/null +++ b/apps/dsm/mods/mod_redis/ModRedis.cpp @@ -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 +#include + +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(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(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* event_params) { + string res = q; + size_t repl_pos = 0; + while (repl_posvar[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 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;ielements;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; diff --git a/apps/dsm/mods/mod_redis/ModRedis.h b/apps/dsm/mods/mod_redis/ModRedis.h new file mode 100644 index 00000000..176025ea --- /dev/null +++ b/apps/dsm/mods/mod_redis/ModRedis.h @@ -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 diff --git a/apps/dsm/mods/mod_sbc/ModSbc.cpp b/apps/dsm/mods/mod_sbc/ModSbc.cpp index 945d263d..1ff4048a 100644 --- a/apps/dsm/mods/mod_sbc/ModSbc.cpp +++ b/apps/dsm/mods/mod_sbc/ModSbc.cpp @@ -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(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 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(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::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; diff --git a/apps/dsm/mods/mod_sbc/ModSbc.h b/apps/dsm/mods/mod_sbc/ModSbc.h index f17bec5a..3adb797b 100644 --- a/apps/dsm/mods/mod_sbc/ModSbc.h +++ b/apps/dsm/mods/mod_sbc/ModSbc.h @@ -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 diff --git a/apps/dsm/mods/mod_utils/ModUtils.cpp b/apps/dsm/mods/mod_utils/ModUtils.cpp index 987ed797..b7995164 100644 --- a/apps/dsm/mods/mod_utils/ModUtils.cpp +++ b/apps/dsm/mods/mod_utils/ModUtils.cpp @@ -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 utils_get_count_files(DSMSession* sc_sess, unsigned int cnt, + const string& basedir, const string& suffix, bool right) { + + vector 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 filenames = utils_get_count_files(sc_sess, cnt, basedir, ".wav", true); + + cnt=0; + for (vector::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 filenames = utils_get_count_files(sc_sess, cnt, basedir, ".wav", false); + + cnt=0; + for (vector::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 filenames = utils_get_count_files(sc_sess, cnt, basedir, "", true); + + cnt=0; + for (vector::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 filenames = utils_get_count_files(sc_sess, cnt, basedir, "", false); + + cnt=0; + for (vector::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 items = explode(cslist, ","); + for (vector::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; diff --git a/apps/dsm/mods/mod_utils/ModUtils.h b/apps/dsm/mods/mod_utils/ModUtils.h index aaf89c7e..eca98fa8 100644 --- a/apps/dsm/mods/mod_utils/ModUtils.h +++ b/apps/dsm/mods/mod_utils/ModUtils.h @@ -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); diff --git a/apps/dsm/mods/mod_xml/ModXml.cpp b/apps/dsm/mods/mod_xml/ModXml.cpp index de658844..c91d1b72 100644 --- a/apps/dsm/mods/mod_xml/ModXml.cpp +++ b/apps/dsm/mods/mod_xml/ModXml.cpp @@ -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(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 res; + + if (NULL == xpath_obj->xpathObj->nodesetval){ + res.push_back(string()); + } else { + xmlNodeSetPtr nodes = xpath_obj->xpathObj->nodesetval; + xmlNodePtr cur; + + for (int i=0;ixpathObj->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::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(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;ixpathObj->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(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(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") diff --git a/apps/dsm/mods/mod_xml/ModXml.h b/apps/dsm/mods/mod_xml/ModXml.h index 5b9ac212..f58d3a8d 100644 --- a/apps/dsm/mods/mod_xml/ModXml.h +++ b/apps/dsm/mods/mod_xml/ModXml.h @@ -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 diff --git a/apps/early_announce/EarlyAnnounce.cpp b/apps/early_announce/EarlyAnnounce.cpp index 53c0896a..bb0106ee 100644 --- a/apps/early_announce/EarlyAnnounce.cpp +++ b/apps/early_announce/EarlyAnnounce.cpp @@ -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) { diff --git a/apps/examples/ivr_examples/call_center.py b/apps/examples/ivr_examples/call_center.py new file mode 100644 index 00000000..e803abe3 --- /dev/null +++ b/apps/examples/ivr_examples/call_center.py @@ -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 + +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) diff --git a/apps/ivr/Ivr.cpp b/apps/ivr/Ivr.cpp index b7797f6b..fb4c449a 100644 --- a/apps/ivr/Ivr.cpp +++ b/apps/ivr/Ivr.cpp @@ -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 */ diff --git a/apps/sbc/CallLeg.cpp b/apps/sbc/CallLeg.cpp index 15b342df..8c04d1c8 100644 --- a/apps/sbc/CallLeg.cpp +++ b/apps/sbc/CallLeg.cpp @@ -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 { diff --git a/apps/sbc/RegisterCache.cpp b/apps/sbc/RegisterCache.cpp index 84d6f7a1..cbd3c30d 100644 --- a/apps/sbc/RegisterCache.cpp +++ b/apps/sbc/RegisterCache.cpp @@ -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; diff --git a/apps/sbc/RegisterDialog.cpp b/apps/sbc/RegisterDialog.cpp index 8c85c723..820e03d7 100644 --- a/apps/sbc/RegisterDialog.cpp +++ b/apps/sbc/RegisterDialog.cpp @@ -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); diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index b5b24f5b..3cdba452 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -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; diff --git a/apps/sbc/SBC.h b/apps/sbc/SBC.h index 0c7e0577..e11f5ba6 100644 --- a/apps/sbc/SBC.h +++ b/apps/sbc/SBC.h @@ -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 diff --git a/apps/sbc/SBCCallLeg.cpp b/apps/sbc/SBCCallLeg.cpp index f099ebd0..5184f2ee 100644 --- a/apps/sbc/SBCCallLeg.cpp +++ b/apps/sbc/SBCCallLeg.cpp @@ -66,7 +66,7 @@ static const SdpPayload *findPayload(const std::vector& payloads, co for (vector::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& 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); diff --git a/apps/sbc/SBCCallProfile.cpp b/apps/sbc/SBCCallProfile.cpp index da70eaee..ce93ef2a 100644 --- a/apps/sbc/SBCCallProfile.cpp +++ b/apps/sbc/SBCCallProfile.cpp @@ -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 &codecs) for (vector::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 { diff --git a/apps/sbc/SBCCallProfile.h b/apps/sbc/SBCCallProfile.h index ae854f9c..425c9efc 100644 --- a/apps/sbc/SBCCallProfile.h +++ b/apps/sbc/SBCCallProfile.h @@ -155,6 +155,8 @@ struct SBCCallProfile bool anonymize_sdp; vector sdpfilter; + vector aleg_sdpfilter; + bool have_aleg_sdpfilter; vector sdpalinesfilter; vector 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), diff --git a/apps/sbc/SBCSimpleRelay.cpp b/apps/sbc/SBCSimpleRelay.cpp index 619a4c44..9cf3d355 100644 --- a/apps/sbc/SBCSimpleRelay.cpp +++ b/apps/sbc/SBCSimpleRelay.cpp @@ -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::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::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(p); + if (h != NULL) { + auth_h.reset(h); + DBG("uac auth enabled for callee session.\n"); + } + } + } + } + } + return 0; } diff --git a/apps/sbc/SBCSimpleRelay.h b/apps/sbc/SBCSimpleRelay.h index 48d65537..d7bb5eac 100644 --- a/apps/sbc/SBCSimpleRelay.h +++ b/apps/sbc/SBCSimpleRelay.h @@ -35,6 +35,8 @@ #include "SBC.h" #include "ExtendedCCInterface.h" +#include "ampi/UACAuthAPI.h" + #include #include 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 cc_ext; + // auth support + std::auto_ptr auth_cred; + std::auto_ptr 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, diff --git a/apps/sbc/call_control/dsm/SBCDSMInstance.cpp b/apps/sbc/call_control/dsm/SBCDSMInstance.cpp index 1d54d42c..778df709 100644 --- a/apps/sbc/call_control/dsm/SBCDSMInstance.cpp +++ b/apps/sbc/call_control/dsm/SBCDSMInstance.cpp @@ -33,6 +33,7 @@ #include "SBCDSMParams.h" #include "AmAdvancedAudio.h" +#include "AmRingTone.h" #include @@ -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()); diff --git a/apps/sbc/call_control/dsm/SBCDSMInstance.h b/apps/sbc/call_control/dsm/SBCDSMInstance.h index b12f7396..d7ec646e 100644 --- a/apps/sbc/call_control/dsm/SBCDSMInstance.h +++ b/apps/sbc/call_control/dsm/SBCDSMInstance.h @@ -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(); diff --git a/apps/sbc/call_control/syslog_cdr/SyslogCDR.cpp b/apps/sbc/call_control/syslog_cdr/SyslogCDR.cpp index c0851a4d..b90a57e7 100644 --- a/apps/sbc/call_control/syslog_cdr/SyslogCDR.cpp +++ b/apps/sbc/call_control/syslog_cdr/SyslogCDR.cpp @@ -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()) { diff --git a/apps/sbc/etc/sbc.conf b/apps/sbc/etc/sbc.conf index b30b482c..033df47d 100644 --- a/apps/sbc/etc/sbc.conf +++ b/apps/sbc/etc/sbc.conf @@ -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 diff --git a/apps/sbc/tools/Makefile b/apps/sbc/tools/Makefile index d1945080..faa4a4f8 100644 --- a/apps/sbc/tools/Makefile +++ b/apps/sbc/tools/Makefile @@ -1,6 +1,8 @@ COREPATH_TOOLS ?= ../../../core +COREPATH?=$(COREPATH_TOOLS) include $(COREPATH_TOOLS)/../Makefile.defs + sbc_scripts = $(wildcard sems-sbc-*) all: install_tools diff --git a/apps/webconference/CallStats.cpp b/apps/webconference/CallStats.cpp index dee65226..a5b5600e 100644 --- a/apps/webconference/CallStats.cpp +++ b/apps/webconference/CallStats.cpp @@ -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 = ""; diff --git a/apps/webconference/Makefile b/apps/webconference/Makefile index b188b051..897398aa 100644 --- a/apps/webconference/Makefile +++ b/apps/webconference/Makefile @@ -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 diff --git a/apps/webconference/RoomInfo.cpp b/apps/webconference/RoomInfo.cpp index 80ced1e6..75328735 100644 --- a/apps/webconference/RoomInfo.cpp +++ b/apps/webconference/RoomInfo.cpp @@ -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::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::iterator it =participants.begin(); - it != participants.end();it++) - if (it->localtag == localtag) { - res = true; - break; - } - return res; + for (list::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::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) { diff --git a/apps/webconference/RoomInfo.h b/apps/webconference/RoomInfo.h index 6be0aabb..8eff5477 100644 --- a/apps/webconference/RoomInfo.h +++ b/apps/webconference/RoomInfo.h @@ -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); diff --git a/apps/webconference/WebConference.cpp b/apps/webconference/WebConference.cpp index 2197a0b7..107534ce 100644 --- a/apps/webconference/WebConference.cpp +++ b/apps/webconference/WebConference.cpp @@ -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::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::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::const_iterator r_it = app_params.find("room"); + map::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(); diff --git a/apps/webconference/WebConference.h b/apps/webconference/WebConference.h index daa72df8..37f5f7ae 100644 --- a/apps/webconference/WebConference.h +++ b/apps/webconference/WebConference.h @@ -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); diff --git a/apps/webconference/WebConferenceDialog.cpp b/apps/webconference/WebConferenceDialog.cpp index 9cc29c0f..d014b5fa 100644 --- a/apps/webconference/WebConferenceDialog.cpp +++ b/apps/webconference/WebConferenceDialog.cpp @@ -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;iisValidConference(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 $$x" ; \ + $(INSTALL-TOUCH) $(DESTDIR)$(bin-prefix)/$(bin-dir)/$$x ; \ + $(INSTALL-BIN) $$r $(DESTDIR)$(bin-prefix)/$(bin-dir)/$$x ; \ + done + + diff --git a/apps/webconference/tools/sems-webconference-addparticipant b/apps/webconference/tools/sems-webconference-addparticipant new file mode 100755 index 00000000..63ca8d8b --- /dev/null +++ b/apps/webconference/tools/sems-webconference-addparticipant @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import sys +from xmlrpclib import * + +if len(sys.argv) != 4: + print "usage: %s " % 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]) diff --git a/apps/webconference/tools/sems-webconference-roomcreate b/apps/webconference/tools/sems-webconference-roomcreate new file mode 100755 index 00000000..55682642 --- /dev/null +++ b/apps/webconference/tools/sems-webconference-roomcreate @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import sys +from xmlrpclib import * + +if len(sys.argv) != 2: + print "usage: %s " % 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]) diff --git a/apps/webconference/tools/sems-webconference-roominfo b/apps/webconference/tools/sems-webconference-roominfo new file mode 100755 index 00000000..074140b3 --- /dev/null +++ b/apps/webconference/tools/sems-webconference-roominfo @@ -0,0 +1,12 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import sys +from xmlrpclib import * + +if len(sys.argv) != 3: + print "usage: %s " % 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]) diff --git a/apps/xmlrpc2di/XMLRPC2DI.cpp b/apps/xmlrpc2di/XMLRPC2DI.cpp index ff1e06ac..7d396989 100644 --- a/apps/xmlrpc2di/XMLRPC2DI.cpp +++ b/apps/xmlrpc2di/XMLRPC2DI.cpp @@ -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; diff --git a/apps/xmlrpc2di/XMLRPC2DI.h b/apps/xmlrpc2di/XMLRPC2DI.h index af808dc7..14977122 100644 --- a/apps/xmlrpc2di/XMLRPC2DI.h +++ b/apps/xmlrpc2di/XMLRPC2DI.h @@ -105,7 +105,7 @@ class XMLRPC2DIServer unsigned int port; string bind_ip; - AmSharedVar running; + AmCondition 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*/ diff --git a/apps/xmlrpc2di/xmlrpc++/Makefile b/apps/xmlrpc2di/xmlrpc++/Makefile index f266b5da..b2b1e02f 100644 --- a/apps/xmlrpc2di/xmlrpc++/Makefile +++ b/apps/xmlrpc2di/xmlrpc++/Makefile @@ -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 diff --git a/core/AmAppTimer.cpp b/core/AmAppTimer.cpp index 323c2834..0b953baf 100644 --- a/core/AmAppTimer.cpp +++ b/core/AmAppTimer.cpp @@ -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(); diff --git a/core/AmArg.cpp b/core/AmArg.cpp index 59778308..2dcbde81 100644 --- a/core/AmArg.cpp +++ b/core/AmArg.cpp @@ -471,3 +471,28 @@ string AmArg::print(const AmArg &a) { } return ""; } + +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"); +} diff --git a/core/AmArg.h b/core/AmArg.h index 085460e2..28dd2b5b 100644 --- a/core/AmArg.h +++ b/core/AmArg.h @@ -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 diff --git a/core/AmAudioFile.cpp b/core/AmAudioFile.cpp index 6f7c9f36..9fc63e54 100644 --- a/core/AmAudioFile.cpp +++ b/core/AmAudioFile.cpp @@ -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); } diff --git a/core/AmAudioFile.h b/core/AmAudioFile.h index daecf8b7..0b56e22f 100644 --- a/core/AmAudioFile.h +++ b/core/AmAudioFile.h @@ -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. */ diff --git a/core/AmAudioMixIn.cpp b/core/AmAudioMixIn.cpp index 4bfa7827..7f350770 100644 --- a/core/AmAudioMixIn.cpp +++ b/core/AmAudioMixIn.cpp @@ -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(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(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 */ diff --git a/core/AmAudioMixIn.h b/core/AmAudioMixIn.h index 59b57d26..e8bfdb8c 100644 --- a/core/AmAudioMixIn.h +++ b/core/AmAudioMixIn.h @@ -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 diff --git a/core/AmB2BMedia.cpp b/core/AmB2BMedia.cpp index 5c76def2..6cff5f1f 100644 --- a/core/AmB2BMedia.cpp +++ b/core/AmB2BMedia.cpp @@ -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) { diff --git a/core/AmB2BSession.cpp b/core/AmB2BSession.cpp index 05e4c6f8..527d6780 100644 --- a/core/AmB2BSession.cpp +++ b/core/AmB2BSession.cpp @@ -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) diff --git a/core/AmBasicSipDialog.cpp b/core/AmBasicSipDialog.cpp index f0aeef49..8a9fc040 100644 --- a/core/AmBasicSipDialog.cpp +++ b/core/AmBasicSipDialog.cpp @@ -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) || diff --git a/core/AmBasicSipDialog.h b/core/AmBasicSipDialog.h index 8cbf67e0..03d84f23 100644 --- a/core/AmBasicSipDialog.h +++ b/core/AmBasicSipDialog.h @@ -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. diff --git a/core/AmCachedAudioFile.cpp b/core/AmCachedAudioFile.cpp index e56ec411..f43ecd41 100644 --- a/core/AmCachedAudioFile.cpp +++ b/core/AmCachedAudioFile.cpp @@ -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; } diff --git a/core/AmConferenceChannel.cpp b/core/AmConferenceChannel.cpp index f2530d40..5724dbec 100644 --- a/core/AmConferenceChannel.cpp +++ b/core/AmConferenceChannel.cpp @@ -1,17 +1,38 @@ #include "AmConferenceChannel.h" +#include "AmConfig.h" +#include "AmUtils.h" #include +#include +#include -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! +} diff --git a/core/AmConferenceChannel.h b/core/AmConferenceChannel.h index 65bb0974..e382e035 100644 --- a/core/AmConferenceChannel.h +++ b/core/AmConferenceChannel.h @@ -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 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(); diff --git a/core/AmConferenceStatus.cpp b/core/AmConferenceStatus.cpp index 14efa2cf..b5384290 100644 --- a/core/AmConferenceStatus.cpp +++ b/core/AmConferenceStatus.cpp @@ -155,7 +155,7 @@ AmConferenceChannel* AmConferenceStatus::getChannel(const string& sess_id, int i sessions_mut.lock(); std::map::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(); diff --git a/core/AmConfig.cpp b/core/AmConfig.cpp index fc56ded7..bb780b98 100644 --- a/core/AmConfig.cpp +++ b/core/AmConfig.cpp @@ -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 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::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::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)); } diff --git a/core/AmConfig.h b/core/AmConfig.h index f1b75089..50fd5eeb 100644 --- a/core/AmConfig.h +++ b/core/AmConfig.h @@ -163,6 +163,7 @@ struct AmConfig static vector 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 */ diff --git a/core/AmDtmfDetector.cpp b/core/AmDtmfDetector.cpp index be19277a..9e5cf12e 100644 --- a/core/AmDtmfDetector.cpp +++ b/core/AmDtmfDetector.cpp @@ -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(); } diff --git a/core/AmMultiPartyMixer.cpp b/core/AmMultiPartyMixer.cpp index b94a5dbd..94754ef2 100644 --- a/core/AmMultiPartyMixer.cpp +++ b/core/AmMultiPartyMixer.cpp @@ -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::reverse_iterator rit = buffer_state.rbegin(); //DEBUG_MIXER_BUFFER_STATE(*((rit + 1).base()), "returned to PutChannelPacket"); return (rit + 1).base(); diff --git a/core/AmOfferAnswer.cpp b/core/AmOfferAnswer.cpp index 390ec09d..5e103706 100644 --- a/core/AmOfferAnswer.cpp +++ b/core/AmOfferAnswer.cpp @@ -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); } diff --git a/core/AmPlaylist.cpp b/core/AmPlaylist.cpp index bfc257b0..7d3ac3a1 100644 --- a/core/AmPlaylist.cpp +++ b/core/AmPlaylist.cpp @@ -116,6 +116,10 @@ AmPlaylist::AmPlaylist(AmEventQueue* q) } +AmPlaylist::~AmPlaylist() { + flush(); +} + void AmPlaylist::addToPlaylist(AmPlaylistItem* item) { items_mut.lock(); diff --git a/core/AmPlaylist.h b/core/AmPlaylist.h index 0cff6edd..c78f56e5 100644 --- a/core/AmPlaylist.h +++ b/core/AmPlaylist.h @@ -87,6 +87,7 @@ class AmPlaylist: public AmAudio public: AmPlaylist(AmEventQueue* q = NULL); + ~AmPlaylist(); bool isEmpty(); diff --git a/core/AmPlugIn.cpp b/core/AmPlugIn.cpp index f210406c..7664e916 100644 --- a/core/AmPlugIn.cpp +++ b/core/AmPlugIn.cpp @@ -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) diff --git a/core/AmPlugIn.h b/core/AmPlugIn.h index 3895ec3a..b8e64852 100644 --- a/core/AmPlugIn.h +++ b/core/AmPlugIn.h @@ -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. diff --git a/core/AmRingTone.cpp b/core/AmRingTone.cpp index c64ea52c..c35d87c8 100644 --- a/core/AmRingTone.cpp +++ b/core/AmRingTone.cpp @@ -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() {} diff --git a/core/AmRtpStream.cpp b/core/AmRtpStream.cpp index 7db47c9a..00903cbd 100644 --- a/core/AmRtpStream.cpp +++ b/core/AmRtpStream.cpp @@ -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; } diff --git a/core/AmSdp.cpp b/core/AmSdp.cpp index dd2ff5ff..3db317cd 100644 --- a/core/AmSdp.cpp +++ b/core/AmSdp.cpp @@ -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::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") diff --git a/core/AmSdp.h b/core/AmSdp.h index 23302fc1..1207f2c3 100644 --- a/core/AmSdp.h +++ b/core/AmSdp.h @@ -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 diff --git a/core/AmSession.cpp b/core/AmSession.cpp index 2f6c5f58..9cf11163 100644 --- a/core/AmSession.cpp +++ b/core/AmSession.cpp @@ -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 diff --git a/core/AmSession.h b/core/AmSession.h index 4d1f1a46..fdd33fd1 100644 --- a/core/AmSession.h +++ b/core/AmSession.h @@ -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() { diff --git a/core/AmSipDialog.cpp b/core/AmSipDialog.cpp index f1e6bc21..bec06024 100644 --- a/core/AmSipDialog.cpp +++ b/core/AmSipDialog.cpp @@ -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... diff --git a/core/AmSipDialog.h b/core/AmSipDialog.h index 65543390..887940de 100644 --- a/core/AmSipDialog.h +++ b/core/AmSipDialog.h @@ -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); diff --git a/core/AmSipDispatcher.cpp b/core/AmSipDispatcher.cpp index b878fee8..0f2e92f8 100644 --- a/core/AmSipDispatcher.cpp +++ b/core/AmSipDispatcher.cpp @@ -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) { diff --git a/core/AmSipMsg.cpp b/core/AmSipMsg.cpp index 7f5433b0..1e4a4a06 100644 --- a/core/AmSipMsg.cpp +++ b/core/AmSipMsg.cpp @@ -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) diff --git a/core/AmSipMsg.h b/core/AmSipMsg.h index 55709909..d2d1a727 100644 --- a/core/AmSipMsg.h +++ b/core/AmSipMsg.h @@ -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 */ diff --git a/core/AmThread.cpp b/core/AmThread.cpp index b96b637f..c33a0fc0 100644 --- a/core/AmThread.cpp +++ b/core/AmThread.cpp @@ -33,9 +33,18 @@ #include 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() diff --git a/core/AmThread.h b/core/AmThread.h index 3512e3bb..5ad448ef 100644 --- a/core/AmThread.h +++ b/core/AmThread.h @@ -43,7 +43,7 @@ class AmMutex pthread_mutex_t m; public: - AmMutex(); + AmMutex(bool recursive = false); ~AmMutex(); void lock(); void unlock(); diff --git a/core/AmUriParser.cpp b/core/AmUriParser.cpp index b9809574..f20e6274 100644 --- a/core/AmUriParser.cpp +++ b/core/AmUriParser.cpp @@ -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; } diff --git a/core/ampi/UACAuthAPI.h b/core/ampi/UACAuthAPI.h index f4a32291..ccae1f0a 100644 --- a/core/ampi/UACAuthAPI.h +++ b/core/ampi/UACAuthAPI.h @@ -34,7 +34,7 @@ using std::string; class DialogControl -: public AmObject +: public virtual AmObject { public: virtual AmBasicSipDialog* getDlg()=0; @@ -52,7 +52,8 @@ class UACAuthCred const string& pwd); }; -class CredentialHolder { +class CredentialHolder +: public virtual AmObject { public: virtual UACAuthCred* getCredentials() = 0; virtual ~CredentialHolder() { } diff --git a/core/etc/sems.conf.sample b/core/etc/sems.conf.sample index bb15cb15..354d744a 100644 --- a/core/etc/sems.conf.sample +++ b/core/etc/sems.conf.sample @@ -28,13 +28,12 @@ ############# configuration for single interface ############ # optional parameter: sip_ip=| # -# - this informs SEMS about the SIP IP where its SIP stack is -# bound to or should be bound to. This also sets +# - SIP IP where the SIP stack is bound to. This also sets # the value used for contact header in outgoing calls and # registrations. # - If neither 'media_ip' nor 'sip_ip' are set, defaults -# to first non-loopback interface. If 'media_ip' is set, -# 'sip_ip' defaults to 'media_ip. +# to first non-loopback interface, and the port configured below +# is ignored. # # Example: # sip_ip=10.0.0.34 @@ -42,16 +41,14 @@ # optional parameter: sip_port= # -# - this informs SEMS about the port where its SIP stack should -# be bound to. +# - port where its SIP stack should be bound to, ignored if sip_ip not set. # default: 5060 # -sip_port=5080 +#sip_port=5080 # optional parameter: media_ip=| # -# - this informs SEMS about the IP address or interface that -# SEMS uses to send and receive media. +# - IP address or interface that SEMS uses to send and receive media. # - If neither 'media_ip' nor 'sip_ip' are set, defaults # to first non-loopback interface. If 'sip_ip' is set, # 'media_ip' defaults to 'sip_ip. @@ -72,10 +69,10 @@ rtp_high_port=60000 # optional parameter: public_ip= # -# - when running SEMS behind certain simple NAT configurations, -# you can use this parameter to inform SEMS of its public IP +# - near end NAT traversal. when running SEMS behind certain static +# NATs, use this parameter to inform SEMS of its public IP # address. If this parameter is set, SEMS will write this value -# into SDP bodies. +# into SDP bodies and Contact. # If this parameter is not set, the local IP address is used. # N.B., there is no support for port translation; the local # RTP port is advertised in SDP in either case. @@ -102,11 +99,10 @@ rtp_high_port=60000 ############# configuration for multiple interfaces ############ # interfaces = # -# 'interfaces' must be set if more than one -# interface is to be used for the same purpose (e.g. -# more than one interface for SIP). Configure additional -# interfaces if networks should be bridged or separate -# networks should be served. +# 'interfaces' must be set if more than one interface is to be used +# for the same purpose (e.g. more than one interface for SIP). +# Configure additional interfaces if networks should be bridged or +# separate networks should be served. # # For each interface, a set of parameters suffixed with the # interface name should be configured. @@ -142,8 +138,7 @@ rtp_high_port=60000 # NAT handling for SIP:sip_nat_handling={yes|no} # # Learn remote next hop address from the source of the address where -# requests are received from. -# This option does not apply to the sbc module. +# requests are received from. This option does not apply to the sbc module. # # default: no # @@ -655,3 +650,10 @@ accept_fr_without_totag=yes # Default: 4 # # sip_server_threads=8 + +# dump conference streams - experimental +# play with: $play -r -c 1 /tmp/123_1_nnnn.s16 +# where is in /tmp/123_1_nnnn.s16.samplerate +# (if it hasn't changed in-between...) +#dump_conference_streams=true +#dump_conference_path=/tmp/ diff --git a/core/jsonxx.cpp b/core/jsonxx.cpp index 88f416cd..58c543d2 100644 --- a/core/jsonxx.cpp +++ b/core/jsonxx.cpp @@ -93,10 +93,10 @@ bool parse_string(std::istream& input, std::string* value) { case 'n': value->push_back('\n'); break; case 'r': value->push_back('\r'); break; case 't': value->push_back('\t'); break; - case 'u': { - // ERROR("todo: unicode\n"); - return false; - } break; + case 'u': + value->push_back('\\'); + value->push_back('u'); + break; default: return false; } } @@ -303,7 +303,7 @@ bool parse_number(std::istream& input, long* value) { return false; } - *value *= powl(10, e_value); + *value *= pow(10, e_value); } return true; diff --git a/core/log.cpp b/core/log.cpp index d1de02fe..933657e3 100644 --- a/core/log.cpp +++ b/core/log.cpp @@ -219,3 +219,84 @@ void log_stacktrace(int ll) } free(strs); } + +/** + * Print a demangled stack backtrace of the caller function + */ +void __lds(int ll, unsigned int max_frames) +{ + // storage array for stack trace address data + void* addrlist[max_frames+1]; + + // retrieve current stack addresses + int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); + + if (addrlen == 0) { + _LOG(ll,""); + return; + } + + // resolve addresses into strings containing "filename(function+address)", + // this array must be free()-ed + char** symbollist = backtrace_symbols(addrlist, addrlen); + + // allocate string which will be filled with the demangled function name + size_t funcnamesize = 256; + char* funcname = (char*)malloc(funcnamesize); + + // iterate over the returned symbol lines. skip the first, it is the + // address of this function. + for (int i = 1; i < addrlen; i++) + { + char *begin_name = 0, *begin_offset = 0, *end_offset = 0; + + // find parentheses and +address offset surrounding the mangled name: + // ./module(function+0x15c) [0x8048a6d] + for (char *p = symbollist[i]; *p; ++p) + { + if (*p == '(') + begin_name = p; + else if (*p == '+') + begin_offset = p; + else if (*p == ')' && begin_offset) { + end_offset = p; + break; + } + } + + if (begin_name && begin_offset && end_offset + && begin_name < begin_offset) + { + *begin_name++ = '\0'; + *begin_offset++ = '\0'; + *end_offset = '\0'; + + // mangled name is now in [begin_name, begin_offset) and caller + // offset in [begin_offset, end_offset). now apply + // __cxa_demangle(): + + int status; + char* ret = abi::__cxa_demangle(begin_name, + funcname, &funcnamesize, &status); + if (status == 0) { + funcname = ret; // use possibly realloc()-ed string + _LOG(ll,"%s : %s+%s", + symbollist[i], funcname, begin_offset); + } + else { + // demangling failed. Output function name as a C function with + // no arguments. + _LOG(ll,"%s : %s()+%s", + symbollist[i], begin_name, begin_offset); + } + } + else + { + // couldn't parse the line? print the whole line. + _LOG(ll,"%s", symbollist[i]); + } + } + + free(funcname); + free(symbollist); +} diff --git a/core/log.h b/core/log.h index 7eb24808..253d8247 100644 --- a/core/log.h +++ b/core/log.h @@ -35,6 +35,10 @@ #include /* pthread_self() */ #include /* backtrace_symbols() */ +#ifdef __cplusplus +#include /* __cxa_demangle() */ +#endif + #ifdef __cplusplus extern "C" { @@ -115,8 +119,8 @@ enum Log_Level { pthread_t tid_ = GET_TID(); \ char msg_[LOG_BUFFER_LEN]; \ int n_ = snprintf(msg_, sizeof(msg_), fmt, ##args); \ - if (msg_[n_ - 1] == '\n') msg_[n_ - 1] = '\0'; \ - \ + if ((n_ < LOG_BUFFER_LEN) && (msg_[n_ - 1] == '\n')) \ + msg_[n_ - 1] = '\0'; \ if (log_stderr) { \ fprintf(stderr, COMPLETE_LOG_FMT); \ fflush(stderr); \ @@ -182,6 +186,8 @@ void log_stacktrace(int ll); #ifdef __cplusplus /* ...only for C++ */ +#define log_demangled_stacktrace __lds +void __lds(int ll, unsigned int max_frames = 63); class AmLoggingFacility; void register_log_hook(AmLoggingFacility*); #endif diff --git a/core/plug-in/ilbc/iLBC_rfc3951/filter.c b/core/plug-in/ilbc/iLBC_rfc3951/filter.c index 6565c2be..78f11584 100644 --- a/core/plug-in/ilbc/iLBC_rfc3951/filter.c +++ b/core/plug-in/ilbc/iLBC_rfc3951/filter.c @@ -156,19 +156,11 @@ o=(float)0.0; - if (istart(); + INFO("Starting RTP receiver\n"); AmRtpReceiver::instance()->start(); @@ -604,6 +631,8 @@ int main(int argc, char* argv[]) } #endif + INFO("SEMS " SEMS_VERSION " (" ARCH "/" OS") started"); + // running the server if(sip_ctrl.run() != -1) success = true; @@ -629,6 +658,9 @@ int main(int argc, char* argv[]) INFO("Disposing plug-ins\n"); AmPlugIn::dispose(); + async_file_writer::instance()->stop(); + async_file_writer::instance()->join(); + #ifndef DISABLE_DAEMON_MODE if (AmConfig::DaemonMode) { unlink(AmConfig::DaemonPidFile.c_str()); diff --git a/core/sip/async_file.cpp b/core/sip/async_file.cpp new file mode 100644 index 00000000..f12bc65f --- /dev/null +++ b/core/sip/async_file.cpp @@ -0,0 +1,125 @@ +#include "async_file.h" +#include "async_file_writer.h" +#include "log.h" + +#define MIN_WRITE_SIZE 128*1024 /* 128 KB */ + +#include + +/* + +Possible issue: + + once a file has been closed, it might take some time until it's + data have been writen. It should be avoided to re-open this same + file in the meantime. + + what can be done so that the exclusive-file container does not wait + while the file gets closed & written? + +*/ + +async_file::async_file(unsigned int buf_len) + : AmMutex(true), fifo_buffer(buf_len), + evbase(NULL),closed(false),error(false),write_thresh(MIN_WRITE_SIZE) +{ + if (buf_len <= MIN_WRITE_SIZE) { + ERROR("application error: async_file with buffer size <=128k (%u), " + "using %u write threshold\n", buf_len, buf_len/2); + write_thresh = buf_len / 2; + } + + evbase = async_file_writer::instance()->get_evbase(); + ev_write = event_new(evbase,-1,0,write_cb,this); +} + +async_file::~async_file() +{ + event_free(ev_write); + ev_write = NULL; +} + +int async_file::write(const void* buf, unsigned int len) +{ + AmLock _l(*this); + if(closed) return Closed; + if(error) return Error; + + int ret = fifo_buffer::write(buf,len); + + if(fifo_buffer::get_buffered_bytes() >= write_thresh) { + event_active(ev_write, 0, 0); + } + + if(ret < 0) return BufferFull; + + return ret; +} + +int async_file::writev(const struct iovec *iov, int iovcnt) +{ + AmLock _l(*this); + if(closed) return Closed; + if(error) return Error; + + int ret = fifo_buffer::writev(iov,iovcnt); + + if(fifo_buffer::get_buffered_bytes() >= write_thresh) { + event_active(ev_write, 0, 0); + } + + if(ret < 0) return BufferFull; + + return ret; +} + +void async_file::close() +{ + AmLock _l(*this); + closed = true; + event_active(ev_write, 0, 0); +} + +void async_file::write_cb(int sd, short what, void* ctx) +{ + ((async_file*)ctx)->write_cycle(); +} + +void async_file::write_cycle() +{ + int read_bs = 0; + + lock(); + read_bs = get_read_bs(); + unlock(); + + while(!error && (read_bs > 0)) { + + int bytes = write_to_file(get_read_ptr(),read_bs); + if(bytes < 0) { + error = true; + ERROR("Error detected: stopped writing"); + break; + } + + lock(); + skip(bytes); + read_bs = get_read_bs(); + unlock(); + } + + lock(); + if(closed) { + if(error || !fifo_buffer::get_buffered_bytes()) + on_flushed(); + else + event_active(ev_write, 0, 0); + } + unlock(); +} + +unsigned int async_file::get_buffered_bytes() +{ + AmLock _l(*this); + return fifo_buffer::get_buffered_bytes(); +} diff --git a/core/sip/async_file.h b/core/sip/async_file.h new file mode 100644 index 00000000..86312fb5 --- /dev/null +++ b/core/sip/async_file.h @@ -0,0 +1,69 @@ +#ifndef _async_file_h_ + +#include "fifo_buffer.h" + +#include + +class async_file + : protected fifo_buffer, + public AmMutex +{ + struct event_base* evbase; + struct event* ev_write; + + bool closed; + bool error; + + /** libevent call-back */ + static void write_cb(int sd, short what, void* ctx); + + /** + * Triggers real writing into the file itself. + * + * If this instance has been closed before, the buffer + * will be flushed and close_file() will be called. + */ + void write_cycle(); + + unsigned int write_thresh; + +protected: + /** + * Write to the file itself + * + * returns the number of bytes written or -1. + */ + virtual int write_to_file(const void* buf, unsigned int len)=0; + + virtual void on_flushed()=0; + +public: + + async_file(unsigned int buf_len); + virtual ~async_file(); + + enum StatusCode { + OK=0, + BufferFull=-1, + Closed=-2, + Error=-3 + }; + + /** + * Write into the file buffer + * + * return -1 in case the buffer could not be written + * into the buffer. + */ + int write(const void* buf, unsigned int len); + int writev(const struct iovec *iov, int iovcnt); + + /** + * Mark the file as closed + */ + void close(); + + unsigned int get_buffered_bytes(); +}; + +#endif diff --git a/core/sip/async_file_writer.cpp b/core/sip/async_file_writer.cpp new file mode 100644 index 00000000..adc35961 --- /dev/null +++ b/core/sip/async_file_writer.cpp @@ -0,0 +1,34 @@ +#include "async_file_writer.h" + +_async_file_writer::_async_file_writer() +{ + evbase = event_base_new(); + + // fake event to prevent the event loop from exiting + ev_default = event_new(evbase,-1,EV_READ|EV_PERSIST,NULL,NULL); + event_add(ev_default,NULL); +} + +_async_file_writer::~_async_file_writer() +{ + event_free(ev_default); + event_base_free(evbase); +} + +void _async_file_writer::start() +{ + event_add(ev_default,NULL); + AmThread::start(); +} + +void _async_file_writer::on_stop() +{ + event_del(ev_default); + event_base_loopexit(evbase,NULL); +} + +void _async_file_writer::run() +{ + /* Start the event loop. */ + event_base_dispatch(evbase); +} diff --git a/core/sip/async_file_writer.h b/core/sip/async_file_writer.h new file mode 100644 index 00000000..65ec9d96 --- /dev/null +++ b/core/sip/async_file_writer.h @@ -0,0 +1,32 @@ +#ifndef _async_file_writer_h_ + +#include "AmThread.h" +#include "singleton.h" + +#include + +class _async_file_writer + : public AmThread +{ + struct event_base* evbase; + struct event* ev_default; + +protected: + _async_file_writer(); + ~_async_file_writer(); + + const char *identify() { return "async_file_writer"; } + void on_stop(); + void run(); + +public: + void start(); + + event_base* get_evbase() const { + return evbase; + } +}; + +typedef singleton<_async_file_writer> async_file_writer; + +#endif diff --git a/core/sip/exclusive_file.cpp b/core/sip/exclusive_file.cpp new file mode 100644 index 00000000..7712db67 --- /dev/null +++ b/core/sip/exclusive_file.cpp @@ -0,0 +1,195 @@ +#include "exclusive_file.h" +#include "singleton.h" +#include "log.h" + +#include +#include +#include +#include + +#include +using std::map; + +#ifndef EXCL_BUFFER_SIZE +#define EXCL_BUFFER_SIZE 1024*1024 /* 1 MB */ +#endif + +class _excl_file_reg +{ + struct excl_file_entry + { + exclusive_file* excl_fp; + unsigned int ref_cnt; + + excl_file_entry() + : excl_fp(NULL), + ref_cnt(0) + {} + }; + + map files; + AmMutex files_mut; + +public: + exclusive_file* get(const string& name, bool& is_new) { + AmLock l(files_mut); + map::iterator it = files.find(name); + if(it != files.end()) { + excl_file_entry& fe = it->second; + if(!fe.ref_cnt) { + ERROR("trying to re-open a file not yet closed"); + return NULL; + } + + fe.ref_cnt++; + is_new = false; + return fe.excl_fp; + } + else { + exclusive_file* fp = new exclusive_file(name); + if(fp->open(is_new) < 0) { + ERROR("could not open '%s': %s",name.c_str(),strerror(errno)); + delete fp; + return NULL; + } + + files[name].excl_fp = fp; + files[name].ref_cnt++; + + if(is_new) fp->lock(); + return fp; + } + } + + void deref(const string& name) { + AmLock l(files_mut); + map::iterator it = files.find(name); + if(it != files.end()) { + excl_file_entry& fe = it->second; + if(!(--fe.ref_cnt)) { + // async delete + // - call close() + // - wait for notification of close before deleting + fe.excl_fp->close(); + } + } + } + + bool delete_on_flushed(const string& name) { + AmLock l(files_mut); + map::iterator it = files.find(name); + if(it != files.end()) { + excl_file_entry& fe = it->second; + if(!fe.ref_cnt) { + delete fe.excl_fp; + fe.excl_fp = NULL; + files.erase(it); + return true; + } + } + return false; + } +}; + +typedef singleton<_excl_file_reg> excl_file_reg; + +exclusive_file::exclusive_file(const string& name) + : async_file(EXCL_BUFFER_SIZE), + name(name),fd(-1) +{} + +exclusive_file::~exclusive_file() +{ + if(fd >= 0) + ::close(fd); + + DBG("just closed %s",name.c_str()); +} + +// +// TODO: add a close() method that closes the underlying async_file +// + +int exclusive_file::open(bool& is_new) +{ + if(fd != -1) { + ERROR("file already open\n"); + return -1; + } + + fd = ::open(name.c_str(),O_WRONLY | O_CREAT | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if(fd < 0) { + ERROR("could not open file '%s': %s", name.c_str(), strerror(errno)); + return -1; + } + + if(lseek(fd,0,SEEK_END) > 0) { + is_new = false; + } + else { + is_new = true; + } + + return 0; +} + +int exclusive_file::write_to_file(const void* buf, unsigned int len) +{ + int res = 0; + int retries = 0; + + do { + res = ::write(fd, buf, len); + + } while((res < 0) + && (errno == EINTR) + && (++retries < 10)); + + if (res != (int)len) { + ERROR("writing to file '%s': %s\n",name.c_str(),strerror(errno)); + } + //else { + //DBG("%i bytes written to %s",res,name.c_str()); + //} + + return res; +} + +void exclusive_file::on_flushed() +{ + excl_file_reg::instance()->delete_on_flushed(name); +} + +int exclusive_file::open(const char* filename, + exclusive_file*& excl_fp, + bool& is_new) +{ + excl_fp = excl_file_reg::instance()->get(filename,is_new); + if(!excl_fp) return -1; + return 0; +} + +void exclusive_file::close(const exclusive_file* excl_fp) +{ + if (excl_fp) { + excl_file_reg::instance()->deref(excl_fp->name); + } +} + +int exclusive_file::write(const void *buf, int len) +{ + DBG("async writting %i bytes to %s",len,name.c_str()); + return (int)async_file::write(buf,len); +} + +int exclusive_file::writev(const struct iovec* iov, int iovcnt) +{ + // int len=0; + // for(int i=0; i +using std::string; + +class _excl_file_reg; + +class exclusive_file + : public async_file +{ + string name; + int fd; + + exclusive_file(const string& name); + ~exclusive_file(); + + int open(bool& is_new); + + // async_file API + int write_to_file(const void* buf, unsigned int len); + void on_flushed(); + + // called only from _excl_file_reg + void close() { async_file::close(); } + + friend class _excl_file_reg; + +public: + static int open(const char* filename, exclusive_file*& excl_fp, bool& is_new); + static void close(const exclusive_file* excl_fp); + + int write(const void *buf, int len); + int writev(const struct iovec* iov, int iovcnt); +}; + +#endif diff --git a/core/sip/fifo_buffer.cpp b/core/sip/fifo_buffer.cpp new file mode 100644 index 00000000..141b5f1b --- /dev/null +++ b/core/sip/fifo_buffer.cpp @@ -0,0 +1,62 @@ +#include "fifo_buffer.h" +#include + +fifo_buffer::fifo_buffer(unsigned int size) + : size(size), free_space(size) +{ + data = new unsigned char [size]; + data_end = data + size; + p_head = p_tail = data; +} + +fifo_buffer::~fifo_buffer() +{ + delete [] data; +} + +int fifo_buffer::write(const void* buf, unsigned int len) +{ + if(len > free_space) { + // write all or nothing! + return -1; + } + + if((p_head >= p_tail) && + (len > data_end - p_head)) { + + // split write + unsigned int buf_end = data_end - p_head; + memcpy(p_head, buf, buf_end); + memcpy(data, (unsigned char*)buf + buf_end, len - buf_end); + p_head = data + len - buf_end; + } + else { + + //direct write + memcpy(p_head,buf,len); + p_head += len; + } + + free_space -= len; + return len; +} + +int fifo_buffer::writev(const struct iovec *iov, int iovcnt) +{ + unsigned int len=0; + + for(int i=0; i free_space) { + // write all or nothing! + return -1; + } + + for(int i=0; i= data_end) + p_tail = p_tail - data_end + data; + free_space += len; + } +}; + +#endif diff --git a/core/sip/msg_logger.cpp b/core/sip/msg_logger.cpp index 18c88c86..0fb53474 100644 --- a/core/sip/msg_logger.cpp +++ b/core/sip/msg_logger.cpp @@ -1,4 +1,5 @@ #include "msg_logger.h" +#include "exclusive_file.h" #include "AmUtils.h" @@ -8,45 +9,38 @@ file_msg_logger::~file_msg_logger() { - fd_mut.lock(); - if(fd >= 0) - close(fd); - fd_mut.unlock(); + exclusive_file::close(excl_fp); } int file_msg_logger::open(const char* filename) { - fd_mut.lock(); - if(fd != -1) { - ERROR("file already open\n"); - fd_mut.unlock(); - return -1; - } - - fd = ::open(filename,O_WRONLY | O_CREAT | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if(fd < 0) { - ERROR("could not open file '%s': %s", filename, strerror(errno)); - fd_mut.unlock(); + if(excl_fp) return 0; + + bool is_new = false; + if(exclusive_file::open(filename,excl_fp,is_new) < 0) { return -1; } - // need not to work for 100% (the access will not to be locked if opened from - // another logger instance) - off_t pos = lseek(fd, 0, SEEK_END); - if (pos == 0) write_file_header(); + if (is_new) { + write_file_header(); + + // exclusive files are created locked + excl_fp->unlock(); + } - fd_mut.unlock(); return 0; } int file_msg_logger::write(const void *buf, int len) { - int res = ::write(fd, buf, len); - if (res != len) { - ERROR("while writing to message log: %s\n",strerror(errno)); - } - return res; + assert(excl_fp != NULL); + return excl_fp->write(buf,len); +} + +int file_msg_logger::writev(const struct iovec *iov, int iovcnt) +{ + assert(excl_fp != NULL); + return excl_fp->writev(iov,iovcnt); } ////////////////////////////////////////////////////////////////////////////////////// @@ -106,10 +100,12 @@ int cf_msg_logger::log(const char* buf, int len, sockaddr_storage* dst_ip, cstring method, int reply_code) { + assert(excl_fp != NULL); + string src = addr2str(src_ip); string dst = addr2str(dst_ip); - AmLock _l(fd_mut); + AmLock _l(*(AmMutex*)excl_fp); write_src_dst(src); write_src_dst(dst); diff --git a/core/sip/msg_logger.h b/core/sip/msg_logger.h index c5835372..a5d60d08 100644 --- a/core/sip/msg_logger.h +++ b/core/sip/msg_logger.h @@ -25,22 +25,24 @@ public: cstring method, int reply_code=0)=0; }; +class exclusive_file; + class file_msg_logger : public msg_logger { - int fd; - protected: - AmMutex fd_mut; + exclusive_file* excl_fp; int write(const void *buf, int len); + int writev(const struct iovec *iov, int iovcnt); + virtual int write_file_header() = 0; public: - file_msg_logger() : fd(-1) {} + file_msg_logger() : excl_fp(NULL) {} ~file_msg_logger(); - int open(const char* filename); + int open(const char* filename); int log(const char* buf, int len, sockaddr_storage* src_ip, sockaddr_storage* dst_ip, diff --git a/core/sip/pcap_logger.cpp b/core/sip/pcap_logger.cpp index a9a36a52..be5ec017 100644 --- a/core/sip/pcap_logger.cpp +++ b/core/sip/pcap_logger.cpp @@ -1,4 +1,5 @@ #include "pcap_logger.h" +#include "exclusive_file.h" #include "log.h" @@ -98,8 +99,12 @@ int pcap_logger::log(const char* buf, int len, return log(buf, len, (sockaddr*)src_ip, (sockaddr*)dst_ip, sizeof(sockaddr_storage)); } -int pcap_logger::log(const char *data, int data_len, struct sockaddr *src, struct sockaddr *dst, size_t addr_len) +int pcap_logger::log(const char *data, int data_len, + struct sockaddr *src, struct sockaddr *dst, + size_t addr_len) { + assert(excl_fp != NULL); + if (((sockaddr_in*)src)->sin_family != AF_INET) { ERROR("writing only IPv4 is supported\n"); return -1; @@ -140,15 +145,21 @@ int pcap_logger::log(const char *data, int data_len, struct sockaddr *src, struc hdr.udp.chksum = ipv4_chksum(sum(&hdr.ip.ip_src, 8) + sum(&hdr.udp, 8) + htons(udp_size) + 0x1100 + sum(data, data_len)); - AmLock _l(fd_mut); + struct iovec buf_vec[2]; - if (write(&hdr, sizeof(hdr)) != sizeof(hdr)) { - // TODO: close the file (is broken anyway) - return -1; - } - if (write(data, data_len) != data_len) { - // TODO: close the file (is broken anyway) + buf_vec[0].iov_base = &hdr; + buf_vec[0].iov_len = sizeof(hdr); + + buf_vec[1].iov_base = (void*)data; + buf_vec[1].iov_len = data_len; + + // no extra locking needed here, as the file + // is automatically locked on write + + if (writev(buf_vec, 2) != (int)(sizeof(hdr) + data_len)) { + // TODO: check return value (buffer full or error) return -1; } + return 0; } diff --git a/core/sip/pcap_logger.h b/core/sip/pcap_logger.h index 949e2aff..432867d5 100644 --- a/core/sip/pcap_logger.h +++ b/core/sip/pcap_logger.h @@ -13,7 +13,9 @@ class pcap_logger: public file_msg_logger int write_file_header(); public: - int log(const char *data, int data_len, struct sockaddr *src, struct sockaddr *dst, size_t addr_len); + int log(const char *data, int data_len, + struct sockaddr *src, struct sockaddr *dst, + size_t addr_len); int log(const char* buf, int len, sockaddr_storage* src_ip, diff --git a/core/sip/raw_sock.cpp b/core/sip/raw_sock.cpp index 20ca02ee..c1b9b728 100644 --- a/core/sip/raw_sock.cpp +++ b/core/sip/raw_sock.cpp @@ -608,6 +608,7 @@ int raw_iphdr_udp4_send(int rsock, const char* buf, unsigned int len, ret=sendmsg(rsock, &snd_msg, 0); #ifndef RAW_IPHDR_INC_AUTO_FRAG } else { + int bytes_sent; ip_payload = len + sizeof(hdr.udp); /* a fragment offset must be a multiple of 8 => its size must also be a multiple of 8, except for the last fragment */ @@ -635,6 +636,7 @@ int raw_iphdr_udp4_send(int rsock, const char* buf, unsigned int len, ret=sendmsg(rsock, &snd_msg, 0); if (unlikely(ret < 0)) goto end; + bytes_sent = ret; /* all the other fragments, include only the ip header */ iov[0].iov_len = sizeof(hdr.ip); iov[1].iov_base = (char*)iov[1].iov_base + iov[1].iov_len; @@ -651,6 +653,7 @@ int raw_iphdr_udp4_send(int rsock, const char* buf, unsigned int len, ret=sendmsg(rsock, &snd_msg, 0); if (unlikely(ret < 0)) goto end; + bytes_sent+=ret; iov[1].iov_base = (char*)iov[1].iov_base + iov[1].iov_len; } /* last fragment */ @@ -664,6 +667,7 @@ int raw_iphdr_udp4_send(int rsock, const char* buf, unsigned int len, ret=sendmsg(rsock, &snd_msg, 0); if (unlikely(ret < 0)) goto end; + ret+=bytes_sent; } end: #endif /* RAW_IPHDR_INC_AUTO_FRAG */ diff --git a/core/sip/sip_trans.cpp b/core/sip/sip_trans.cpp index ef961927..c969b34b 100644 --- a/core/sip/sip_trans.cpp +++ b/core/sip/sip_trans.cpp @@ -73,7 +73,8 @@ sip_trans::sip_trans() retr_socket(NULL), retr_len(0), last_rseq(0), - logger(NULL) + logger(NULL), + canceled(false) { memset(timers,0,SIP_TRANS_TIMERS*sizeof(void*)); } diff --git a/core/sip/sip_trans.h b/core/sip/sip_trans.h index 2e338c2c..b434e247 100644 --- a/core/sip/sip_trans.h +++ b/core/sip/sip_trans.h @@ -156,6 +156,9 @@ class sip_trans /** message logging */ msg_logger* logger; + /** request canceled? */ + bool canceled; + /** * Tells if a specific timer is set * diff --git a/core/sip/trans_layer.cpp b/core/sip/trans_layer.cpp index b602b254..ce5bcdb5 100644 --- a/core/sip/trans_layer.cpp +++ b/core/sip/trans_layer.cpp @@ -1414,6 +1414,12 @@ int _trans_layer::cancel(trans_ticket* tt, const cstring& dialog_id, return 0; } + if(t->canceled) { + DBG("Transaction has already been canceled\n"); + bucket->unlock(); + return 0; + } + sip_msg* req = t->msg; // RFC 3261 says: SHOULD NOT be sent for other request @@ -1544,16 +1550,20 @@ int _trans_layer::cancel(trans_ticket* tt, const cstring& dialog_id, DBG("Could not update state for UAC transaction\n"); delete p_msg; } - else if(t->logger) { - sockaddr_storage src_ip; - p_msg->local_socket->copy_addr_to(&src_ip); - t->logger->log(p_msg->buf,p_msg->len,&src_ip, - &p_msg->remote_ip,cancel_str); + else { + t->canceled = true; - if(!cancel_t->logger) { - cancel_t->logger = t->logger; - inc_ref(t->logger); - } + if(t->logger) { + sockaddr_storage src_ip; + p_msg->local_socket->copy_addr_to(&src_ip); + t->logger->log(p_msg->buf,p_msg->len,&src_ip, + &p_msg->remote_ip,cancel_str); + + if(!cancel_t->logger) { + cancel_t->logger = t->logger; + inc_ref(t->logger); + } + } } } @@ -1716,7 +1726,7 @@ void _trans_layer::process_rcvd_msg(sip_msg* msg) DBG("Reply matched an existing transaction\n"); - if(t->logger) { + if(t->logger && msg->local_socket && msg->buf && msg->len) { t->logger->log(msg->buf,msg->len,&msg->remote_ip, &msg->local_ip,get_cseq(msg)->method_str, msg->u.reply->code); diff --git a/core/sip/wheeltimer.h b/core/sip/wheeltimer.h index b17ad710..0b236e9c 100644 --- a/core/sip/wheeltimer.h +++ b/core/sip/wheeltimer.h @@ -119,7 +119,11 @@ protected: public: //clock reference volatile u_int32_t wall_clock; // 32 bits +#ifdef __LP64__ atomic_int64 unix_clock; // 64 bits +#else + atomic_int unix_clock; // 32 bits +#endif void insert_timer(timer* t); void remove_timer(timer* t); diff --git a/core/tests/test_uriparser.cpp b/core/tests/test_uriparser.cpp index 53b43e3d..b5c1169a 100644 --- a/core/tests/test_uriparser.cpp +++ b/core/tests/test_uriparser.cpp @@ -130,7 +130,19 @@ FCTMF_SUITE_BGN(test_uriparser) { fct_chk ( URL_decode(dst)==src ); fct_chk ( URL_encode(src)==dst ); fct_chk ( URL_decode(URL_encode(src))==src ); + } FCT_TEST_END(); + FCT_TEST_BGN(uriparser_params_dname5) { + AmUriParser p; + size_t end; + fct_chk(p.parse_contact(";q=\"0.1\";param", 0, end)); + fct_chk( p.params.size()==2); } FCT_TEST_END(); + FCT_TEST_BGN(uriparser_params_dname6) { + AmUriParser p; + size_t end; + fct_chk(p.parse_contact(";+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video;mobility=\"mobile\"", 0, end)); + fct_chk( p.params.size()==3); + } FCT_TEST_END(); } FCTMF_SUITE_END(); diff --git a/doc/Readme.early_announce.txt b/doc/Readme.early_announce.txt index 024e845a..574ed09e 100644 --- a/doc/Readme.early_announce.txt +++ b/doc/Readme.early_announce.txt @@ -66,6 +66,10 @@ mysql_passwd=secret # Database where audio is stored (optional, defaults to 'sems') mysql_db=sems +# CA certificate of MySQL server (optional, set if MySQL access over SSL +# is desired) +mysql_ca_cert=/etc/sems/mysql-ssl-ca-cert.pem + # Value of 'application' field in early_announce audio records (optional, # defaults to 'early_announce') application=early_announce diff --git a/doc/Readme.sbc.txt b/doc/Readme.sbc.txt index 54bf0f90..a5a8acf7 100644 --- a/doc/Readme.sbc.txt +++ b/doc/Readme.sbc.txt @@ -135,7 +135,7 @@ e.g. the request-URI can be set to the user part of the P-Asserted-Identity header combined with the host part of the To. The patterns which can be used are the following: - + $m (or $m. if something follows) - Method $r (or $r. if something follows) - R-URI $f (or $f. if something follows) - From $t (or $t. if something follows) - To @@ -326,6 +326,9 @@ persist and how short it should be to the UA. E.g. if the UA should periodicly re-REGISTER every 60 seconds, but to the upstream registrar the registration should persist 1h, min_reg_expires=3600 and max_ua_expires=60 should be set. +A sample configuration with this kind of setup can be found in +doc/sbc/sample_config_regcache + For a local registrar (i.e. operation without an upstream registrar), see the 'registrar' call control module. @@ -347,6 +350,11 @@ set to transparent, the SDP is parsed and reconstructed (SDP sanity check). Codecs may be filtered out by their payload names in whitelist or blacklist modes. The payload names in the list are case-insensitive (PCMU==pcmu). +If codecs in the two legs should be filtered separately, which is useful for +forcing transcoding codecs, a separate aleg_sdp_filter and aleg_sdpfilter_list +can be set. If this is not set, the sdp_filter/sdpfilter_list is applied to +both call legs. + The s, u and o-lines of the SDP can be anonymized with the setting sdp_anonymize=yes. @@ -480,7 +488,9 @@ party to choose from more codecs than used in the original SDP. In case of another codec preference you can configure codec_preference resp. codec_preference_aleg as described above and choose if codecs should be ordered before adding transcoder codecs or after using prefer_existing_codecs resp. -prefer_existing_codecs_aleg call profile options. +prefer_existing_codecs_aleg call profile options. prefer_existing_codecs and +prefer_existing_codecs_aleg only has effect if codec_preference/codec_preference_aleg +is set. Transcoding related call profile options: @@ -505,7 +515,8 @@ Transcoding related call profile options: prefer_existing_codecs Describes if codecs present in SDP from caller are preferred to added - transcoder codecs. + transcoder codecs. This parameter only has effect in case the codec + preference is set. If this parameter is set to "yes" transcoder codecs are added at the end of codec lists in SDP body AFTER ordering using codec_preference is done. @@ -517,7 +528,8 @@ Transcoding related call profile options: prefer_existing_codecs_aleg Describes if codecs present in SDP from callee are preferred to added - transcoder codecs. + transcoder codecs. This parameter only has effect in case the codec + preference is set for the a leg. If this parameter is set to "yes" transcoder codecs are added at the end of codec lists in SDP body AFTER ordering using codec_preference_aleg is done. @@ -526,6 +538,9 @@ Transcoding related call profile options: added BEFORE ordering using codec_preference_aleg is done and thus may become preferred ones. +Use sdp_filter and aleg_sdp_filter together with the transcoder settings +to force use of transcoded codecs. + Transcoder statistics can be checked via "printCallStats" SBC DI method or can be put into additional headers within reply generated to OPTIONS request. To achive that set global options: options_transcoder_out_stats_hdr and diff --git a/doc/Readme.webconference.txt b/doc/Readme.webconference.txt index 5d085d22..d492064b 100644 --- a/doc/Readme.webconference.txt +++ b/doc/Readme.webconference.txt @@ -104,6 +104,10 @@ roomInfo(string room, string adminpin): code/result: 0 OK 1 wrong adminpin + +---- +addParticipant(string conf_id, string participant_id, string number) + add participant to conf_id and set participant_id. room must be created before! ---- dialout(string room, string adminpin, string callee, string from_user, string domain, diff --git a/doc/dsm/dsm_sbc_syntax.txt b/doc/dsm/dsm_sbc_syntax.txt index be00a927..bc3724c0 100644 --- a/doc/dsm/dsm_sbc_syntax.txt +++ b/doc/dsm/dsm_sbc_syntax.txt @@ -244,3 +244,27 @@ sbc.addToMediaProcessor - add sbc session to media processor (no playlist s sbc.removeFromMediaProcessor - remove sbc session from media processor sbc.streamsSetReceiving(bool receiving_a, bool receiving_b) - pause A/B streams: drop received packets + +sbc.clearExtLocalTag() - clear (externally used) local tag for the SBC call leg + +sbc.setExtLocalTag(string new_local_tag) - set (externally used) local tag for the SBC call leg to specified one + +sbc.setLastReq - save a copy of request object for later use by mod_dlg, e.g. + + transition "initial invite" RUN - invite / { + log(3, "initial invite"); + if sbc.isALeg() { + sbc.setLastReq(); + } + } -> RUN; + + transition "200 from callee" RUN - B2B.otherReply(#sip_code == 200); test(#trans_method=="INVITE") / { + if test($my_condition=="true") { + dlg.reply($config.refuse_code, $config.refuse_reason); + set(#StopProcessing="true"); + ... end the other call leg ... + ... + +sbc.testSDPConnectionAddress(string address_list) - test SDP connection address, sets + $match_connection_addr = "true" or "false"; only in B2B.otherReply() or sipReply events! + e.g. sbc.testSDPConnectionAddress("10.0.0.2,10.0.0.1") diff --git a/doc/dsm/dsm_syntax.txt b/doc/dsm/dsm_syntax.txt index 075fe79b..52253795 100644 --- a/doc/dsm/dsm_syntax.txt +++ b/doc/dsm/dsm_syntax.txt @@ -4,6 +4,8 @@ DSM quick reference Syntax ====== -- comment +# also comment + #include "script.dsm" #include "/path/to/anotherscript.dsm" import(mod_name); @@ -87,6 +89,9 @@ DSM flow e.g. set($var="text"); set($var=$var2); set($var=#key) sets($var=value) e.g. sets($var="text and some $variable and some #param"); + sets($var="$(variable1)$(variable2)"); + sets($var="this is a single hash: ##") + sets($var="a single at on a date with a single dollar: @@$$") var($dstvar=srcvarname) e.g. var($dstvar=$var_counter) param($dstvar=srcparamname) @@ -104,11 +109,17 @@ DSM flow set variable $dst to size of array (e.g. $arrayname[0], $arrayname[1] set, $dst set to 2) + arrayIndex($array, key) - find key in $array, set $index to its index, or "nil" if not found + arrayIndex($array, $var) - find $var in $array, set $var.index to its index, or "nil" if not found + inc($var) clear($var) - clearArray($var) + clearStruct($var) clears all var.* variables + clearArray($var) + clears all var[* variables + Playing prompts and file I/O ---------------------------- @@ -136,6 +147,14 @@ Playing prompts and file I/O playSilence(millisec) - play silence for millisec ms playSilenceFront(millisec) - play silence for millisec ms at first position in playlist + playRingtone(varname, bool front) - play ringtone - parameters in varname + e.g. set($r.length=10000); + set($r.on=150); + set($r.off=550); + set($r.f=550); + set($r.f2=850); + playRingtone($r,true); + recordFile(filename) Throws "file" exeption with #path if file can not be opened for recording @@ -236,6 +255,7 @@ B2B call control log(level, text) e.g. log(1, $var1) + logs(level, "text with $(variable_name) and #(paramname) replacements") -- log all variables: logVars(level) -- log all selects: @@ -243,7 +263,11 @@ B2B call control -- log all Params (only in actions of a transition): logParams(level) -- log everything: - logAll(level) + logAll(level) + dbg("some debug with $(replaced_var)") + info("some info $(message)") + warn("warn #(message_parameter)") + error("error in call @(local_tag)") Timers ------ @@ -340,6 +364,9 @@ brackets match. start start of a session (onStart). This event is processed before any other. +beforeDestroy + end of a session (onBeforeDestroy). This event is processed after any other. + hangup bye/cancel received parameters: diff --git a/doc/dsm/mods/Readme.mod_conference.txt b/doc/dsm/mods/Readme.mod_conference.txt index 1df3b3ea..f5063d01 100644 --- a/doc/dsm/mods/Readme.mod_conference.txt +++ b/doc/dsm/mods/Readme.mod_conference.txt @@ -48,3 +48,10 @@ conference.setupMixIn(float level, unsigned int seconds) conference.playMixIn(string filename) - mix in a file + +conference.playMixInList(string filename [, loop=true]) + - add a file to the mix-in playlist + - the list is setup when this is called for the first time and set as mixin source + +conference.flushMixInList() + - flush mix-in playlist diff --git a/doc/dsm/mods/Readme.mod_groups.txt b/doc/dsm/mods/Readme.mod_groups.txt index 63248a1f..45512294 100644 --- a/doc/dsm/mods/Readme.mod_groups.txt +++ b/doc/dsm/mods/Readme.mod_groups.txt @@ -21,6 +21,9 @@ Actions: groups.postEvent(groupname, var1;var2) - post event to groupname with var1 and var2 groups.postEvent(groupname, var) - post event to groupname with all variables + groups.get(arrayname=groupname) - get all ltags of troupname in arrayname + groups.getSize(varname=groupname) - get size of group in varname + Example: import(mod_groups); diff --git a/doc/dsm/mods/Readme.mod_utils.txt b/doc/dsm/mods/Readme.mod_utils.txt index 413f6702..7597e033 100644 --- a/doc/dsm/mods/Readme.mod_utils.txt +++ b/doc/dsm/mods/Readme.mod_utils.txt @@ -7,10 +7,24 @@ Actions: * sets $errno (arg) utils.playCountLeft(int cnt [, string basedir]) - play count for laguages that have single digits befire the 10s (like german) + play count for laguages that have single digits before the 10s (like german) * Throws "file" exeption with #path if file can not be opened * sets $errno (arg) + utils.getCountRight(int cnt [, string basedir]) + get filenames for a number for laguages that have single digits after the 10s (like english) + into count_file[n] (i.e. count_file[0] .. count_file[n]) + * sets $errno (arg) + + utils.getCountLeft(int cnt [, string basedir]) + get filenames for a number for laguages that have single digits before the 10s (like german) + into count_file[n] (i.e. count_file[0] .. count_file[n]) + * sets $errno (arg) + + utils.getCountRightNoSuffix(int cnt [, string basedir]) + utils.getCountLeftNoSuffix(int cnt [, string basedir]) + as above but without .wav suffix + utils.spell(string word[, string basedir]) plays each character in the word (e.g. utils.spell(321,wav/digits/) plays wav/digits/3.wav, wav/digits/2.wav, wav/digits/1.wav @@ -55,3 +69,9 @@ utils.playRingTone(length [, on [, off [, f [, f2]]]]) Example: utils.playRingTone(0, 1000, 4000, 425, 0); -- Germany + +Conditions +---------- + +utils.isInList(key, cs_list) - match if key is in comma-separated list + e.g. if utils.isInList(#sip_code, "404, 405") { ... } diff --git a/doc/dsm/mods/Readme.mod_xml.txt b/doc/dsm/mods/Readme.mod_xml.txt index d096e81f..74fcb5ca 100644 --- a/doc/dsm/mods/Readme.mod_xml.txt +++ b/doc/dsm/mods/Readme.mod_xml.txt @@ -27,6 +27,18 @@ xml.XPathResultCount($cntvar=xpath_object) Example: xml.XPathResultCount($rescnt="substatus.xpath"); +xml.getXPathResult($dstvar=xpath_object) + save contents of xpath result object into $dstvar, + or $dstvar[0] .. $dstvar[n] if more than one result + +xml.printXPathResult($dstvar=xpath_object) + print information abut XPath result into $dstvar (with object names) + +xml.updateXPathResult(xpath_object=content) - update xpath object with content (xml string) +xml.updateXPathResult(xpath_object[index]=content) - update only object at position index + +xml.docDump($varname=xml_object) - dump xml_object to variable $varname + xml.setLoglevel(level) set libxml2 error logging level. Default: error Valid: error, warn, info, debug diff --git a/doc/sbc/sample_config_regcache/exin.sbcprofile.conf b/doc/sbc/sample_config_regcache/exin.sbcprofile.conf new file mode 100644 index 00000000..36936f74 --- /dev/null +++ b/doc/sbc/sample_config_regcache/exin.sbcprofile.conf @@ -0,0 +1,14 @@ + +# SIP NAT handling: recommended if dealing with far end NATs +dlg_nat_handling=yes + +# destination IP[:port] for outgoing requests +next_hop=192.168.5.110:5060 +#10.99.250.26:5060 + +## RTP relay +# enable RTP relaying (bridging): +enable_rtprelay=yes + +# setting RURI domain here to proxy (in case of domain name, not necessary...) +RURI=sip:$rU@192.168.5.110 \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/inex.sbcprofile.conf b/doc/sbc/sample_config_regcache/inex.sbcprofile.conf new file mode 100644 index 00000000..e47ee885 --- /dev/null +++ b/doc/sbc/sample_config_regcache/inex.sbcprofile.conf @@ -0,0 +1 @@ +enable_reg_caching=yes \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/interface_map.conf b/doc/sbc/sample_config_regcache/interface_map.conf new file mode 100644 index 00000000..af2fed6f --- /dev/null +++ b/doc/sbc/sample_config_regcache/interface_map.conf @@ -0,0 +1,2 @@ +192\.168\.5\.110=>inex +192\.168\.6\.110=>exin diff --git a/doc/sbc/sample_config_regcache/method_map.conf b/doc/sbc/sample_config_regcache/method_map.conf new file mode 100644 index 00000000..55949f3d --- /dev/null +++ b/doc/sbc/sample_config_regcache/method_map.conf @@ -0,0 +1 @@ +REGISTER=>reg \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/readme.txt b/doc/sbc/sample_config_regcache/readme.txt new file mode 100644 index 00000000..e690a01b --- /dev/null +++ b/doc/sbc/sample_config_regcache/readme.txt @@ -0,0 +1,16 @@ +Sample SBC confguration with two interfaces and registration caching + +SEMS listening at 192.168.6.110:5060 ("external interface") +SEMS listening at 192.168.5.110:5080 ("internal interface") +Registrar at 192.168.5.110:5060 + +clients registering to username@192.168.6.110 + +SEMS is setting the RURI domain towards the registrar to 192.168.5.110, could be removed +if the registrar accepts DNS name. + +setup & try in-tree e.g. like this: + +git clone https://github.com/sems-server/sems.git +cd sems; make; cd core +./sems -f ../doc/sbc/sample_config_regcache/sems.conf \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/reg.sbcprofile.conf b/doc/sbc/sample_config_regcache/reg.sbcprofile.conf new file mode 100644 index 00000000..d031a791 --- /dev/null +++ b/doc/sbc/sample_config_regcache/reg.sbcprofile.conf @@ -0,0 +1,12 @@ +# registration cache +enable_reg_caching=yes +# register upstream every 3600 sec +min_reg_expires=3600 +# and make UA re-register every 60 sec +max_ua_expires=60 + +next_hop=192.168.5.110:5060 + +# setting RURI here to registrar, in order to make my registrar accept it +# if it's DNS name, the registrar should accept it without modification +RURI=sip:192.168.5.110 \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/sbc.conf b/doc/sbc/sample_config_regcache/sbc.conf new file mode 100644 index 00000000..e5e6a11a --- /dev/null +++ b/doc/sbc/sample_config_regcache/sbc.conf @@ -0,0 +1,6 @@ +profiles=exin,inex,reg +# load one regex-mapping for method, one for interface (here: received IP) +regex_maps=method_map,interface_map + +# first try method (REGISTER), then map on received interface (here: received IP) +active_profile=$M($m=>method_map),$M($Ri=>interface_map) \ No newline at end of file diff --git a/doc/sbc/sample_config_regcache/sems.conf b/doc/sbc/sample_config_regcache/sems.conf new file mode 100644 index 00000000..2ef8d105 --- /dev/null +++ b/doc/sbc/sample_config_regcache/sems.conf @@ -0,0 +1,35 @@ + +# sample sbc configuration, with one external IP (192.168.6.110) and one internal (192.168.5.110) facing registrar +# + +# debug log to foreground +fork=no +stderr=yes +loglevel=3 + +# running in source tree - otherwise change these +plugin_path=lib/ +plugin_config_path=../doc/sbc/sample_config_regcache/ + + +load_plugins=session_timer;uac_auth;sbc + +interfaces=intern,extern + +sip_ip_intern=192.168.5.110 +sip_port_intern=5080 +media_ip_intern=192.168.5.110 +rtp_low_port_intern=10000 +rtp_high_port_intern=20000 + +sip_ip_extern=192.168.6.110 +sip_port_extern=5060 +media_ip_extern=192.168.6.110 +rtp_low_port_extern=20000 +rtp_high_port_extern=30000 +public_ip_extern=192.168.6.110 +# sig_sock_opts_extern=force_via_address +# tcp_connect_timeout_extern=1000 +# tcp_idle_timeout_extern=900000 + +application=sbc diff --git a/pkg/deb/jessie/sems.default b/pkg/deb/jessie/sems.default index c2774ef2..0c9939e9 100644 --- a/pkg/deb/jessie/sems.default +++ b/pkg/deb/jessie/sems.default @@ -2,8 +2,8 @@ # # this file is sourced by SEMS init script /etc/init.d/sems -# Don't start with default config as we need to deploy the ngcp-templates first -SEMS_RUN="no" +# start with default config (even if someone might want to configure an application first) +SEMS_RUN="yes" # ser configuration file SEMS_CFG_FILE="/etc/sems/sems.conf" diff --git a/tools/Makefile b/tools/Makefile index c0fe133f..b65c074f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -18,7 +18,7 @@ install_tools: $(DESTDIR)$(bin-prefix)/$(bin-dir) done sems-logfile-callextract: logfile-splitter.o - g++ -o sems-logfile-callextract logfile-splitter.o + $(LD) -o sems-logfile-callextract logfile-splitter.o -lstdc++ clean: rm -f logfile-splitter.o sems-logfile-callextract