/** * callfork.cpp * This file is part of the YATE Project http://YATE.null.ro * * Call Forker * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program 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. */ #include using namespace TelEngine; namespace { // anonymous #define MOD_PREFIX "fork" static ObjList s_calls; static int s_current = 0; class ForkSlave; class ForkMaster : public CallEndpoint, public DebugEnabler { friend class ForkSlave; public: ForkMaster(ObjList* targets, int lvl); virtual ~ForkMaster(); virtual void disconnected(bool final, const char* reason); bool startCalling(Message& msg); void checkTimer(const Time& tmr); void lostSlave(ForkSlave* slave, const char* reason); bool msgAnswered(Message& msg, const String& dest); bool msgProgress(Message& msg, const String& dest); const ObjList& slaves() const { return m_slaves; } bool msgToSlaves(const Message& msg, const String& match); protected: bool clearRinging(const String& id); void clear(bool softly); String* getNextDest(); bool forkSlave(String* dest); bool callContinue(); RefPointer m_discPeer; ObjList m_slaves; String m_ringing; Regexp m_failures; int m_index; bool m_answered; bool m_rtpForward; bool m_rtpStrict; bool m_fake; ObjList* m_targets; Message* m_exec; u_int64_t m_timer; bool m_timerDrop; bool m_execNext; bool m_chanMsgs; bool m_failuresRev; bool m_setId; String m_reason; String m_media; unsigned int m_targetIdx; int m_level; }; class ForkSlave : public CallEndpoint, public DebugEnabler { public: enum Type { Unknown = 0, Regular, Auxiliar, Persistent }; ForkSlave(ForkMaster* master, const char* id); virtual ~ForkSlave(); virtual void destroyed(); virtual void disconnected(bool final, const char* reason); void clearMaster(RefPointer* master = 0); inline void lostMaster(const char* reason) { clearMaster(); disconnect(reason); } inline bool isMaster(ForkMaster* master) { return master && m_master == master; } inline Type type() const { return m_type; } inline void setType(Type type) { m_type = type; } protected: ForkMaster* m_master; Type m_type; int m_level; }; class ForkModule : public Module { public: ForkModule(); virtual ~ForkModule(); bool unload(); bool msgToSlaves(const Message& msg, const String& match); protected: virtual void initialize(); virtual bool received(Message& msg, int id); virtual void statusParams(String& str); bool msgExecute(Message& msg); bool msgLocate(Message& msg, bool masquerade); bool msgToMaster(Message& msg, bool answer); bool m_hasRelays; }; static TokenDict s_calltypes[] = { { "regular", ForkSlave::Regular }, { "auxiliar", ForkSlave::Auxiliar }, { "persistent", ForkSlave::Persistent }, { 0, 0 } }; INIT_PLUGIN(ForkModule); UNLOAD_PLUGIN(unloadNow) { if (unloadNow) return __plugin.unload(); return true; } class ForkRelay : public MessageHandler { public: inline ForkRelay(const char* name, const char* match, int priority) : MessageHandler(name,priority,__plugin.name()), m_match(match) { } virtual bool received(Message& msg) { return __plugin.msgToSlaves(msg,m_match); } private: String m_match; }; ForkMaster::ForkMaster(ObjList* targets, int lvl) : m_index(0), m_answered(false), m_rtpForward(false), m_rtpStrict(false), m_fake(false), m_targets(targets), m_exec(0), m_timer(0), m_timerDrop(false), m_execNext(false), m_chanMsgs(false), m_failuresRev(false), m_setId(false), m_reason("hangup"), m_targetIdx(0), m_level(lvl) { String tmp(MOD_PREFIX "/"); tmp << ++s_current; setId(tmp); debugName(id()); debugChain(&__plugin); if (m_level > 0) debugLevel(m_level); s_calls.append(this); DDebug(this,DebugAll,"ForkMaster::ForkMaster(%p) [%p]",targets,this); } ForkMaster::~ForkMaster() { DDebug(this,DebugAll,"ForkMaster::~ForkMaster() [%p]",this); m_timer = 0; CallEndpoint::commonMutex().lock(); s_calls.remove(this,false); CallEndpoint::commonMutex().unlock(); clear(false); if (m_discPeer && !m_answered) { RefPointer call = m_discPeer->getPeer(); if (call) { Message* r = new Message("chan.replaced",0,true); r->addParam("id",id()); r->addParam("newid",call->id()); r->addParam("peerid",m_discPeer->id()); r->userData(this); Engine::enqueue(r); call = 0; } } m_discPeer = 0; if (m_chanMsgs) { Message* msg = new Message("chan.hangup"); msg->addParam("id",id()); msg->addParam("cdrtrack",String::boolText(false)); Engine::enqueue(msg); } } void ForkMaster::disconnected(bool final, const char* reason) { CallEndpoint::disconnected(final,reason); if (m_chanMsgs && !(final || m_answered || m_discPeer)) { Message* msg = new Message("chan.disconnected"); msg->addParam("id",id()); if (m_exec) msg->copyParams(*m_exec,"error,reason"); msg->userData(this); Engine::enqueue(msg); } } String* ForkMaster::getNextDest() { String* ret = 0; while (!ret && m_targets && m_targets->count()) ret = static_cast(m_targets->remove(false)); return ret; } bool ForkMaster::forkSlave(String* dest) { if (null(dest)) return false; #ifdef XDEBUG Debugger dbg(debugAt(DebugAll) ? DebugAll : 50, "ForkMaster forkSlave"," '%s' dest='%s' [%p]",id().c_str(),dest->c_str(),this); #endif bool ok = false; m_exec->clearParam("error"); m_exec->clearParam("reason"); Message msgCopy(*m_exec); msgCopy.setParam("callto",*dest); msgCopy.setParam("rtp_forward",String::boolText(m_rtpForward)); msgCopy.setParam("cdrtrack",String::boolText(false)); NamedList* params = YOBJECT(NamedList,dest); if (params) msgCopy.copyParams(*params); const char* error = "failure"; if (m_execNext) { RefPointer peer = getPeer(); if (!peer) { clear(false); return false; } Debug(this,DebugCall,"Call '%s' directly to target '%s' [%p]", peer->id().c_str(),dest->c_str(),this); m_discPeer = peer; msgCopy.userData(peer); msgCopy.setParam("id",peer->id()); msgCopy.clearParam("cdrtrack"); if (!Engine::dispatch(msgCopy)) { error = msgCopy.getValue("error",error); Debug(this,DebugNote,"Call '%s' failed non-fork to target '%s', error '%s' [%p]", getPeerId().c_str(),dest->c_str(),error,this); return false; } clear(false); return true; } String tmp(id()); tmp << "/" << ++m_index; ForkSlave* slave = new ForkSlave(this,tmp); msgCopy.setParam("id",tmp); msgCopy.userData(slave); bool autoring = false; if (Engine::dispatch(msgCopy)) { ok = true; autoring = msgCopy.getBoolValue("fork.autoring"); if (m_ringing.null() && (autoring || msgCopy.getBoolValue("fork.ringer"))) m_ringing = tmp; else autoring = false; if (m_rtpForward) { String rtp(msgCopy.getValue("rtp_forward")); if (rtp != "accepted") { error = "nomedia"; int level = DebugWarn; if (m_rtpStrict) { ok = false; level = DebugCall; } Debug(this,level,"Call '%s' did not get RTP forward from '%s' target '%s' [%p]", getPeerId().c_str(),slave->getPeerId().c_str(),dest->c_str(),this); } } m_exec->copyParams(msgCopy,"error,reason,rtp_forward"); } else error = msgCopy.getValue("error",error); XDebug(this,DebugAll,"Executed slave (%p) '%s' refs=%u ok=%u [%p]", slave,slave->id().c_str(),slave->refcount(),ok,this); msgCopy.userData(0); // Avoid adding slave to list if already terminated (master reset) // Avoid adding a slave with refcount=1:this will trigger re-enter in callContinue() on slave destroy bool master = slave->isMaster(this); if (ok && master && slave->refcount() > 1) { ForkSlave::Type type = static_cast(msgCopy.getIntValue("fork.calltype",s_calltypes,ForkSlave::Regular)); Debug(this,DebugCall,"Call '%s' calling on %s '%s' target '%s' [%p]", getPeerId().c_str(),lookup(type,s_calltypes),tmp.c_str(),dest->c_str(),this); slave->setType(type); m_slaves.append(slave); XDebug(this,DebugInfo,"Added slave (%p) '%s' refs=%u [%p]", slave,slave->id().c_str(),slave->refcount(),this); if (autoring) { Message* ring = new Message(msgCopy.getValue("fork.automessage","call.ringing")); ring->addParam("id",slave->getPeerId()); ring->addParam("peerid",tmp); ring->addParam("targetid",tmp); Engine::enqueue(ring); } } else { if (!ok) Debug(this,DebugNote,"Call '%s' failed on '%s' target '%s', error '%s' [%p]", getPeerId().c_str(),tmp.c_str(),dest->c_str(),error,this); else if (!master) Debug(this,DebugAll,"Call '%s' target '%s' slave '%s' lost master during execute [%p]", getPeerId().c_str(),dest->c_str(),tmp.c_str(),this); else Debug(this,DebugAll,"Call '%s' target '%s' slave '%s' execute succeeded with no peer [%p]", getPeerId().c_str(),dest->c_str(),tmp.c_str(),this); ok = false; slave->lostMaster(error); } slave->deref(); return ok; } bool ForkMaster::startCalling(Message& msg) { #ifdef XDEBUG Debugger dbg(debugAt(DebugInfo) ? DebugInfo : 50, "ForkMaster startCalling"," '%s' [%p]",id().c_str(),this); #endif m_exec = new Message(msg); m_chanMsgs = msg.getBoolValue("fork.chanmsgs",(msg.getParam("pbxoper") != 0)); if (m_chanMsgs) { Message* m = new Message("chan.startup"); m->addParam("id",id()); m->addParam("module",__plugin.name()); m->addParam("status","outgoing"); m->addParam("cdrtrack",String::boolText(false)); m->addParam("pbxguest",String::boolText(true)); m->addParam("fork.origid",getPeerId()); m->copyParams(msg,"caller,callername,called,billid,username"); Engine::enqueue(m); } // stoperror is OBSOLETE m_failures = msg.getValue("fork.stop",msg.getValue("stoperror")); if (m_failures.endsWith("^")) { m_failuresRev = true; m_failures = m_failures.substr(0,m_failures.length()-1); } m_setId = msg.getBoolValue("fork.setid"); m_exec->clearParam("stoperror"); m_exec->clearParam("fork.stop"); m_exec->clearParam("fork.setid"); m_exec->clearParam("peerid"); m_exec->setParam("fork.master",id()); m_exec->setParam("fork.origid",getPeerId()); m_rtpForward = msg.getBoolValue("rtp_forward"); m_rtpStrict = msg.getBoolValue("rtpstrict"); if (!callContinue()) { const char* err = m_exec->getValue("reason"); if (err) msg.setParam("reason",err); err = m_exec->getValue("error"); msg.setParam("error",err); XDebug(this,DebugAll,"startCalling failed refs=%u [%p]",refcount(),this); disconnect(err); return false; } if (m_rtpForward) { String tmp(m_exec->getValue("rtp_forward")); if (tmp != "accepted") { // no RTP forwarding from now on m_rtpForward = false; tmp = String::boolText(false); } msg.setParam("rtp_forward",tmp); } msg.setParam("peerid",id()); msg.setParam("targetid",id()); return true; } bool ForkMaster::callContinue() { #ifdef XDEBUG Debugger dbg(debugAt(DebugInfo) ? DebugInfo : 50, "ForkMaster callContinue"," '%s' [%p]",id().c_str(),this); #endif m_timer = 0; m_timerDrop = false; int forks = 0; while (m_exec && !m_answered) { // get the fake media source at start of each group m_media = m_exec->getValue("fork.fake"); String* dest = getNextDest(); if (!dest) break; XDebug(this,DebugAll,"Handling target #%u '%s' [%p]",++m_targetIdx,dest->c_str(),this); if (dest->startSkip("|",false)) { m_execNext = false; if (*dest) { String tmp(*dest); int tout = 0; if (tmp.startSkip("next=",false) && ((tout = tmp.toInteger()) > 0)) { m_timer = 1000 * tout + Time::now(); m_timerDrop = false; } else if (tmp.startSkip("drop=",false) && ((tout = tmp.toInteger()) > 0)) { m_timer = 1000 * tout + Time::now(); m_timerDrop = true; } else if (tmp.startSkip("exec=",false) && ((tout = tmp.toInteger()) > 0)) { m_timer = 1000 * tout + Time::now(); m_timerDrop = true; m_execNext = true; } else if (tmp == "exec") m_execNext = true; else Debug(this,DebugMild,"Call '%s' ignoring modifier '%s' [%p]", getPeerId().c_str(),dest->c_str(),this); } dest->destruct(); if (forks) break; m_timer = 0; m_timerDrop = false; continue; } if (forkSlave(dest)) ++forks; dest->destruct(); } XDebug(this,DebugAll,"Exiting callContinue forks=%u [%p]",forks,this); return (forks > 0); } void ForkMaster::checkTimer(const Time& tmr) { if (!m_timer || m_timer > tmr) return; m_timer = 0; if (m_timerDrop) { m_timerDrop = false; Debug(this,DebugNote,"Call '%s' dropping slaves on timer [%p]", getPeerId().c_str(),this); clear(true); } else Debug(this,DebugNote,"Call '%s' calling more on timer [%p]", getPeerId().c_str(),this); callContinue(); } void ForkMaster::lostSlave(ForkSlave* slave, const char* reason) { Lock lock(CallEndpoint::commonMutex()); bool ringing = clearRinging(slave->id()); #ifdef XDEBUG GenObject* gen = #endif m_slaves.remove(slave,false); #ifdef XDEBUG XDebug(this,gen ? DebugInfo : DebugMild,"Removed%s slave (%p) '%s' refs=%u [%p]", (gen ? "":" MISSING"),slave,slave->id().c_str(),slave->refcount(),this); #endif if (m_answered) return; if (reason) m_exec->setParam("fork.reason",reason); if (reason && m_failures && (m_failures.matches(reason) != m_failuresRev)) { Debug(this,DebugCall,"Call '%s' terminating early on reason '%s' [%p]", getPeerId().c_str(),reason,this); } else { // Slave have no type: we are still processing it, continue from processing point if (!slave->type()) return; unsigned int regulars = 0; unsigned int auxiliars = 0; unsigned int persistents = 0; for (ObjList* l = m_slaves.skipNull(); l; l = l->skipNext()) { switch (static_cast(l->get())->type()) { case ForkSlave::Auxiliar: auxiliars++; break; case ForkSlave::Persistent: persistents++; break; default: regulars++; break; } } Debug(this,DebugNote, "Call '%s' lost%s slave '%s' reason '%s' remaining %u regulars, %u auxiliars, %u persistent [%p]", getPeerId().c_str(),ringing ? " ringing" : "", slave->id().c_str(),reason, regulars,auxiliars,persistents,this); if (auxiliars && !regulars) { Debug(this,DebugNote,"Dropping remaining %u auxiliars [%p]",auxiliars,this); clear(true); } if (regulars || callContinue()) return; Debug(this,DebugCall,"Call '%s' failed after %d attempts with reason '%s' [%p]", getPeerId().c_str(),m_index,reason,this); } m_timer = 0; lock.drop(); disconnect(reason); } bool ForkMaster::msgAnswered(Message& msg, const String& dest) { Lock lock(CallEndpoint::commonMutex()); m_timer = 0; // make sure only the first succeeds if (m_answered) return false; RefPointer peer = getPeer(); if (!peer) return false; ForkSlave* slave = static_cast(m_slaves[dest]); if (!slave) return false; RefPointer call = slave->getPeer(); if (!call) return false; m_media.clear(); m_fake = false; m_answered = true; m_reason = msg.getValue("reason","pickup"); Debug(this,DebugCall,"Call '%s' answered on '%s' by '%s' [%p]", peer->id().c_str(),dest.c_str(),call->id().c_str(),this); if (m_setId) { msg.setParam("fork.origid",msg.getValue("id")); msg.setParam("id",id()); } else msg.setParam("fork.master",id()); msg.setParam("peerid",peer->id()); msg.setParam("targetid",peer->id()); Message* r = new Message("chan.replaced",0,true); r->addParam("id",id()); r->addParam("newid",call->id()); r->addParam("peerid",peer->id()); r->addParam("id.1",dest); r->addParam("newid.1",peer->id()); r->addParam("peerid.1",call->id()); lock.drop(); clearEndpoint(); call->connect(peer); Engine::enqueue(r); return true; } bool ForkMaster::msgProgress(Message& msg, const String& dest) { Lock lock(CallEndpoint::commonMutex()); if (m_answered) return false; if (m_ringing && (m_ringing != dest)) return false; ForkSlave* slave = static_cast(m_slaves[dest]); if (!slave) return false; RefPointer peer = getPeer(); if (!peer) return false; RefPointer dataEp = getEndpoint(); if (m_ringing.null()) m_ringing = dest; if (m_fake || !dataEp) { const CallEndpoint* call = slave->getPeer(); if (!call) call = static_cast(msg.userObject(YATOM("CallEndpoint"))); if (call) { dataEp = call->getEndpoint(); if (dataEp) { // don't use the media if it has no format and fake is possible if ((m_fake || m_media) && !(dataEp->getSource() && dataEp->getSource()->getFormat())) dataEp = 0; else { m_fake = false; setEndpoint(dataEp); m_media.clear(); } } } } if (m_setId) { msg.setParam("fork.origid",msg.getValue("id")); msg.setParam("id",id()); } else msg.setParam("fork.master",id()); msg.setParam("peerid",peer->id()); msg.setParam("targetid",peer->id()); if (m_media) { Debug(this,DebugInfo,"Call '%s' faking media '%s'", peer->id().c_str(),m_media.c_str()); String newMsg; if (m_exec) newMsg = m_exec->getValue("fork.fakemessage"); Message m("chan.attach"); m.userData(this); m.addParam("id",id()); m.addParam("source",m_media); m.addParam("single",String::boolText(true)); if (m_exec) m.copyParam(*m_exec,"autorepeat"); m_media.clear(); lock.drop(); if (Engine::dispatch(m)) { m_fake = true; if (newMsg) msg = newMsg; } } Debug(this,DebugNote,"Call '%s' going on '%s' to '%s'%s%s [%p]", peer->id().c_str(),dest.c_str(),msg.getValue("id"), (dataEp || m_fake) ? " with audio data" : "", m_fake ? " (fake)" : "",this); return true; } bool ForkMaster::msgToSlaves(const Message& msg, const String& match) { bool ok = false; for (ObjList* l = m_slaves.skipNull(); l; l = l->skipNext()) { ForkSlave* slave = static_cast(l->get()); if (slave->type() == ForkSlave::Auxiliar) continue; Message* m = new Message(msg); m->setParam(match,slave->getPeerId()); m->userData(msg.userData()); ok = Engine::enqueue(m) || ok; } return ok; } bool ForkMaster::clearRinging(const String& id) { if (m_ringing != id) return false; m_fake = false; m_ringing.clear(); clearEndpoint(); return true; } void ForkMaster::clear(bool softly) { XDebug(this,DebugAll,"Clearing [%p]",this); RefPointer slave; CallEndpoint::commonMutex().lock(); ListIterator iter(m_slaves); while ((slave = static_cast(iter.get()))) { if (softly && (slave->type() == ForkSlave::Persistent)) continue; clearRinging(slave->id()); m_slaves.remove(slave,false); slave->clearMaster(); CallEndpoint::commonMutex().unlock(); slave->lostMaster(m_reason); CallEndpoint::commonMutex().lock(); slave = 0; } if (softly) { CallEndpoint::commonMutex().unlock(); return; } TelEngine::destruct(m_exec); TelEngine::destruct(m_targets); CallEndpoint::commonMutex().unlock(); } ForkSlave::ForkSlave(ForkMaster* master, const char* id) : CallEndpoint(id), m_master(master), m_type(Unknown), m_level(0) { debugName(CallEndpoint::id()); if (master) { debugChain(master); m_level = master->m_level; if (m_level > 0) debugLevel(m_level); } DDebug(this,DebugAll,"ForkSlave::ForkSlave(%s) [%p]",master->id().c_str(),this); } ForkSlave::~ForkSlave() { DDebug(this,DebugAll,"ForkSlave::~ForkSlave() [%p]",this); } void ForkSlave::destroyed() { XDebug(this,DebugAll,"Destroying [%p]",this); CallEndpoint::commonMutex().lock(); RefPointer master; clearMaster(&master); CallEndpoint::commonMutex().unlock(); if (master) master->lostSlave(this,0); XDebug(this,DebugAll,"Destroyed [%p]",this); CallEndpoint::destroyed(); } void ForkSlave::disconnected(bool final, const char* reason) { XDebug(this,DebugAll,"Disconnected refs=%u [%p]",refcount(),this); CallEndpoint::commonMutex().lock(); RefPointer master; clearMaster(&master); CallEndpoint::commonMutex().unlock(); CallEndpoint::disconnected(final,reason); if (master) master->lostSlave(this,reason); } void ForkSlave::clearMaster(RefPointer* master) { if (master) *master = m_master; m_master = 0; debugChain(&__plugin); if (m_level > 0) debugLevel(m_level); } ForkModule::ForkModule() : Module("callfork","misc"), m_hasRelays(false) { Output("Loaded module Call Forker"); } ForkModule::~ForkModule() { Output("Unloading module Call Forker"); } void ForkModule::initialize() { Output("Initializing module Call Forker"); setup(); if (!m_hasRelays) { static const String s_prio("priorities"); Configuration cfg(Engine::configFile("callfork")); installRelay(Execute,cfg.getIntValue(s_prio,messageName(Execute),100)); installRelay(Masquerade,cfg.getIntValue(s_prio,messageName(Masquerade),10)); installRelay(Locate,cfg.getIntValue(s_prio,messageName(Locate),40)); installRelay(Answered,cfg.getIntValue(s_prio,messageName(Answered),20)); installRelay(Ringing,cfg.getIntValue(s_prio,messageName(Ringing),20)); installRelay(Progress,cfg.getIntValue(s_prio,messageName(Progress),20)); int prio = cfg.getIntValue(s_prio,"generic",100); const NamedList* generic = cfg.getSection("messages"); if (generic) { NamedIterator iter(*generic); while (!iter.eof()) { const NamedString* item = iter.get(); if (TelEngine::null(item)) continue; switch (relayId(item->name())) { case 0: case Tone: case Text: case Update: case Control: case MsgExecute: break; default: Debug(this,DebugWarn,"Refusing to fork message '%s'",item->name().c_str()); continue; } int p = cfg.getIntValue(s_prio,item->name(),prio); ForkRelay* r = new ForkRelay(item->name(),*item,p); Debug(this,DebugInfo,"Will fork messages '%s' matching '%s' priority %d", item->name().c_str(),item->c_str(),p); Engine::install(r); m_hasRelays = true; } } else { int p = cfg.getIntValue(s_prio,"chan.dtmf",prio); Debug(this,DebugInfo,"Default fork for 'chan.dtmf' matching 'peerid' priority %d",p); Engine::install(new ForkRelay("chan.dtmf","peerid",p)); m_hasRelays = true; } } } bool ForkModule::unload() { if (m_hasRelays) return false; Lock lock(CallEndpoint::commonMutex(),500000); if (!lock.locked()) return false; if (s_calls.count()) return false; uninstallRelays(); return true; } void ForkModule::statusParams(String& str) { CallEndpoint::commonMutex().lock(); str.append("total=",",") << s_current << ",forks=" << s_calls.count(); CallEndpoint::commonMutex().unlock(); } bool ForkModule::msgExecute(Message& msg) { CallEndpoint* ch = YOBJECT(CallEndpoint,msg.userData()); if (!ch) return false; String dest(msg.getParam("callto")); if (!dest.startSkip(MOD_PREFIX)) return false; ObjList* targets = 0; if (dest) targets = dest.split(' ',false); else { for (int n = 1; true; n++) { String prefix; prefix << "callto." << n; NamedString* ns = msg.getParam(prefix); if (!ns) break; if (TelEngine::null(ns)) continue; // Set target parameters from enclosed list // Override/add new params from message sub-params NamedPointer* np = YOBJECT(NamedPointer,ns); NamedList* target = YOBJECT(NamedList,np); if (target) { np->takeData(); target->assign(*ns); } else target = new NamedList(*ns); target->copySubParams(msg,prefix + "."); if (!targets) targets = new ObjList; targets->append(target); // Clear from initial message msg.clearParam(prefix,'.'); } } if (!(targets && targets->skipNull())) { msg.setParam("error","failure"); TelEngine::destruct(targets); return false; } CallEndpoint::commonMutex().lock(); ForkMaster* master = new ForkMaster(targets,msg.getIntValue(YSTRING("fork.debug_level"))); bool ok = master->connect(ch,msg.getValue("reason")) && master->startCalling(msg); CallEndpoint::commonMutex().unlock(); master->deref(); return ok; } bool ForkModule::msgLocate(Message& msg, bool masquerade) { String tmp(msg.getParam("id")); if (!tmp.startsWith(MOD_PREFIX "/")) return false; Lock lock(CallEndpoint::commonMutex()); CallEndpoint* c = static_cast(s_calls[tmp]); if (!c) { ForkMaster* m = static_cast(s_calls[tmp.substr(0,tmp.rfind('/'))]); if (m) c = static_cast(m->slaves()[tmp]); } if (!c) return false; if (masquerade) { tmp = msg.getValue("message"); if (tmp.null()) return false; msg.clearParam("message"); msg = tmp; if (tmp == "call.answered") msg.setParam("cdrcreate",String::boolText(false)); else if (tmp == "call.execute") msg.setParam("cdrtrack",String::boolText(false)); if (c->getPeer()) msg.setParam("peerid",c->getPeerId()); } msg.userData(c); return !masquerade; } bool ForkModule::msgToMaster(Message& msg, bool answer) { String dest(msg.getParam("peerid")); if (dest.null()) dest = msg.getParam("targetid"); if (!dest.startsWith(MOD_PREFIX "/")) return false; int slash = dest.rfind('/'); CallEndpoint::commonMutex().lock(); // the fork master will be kept referenced until we finish the work RefPointer m = static_cast(s_calls[dest.substr(0,slash)]); CallEndpoint::commonMutex().unlock(); if (m) return answer ? m->msgAnswered(msg,dest) : m->msgProgress(msg,dest); return false; } bool ForkModule::msgToSlaves(const Message& msg, const String& match) { if (match.null()) return false; const String* param = msg.getParam(match); if (TelEngine::null(param)) return false; if (!param->startsWith(MOD_PREFIX "/")) return false; Lock lock(CallEndpoint::commonMutex()); ForkMaster* m = static_cast(s_calls[*param]); return m && m->msgToSlaves(msg,match); } bool ForkModule::received(Message& msg, int id) { switch (id) { case Execute: return msgExecute(msg); case Locate: return msgLocate(msg,false); case Masquerade: return msgLocate(msg,true); case Answered: while (msgToMaster(msg,true)) ; return false; case Progress: case Ringing: while (msgToMaster(msg,false)) ; return false; case Timer: CallEndpoint::commonMutex().lock(); for (ObjList* l = s_calls.skipNull(); l; l = l->skipNext()) { RefPointer m = static_cast(l->get()); if (m) m->checkTimer(msg.msgTime()); } CallEndpoint::commonMutex().unlock(); // fall through default: return Module::received(msg,id); } } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */