From f2553f398ded57fbe31245c3a9d0d375c5a9cc33 Mon Sep 17 00:00:00 2001 From: marian Date: Tue, 12 Feb 2008 13:54:29 +0000 Subject: [PATCH] Changed jabber library layout. Specialized services can be attached to the engine to process specific stream events. git-svn-id: http://voip.null.ro/svn/yate@1721 acf43c95-373e-0410-b603-e72c3f656dc1 --- libs/yjingle/jbengine.cpp | 2349 ++++++++++++++++++++---------------- libs/yjingle/jbstream.cpp | 1550 +++++++++++++----------- libs/yjingle/jgengine.cpp | 299 +++-- libs/yjingle/session.cpp | 1362 +++++++++------------ libs/yjingle/xmlparser.cpp | 12 + libs/yjingle/xmlparser.h | 252 ++-- libs/yjingle/xmpputils.cpp | 82 +- libs/yjingle/xmpputils.h | 504 +++++--- libs/yjingle/yatejabber.h | 1981 +++++++++++++++++------------- libs/yjingle/yatejingle.h | 991 +++++++-------- modules/yjinglechan.cpp | 2033 +++++++++++++++---------------- 11 files changed, 6017 insertions(+), 5398 deletions(-) diff --git a/libs/yjingle/jbengine.cpp b/libs/yjingle/jbengine.cpp index 48f2c90f..478d4259 100644 --- a/libs/yjingle/jbengine.cpp +++ b/libs/yjingle/jbengine.cpp @@ -22,171 +22,497 @@ */ #include +#include using namespace TelEngine; +TokenDict JBEvent::s_type[] = { + {"Terminated", Terminated}, + {"Destroy", Destroy}, + {"Running", Running}, + {"WriteFail", WriteFail}, + {"Presence", Presence}, + {"Message", Message}, + {"Iq", Iq}, + {"IqError", IqError}, + {"IqResult", IqResult}, + {"IqDiscoInfoGet", IqDiscoInfoGet}, + {"IqDiscoInfoSet", IqDiscoInfoSet}, + {"IqDiscoInfoRes", IqDiscoInfoRes}, + {"IqDiscoInfoErr", IqDiscoInfoErr}, + {"IqDiscoItemsGet", IqDiscoItemsGet}, + {"IqDiscoItemsSet", IqDiscoItemsSet}, + {"IqDiscoItemsRes", IqDiscoItemsRes}, + {"IqDiscoItemsErr", IqDiscoItemsErr}, + {"IqCommandGet", IqCommandGet}, + {"IqCommandSet", IqCommandSet}, + {"IqCommandRes", IqCommandRes}, + {"IqCommandErr", IqCommandErr}, + {"IqJingleGet", IqJingleGet}, + {"IqJingleSet", IqJingleSet}, + {"IqJingleRes", IqJingleRes}, + {"IqJingleErr", IqJingleErr}, + {"Unhandled", Unhandled}, + {"Invalid", Invalid}, + {0,0} +}; + +TokenDict XMPPUser::s_subscription[] = { + {"none", None}, + {"to", To}, + {"from", From}, + {"both", Both}, + {0,0}, +}; + +TokenDict JBEngine::s_protoName[] = { + {"component", Component}, + {"client", Client}, + {0,0} +}; + +static TokenDict s_serviceType[] = { + {"jingle", JBEngine::ServiceJingle}, + {"iq", JBEngine::ServiceIq}, + {"message", JBEngine::ServiceMessage}, + {"presence", JBEngine::ServicePresence}, + {"command", JBEngine::ServiceCommand}, + {"disco", JBEngine::ServiceDisco}, + {"stream", JBEngine::ServiceStream}, + {"write-fail", JBEngine::ServiceWriteFail}, + {0,0} +}; + +static TokenDict s_threadNames[] = { + {"Jabber stream connect", JBThread::StreamConnect}, + {"Engine receive", JBThread::EngineReceive}, + {"Engine process", JBThread::EngineProcess}, + {"Presence", JBThread::Presence}, + {"Jingle", JBThread::Jingle}, + {"Message", JBThread::Message}, + {0,0} +}; + +TokenDict JIDResource::s_show[] = { + {"away", ShowAway}, + {"chat", ShowChat}, + {"dnd", ShowDND}, + {"xa", ShowXA}, + {0,0}, +}; + +TokenDict JBPresence::s_presence[] = { + {"error", Error}, + {"probe", Probe}, + {"subscribe", Subscribe}, + {"subscribed", Subscribed}, + {"unavailable", Unavailable}, + {"unsubscribe", Unsubscribe}, + {"unsubscribed", Unsubscribed}, + {0,0} +}; + +// Private thread class +class JBPrivateThread : public Thread, public JBThread +{ +public: + inline JBPrivateThread(Type type, JBThreadList* list, void* client, + int sleep, int prio) + : Thread(lookup(type,s_threadNames),Thread::priority((Priority)prio)), + JBThread(type,list,client,sleep) + {} + virtual void cancelThread(bool hard = false) + { Thread::cancel(hard); } + virtual void run() + { JBThread::runClient(); } +}; + +/** + * JBThread + */ +// Constructor. Append itself to the list +JBThread::JBThread(Type type, JBThreadList* list, void* client, int sleep) + : m_type(type), + m_owner(list), + m_client(client), + m_sleep(sleep) +{ + if (!m_owner) + return; + Lock lock(m_owner->m_mutex); + m_owner->m_threads.append(this)->setDelete(false); +} + +// Destructor. Remove itself from the list +JBThread::~JBThread() +{ + Debug("jabber",DebugAll,"'%s' private thread terminated client=(%p) [%p]", + lookup(m_type,s_threadNames),m_client,this); + if (!m_owner) + return; + Lock lock(m_owner->m_mutex); + m_owner->m_threads.remove(this,false); +} + +// Create and start a private thread +bool JBThread::start(Type type, JBThreadList* list, void* client, + int sleep, int prio) +{ + Lock lock(list->m_mutex); + const char* error = 0; + bool ok = !list->m_cancelling; + if (ok) + ok = (new JBPrivateThread(type,list,client,sleep,prio))->startup(); + else + error = ". Owner's threads are beeing cancelled"; + if (!ok) + Debug("jabber",DebugNote,"'%s' private thread failed to start client=(%p)%s", + lookup(type,s_threadNames),client,error?error:""); + return ok; +} + +// Process the client +void JBThread::runClient() +{ + if (!m_client) + return; + Debug("jabber",DebugAll,"'%s' private thread is running client=(%p) [%p]", + lookup(m_type,s_threadNames),m_client,this); + switch (m_type) { + case StreamConnect: + ((JBStream*)m_client)->connect(); + break; + case EngineProcess: + while (true) + if (!((JBEngine*)m_client)->process(Time::msecNow())) + Thread::msleep(m_sleep,true); + else + Thread::check(true); + break; + case EngineReceive: + while (true) + if (!((JBEngine*)m_client)->receive()) + Thread::msleep(m_sleep,true); + else + Thread::check(true); + break; + case Presence: + while (true) + if (!((JBPresence*)m_client)->process()) + Thread::msleep(m_sleep,true); + else + Thread::check(true); + break; + case Jingle: + while (true) { + bool ok = false; + while (true) { + if (Thread::check(false)) + break; + JGEvent* event = ((JGEngine*)m_client)->getEvent(Time::msecNow()); + if (!event) + break; + ok = true; + ((JGEngine*)m_client)->processEvent(event); + } + if (!ok) + Thread::msleep(m_sleep,true); + else + Thread::check(true); + } + break; + case Message: + while (true) { + JBEvent* event = ((JBMessage*)m_client)->getMessage(); + if (event) { + ((JBMessage*)m_client)->processMessage(event); + Thread::check(true); + } + else + Thread::yield(true); + } + break; + default: + Debug(DebugStub,"JBThread::run() unhandled type %u",m_type); + } +} + + +/** + * JBThreadList + */ +void JBThreadList::cancelThreads(bool wait, bool hard) +{ + // Destroy private threads + m_mutex.lock(); + for (ObjList* o = m_threads.skipNull(); o; o = o->skipNext()) { + JBThread* p = static_cast(o->get()); + p->cancelThread(hard); + } + m_cancelling = true; + m_mutex.unlock(); + // Wait to terminate + if (!hard && wait) + while (m_threads.skipNull()) + Thread::yield(); + m_cancelling = false; +} + + +static XMPPNamespace s_ns; // Used to shorten the code (call static methods) + +// Default values +#define JB_RESTART_COUNT 2 // Stream restart counter default value +#define JB_RESTART_COUNT_MIN 1 +#define JB_RESTART_COUNT_MAX 10 + +#define JB_RESTART_UPDATE 15000 // Stream restart counter update interval +#define JB_RESTART_UPDATE_MIN 5000 +#define JB_RESTART_UPDATE_MAX 300000 + +// Presence values +#define JINGLE_VERSION "1.0" // Version capability +#define JINGLE_VOICE "voice-v1" // Voice capability for Google Talk + /** * JBEngine */ -static XMPPNamespace s_ns; - -// Default values -#define JB_STREAM_RESTART_COUNT 2 // Stream restart counter default value -#define JB_STREAM_RESTART_COUNT_MIN 1 -#define JB_STREAM_RESTART_COUNT_MAX 10 - -#define JB_STREAM_RESTART_UPDATE 15000 // Stream restart counter update interval -#define JB_STREAM_RESTART_UPDATE_MIN 5000 -#define JB_STREAM_RESTART_UPDATE_MAX 300000 - -// Sleep time for threads -#define SLEEP_READSOCKET 2 // Read socket -#define SLEEP_PROCESSPRESENCE 2 // Process presence events -#define SLEEP_PROCESSTIMEOUT 10 // Process presence timeout - -// Presence values -#define PRESENCE_PROBE_INTERVAL 1800000 -#define PRESENCE_EXPIRE_INTERVAL 300000 - -#define JINGLE_VERSION "1.0" // Version capability -#define JINGLE_VOICE "voice-v1" // Voice capability for Google Talk - -JBEngine::JBEngine() +JBEngine::JBEngine(Protocol proto) : Mutex(true), - m_clientsMutex(true), - m_presence(0), - m_identity(0), - m_restartUpdateTime(0), - m_restartUpdateInterval(JB_STREAM_RESTART_UPDATE), - m_restartCount(JB_STREAM_RESTART_COUNT), - m_printXml(false), - m_streamID(0), - m_serverMutex(true) + m_protocol(proto), + m_restartUpdateInterval(JB_RESTART_UPDATE), + m_restartCount(JB_RESTART_COUNT), + m_printXml(false), + m_identity(0), + m_componentCheckFrom(1), + m_serverMutex(true), + m_servicesMutex(true), + m_initialized(false) { + for (int i = 0; i < ServiceCount; i++) + m_services[i].setDelete(false); debugName("jbengine"); - m_identity = new JIDIdentity(JIDIdentity::Gateway,JIDIdentity::GatewayGeneric); - //m_features.add(XMPPNamespace::Command); - m_features.add(XMPPNamespace::Jingle); - m_features.add(XMPPNamespace::JingleAudio); - m_features.add(XMPPNamespace::Dtmf); - m_features.add(XMPPNamespace::DiscoInfo); - XDebug(this,DebugAll,"JBEngine. [%p]",this); + XDebug(this,DebugAll,"JBEngine [%p]",this); } JBEngine::~JBEngine() { cleanup(); - if (m_identity) - m_identity->deref(); - XDebug(this,DebugAll,"~JBEngine. [%p]",this); + cancelThreads(); + // Remove streams if alive + if (m_streams.skipNull()) { + Debug(this,DebugNote,"Engine destroyed while still owning streams [%p]",this); + ListIterator iter(m_streams); + for (GenObject* o = 0; 0 != (o = iter.get());) + TelEngine::destruct(static_cast(o)); + } + TelEngine::destruct((RefObject*)m_identity); + XDebug(this,DebugAll,"~JBEngine [%p]",this); } +// Cleanup streams. Stop all threads owned by this engine. Release memory +void JBEngine::destruct() +{ + cleanup(); + cancelThreads(); + GenObject::destruct(); +} + +// Initialize the engine's parameters void JBEngine::initialize(const NamedList& params) { - clearServerList(); + debugLevel(params.getIntValue("debug_level",debugLevel())); + + int recv = -1, proc = -1; + + if (!m_initialized) { + // Build engine Jabber identity and features + if (m_protocol == Component) + m_identity = new JIDIdentity(JIDIdentity::Gateway,JIDIdentity::GatewayGeneric); + else + m_identity = new JIDIdentity(JIDIdentity::Account,JIDIdentity::AccountRegistered); + m_features.add(XMPPNamespace::Jingle); + m_features.add(XMPPNamespace::JingleAudio); + m_features.add(XMPPNamespace::Dtmf); + m_features.add(XMPPNamespace::DiscoInfo); + + recv = params.getIntValue("private_receive_threads",1); + for (int i = 0; i < recv; i++) + JBThread::start(JBThread::EngineReceive,this,this,2,Thread::Normal); + proc = params.getIntValue("private_process_threads",1); + for (int i = 0; i < proc; i++) + JBThread::start(JBThread::EngineProcess,this,this,2,Thread::Normal); + } + + m_serverMutex.lock(); + m_server.clear(); + m_serverMutex.unlock(); + m_printXml = params.getBoolValue("printxml",false); // Alternate domain names - m_alternateDomain = params.getValue("extra_domain"); + m_alternateDomain.set(0,params.getValue("extra_domain")); // Stream restart update interval m_restartUpdateInterval = - params.getIntValue("stream_restartupdateinterval",JB_STREAM_RESTART_UPDATE); - if (m_restartUpdateInterval < JB_STREAM_RESTART_UPDATE_MIN) - m_restartUpdateInterval = JB_STREAM_RESTART_UPDATE_MIN; + params.getIntValue("stream_restartupdateinterval",JB_RESTART_UPDATE); + if (m_restartUpdateInterval < JB_RESTART_UPDATE_MIN) + m_restartUpdateInterval = JB_RESTART_UPDATE_MIN; else - if (m_restartUpdateInterval > JB_STREAM_RESTART_UPDATE_MAX) - m_restartUpdateInterval = JB_STREAM_RESTART_UPDATE_MAX; + if (m_restartUpdateInterval > JB_RESTART_UPDATE_MAX) + m_restartUpdateInterval = JB_RESTART_UPDATE_MAX; // Stream restart count m_restartCount = - params.getIntValue("stream_restartcount",JB_STREAM_RESTART_COUNT); - if (m_restartCount < JB_STREAM_RESTART_COUNT_MIN) - m_restartCount = JB_STREAM_RESTART_COUNT_MIN; + params.getIntValue("stream_restartcount",JB_RESTART_COUNT); + if (m_restartCount < JB_RESTART_COUNT_MIN) + m_restartCount = JB_RESTART_COUNT_MIN; else - if (m_restartCount > JB_STREAM_RESTART_COUNT_MAX) - m_restartCount = JB_STREAM_RESTART_COUNT_MAX; + if (m_restartCount > JB_RESTART_COUNT_MAX) + m_restartCount = JB_RESTART_COUNT_MAX; // XML parser max receive buffer XMLParser::s_maxDataBuffer = params.getIntValue("xmlparser_maxbuffer",XMLPARSER_MAXDATABUFFER); // Default resource m_defaultResource = params.getValue("default_resource","yate"); - if (debugAt(DebugAll)) { + // Check 'from' attribute for component streams + String chk = params.getValue("component_checkfrom"); + if (chk == "none") + m_componentCheckFrom = 0; + else if (chk == "remote") + m_componentCheckFrom = 2; + else + m_componentCheckFrom = 1; + + if (debugAt(DebugInfo)) { String s; - s << "\r\ndefault_resource=" << m_defaultResource; - s << "\r\nstream_restartupdateinterval=" << (unsigned int)m_restartUpdateInterval; - s << "\r\nstream_restartcount=" << (unsigned int)m_restartCount; - s << "\r\nxmlparser_maxbuffer=" << (unsigned int)XMLParser::s_maxDataBuffer; - s << "\r\nprintxml=" << String::boolText(m_printXml); - Debug(this,DebugAll,"Initialized:%s",s.c_str()); + s << " protocol=" << lookup(m_protocol,s_protoName); + s << " default_resource=" << m_defaultResource; + s << " component_checkfrom=" << m_componentCheckFrom; + s << " stream_restartupdateinterval=" << (unsigned int)m_restartUpdateInterval; + s << " stream_restartcount=" << (unsigned int)m_restartCount; + s << " xmlparser_maxbuffer=" << (unsigned int)XMLParser::s_maxDataBuffer; + s << " printxml=" << m_printXml; + if (recv > -1) + s << " private_receive_threads=" << recv; + if (proc > -1) + s << " private_process_threads=" << proc; + Debug(this,DebugInfo,"Jabber engine initialized:%s [%p]",s.c_str(),this); } + + m_initialized = true; } +// Terminate all streams void JBEngine::cleanup() { Lock lock(this); - DDebug(this,DebugAll,"Cleanup."); - ObjList* obj = m_streams.skipNull(); - for (; obj; obj = m_streams.skipNext()) { - JBComponentStream* s = static_cast(obj->get()); - s->terminate(true,true,XMPPUtils::createStreamError(XMPPError::Shutdown),true); + for (ObjList* o = m_streams.skipNull(); o; o = o->skipNext()) { + JBStream* s = static_cast(o->get()); + s->terminate(true,0,XMPPError::Shutdown,0,true); } } +// Set the default component server to use void JBEngine::setComponentServer(const char* domain) { + if (m_protocol != Component) + return; Lock lock(m_serverMutex); - JBServerInfo* p = getServer(domain,true); + XMPPServerInfo* p = findServerInfo(domain,true); // If doesn't exists try to get the first one from the list if (!p) { ObjList* obj = m_server.skipNull(); - p = obj ? static_cast(obj->get()) : 0; + p = obj ? static_cast(obj->get()) : 0; } - else - p->deref(); if (!p) { - Debug(this,DebugNote,"No default component server."); + Debug(this,DebugNote,"No default component server [%p]",this); return; } - m_componentDomain = p->name(); + m_componentDomain.set(0,p->name()); m_componentAddr = p->address(); - DDebug(this,DebugAll,"Default component server set to '%s' (%s).", - m_componentDomain.c_str(),m_componentAddr.c_str()); + DDebug(this,DebugAll,"Default component server set to '%s' (%s) [%p]", + m_componentDomain.c_str(),m_componentAddr.c_str(),this); } -JBComponentStream* JBEngine::getStream(const char* domain, bool create) +// Get a stream. Create it not found and requested +// For the component protocol, the jid parameter may contain the domain to find, +// otherwise, the default component will be used +// For the client protocol, the jid parameter must contain the full +// user's jid (including the resource) +JBStream* JBEngine::getStream(const JabberID* jid, bool create, const char* pwd) { Lock lock(this); - if (!domain) - domain = m_componentDomain; - JBComponentStream* stream = findStream(domain); - XDebug(this,DebugAll, - "getStream. Remote: '%s'. Stream exists: %s (%p). Create: %s.", - domain,stream?"YES":"NO",stream,create?"YES":"NO"); + if (exiting()) + return 0; + const JabberID* remote = jid; + // Set remote to be a valid pointer + if (!remote) + if (m_protocol == Component) + remote = &m_componentDomain; + else + return 0; + // Find the stream + JBStream* stream = 0; + for (ObjList* o = m_streams.skipNull(); o; o = o->skipNext()) { + stream = static_cast(o->get()); + if (stream->remote() == *remote) + break; + stream = 0; + } + if (!stream && create) { + XMPPServerInfo* info = findServerInfo(remote->domain(),true); + if (!info) { + Debug(this,DebugNote,"No server info to create stream to '%s' [%p]", + remote->c_str(),this); + return 0; + } SocketAddr addr(PF_INET); - addr.host(domain); - addr.port(getPort(addr.host())); - stream = new JBComponentStream(this,domain,addr); + addr.host(info->address()); + addr.port(info->port()); + if (m_protocol == Component) + stream = new JBComponentStream(this,JabberID(0,info->identity(),0), + *remote,info->password(),addr, + true,m_restartCount,m_restartUpdateInterval); + else + stream = new JBClientStream(this,*jid,pwd,addr,m_restartCount,m_restartUpdateInterval); m_streams.append(stream); } return ((stream && stream->ref()) ? stream : 0); } +// Try to get a stream if stream parameter is 0 +bool JBEngine::getStream(JBStream*& stream, bool& release, const char* pwd) +{ + release = false; + if (stream) + return true; + stream = getStream(0,true,pwd); + if (stream) { + release = true; + return true; + } + return false; +} + +// Keep calling receive() for each stream until no data is received or the thread is terminated bool JBEngine::receive() { bool ok = false; lock(); ListIterator iter(m_streams); for (;;) { - JBComponentStream* stream = static_cast(iter.get()); + JBStream* stream = static_cast(iter.get()); // End of iteration? if (!stream) break; // Get a reference - RefPointer sref = stream; + RefPointer sref = stream; if (!sref) continue; // Read socket unlock(); + if (Thread::check(false)) + return false; if (sref->receive()) ok = true; lock(); @@ -195,444 +521,378 @@ bool JBEngine::receive() return ok; } -void JBEngine::runReceive() -{ - while (1) { - if (!receive()) - Thread::msleep(SLEEP_READSOCKET,true); - } -} - -JBEvent* JBEngine::getEvent(u_int64_t time) +// Get events from the streams owned by this engine +bool JBEngine::process(u_int64_t time) { lock(); ListIterator iter(m_streams); for (;;) { - JBComponentStream* stream = static_cast(iter.get()); + if (Thread::check(false)) + break; + JBStream* stream = static_cast(iter.get()); // End of iteration? if (!stream) break; // Get a reference - RefPointer sref = stream; + RefPointer sref = stream; if (!sref) continue; // Get event unlock(); JBEvent* event = sref->getEvent(time); - if (event) - // Check if the engine should process these events - switch (event->type()) { - case JBEvent::Presence: - if (!(m_presence && m_presence->receive(event))) - return event; - break; - case JBEvent::IqDiscoGet: - case JBEvent::IqDiscoSet: - case JBEvent::IqDiscoRes: { - JabberID jid(event->to()); - if (jid.node()) { - if (!(m_presence && m_presence->receive(event))) - return event; - break; - } - if (!processDiscoInfo(event)) - return event; - break; - } - case JBEvent::IqCommandGet: - case JBEvent::IqCommandSet: - case JBEvent::IqCommandRes: { - JabberID jid(event->to()); - if (!processCommand(event)) - return event; - break; - } - case JBEvent::Invalid: - event->deref(); - break; - case JBEvent::Terminated: - // Restart stream - connect(event->stream()); - event->deref(); - break; - // Unhandled 'iq' element - case JBEvent::Iq: - DDebug(this,DebugInfo, - "getEvent. Event((%p): %u). Unhandled 'iq' element.",event,event->type()); - stream->sendIqError(event->releaseXML(),XMPPError::TypeCancel, - XMPPError::SFeatureNotImpl); - event->deref(); - break; - default: - return event; - } + if (!event) + return false; + + Lock serviceLock(m_servicesMutex); + // Send events to the registered services + switch (event->type()) { + case JBEvent::Message: + if (received(ServiceMessage,event)) + return true; + break; + case JBEvent::IqJingleGet: + case JBEvent::IqJingleSet: + case JBEvent::IqJingleRes: + case JBEvent::IqJingleErr: + if (received(ServiceJingle,event)) + return true; + break; + case JBEvent::Iq: + case JBEvent::IqError: + case JBEvent::IqResult: + if (received(ServiceIq,event)) + return true; + break; + case JBEvent::Presence: + if (received(ServicePresence,event)) + return true; + break; + case JBEvent::IqDiscoInfoGet: + case JBEvent::IqDiscoInfoSet: + case JBEvent::IqDiscoInfoRes: + case JBEvent::IqDiscoInfoErr: + case JBEvent::IqDiscoItemsGet: + case JBEvent::IqDiscoItemsSet: + case JBEvent::IqDiscoItemsRes: + case JBEvent::IqDiscoItemsErr: + if (received(ServiceDisco,event)) + return true; + if (processDisco(event)) + event = 0; + break; + case JBEvent::IqCommandGet: + case JBEvent::IqCommandSet: + case JBEvent::IqCommandRes: + case JBEvent::IqCommandErr: + if (received(ServiceCommand,event)) + return true; + if (processCommand(event)) + event = 0; + break; + case JBEvent::WriteFail: + if (received(ServiceWriteFail,event)) + return true; + break; + case JBEvent::Terminated: + case JBEvent::Destroy: + case JBEvent::Running: + if (received(ServiceStream,event)) + return true; + break; + default: ; + } + serviceLock.drop(); + if (event) { + Debug(this,DebugAll,"Delete unhandled event (%p,%s) [%p]", + event,event->name(),this); + TelEngine::destruct(event); + } lock(); } unlock(); - return 0; + return false; } -bool JBEngine::remoteIdExists(const JBComponentStream* stream) +// Check for duplicate stream id at a remote server +bool JBEngine::checkDupId(const JBStream* stream) { - // IDs for incoming streams are generated by this engine - // Check the stream source and ID - if (!stream) + if (!(stream && stream->outgoing())) return false; Lock lock(this); - ObjList* obj = m_streams.skipNull(); - for (; obj; obj = obj->skipNext()) { - JBComponentStream* s = static_cast(obj->get()); - if (s != stream && - s->remoteName() == stream->remoteName() && - s->id() == stream->id()) + for (ObjList* o = m_streams.skipNull(); o; o = o->skipNext()) { + JBStream* s = static_cast(o->get()); + if (s != stream && s->outgoing() && + s->remote() == stream->remote() && s->id() == stream->id()) return true; } return false; } -void JBEngine::createSHA1(String& sha, const String& id, - const String& password) -{ - SHA1 sha1; - sha1 << id << password; - sha = sha1.hexDigest(); -} - -bool JBEngine::checkSHA1(const String& sha, const String& id, - const String& password) -{ - String tmp; - createSHA1(tmp,id,password); - return tmp == sha; -} - -void JBEngine::timerTick(u_int64_t time) -{ - if (m_restartUpdateTime > time) - return; - // Update server streams counters - Lock lock(m_serverMutex); - ObjList* obj = m_server.skipNull(); - for (; obj; obj = obj->skipNext()) { - JBServerInfo* server = static_cast(obj->get()); - if (server->restartCount() < m_restartCount) { - server->incRestart(); - DDebug(this,DebugAll, - "Increased stream restart counter (%u) for '%s'.", - server->restartCount(),server->name().c_str()); - } - // Check if stream should be restarted (created) - if (server->autoRestart()) { - JBComponentStream* stream = getStream(server->name(),true); - if (stream) - stream->deref(); - } - } - // Update next update time - m_restartUpdateTime = time + m_restartUpdateInterval; -} - -bool JBEngine::connect(JBComponentStream* stream) +// Check the 'from' attribute received by a Component stream at startup +// 0: no check 1: local identity 2: remote identity +bool JBEngine::checkComponentFrom(JBComponentStream* stream, const char* from) { if (!stream) return false; - stream->connect(); - return true; + JabberID tmp(from); + switch (m_componentCheckFrom) { + case 1: + return stream->local() == tmp; + case 2: + return stream->remote() == tmp; + case 0: + return true; + } + return false; } -void JBEngine::returnEvent(JBEvent* event) +// Asynchronously call the connect method of the given stream if the stream is idle +void JBEngine::connect(JBStream* stream) { - if (!event) - return; - if (event->type() == JBEvent::Message && processMessage(event)) - return; - DDebug(this,DebugAll, - "returnEvent. Delete event((%p): %u).",event,event->type()); - event->deref(); + if (stream && stream->state() == JBStream::Idle) + JBThread::start(JBThread::StreamConnect,this,stream,2,Thread::Normal); } -bool JBEngine::acceptOutgoing(const String& remoteAddr, - String& password) -{ - bool b = getServerPassword(password,remoteAddr,false); - XDebug(this,DebugAll, - "acceptOutgoing. To: '%s'. %s.",remoteAddr.c_str(), - b ? "Accepted" : "Not accepted"); - return b; -} - -int JBEngine::getPort(const String& remoteAddr) -{ - int port = 0; - getServerPort(port,remoteAddr,false); - XDebug(this,DebugAll, - "getPort. For: '%s'. Port: %d",remoteAddr.c_str(),port); - return port; -} - -void JBEngine::appendServer(JBServerInfo* server, bool open) +// Append server info the list +void JBEngine::appendServer(XMPPServerInfo* server, bool open) { if (!server) return; // Add if doesn't exists. Delete if already in the list - JBServerInfo* p = getServer(server->name(),true); + XMPPServerInfo* p = findServerInfo(server->name(),true); if (!p) { m_serverMutex.lock(); m_server.append(server); + Debug(this,DebugAll,"Added server '%s' port=%d [%p]", + server->name().c_str(),server->port(),this); m_serverMutex.unlock(); } - else { - server->deref(); - p->deref(); - } + else + TelEngine::destruct(server); // Open stream - if (open) { - JBComponentStream* stream = getStream(server->name()); + if (open && m_protocol == Component) { + JabberID jid(0,server->name(),0); + JBStream* stream = getStream(&jid); if (stream) - stream->deref(); + TelEngine::destruct(stream); } } -bool JBEngine::getServerIdentity(String& destination, +// Get the identity of the given server +bool JBEngine::getServerIdentity(String& destination, bool full, const char* token, bool domain) { Lock lock(m_serverMutex); - JBServerInfo* server = getServer(token,domain); + XMPPServerInfo* server = findServerInfo(token,domain); if (!server) return false; - destination = server->identity(); - server->deref(); + destination = full ? server->fullIdentity() : server->identity(); return true; } -bool JBEngine::getFullServerIdentity(String& destination, const char* token, - bool domain) -{ - Lock lock(m_serverMutex); - JBServerInfo* server = getServer(token,domain); - if (!server) - return false; - destination = server->fullIdentity(); - server->deref(); - return true; -} - -bool JBEngine::getStreamRestart(const char* token, bool domain) -{ - Lock lock(m_serverMutex); - JBServerInfo* server = getServer(token,domain); - if (!server) - return false; - bool restart = server->getRestart(); - if (restart) - DDebug(this,DebugAll,"Decreased stream restart counter (%u) for '%s'.", - server->restartCount(),server->name().c_str()); - server->deref(); - return restart; -} - -bool JBEngine::processDiscoInfo(JBEvent* event) -{ - JBComponentStream* stream = event->stream(); - // Check if we have a stream and this engine is the destination - if (!(stream && event->element())) - return false; - //TODO: Check if the engine is the destination. - // The destination might be a user - switch (event->type()) { - case JBEvent::IqDiscoGet: - { - DDebug(this,DebugAll,"processDiscoInfo. Get. From '%s' to '%s'.", - event->from().c_str(),event->to().c_str()); - // Create response - XMLElement* query = XMPPUtils::createElement(XMLElement::Query, - XMPPNamespace::DiscoInfo); - // Set the identity & features - if (m_identity) { - *(String*)(m_identity) = stream->localName(); - query->addChild(m_identity->toXML()); - } - m_features.addTo(query); - XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,event->to(), - event->from(),event->id()); - iq->addChild(query); - // Send response - stream->sendStanza(iq); - } - break; - case JBEvent::IqDiscoRes: - DDebug(this,DebugAll,"processDiscoInfo. Result. From '%s' to '%s'.", - event->from().c_str(),event->to().c_str()); - break; - case JBEvent::IqDiscoSet: - DDebug(this,DebugAll,"processDiscoInfo. Set. From '%s' to '%s'.", - event->from().c_str(),event->to().c_str()); - break; - default: - Debug(this,DebugNote,"processDiscoInfo. From '%s' to '%s'. Unhandled.", - event->from().c_str(),event->to().c_str()); - } - // Release event - event->deref(); - return true; -} - -bool JBEngine::processCommand(JBEvent* event) -{ - JBComponentStream* stream = event->stream(); - if (!(event && event->element() && event->child())) { - event->deref(); - return true; - } - //TODO: Check if the engine is the destination. - // The destination might be a user - switch (event->type()) { - case JBEvent::IqCommandGet: - case JBEvent::IqCommandSet: - DDebug(this,DebugAll,"processCommand. From '%s' to '%s'.", - event->from().c_str(),event->to().c_str()); - stream->sendIqError(event->releaseXML(),XMPPError::TypeCancel, - XMPPError::SFeatureNotImpl); - break; - case JBEvent::IqDiscoRes: - DDebug(this,DebugAll,"processCommand. Result. From '%s' to '%s'.", - event->from().c_str(),event->to().c_str()); - break; - default: - Debug(this,DebugNote,"processCommand. From '%s' to '%s'. Unhandled.", - event->from().c_str(),event->to().c_str()); - } - // Release event - event->deref(); - return true; -} - -JBComponentStream* JBEngine::findStream(const String& remoteName) -{ - ObjList* obj = m_streams.skipNull(); - for (; obj; obj = obj->skipNext()) { - JBComponentStream* stream = static_cast(obj->get()); - if (stream->remoteName() == remoteName) - return stream; - } - return 0; -} - -void JBEngine::removeStream(JBComponentStream* stream, bool del) -{ - if (!m_streams.remove(stream,del)) - return; - DDebug(this,DebugAll, - "removeStream (%p). Deleted: %s.",stream,del?"YES":"NO"); -} - -void JBEngine::addClient(JBClient* client) -{ - if (!client) - return; - // Check existence - Lock lock(m_clientsMutex); - if (m_clients.find(client)) - return; - m_clients.append(client); -} - -void JBEngine::removeClient(JBClient* client) -{ - if (!client) - return; - Lock lock(m_clientsMutex); - m_clients.remove(client,false); -} - -JBServerInfo* JBEngine::getServer(const char* token, bool domain) +// Find a server info object +XMPPServerInfo* JBEngine::findServerInfo(const char* token, bool domain) { if (!token) token = domain ? m_componentDomain : m_componentAddr; if (!token) return 0; - ObjList* obj = m_server.skipNull(); - JBServerInfo* server = 0; if (domain) - for (; obj; obj = obj->skipNext()) { - server = static_cast(obj->get()); - if (server->name() == token) - break; - server = 0; + for (ObjList* o = m_server.skipNull(); o; o = o->skipNext()) { + XMPPServerInfo* server = static_cast(o->get()); + if (server->name() &= token) + return server; } else - for (; obj; obj = obj->skipNext()) { - server = static_cast(obj->get()); + for (ObjList* o = m_server.skipNull(); o; o = o->skipNext()) { + XMPPServerInfo* server = static_cast(o->get()); if (server->address() == token) - break; - server = 0; + return server; } - return (server && server->ref()) ? server : 0; + return 0; } -bool JBEngine::processMessage(JBEvent* event) +// Attach a service to this engine +void JBEngine::attachService(JBService* service, Service type, int prio) { - DDebug(this,DebugInfo, - "JBEngine::processMessage. From: '%s'. To: '%s'.", - event->from().c_str(),event->to().c_str()); + if (!service) + return; + Lock lock(m_servicesMutex); + if (m_services[type].find(service)) + return; + if (prio == -1) + prio = service->priority(); + ObjList* insert = m_services[type].skipNull(); + for (; insert; insert = insert->skipNext()) { + JBService* tmp = static_cast(insert->get()); + if (prio <= tmp->priority()) { + insert->insert(service); + break; + } + } + if (!insert) + m_services[type].append(service); + Debug(this,DebugInfo,"Attached service (%p) '%s' type=%s priority=%d [%p]", + service,service->debugName(),lookup(type,s_serviceType),prio,this); +} + +// Remove a service from all event handlers of this engine. +void JBEngine::detachService(JBService* service) +{ + if (!service) + return; + Lock lock(m_servicesMutex); + for (int i = 0; i < ServiceCount; i++) { + GenObject* o = m_services[i].find(service); + if (!o) + continue; + m_services[i].remove(service,false); + Debug(this,DebugInfo,"Removed service (%p) '%s' type=%s [%p]", + service,service->debugName(),lookup(i,s_serviceType),this); + } +} + +// Process disco info events +bool JBEngine::processDisco(JBEvent* event) +{ + JBStream* stream = event->stream(); + XMLElement* child = event->child(); + + // Check if we sould or can respond to it + if (!(event->type() == JBEvent::IqDiscoInfoGet && stream && child)) + return false; + + // Create response + XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,event->to(),event->from(),event->id()); + XMLElement* query = XMPPUtils::createElement(XMLElement::Query,XMPPNamespace::DiscoInfo); + m_features.addTo(query); + if (m_identity) { + *(String*)(m_identity) = stream->local(); + query->addChild(m_identity->toXML()); + } + iq->addChild(query); + stream->sendStanza(iq); + TelEngine::destruct(event); + return true; +} + +// Process commands +bool JBEngine::processCommand(JBEvent* event) +{ + JBStream* stream = event->stream(); + if (!stream || + (event->type() != JBEvent::IqCommandGet && event->type() != JBEvent::IqCommandSet)) + return false; + + // Send error + XMLElement* err = XMPPUtils::createIq(XMPPUtils::IqError,event->to(),event->from(),event->id()); + err->addChild(event->releaseXML()); + err->addChild(XMPPUtils::createError(XMPPError::TypeCancel,XMPPError::SFeatureNotImpl)); + stream->sendStanza(err); + TelEngine::destruct(event); + return true; +} + +// Find a service to process a received event +bool JBEngine::received(Service service, JBEvent* event) +{ + if (!event) + return false; + for (ObjList* o = m_services[service].skipNull(); o; o = o->skipNext()) { + JBService* service = static_cast(o->get()); + XDebug(this,DebugAll,"Sending event (%p,%s) to service '%s' [%p]", + event,event->name(),service->debugName(),this); + if (service->received(event)) + return true; + } return false; } -bool JBEngine::getServerPassword(String& destination, - const char* token, bool domain) + +/** + * JBService + */ +JBService::JBService(JBEngine* engine, const char* name, + const NamedList* params, int prio) + : Mutex(true), + m_initialized(false), + m_engine(engine), + m_priority(prio) { - Lock lock(m_serverMutex); - JBServerInfo* server = getServer(token,domain); - if (!server) + debugName(name); + XDebug(this,DebugAll,"Jabber service created [%p]",this); + if (params) + initialize(*params); +} + +JBService::~JBService() +{ + XDebug(this,DebugAll,"JBService::~JBService() [%p]",this); +} + +// Remove from engine. Release memory +void JBService::destruct() +{ + if (m_engine) + m_engine->detachService(this); + Debug(this,DebugAll,"Jabber service destroyed [%p]",this); + GenObject::destruct(); +} + +// Accept an event from the engine +bool JBService::accept(JBEvent* event, bool& processed, bool& insert) +{ + Debug(this,DebugStub,"JBService::accept(%p)",event); + return false; +} + +// Receive an event from engine +bool JBService::received(JBEvent* event) +{ + if (!event) return false; - destination = server->password(); - server->deref(); + bool insert = false; + bool processed = false; + Lock lock(this); + XDebug(this,DebugAll,"Receiving (%p,%s) [%p]",event,event->name(),this); + if (!accept(event,processed,insert)) { + DDebug(this,DebugAll,"Event (%p,%s) not accepted [%p]", + event,event->name(),this); + return false; + } + if (processed) { + DDebug(this,DebugAll,"Event (%p,%s) processed [%p]", + event,event->name(),this); + return true; + } + Debug(this,DebugAll,"%s event (%p,%s) [%p]", + insert?"Inserting":"Appending",event,event->name(),this); + event->releaseStream(); + if (insert) + m_events.insert(event); + else + m_events.append(event); return true; } -bool JBEngine::getServerPort(int& destination, - const char* token, bool domain) -{ - Lock lock(m_serverMutex); - JBServerInfo* server = getServer(token,domain); - if (!server) - return false; - destination = server->port(); - server->deref(); - return true; -} - -void JBEngine::setPresenceServer(JBPresence* presence) -{ - if (m_presence) - return; - Lock lock(this); - if (presence) - m_presence = presence; -} - -void JBEngine::unsetPresenceServer(JBPresence* presence) +// Get an event from queue +JBEvent* JBService::deque() { Lock lock(this); - if (m_presence == presence) - m_presence = 0; + ObjList* obj = m_events.skipNull(); + if (!obj) + return 0; + JBEvent* event = static_cast(obj->remove(false)); + DDebug(this,DebugAll,"Dequeued event (%p,%s) [%p]", + event,event->name(),this); + return event; } + /** * JBEvent */ -JBEvent::JBEvent(Type type, JBComponentStream* stream) - : m_type(type), - m_stream(0), - m_link(true), - m_element(0), - m_child(0) -{ - init(stream,0); -} - -JBEvent::JBEvent(Type type, JBComponentStream* stream, - XMLElement* element, XMLElement* child) +JBEvent::JBEvent(Type type, JBStream* stream, XMLElement* element, XMLElement* child) : m_type(type), m_stream(0), m_link(true), @@ -643,8 +903,7 @@ JBEvent::JBEvent(Type type, JBComponentStream* stream, m_type = Invalid; } -JBEvent::JBEvent(Type type, JBComponentStream* stream, - XMLElement* element, const String& senderID) +JBEvent::JBEvent(Type type, JBStream* stream, XMLElement* element, const String& senderID) : m_type(type), m_stream(0), m_link(true), @@ -660,11 +919,11 @@ JBEvent::~JBEvent() { if (m_stream) { releaseStream(); - m_stream->deref(); + TelEngine::destruct(m_stream); } if (m_element) delete m_element; - XDebug(DebugAll,"JBEvent::~JBEvent. [%p]",this); + XDebug(DebugAll,"JBEvent::~JBEvent [%p]",this); } void JBEvent::releaseStream() @@ -675,7 +934,7 @@ void JBEvent::releaseStream() } } -bool JBEvent::init(JBComponentStream* stream, XMLElement* element) +bool JBEvent::init(JBStream* stream, XMLElement* element) { bool bRet = true; if (stream && stream->ref()) @@ -683,42 +942,77 @@ bool JBEvent::init(JBComponentStream* stream, XMLElement* element) else bRet = false; m_element = element; - XDebug(DebugAll,"JBEvent::JBEvent. Type: %u. Stream (%p). Element: (%p). [%p]", - m_type,m_stream,m_element,this); + XDebug(DebugAll,"JBEvent::init type=%s stream=(%p) xml=(%p) [%p]", + name(),m_stream,m_element,this); + if (!m_element) + return bRet; + + // Decode some data + if (m_type == Message) { + XMLElement* body = m_element->findFirstChild("body"); + if (body) + m_text = body->getText(); + } + else + XMPPUtils::decodeError(m_element,m_text,m_text); + // Most elements have these parameters: + m_stanzaType = m_element->getAttribute("type"); + m_from = m_element->getAttribute("from"); + m_to = m_element->getAttribute("to"); + m_id = m_element->getAttribute("id"); return bRet; } + /** - * JBClient + * JBMessage */ -JBClient::JBClient(JBEngine* engine) - : m_engine(0) +// Initialize service. Create private threads +void JBMessage::initialize(const NamedList& params) { - if (engine && engine->ref()) { - m_engine = engine; - m_engine->addClient(this); + debugLevel(params.getIntValue("debug_level",debugLevel())); + + if (m_initialized) + return; + m_initialized = true; + m_syncProcess = params.getBoolValue("sync_process",m_syncProcess); + if (debugAt(DebugInfo)) { + String s; + s << " synchronous_process=" << m_syncProcess; + Debug(this,DebugInfo,"Jabber Message service initialized:%s [%p]", + s.c_str(),this); + } + if (!m_syncProcess) { + int c = params.getIntValue("private_process_threads",1); + for (int i = 0; i < c; i++) + JBThread::start(JBThread::Message,this,this,2,Thread::Normal); } } -JBClient::~JBClient() +// Message processor +void JBMessage::processMessage(JBEvent* event) { - if (m_engine) { - m_engine->removeClient(this); - m_engine->deref(); - } + Debug(this,DebugStub,"Default message processing. Deleting (%p)",event); + TelEngine::destruct(event); } +// Accept an event from the engine and process it if configured to do that +bool JBMessage::accept(JBEvent* event, bool& processed, bool& insert) +{ + if (event->type() != JBEvent::Message) + return false; + if (m_syncProcess) { + processed = true; + processMessage(event); + } + return true; +} + + /** * JIDResource */ -TokenDict JIDResource::s_show[] = { - {"away", ShowAway}, - {"chat", ShowChat}, - {"dnd", ShowDND}, - {"xa", ShowXA}, - {0,0}, - }; - +// Set the presence for this resource bool JIDResource::setPresence(bool value) { Presence p = value ? Available : Unavailable; @@ -728,6 +1022,7 @@ bool JIDResource::setPresence(bool value) return true; } +// Change this resource from received element bool JIDResource::fromXML(XMLElement* element) { if (!(element && element->type() == XMLElement::Presence)) @@ -764,6 +1059,7 @@ bool JIDResource::fromXML(XMLElement* element) return capsChanged || presenceChanged; } +// Append this resource's capabilities to a given element void JIDResource::addTo(XMLElement* element) { if (!element) @@ -784,31 +1080,38 @@ void JIDResource::addTo(XMLElement* element) element->addChild(c); } + /** * JIDResourceList */ +// Add a resource to the list bool JIDResourceList::add(const String& name) { + Lock lock(this); if (get(name)) return false; m_resources.append(new JIDResource(name)); return true; } +// Add a resource to the list bool JIDResourceList::add(JIDResource* resource) { if (!resource) return false; + Lock lock(this); if (get(resource->name())) { - resource->deref(); + TelEngine::destruct(resource); return false; } m_resources.append(resource); return true; } +// Get a resource from list JIDResource* JIDResourceList::get(const String& name) { + Lock lock(this); ObjList* obj = m_resources.skipNull(); for (; obj; obj = obj->skipNext()) { JIDResource* res = static_cast(obj->get()); @@ -818,8 +1121,10 @@ JIDResource* JIDResourceList::get(const String& name) return 0; } +// Get the first resource with audio capabilities JIDResource* JIDResourceList::getAudio(bool availableOnly) { + Lock lock(this); ObjList* obj = m_resources.skipNull(); for (; obj; obj = obj->skipNext()) { JIDResource* res = static_cast(obj->get()); @@ -830,17 +1135,41 @@ JIDResource* JIDResourceList::getAudio(bool availableOnly) return 0; } +/** + * JIDFeatureList + */ +// Find a specific feature +JIDFeature* JIDFeatureList::get(XMPPNamespace::Type feature) +{ + ObjList* obj = m_features.skipNull(); + for (; obj; obj = obj->skipNext()) { + JIDFeature* f = static_cast(obj->get()); + if (*f == feature) + return f; + } + return 0; +} + +// Build an XML element and add it to the destination +XMLElement* JIDFeatureList::addTo(XMLElement* element) +{ + if (!element) + return 0; + ObjList* obj = m_features.skipNull(); + for (; obj; obj = obj->skipNext()) { + JIDFeature* f = static_cast(obj->get()); + XMLElement* feature = new XMLElement(XMLElement::Feature); + feature->setAttribute("var",s_ns[*f]); + element->addChild(feature); + } + return element; +} + + /** * XMPPUser */ -TokenDict XMPPUser::s_subscription[] = { - {"none", None}, - {"to", To}, - {"from", From}, - {"both", Both}, - {0,0}, - }; - +// Constructor XMPPUser::XMPPUser(XMPPUserRoster* local, const char* node, const char* domain, Subscription sub, bool subTo, bool sendProbe) : Mutex(true), @@ -852,14 +1181,13 @@ XMPPUser::XMPPUser(XMPPUserRoster* local, const char* node, const char* domain, { if (local && local->ref()) m_local = local; - if (!m_local) - Debug(m_local->engine(),DebugFail, - "XMPPUser. No local user for '%s'. [%p]",m_jid.c_str(),this); - else - DDebug(m_local->engine(),DebugNote, - "XMPPUser. Local: (%p): %s. Remote: %s. [%p]", - m_local,m_local->jid().c_str(),m_jid.c_str(),this); + else { + Debug(DebugFail,"XMPPUser. No local user for remote=%s [%p]",m_jid.c_str(),this); + return; + } m_local->addUser(this); + DDebug(m_local->engine(),DebugInfo,"User(%s). Added remote=%s [%p]", + m_local->jid().c_str(),m_jid.c_str(),this); // Update subscription switch (sub) { case None: @@ -885,14 +1213,17 @@ XMPPUser::XMPPUser(XMPPUserRoster* local, const char* node, const char* domain, XMPPUser::~XMPPUser() { - DDebug(m_local->engine(),DebugNote, "~XMPPUser. Local: %s. Remote: %s. [%p]", + if (!m_local) + return; + DDebug(m_local->engine(),DebugInfo, "~XMPPUser() local=%s remote=%s [%p]", m_local->jid().c_str(),m_jid.c_str(),this); // Remove all local resources: this will make us unavailable clearLocalRes(); m_local->removeUser(this); - m_local->deref(); + TelEngine::destruct(m_local); } +// Add a resource for the user bool XMPPUser::addLocalRes(JIDResource* resource) { if (!resource) @@ -901,15 +1232,16 @@ bool XMPPUser::addLocalRes(JIDResource* resource) if (!m_localRes.add(resource)) return false; DDebug(m_local->engine(),DebugAll, - "XMPPUser. Added local resource '%s'. Audio: %s. [%p]", - resource->name().c_str(), - resource->hasCap(JIDResource::CapAudio)?"YES":"NO",this); + "User(%s). Added resource name=%s audio=%s [%p]", + m_local->jid().c_str(),resource->name().c_str(), + String::boolText(resource->hasCap(JIDResource::CapAudio)),this); resource->setPresence(true); if (subscribedFrom()) sendPresence(resource,0,true); return true; } +// Remove a resource of the user void XMPPUser::removeLocalRes(JIDResource* resource) { if (!(resource && m_localRes.get(resource->name()))) @@ -919,11 +1251,11 @@ void XMPPUser::removeLocalRes(JIDResource* resource) if (subscribedFrom()) sendPresence(resource,0); m_localRes.remove(resource); - DDebug(m_local->engine(),DebugAll, - "XMPPUser. Removed local resource '%s'. [%p]", - resource->name().c_str(),this); + DDebug(m_local->engine(),DebugAll,"User(%s). Removed resource name=%s [%p]", + m_local->jid().c_str(),resource->name().c_str(),this); } +// Remove all user's resources void XMPPUser::clearLocalRes() { Lock lock(this); @@ -932,24 +1264,28 @@ void XMPPUser::clearLocalRes() sendUnavailable(0); } +// Process an error stanza void XMPPUser::processError(JBEvent* event) { String code, type, error; JBPresence::decodeError(event->element(),code,type,error); - DDebug(m_local->engine(),DebugAll,"XMPPUser. Error. '%s'. Code: '%s'. [%p]", - error.c_str(),code.c_str(),this); + DDebug(m_local->engine(),DebugAll,"User(%s). Received error=%s code=%s [%p]", + m_local->jid().c_str(),error.c_str(),code.c_str(),this); } +// Process presence probe void XMPPUser::processProbe(JBEvent* event, const String* resName) { updateTimeout(true); - XDebug(m_local->engine(),DebugAll,"XMPPUser. Process probe. [%p]",this); + XDebug(m_local->engine(),DebugAll,"User(%s). Received probe [%p]", + m_local->jid().c_str(),this); if (resName) notifyResource(false,*resName,event->stream(),true); else notifyResources(false,event->stream(),true); } +// Process presence stanzas bool XMPPUser::processPresence(JBEvent* event, bool available, const JabberID& from) { updateTimeout(true); @@ -967,10 +1303,11 @@ bool XMPPUser::processPresence(JBEvent* event, bool available, const JabberID& f for (; (obj = iter.get());) { JIDResource* res = static_cast(obj); if (res->setPresence(false)) { - DDebug(m_local->engine(),DebugNote, - "XMPPUser. Local user (%p). Resource (%s) changed state: %s. Audio: %s. [%p]", - m_local,res->name().c_str(),res->available()?"available":"unavailable", - res->hasCap(JIDResource::CapAudio)?"YES":"NO",this); + DDebug(m_local->engine(),DebugInfo, + "User(%s). Resource %s state=%s audio=%s [%p]", + m_local->jid().c_str(),res->name().c_str(), + res->available()?"available":"unavailable", + String::boolText(res->hasCap(JIDResource::CapAudio)),this); notify = false; m_local->engine()->notifyPresence(this,res); } @@ -1001,16 +1338,17 @@ bool XMPPUser::processPresence(JBEvent* event, bool available, const JabberID& f if (!res) { res = new JIDResource(from.resource()); m_remoteRes.add(res); - DDebug(m_local->engine(),DebugNote, - "XMPPUser. Local user (%p). Added new remote resource (%s). [%p]", - m_local,res->name().c_str(),this); + DDebug(m_local->engine(),DebugInfo, + "User(%s). remote=%s added resource '%s' [%p]", + m_local->jid().c_str(),from.bare().c_str(),res->name().c_str(),this); } // Changed: notify if (res->fromXML(event->element())) { - DDebug(m_local->engine(),DebugNote, - "XMPPUser. Local user (%p). Resource (%s) changed state: %s. Audio: %s. [%p]", - m_local,res->name().c_str(),res->available()?"available":"unavailable", - res->hasCap(JIDResource::CapAudio)?"YES":"NO",this); + DDebug(m_local->engine(),DebugInfo, + "User(%s). remote=%s resource %s state=%s audio=%s [%p]", + m_local->jid().c_str(),from.bare().c_str(),res->name().c_str(), + res->available()?"available":"unavailable", + String::boolText(res->hasCap(JIDResource::CapAudio)),this); m_local->engine()->notifyPresence(this,res); } if (!available && m_local->engine()->delUnavailable()) { @@ -1025,6 +1363,7 @@ bool XMPPUser::processPresence(JBEvent* event, bool available, const JabberID& f return true; } +// Process subscribe requests void XMPPUser::processSubscribe(JBEvent* event, JBPresence::Presence type) { Lock lock(this); @@ -1038,16 +1377,12 @@ void XMPPUser::processSubscribe(JBEvent* event, JBPresence::Presence type) // Approve if auto subscribing if ((m_local->engine()->autoSubscribe() & From)) sendSubscribe(JBPresence::Subscribed,event->stream()); - DDebug(m_local->engine(),DebugAll, - "XMPPUser. processSubscribe. Subscribing. [%p]",this); break; case JBPresence::Subscribed: // Already subscribed to remote user: do nothing if (subscribedTo()) return; updateSubscription(false,true,event->stream()); - DDebug(m_local->engine(),DebugAll, - "XMPPUser. processSubscribe. Subscribed. [%p]",this); break; case JBPresence::Unsubscribe: // Already unsubscribed from us: confirm it @@ -1058,16 +1393,12 @@ void XMPPUser::processSubscribe(JBEvent* event, JBPresence::Presence type) // Approve if auto subscribing if ((m_local->engine()->autoSubscribe() & From)) sendSubscribe(JBPresence::Unsubscribed,event->stream()); - DDebug(m_local->engine(),DebugAll, - "XMPPUser. processSubscribe. Unsubscribing. [%p]",this); break; case JBPresence::Unsubscribed: // If not subscribed to remote user ignore the unsubscribed confirmation if (!subscribedTo()) return; updateSubscription(false,false,event->stream()); - DDebug(m_local->engine(),DebugAll, - "XMPPUser. processSubscribe. Unsubscribed. [%p]",this); break; default: return; @@ -1076,18 +1407,20 @@ void XMPPUser::processSubscribe(JBEvent* event, JBPresence::Presence type) m_local->engine()->notifySubscribe(this,type); } -bool XMPPUser::probe(JBComponentStream* stream, u_int64_t time) +// Probe a remote user +bool XMPPUser::probe(JBStream* stream, u_int64_t time) { Lock lock(this); updateTimeout(false,time); - XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending '%s'. [%p]", - JBPresence::presenceText(JBPresence::Probe),this); + XDebug(m_local->engine(),DebugAll,"User(%s). Sending probe [%p]", + m_local->jid().c_str(),this); XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(), JBPresence::Probe); return m_local->engine()->sendStanza(xml,stream); } -bool XMPPUser::sendSubscribe(JBPresence::Presence type, JBComponentStream* stream) +// Send a subscribe request +bool XMPPUser::sendSubscribe(JBPresence::Presence type, JBStream* stream) { Lock lock(this); bool from = false; @@ -1106,8 +1439,9 @@ bool XMPPUser::sendSubscribe(JBPresence::Presence type, JBComponentStream* strea default: return false; } - XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending '%s'. [%p]", - JBPresence::presenceText(type),this); + XDebug(m_local->engine(),DebugAll,"User(%s). Sending '%s' to %s [%p]", + m_local->jid().c_str(),JBPresence::presenceText(type), + m_jid.bare().c_str(),this); XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(),type); bool result = m_local->engine()->sendStanza(xml,stream); // Set subscribe data. Not for subscribe/unsubscribe @@ -1116,6 +1450,7 @@ bool XMPPUser::sendSubscribe(JBPresence::Presence type, JBComponentStream* strea return result; } +// Check timeouts bool XMPPUser::timeout(u_int64_t time) { Lock lock(this); @@ -1128,13 +1463,15 @@ bool XMPPUser::timeout(u_int64_t time) return false; // Timeout. Clear resources & Notify Debug(m_local->engine(),DebugNote, - "XMPPUser. Local user (%p). Timeout. [%p]",m_local,this); + "User(%s). Remote %s expired. Set unavailable [%p]", + m_local->jid().c_str(),m_jid.bare().c_str(),this); m_remoteRes.clear(); m_local->engine()->notifyPresence(0,m_jid,m_local->jid(),false); return true; } -bool XMPPUser::sendPresence(JIDResource* resource, JBComponentStream* stream, +// Send presence notifications for all resources +bool XMPPUser::sendPresence(JIDResource* resource, JBStream* stream, bool force) { Lock lock(this); @@ -1166,8 +1503,9 @@ bool XMPPUser::sendPresence(JIDResource* resource, JBComponentStream* stream, return m_local->engine()->sendStanza(xml,stream); } +// Notify the presence of a given resource void XMPPUser::notifyResource(bool remote, const String& name, - JBComponentStream* stream, bool force) + JBStream* stream, bool force) { if (remote) { Lock lock(&m_remoteRes); @@ -1182,7 +1520,8 @@ void XMPPUser::notifyResource(bool remote, const String& name, sendPresence(res,stream,force); } -void XMPPUser::notifyResources(bool remote, JBComponentStream* stream, bool force) +// Notify the presence for all resources +void XMPPUser::notifyResources(bool remote, JBStream* stream, bool force) { if (remote) { Lock lock(&m_remoteRes); @@ -1201,15 +1540,18 @@ void XMPPUser::notifyResources(bool remote, JBComponentStream* stream, bool forc } } -bool XMPPUser::sendUnavailable(JBComponentStream* stream) +// Send unavailable +bool XMPPUser::sendUnavailable(JBStream* stream) { - XDebug(m_local->engine(),DebugAll,"XMPPUser. Sending 'unavailable'. [%p]",this); + XDebug(m_local->engine(),DebugAll,"User(%s). Sending 'unavailable' to %s [%p]", + m_local->jid().c_str(),m_jid.bare().c_str(),this); XMLElement* xml = JBPresence::createPresence(m_local->jid().bare(),m_jid.bare(), JBPresence::Unavailable); return m_local->engine()->sendStanza(xml,stream); } -void XMPPUser::updateSubscription(bool from, bool value, JBComponentStream* stream) +// Update the subscription state for a remote user +void XMPPUser::updateSubscription(bool from, bool value, JBStream* stream) { Lock lock(this); // Don't update if nothing changed @@ -1222,9 +1564,9 @@ void XMPPUser::updateSubscription(bool from, bool value, JBComponentStream* stre m_subscription |= s; else m_subscription &= ~s; - DDebug(m_local->engine(),DebugNote, - "XMPPUser. Local user (%p). Subscription updated. State '%s'. [%p]", - m_local,subscribeText(m_subscription),this); + DDebug(m_local->engine(),DebugInfo,"User(%s). remote=%s subscription=%s [%p]", + m_local->jid().c_str(),m_jid.bare().c_str(), + subscribeText(m_subscription),this); // Send presence if remote user is subscribed to us if (from && subscribedFrom()) { sendUnavailable(stream); @@ -1232,6 +1574,7 @@ void XMPPUser::updateSubscription(bool from, bool value, JBComponentStream* stre } } +// Update some timeout values void XMPPUser::updateTimeout(bool from, u_int64_t time) { Lock lock(this); @@ -1252,7 +1595,7 @@ XMPPUserRoster::XMPPUserRoster(JBPresence* engine, const char* node, m_engine(engine) { m_engine->addRoster(this); - DDebug(m_engine,DebugNote, "XMPPUserRoster. %s. [%p]",m_jid.c_str(),this); + DDebug(m_engine,DebugInfo, "XMPPUserRoster. %s [%p]",m_jid.c_str(),this); } XMPPUserRoster::~XMPPUserRoster() @@ -1261,9 +1604,11 @@ XMPPUserRoster::~XMPPUserRoster() lock(); m_remote.clear(); unlock(); - DDebug(m_engine,DebugNote, "~XMPPUserRoster. %s. [%p]",m_jid.c_str(),this); + DDebug(m_engine,DebugInfo, "~XMPPUserRoster. %s [%p]",m_jid.c_str(),this); } +// Get a remote user from roster +// Add a new one if requested XMPPUser* XMPPUserRoster::getUser(const JabberID& jid, bool add, bool* added) { Lock lock(this); @@ -1271,7 +1616,7 @@ XMPPUser* XMPPUserRoster::getUser(const JabberID& jid, bool add, bool* added) XMPPUser* u = 0; for (; obj; obj = obj->skipNext()) { u = static_cast(obj->get()); - if (jid.bare() == u->jid().bare()) + if (jid.bare() &= u->jid().bare()) break; u = 0; } @@ -1281,17 +1626,22 @@ XMPPUser* XMPPUserRoster::getUser(const JabberID& jid, bool add, bool* added) u = new XMPPUser(this,jid.node(),jid.domain(),XMPPUser::From); if (added) *added = true; + Debug(m_engine,DebugAll,"User(%s) added remote=%s [%p]", + m_jid.c_str(),u->jid().bare().c_str(),this); } return u->ref() ? u : 0; } +// Remove an user from roster bool XMPPUserRoster::removeUser(const JabberID& remote) { Lock lock(this); ObjList* obj = m_remote.skipNull(); for (; obj; obj = obj->skipNext()) { XMPPUser* u = static_cast(obj->get()); - if (remote.bare() == u->jid().bare()) { + if (remote.bare() &= u->jid().bare()) { + Debug(m_engine,DebugAll,"User(%s) removed remote=%s [%p]", + m_jid.c_str(),u->jid().bare().c_str(),this); m_remote.remove(u,true); break; } @@ -1299,6 +1649,7 @@ bool XMPPUserRoster::removeUser(const JabberID& remote) return (0 != m_remote.skipNull()); } +// Check the presence timeout for all remote users bool XMPPUserRoster::timeout(u_int64_t time) { Lock lock(this); @@ -1315,52 +1666,45 @@ bool XMPPUserRoster::timeout(u_int64_t time) /** * JBPresence */ -TokenDict JBPresence::s_presence[] = { - {"error", Error}, - {"probe", Probe}, - {"subscribe", Subscribe}, - {"subscribed", Subscribed}, - {"unavailable", Unavailable}, - {"unsubscribe", Unsubscribe}, - {"unsubscribed", Unsubscribed}, - {0,0} - }; -JBPresence::JBPresence(JBEngine* engine, const NamedList& params) - : JBClient(engine), - Mutex(true), - m_autoSubscribe((int)XMPPUser::None), - m_delUnavailable(false), - m_addOnSubscribe(false), - m_addOnProbe(false), - m_addOnPresence(false), - m_autoProbe(true), - m_probeInterval(0), - m_expireInterval(0) +// Build the service +JBPresence::JBPresence(JBEngine* engine, const NamedList* params, int prio) + : JBService(engine,"jbpresence",params,prio), + m_autoSubscribe((int)XMPPUser::None), + m_delUnavailable(false), + m_autoRoster(false), + m_addOnSubscribe(false), + m_addOnProbe(false), + m_addOnPresence(false), + m_autoProbe(true), + m_probeInterval(1800000), + m_expireInterval(300000) { - debugName("jbpresence"); - XDebug(this,DebugAll,"JBPresence. [%p]",this); - if (m_engine) - m_engine->setPresenceServer(this); - initialize(params); } JBPresence::~JBPresence() { - cleanup(); - if (m_engine) - m_engine->unsetPresenceServer(this); - m_events.clear(); - XDebug(this,DebugAll,"~JBPresence. [%p]",this); + cancelThreads(); + Lock lock(this); + ListIterator iter(m_rosters); + GenObject* obj; + for (; (obj = iter.get());) { + XMPPUserRoster* ur = static_cast(obj); + ur->cleanup(); + m_rosters.remove(ur,true); + } } +// Initialize the service void JBPresence::initialize(const NamedList& params) { + debugLevel(params.getIntValue("debug_level",debugLevel())); + m_autoSubscribe = XMPPUser::subscribeType(params.getValue("auto_subscribe")); m_delUnavailable = params.getBoolValue("delete_unavailable",true); m_autoProbe = params.getBoolValue("auto_probe",true); - if (m_engine) { - JBServerInfo* info = m_engine->getServer(); + if (engine()) { + XMPPServerInfo* info = engine()->findServerInfo(engine()->componentServer(),true); if (info) { m_addOnSubscribe = m_addOnProbe = m_addOnPresence = info->roster(); // Automatically process (un)subscribe and probe requests if no roster @@ -1368,491 +1712,166 @@ void JBPresence::initialize(const NamedList& params) m_autoProbe = true; m_autoSubscribe = XMPPUser::From; } - info->deref(); } } - m_probeInterval = params.getIntValue("probe_interval",PRESENCE_PROBE_INTERVAL); - m_expireInterval = params.getIntValue("expire_interval",PRESENCE_EXPIRE_INTERVAL); - if (debugAt(DebugAll)) { + + m_probeInterval = 1000 * params.getIntValue("probe_interval",m_probeInterval/1000); + m_expireInterval = 1000 * params.getIntValue("expire_interval",m_expireInterval/1000); + + m_autoRoster = m_addOnSubscribe || m_addOnProbe || m_addOnPresence; + + if (debugAt(DebugInfo)) { String s; - s << "\r\nauto_subscribe=" << XMPPUser::subscribeText(m_autoSubscribe); - s << "\r\ndelete_unavailable=" << String::boolText(m_delUnavailable); - s << "\r\nadd_onsubscribe=" << String::boolText(m_addOnSubscribe); - s << "\r\nadd_onprobe=" << String::boolText(m_addOnProbe); - s << "\r\nadd_onpresence=" << String::boolText(m_addOnPresence); - s << "\r\nauto_probe=" << String::boolText(m_autoProbe); - s << "\r\nprobe_interval=" << (unsigned int)m_probeInterval; - s << "\r\nexpire_interval=" << (unsigned int)m_expireInterval; - Debug(this,DebugAll,"Initialized:%s",s.c_str()); + s << " auto_subscribe=" << XMPPUser::subscribeText(m_autoSubscribe); + s << " delete_unavailable=" << String::boolText(m_delUnavailable); + s << " add_onsubscribe=" << String::boolText(m_addOnSubscribe); + s << " add_onprobe=" << String::boolText(m_addOnProbe); + s << " add_onpresence=" << String::boolText(m_addOnPresence); + s << " auto_probe=" << String::boolText(m_autoProbe); + s << " probe_interval=" << (unsigned int)m_probeInterval; + s << " expire_interval=" << (unsigned int)m_expireInterval; + Debug(this,DebugInfo,"Jabber Presence service initialized:%s [%p]", + s.c_str(),this); + } + + if (!m_initialized) { + m_initialized = true; + int c = params.getIntValue("private_process_threads",1); + for (int i = 0; i < c; i++) + JBThread::start(JBThread::Presence,this,this,2,Thread::Normal); } } -bool JBPresence::receive(JBEvent* event) +// Accept an event from the engine +bool JBPresence::accept(JBEvent* event, bool& processed, bool& insert) { if (!event) return false; + bool disco = false; switch (event->type()) { + case JBEvent::IqDiscoInfoGet: + case JBEvent::IqDiscoInfoSet: + case JBEvent::IqDiscoInfoRes: + case JBEvent::IqDiscoInfoErr: + case JBEvent::IqDiscoItemsGet: + case JBEvent::IqDiscoItemsSet: + case JBEvent::IqDiscoItemsRes: + case JBEvent::IqDiscoItemsErr: + disco = true; case JBEvent::Presence: - case JBEvent::IqDiscoGet: - case JBEvent::IqDiscoSet: - case JBEvent::IqDiscoRes: + insert = false; break; default: return false; } - XDebug(this,DebugAll,"Received event."); - Lock lock(this); - event->releaseStream(); - m_events.append(event); + + JabberID jid(event->to()); + // Check destination. + // Don't accept disco stanzas without node (reroute them to the engine) + // Presence stanzas might be a brodcast (no 'to' attribute) + if (disco) { + if (!jid.node()) + return false; + if (validDomain(jid.domain())) + return true; + } + else if (!event->to() || validDomain(jid.domain())) + return true; + + Debug(this,DebugNote,"Received element with invalid domain '%s' [%p]", + jid.domain().c_str(),this); + // Respond only if stanza is not a response + if (event->stanzaType() != "error" && event->stanzaType() != "result") { + const String* id = event->id().null() ? 0 : &(event->id()); + sendError(XMPPError::SNoRemote,event->to(),event->from(), + event->releaseXML(),event->stream(),id); + } + processed = true; + TelEngine::destruct(event); return true; } +// Process received events bool JBPresence::process() { - Lock lock(this); - ObjList* obj = m_events.skipNull(); - if (!obj) + if (Thread::check(false)) + return false; + Lock lock(this); + JBEvent* event = deque(); + if (!event) return false; - JBEvent* event = static_cast(obj->get()); - m_events.remove(event,false); JabberID local(event->to()); JabberID remote(event->from()); switch (event->type()) { - case JBEvent::IqDiscoGet: - case JBEvent::IqDiscoSet: - case JBEvent::IqDiscoRes: - if (checkDestination(event,local)) - processDisco(event,local,remote); - event->deref(); + case JBEvent::IqDiscoInfoGet: + case JBEvent::IqDiscoInfoSet: + case JBEvent::IqDiscoInfoRes: + case JBEvent::IqDiscoInfoErr: + case JBEvent::IqDiscoItemsGet: + case JBEvent::IqDiscoItemsSet: + case JBEvent::IqDiscoItemsRes: + case JBEvent::IqDiscoItemsErr: + processDisco(event,local,remote); + TelEngine::destruct(event); return true; default: ; } - XDebug(this,DebugAll,"Process presence: '%s'.",event->stanzaType().c_str()); + DDebug(this,DebugAll,"Process presence: '%s' [%p]",event->stanzaType().c_str(),this); Presence p = presenceType(event->stanzaType()); switch (p) { case JBPresence::Error: - if (checkDestination(event,local)) - processError(event,local,remote); + processError(event,local,remote); break; case JBPresence::Probe: - if (checkDestination(event,local)) - processProbe(event,local,remote); + processProbe(event,local,remote); break; case JBPresence::Subscribe: case JBPresence::Subscribed: case JBPresence::Unsubscribe: case JBPresence::Unsubscribed: - if (checkDestination(event,local)) - processSubscribe(event,p,local,remote); + processSubscribe(event,p,local,remote); break; case JBPresence::Unavailable: // Check destination only if we have one. No destination: broadcast +//TODO: Fix it +#if 0 if (local && !checkDestination(event,local)) break; +#endif processUnavailable(event,local,remote); break; default: // Check destination only if we have one. No destination: broadcast +//TODO: Fix it +#if 0 if (local && !checkDestination(event,local)) break; if (!event->element()) break; +#endif // Simple presence shouldn't have a type if (event->element()->getAttribute("type")) { Debug(this,DebugNote, - "Received unexpected presence type '%s' from '%s' to '%s'.", - event->element()->getAttribute("type"),remote.c_str(),local.c_str()); + "Received unexpected presence type=%s from=%s to=%s [%p]", + event->element()->getAttribute("type"),remote.c_str(),local.c_str(),this); sendError(XMPPError::SFeatureNotImpl,local,remote, event->releaseXML(),event->stream()); break; } processPresence(event,local,remote); } - event->deref(); + TelEngine::destruct(event); return true; } -void JBPresence::runProcess() -{ - while(1) { - if (!process()) - Thread::msleep(SLEEP_PROCESSPRESENCE,true); - } -} - -void JBPresence::processDisco(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "processDisco. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - event,event->type(),local.c_str(),remote.c_str()); - if (event->type() == JBEvent::IqDiscoRes || !event->stream()) - return; - bool error = true; - XMPPNamespace::Type type; - // Check error and query type - while (true) { - if (!event->child()) - break; - if (event->child()->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoInfo])) - type = XMPPNamespace::DiscoInfo; - else if (event->child()->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoItems])) - type = XMPPNamespace::DiscoItems; - else - break; - error = false; - break; - } - if (error) { - DDebug(this,DebugNote,"Received unacceptable 'disco' query"); - sendError(XMPPError::SFeatureNotImpl,local,remote,event->releaseXML(), - event->stream(),&(event->id())); - return; - } - JabberID from(event->to()); - if (from.resource().null() && m_engine) - from.resource(m_engine->defaultResource()); - // Create response: add identity and features - XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,from,event->from(),event->id()); - XMLElement* query = XMPPUtils::createElement(XMLElement::Query,type); - JIDIdentity* identity = new JIDIdentity(JIDIdentity::Client,JIDIdentity::ComponentGeneric); - query->addChild(identity->toXML()); - identity->deref(); - JIDFeatureList fl; - fl.add(XMPPNamespace::CapVoiceV1); - fl.addTo(query); - iq->addChild(query); - sendStanza(iq,event->stream()); -} - -void JBPresence::processError(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "processError. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - event,event->type(),local.c_str(),remote.c_str()); - XMPPUser* user = getRemoteUser(local,remote,false,0,false,0); - if (!user) { - DDebug(this,DebugAll, - "Received 'error' for non user. Local: '%s'. Remote: '%s'.", - local.c_str(),remote.c_str()); - return; - } - user->processError(event); - user->deref(); -} - -void JBPresence::processProbe(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "processProbe. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - event,event->type(),local.c_str(),remote.c_str()); - bool newUser = false; - XMPPUser* user = getRemoteUser(local,remote,m_addOnProbe,0,m_addOnProbe,&newUser); - if (!user) { - DDebug(this,DebugAll, - "Received 'probe' for non user. Local: '%s'. Remote: '%s'.", - local.c_str(),remote.c_str()); - if (m_autoProbe) { - XMLElement* stanza = createPresence(local.bare(),remote); - JIDResource* resource = new JIDResource(m_engine->defaultResource(),JIDResource::Available, - JIDResource::CapAudio); - resource->addTo(stanza); - resource->deref(); - if (event->stream()) - event->stream()->sendStanza(stanza); - else - delete stanza; - } - else if (!notifyProbe(event,local,remote)) - sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), - event->stream()); - return; - } - if (newUser) - notifyNewUser(user); - String resName = local.resource(); - if (resName.null()) - user->processProbe(event); - else - user->processProbe(event,&resName); - user->deref(); -} - -void JBPresence::processSubscribe(JBEvent* event, Presence presence, - const JabberID& local, const JabberID& remote) -{ - XDebug(this,DebugAll, - "Process '%s'. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - presenceText(presence),event,event->type(),local.c_str(),remote.c_str()); - bool addLocal = (presence == Subscribe) ? m_addOnSubscribe : false; - bool newUser = false; - XMPPUser* user = getRemoteUser(local,remote,addLocal,0,addLocal,&newUser); - if (!user) { - DDebug(this,DebugAll, - "Received '%s' for non user. Local: '%s'. Remote: '%s'.", - presenceText(presence),local.c_str(),remote.c_str()); - if (!notifySubscribe(event,local,remote,presence) && - (presence != Subscribed && presence != Unsubscribed)) - sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), - event->stream()); - return; - } - if (newUser) - notifyNewUser(user); - user->processSubscribe(event,presence); - user->deref(); -} - -void JBPresence::processUnavailable(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "processUnavailable. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - event,event->type(),local.c_str(),remote.c_str()); - // Don't add if delUnavailable is true - bool addLocal = m_addOnPresence && !m_delUnavailable; - // Check if broadcast - if (local.null()) { - Lock lock(this); - ObjList* obj = m_rosters.skipNull(); - for (; obj; obj = obj->skipNext()) { - XMPPUserRoster* roster = static_cast(obj->get()); - bool newUser = false; - XMPPUser* user = getRemoteUser(roster->jid(),remote,addLocal,0, - addLocal,&newUser); - if (!user) - continue; - if (newUser) - notifyNewUser(user); - if (!user->processPresence(event,false,remote)) - removeRemoteUser(local,remote); - user->deref(); - } - return; - } - // Not broadcast: find user - bool newUser = false; - XMPPUser* user = getRemoteUser(local,remote,addLocal,0,addLocal,&newUser); - if (!user) { - DDebug(this,DebugAll, - "Received 'unavailable' for non user. Local: '%s'. Remote: '%s'.", - local.c_str(),remote.c_str()); - if (!notifyPresence(event,local,remote,false)) - sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), - event->stream()); - return; - } - if (newUser) - notifyNewUser(user); - if (!user->processPresence(event,false,remote)) - removeRemoteUser(local,remote); - user->deref(); -} - -void JBPresence::processPresence(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "processPresence. Event: ((%p): %u). Local: '%s'. Remote: '%s'.", - event,event->type(),local.c_str(),remote.c_str()); - // Check if broadcast - if (local.null()) { - Lock lock(this); - ObjList* obj = m_rosters.skipNull(); - for (; obj; obj = obj->skipNext()) { - XMPPUserRoster* roster = static_cast(obj->get()); - bool newUser = false; - XMPPUser* user = getRemoteUser(roster->jid(),remote, - m_addOnPresence,0,m_addOnPresence,&newUser); - if (!user) - continue; - if (newUser) - notifyNewUser(user); - user->processPresence(event,true,remote); - user->deref(); - } - return; - } - // Not broadcast: find user - bool newUser = false; - XMPPUser* user = getRemoteUser(local,remote,m_addOnPresence,0,m_addOnPresence, - &newUser); - if (!user) { - DDebug(this,DebugAll, - "Received presence for non user. Local: '%s'. Remote: '%s'.", - local.c_str(),remote.c_str()); - if (!notifyPresence(event,local,remote,true)) - sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), - event->stream()); - return; - } - if (newUser) - notifyNewUser(user); - user->processPresence(event,true,remote); - user->deref(); -} - -bool JBPresence::notifyProbe(JBEvent* event, const JabberID& local, - const JabberID& remote) -{ - XDebug(this,DebugAll, - "JBPresence::notifyProbe. Local: '%s'. Remote: '%s'.", - local.c_str(),remote.c_str()); - return false; -} - -bool JBPresence::notifySubscribe(JBEvent* event, const JabberID& local, - const JabberID& remote, Presence presence) -{ - XDebug(this,DebugAll, - "JBPresence::notifySubscribe(%s). Local: '%s'. Remote: '%s'.", - presenceText(presence),local.c_str(),remote.c_str()); - return false; -} - -void JBPresence::notifySubscribe(XMPPUser* user, Presence presence) -{ - XDebug(this,DebugAll, - "JBPresence::notifySubscribe(%s). User: (%p).", - presenceText(presence),user); -} - -bool JBPresence::notifyPresence(JBEvent* event, const JabberID& local, - const JabberID& remote, bool available) -{ - XDebug(this,DebugAll, - "JBPresence::notifyPresence(%s). Local: '%s'. Remote: '%s'.", - available?"available":"unavailable",local.c_str(),remote.c_str()); - return false; -} - -void JBPresence::notifyPresence(XMPPUser* user, JIDResource* resource) -{ - XDebug(this,DebugAll, - "JBPresence::notifyPresence. User: (%p). Resource: (%p).", - user,resource); -} - -void JBPresence::notifyNewUser(XMPPUser* user) -{ - XDebug(this,DebugAll,"JBPresence::notifyNewUser. User: (%p).",user); -} - -XMPPUserRoster* JBPresence::getRoster(const JabberID& jid, bool add, bool* added) -{ - if (jid.node().null() || jid.domain().null()) - return 0; - Lock lock(this); - ObjList* obj = m_rosters.skipNull(); - for (; obj; obj = obj->skipNext()) { - XMPPUserRoster* ur = static_cast(obj->get()); - if (jid.bare() == ur->jid().bare()) - return ur->ref() ? ur : 0; - } - if (!add) - return 0; - if (added) - *added = true; - XMPPUserRoster* ur = new XMPPUserRoster(this,jid.node(),jid.domain()); - return ur->ref() ? ur : 0; -} - -XMPPUser* JBPresence::getRemoteUser(const JabberID& local, const JabberID& remote, - bool addLocal, bool* addedLocal, bool addRemote, bool* addedRemote) -{ - XMPPUserRoster* ur = getRoster(local,addLocal,addedLocal); - if (!ur) - return 0; - XMPPUser* user = ur->getUser(remote,addRemote,addedRemote); - ur->deref(); - return user; -} - -void JBPresence::removeRemoteUser(const JabberID& local, const JabberID& remote) -{ - Lock lock(this); - ObjList* obj = m_rosters.skipNull(); - XMPPUserRoster* ur = 0; - for (; obj; obj = obj->skipNext()) { - ur = static_cast(obj->get()); - if (local.bare() == ur->jid().bare()) { - if (ur->removeUser(remote)) - ur = 0; - break; - } - ur = 0; - } - if (ur) - m_rosters.remove(ur,true); -} - -bool JBPresence::validDomain(const String& domain) -{ - if (m_engine->getAlternateDomain() && (domain == m_engine->getAlternateDomain())) - return true; - JBServerInfo* server = m_engine->getServer(); - if (!server) - return false; - bool ok = (domain == server->identity() || domain == server->fullIdentity()); - server->deref(); - return ok; -} - -bool JBPresence::getStream(JBComponentStream*& stream, bool& release) -{ - release = false; - if (stream) - return true; - stream = m_engine->getStream(0,!m_engine->exiting()); - if (stream) { - release = true; - return true; - } - if (!m_engine->exiting()) - DDebug(m_engine,DebugGoOn,"No stream to send data."); - return false; -} - -bool JBPresence::sendStanza(XMLElement* element, JBComponentStream* stream) -{ - if (!element) - return true; - bool release = false; - if (!getStream(stream,release)) { - delete element; - return false; - } - JBComponentStream::Error res = stream->sendStanza(element); - if (release) - stream->deref(); - if (res == JBComponentStream::ErrorContext || - res == JBComponentStream::ErrorNoSocket) - return false; - return true; -} - -bool JBPresence::sendError(XMPPError::Type type, - const String& from, const String& to, - XMLElement* element, JBComponentStream* stream, const String* id) -{ - XMLElement* xml = 0; - XMLElement::Type t = element ? element->type() : XMLElement::Invalid; - if (t == XMLElement::Iq) - xml = XMPPUtils::createIq(XMPPUtils::IqError,from,to,id ? id->c_str() : element->getAttribute("id")); - else - xml = createPresence(from,to,Error); - xml->addChild(element); - xml->addChild(XMPPUtils::createError(XMPPError::TypeModify,type)); - return sendStanza(xml,stream); -} - +// Check timeouts for all users' roster void JBPresence::checkTimeout(u_int64_t time) { lock(); ListIterator iter(m_rosters); for (;;) { + if (Thread::check(false)) + break; XMPPUserRoster* ur = static_cast(iter.get()); // End of iteration? if (!ur) @@ -1873,14 +1892,324 @@ void JBPresence::checkTimeout(u_int64_t time) unlock(); } -void JBPresence::runCheckTimeout() +// Process received disco +void JBPresence::processDisco(JBEvent* event, const JabberID& local, + const JabberID& remote) { - while (1) { - checkTimeout(Time::msecNow()); - Thread::msleep(SLEEP_PROCESSTIMEOUT,true); - } + XDebug(this,DebugAll,"processDisco event=(%p,%s) local=%s remote=%s [%p]", + event,event->name(),local.c_str(),remote.c_str(),this); + if (event->type() != JBEvent::IqDiscoInfoGet || !event->stream()) + return; + JabberID from(event->to()); + if (from.resource().null() && engine()) + from.resource(engine()->defaultResource()); + // Create response: add identity and features + XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqResult,from,event->from(),event->id()); + XMLElement* query = XMPPUtils::createElement(XMLElement::Query,XMPPNamespace::DiscoInfo); + JIDIdentity* identity = new JIDIdentity(JIDIdentity::Client,JIDIdentity::ComponentGeneric); + query->addChild(identity->toXML()); + identity->deref(); + JIDFeatureList fl; + fl.add(XMPPNamespace::CapVoiceV1); + fl.addTo(query); + iq->addChild(query); + sendStanza(iq,event->stream()); } +void JBPresence::processError(JBEvent* event, const JabberID& local, + const JabberID& remote) +{ + XDebug(this,DebugAll,"processError event=(%p,%s) local=%s remote=%s [%p]", + event,event->name(),local.c_str(),remote.c_str(),this); + XMPPUser* user = recvGetRemoteUser("error",local,remote); + if (user) + user->processError(event); + TelEngine::destruct(user); +} + +void JBPresence::processProbe(JBEvent* event, const JabberID& local, + const JabberID& remote) +{ + XDebug(this,DebugAll,"processProbe event=(%p,%s) local=%s remote=%s [%p]", + event,event->name(),local.c_str(),remote.c_str(),this); + bool newUser = false; + XMPPUser* user = recvGetRemoteUser("probe",local,remote,m_addOnProbe,0, + m_addOnProbe,&newUser); + if (!user) { + if (m_autoProbe) { + XMLElement* stanza = createPresence(local.bare(),remote); + JIDResource* resource = new JIDResource(engine()->defaultResource(), + JIDResource::Available,JIDResource::CapAudio); + resource->addTo(stanza); + TelEngine::destruct(resource); + if (event->stream()) + event->stream()->sendStanza(stanza); + else + delete stanza; + } + else if (!notifyProbe(event,local,remote)) + sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), + event->stream()); + return; + } + if (newUser) + notifyNewUser(user); + String resName = local.resource(); + if (resName.null()) + user->processProbe(event); + else + user->processProbe(event,&resName); + TelEngine::destruct(user); +} + +void JBPresence::processSubscribe(JBEvent* event, Presence presence, + const JabberID& local, const JabberID& remote) +{ + XDebug(this,DebugAll, + "processSubscribe '%s' event=(%p,%s) local=%s remote=%s [%p]", + presenceText(presence),event,event->name(),local.c_str(),remote.c_str(),this); + bool addLocal = (presence == Subscribe) ? m_addOnSubscribe : false; + bool newUser = false; + XMPPUser* user = recvGetRemoteUser(presenceText(presence),local,remote, + addLocal,0,addLocal,&newUser); + if (!user) { + if (!notifySubscribe(event,local,remote,presence) && + (presence != Subscribed && presence != Unsubscribed)) + sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), + event->stream()); + return; + } + if (newUser) + notifyNewUser(user); + user->processSubscribe(event,presence); + TelEngine::destruct(user); +} + +void JBPresence::processUnavailable(JBEvent* event, const JabberID& local, + const JabberID& remote) +{ + XDebug(this,DebugAll,"processUnavailable event=(%p,%s) local=%s remote=%s [%p]", + event,event->name(),local.c_str(),remote.c_str(),this); + // Don't add if delUnavailable is true + bool addLocal = m_addOnPresence && !m_delUnavailable; + // Check if broadcast + if (local.null()) { + Lock lock(this); + ObjList* obj = m_rosters.skipNull(); + for (; obj; obj = obj->skipNext()) { + XMPPUserRoster* roster = static_cast(obj->get()); + bool newUser = false; + XMPPUser* user = getRemoteUser(roster->jid(),remote,addLocal,0, + addLocal,&newUser); + if (!user) + continue; + if (newUser) + notifyNewUser(user); + if (!user->processPresence(event,false,remote)) + removeRemoteUser(local,remote); + TelEngine::destruct(user); + } + return; + } + // Not broadcast: find user + bool newUser = false; + XMPPUser* user = recvGetRemoteUser("unavailable",local,remote, + addLocal,0,addLocal,&newUser); + if (!user) { + if (!notifyPresence(event,local,remote,false)) + sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), + event->stream()); + return; + } + if (newUser) + notifyNewUser(user); + if (!user->processPresence(event,false,remote)) + removeRemoteUser(local,remote); + TelEngine::destruct(user); +} + +void JBPresence::processPresence(JBEvent* event, const JabberID& local, + const JabberID& remote) +{ + XDebug(this,DebugAll,"processPresence event=(%p,%s) local=%s remote=%s [%p]", + event,event->name(),local.c_str(),remote.c_str(),this); + // Check if broadcast + if (local.null()) { + Lock lock(this); + ObjList* obj = m_rosters.skipNull(); + for (; obj; obj = obj->skipNext()) { + XMPPUserRoster* roster = static_cast(obj->get()); + bool newUser = false; + XMPPUser* user = getRemoteUser(roster->jid(),remote, + m_addOnPresence,0,m_addOnPresence,&newUser); + if (!user) + continue; + if (newUser) + notifyNewUser(user); + user->processPresence(event,true,remote); + TelEngine::destruct(user); + } + return; + } + // Not broadcast: find user + bool newUser = false; + XMPPUser* user = recvGetRemoteUser("",local,remote, + m_addOnPresence,0,m_addOnPresence,&newUser); + if (!user) { + if (!notifyPresence(event,local,remote,true)) + sendError(XMPPError::SItemNotFound,local,remote,event->releaseXML(), + event->stream()); + return; + } + if (newUser) + notifyNewUser(user); + user->processPresence(event,true,remote); + TelEngine::destruct(user); +} + +bool JBPresence::notifyProbe(JBEvent* event, const JabberID& local, + const JabberID& remote) +{ + DDebug(this,DebugStub,"notifyProbe local=%s remote=%s [%p]", + local.c_str(),remote.c_str(),this); + return false; +} + +bool JBPresence::notifySubscribe(JBEvent* event, const JabberID& local, + const JabberID& remote, Presence presence) +{ + DDebug(this,DebugStub,"notifySubscribe local=%s remote=%s [%p]", + local.c_str(),remote.c_str(),this); + return false; +} + +void JBPresence::notifySubscribe(XMPPUser* user, Presence presence) +{ + DDebug(this,DebugStub,"notifySubscribe user=%p [%p]",user,this); +} + +bool JBPresence::notifyPresence(JBEvent* event, const JabberID& local, + const JabberID& remote, bool available) +{ + DDebug(this,DebugStub,"notifyPresence local=%s remote=%s [%p]", + local.c_str(),remote.c_str(),this); + return false; +} + +void JBPresence::notifyPresence(XMPPUser* user, JIDResource* resource) +{ + DDebug(this,DebugStub,"notifyPresence user=%p [%p]",user,this); +} + +void JBPresence::notifyNewUser(XMPPUser* user) +{ + DDebug(this,DebugStub,"notifyNewUser user=%p [%p]",user,this); +} + +// Get a user's roster. Add a new one if requested +XMPPUserRoster* JBPresence::getRoster(const JabberID& jid, bool add, bool* added) +{ + if (jid.node().null() || jid.domain().null()) + return 0; + Lock lock(this); + ObjList* obj = m_rosters.skipNull(); + for (; obj; obj = obj->skipNext()) { + XMPPUserRoster* ur = static_cast(obj->get()); + if (jid.bare() &= ur->jid().bare()) + return ur->ref() ? ur : 0; + } + if (!add) + return 0; + if (added) + *added = true; + XMPPUserRoster* ur = new XMPPUserRoster(this,jid.node(),jid.domain()); + DDebug(this,DebugAll,"Added roster for %s [%p]",jid.bare().c_str(),this); + return ur->ref() ? ur : 0; +} + +// Get a remote user's roster +XMPPUser* JBPresence::getRemoteUser(const JabberID& local, const JabberID& remote, + bool addLocal, bool* addedLocal, bool addRemote, bool* addedRemote) +{ + DDebug(this,DebugAll,"getRemoteUser local=%s add=%s remote=%s add=%s [%p]", + local.bare().c_str(),String::boolText(addLocal), + remote.bare().c_str(),String::boolText(addRemote),this); + XMPPUserRoster* ur = getRoster(local,addLocal,addedLocal); + if (!ur) + return 0; + XMPPUser* user = ur->getUser(remote,addRemote,addedRemote); + TelEngine::destruct(ur); + return user; +} + +void JBPresence::removeRemoteUser(const JabberID& local, const JabberID& remote) +{ + Lock lock(this); + ObjList* obj = m_rosters.skipNull(); + XMPPUserRoster* ur = 0; + for (; obj; obj = obj->skipNext()) { + ur = static_cast(obj->get()); + if (local.bare() &= ur->jid().bare()) { + if (ur->removeUser(remote)) + ur = 0; + break; + } + ur = 0; + } + if (ur) + m_rosters.remove(ur,true); +} + +// Check if a ddestination domain is a valid one +bool JBPresence::validDomain(const String& domain) +{ + if (engine()->getAlternateDomain() && + (engine()->getAlternateDomain().domain() &= domain)) + return true; + XMPPServerInfo* server = engine()->findServerInfo(engine()->componentServer(),true); + if (!server) + return false; + bool ok = ((domain &= server->identity()) || (domain &= server->fullIdentity())); + return ok; +} + +// Send a stanza +bool JBPresence::sendStanza(XMLElement* element, JBStream* stream) +{ + if (!element) + return true; + bool release = false; + if (!engine()->getStream(stream,release)) { + delete element; + return false; + } + JBStream::Error res = stream->sendStanza(element); + if (release) + TelEngine::destruct(stream); + if (res == JBStream::ErrorContext || + res == JBStream::ErrorNoSocket) + return false; + return true; +} + +// Send an error stanza +bool JBPresence::sendError(XMPPError::Type type, + const String& from, const String& to, + XMLElement* element, JBStream* stream, const String* id) +{ + XMLElement* xml = 0; + XMLElement::Type t = element ? element->type() : XMLElement::Invalid; + if (t == XMLElement::Iq) + xml = XMPPUtils::createIq(XMPPUtils::IqError,from,to, + id ? id->c_str() : element->getAttribute("id")); + else + xml = createPresence(from,to,Error); + xml->addChild(element); + xml->addChild(XMPPUtils::createError(XMPPError::TypeModify,type)); + return sendStanza(xml,stream); +} + +// Create a presence stanza XMLElement* JBPresence::createPresence(const char* from, const char* to, Presence type) { @@ -1891,6 +2220,7 @@ XMLElement* JBPresence::createPresence(const char* from, return presence; } +// Decode a presence error stanza bool JBPresence::decodeError(const XMLElement* element, String& code, String& type, String& error) { @@ -1910,37 +2240,10 @@ bool JBPresence::decodeError(const XMLElement* element, return true; } -bool JBPresence::checkDestination(JBEvent* event, const JabberID& jid) -{ - if (!event || validDomain(jid.domain())) - return true; - bool respond = true; - switch (event->type()) { - case JBEvent::IqDiscoGet: - case JBEvent::IqDiscoSet: - break; - case JBEvent::IqDiscoRes: - respond = false; - break; - default: - // Never respond to responses: type error or result - if (event->stanzaType() == "error" || event->stanzaType() == "result") - respond = false; - } - Debug(this,DebugNote,"Received element with invalid domain '%s'. Send error: %s", - jid.domain().c_str(),String::boolText(respond)); - if (respond) { - const String* id = event->id().null() ? 0 : &(event->id()); - sendError(XMPPError::SNoRemote,event->to(),event->from(), - event->releaseXML(),event->stream(),id); - } - return false; -} - void JBPresence::cleanup() { Lock lock(this); - DDebug(this,DebugAll,"Cleanup."); + DDebug(this,DebugAll,"Cleanup [%p]",this); ListIterator iter(m_rosters); GenObject* obj; for (; (obj = iter.get());) { @@ -1950,6 +2253,18 @@ void JBPresence::cleanup() } } +inline XMPPUser* JBPresence::recvGetRemoteUser(const char* type, + const JabberID& local, const JabberID& remote, + bool addLocal, bool* addedLocal, bool addRemote, bool* addedRemote) +{ + XMPPUser* user = getRemoteUser(local,remote,addLocal,addedLocal,addRemote,addedRemote); + if (!user) + Debug(this,DebugAll, + "No destination for received presence type=%s local=%s remote=%s [%p]", + type,local.c_str(),remote.c_str(),this); + return user; +} + void JBPresence::addRoster(XMPPUserRoster* ur) { Lock lock(this); diff --git a/libs/yjingle/jbstream.cpp b/libs/yjingle/jbstream.cpp index 70e01fff..a31e059e 100644 --- a/libs/yjingle/jbstream.cpp +++ b/libs/yjingle/jbstream.cpp @@ -28,45 +28,162 @@ using namespace TelEngine; static XMPPNamespace s_ns; static XMPPError s_err; -static const char* s_declaration = ""; +static TokenDict s_streamState[] = { + {"Idle", JBStream::Idle}, + {"Connecting", JBStream::Connecting}, + {"Started", JBStream::Started}, + {"Securing", JBStream::Securing}, + {"Auth", JBStream::Auth}, + {"Running", JBStream::Running}, + {"Destroy", JBStream::Destroy}, + {0,0}, +}; -// Just a shorter code -inline XMLElement* errorHostGone() -{ - return XMPPUtils::createStreamError(XMPPError::HostGone); -} /** - * JBComponentStream + * JBSocket */ -JBComponentStream::JBComponentStream(JBEngine* engine, const String& remoteName, - const SocketAddr& remoteAddr) - : Mutex(true), - m_state(Terminated), - m_remoteName(remoteName), - m_remoteAddr(remoteAddr), - m_engine(engine), - m_socket(0), - m_receiveMutex(true), - m_lastEvent(0), - m_terminateEvent(0) +bool JBSocket::connect(const SocketAddr& addr, bool& terminated) { - Debug(m_engine,DebugAll,"JBComponentStream. [%p]",this); - if (!engine) - return; - // Init data - m_engine->getServerIdentity(m_localName,remoteName); - // Start - m_engine->connect(this); + terminate(); + Lock2 lck1(m_streamMutex,m_receiveMutex); + m_socket = new Socket(PF_INET,SOCK_STREAM); + lck1.drop(); + terminated = false; + bool res = m_socket->connect(addr); + // Lock again to update data + Lock2 lck2(m_streamMutex,m_receiveMutex); + bool ok = false; + while (true) { + if (!m_socket) { + Debug(m_engine,DebugMild, + "Stream. Socket deleted while connecting [%p]",m_stream); + terminated = true; + break; + } + // Check connect result + if (!res) { + Debug(m_engine,DebugWarn, + "Stream. Failed to connect socket to '%s:%d'. %d: '%s' [%p]", + addr.host().c_str(),addr.port(), + m_socket->error(),::strerror(m_socket->error()),m_stream); + break; + } + // Connected + ok = true; + m_socket->setBlocking(false); + DDebug(m_engine,DebugAll,"Stream. Connected to '%s:%d'. [%p]", + addr.host().c_str(),addr.port(),m_stream); + break; + } + lck2.drop(); + if (!ok) + terminate(); + return ok; } -JBComponentStream::~JBComponentStream() +void JBSocket::terminate() { - Debug(m_engine,DebugAll,"~JBComponentStream. [%p]",this); + Lock2 lck(m_streamMutex,m_receiveMutex); + Socket* tmp = m_socket; + m_socket = 0; + if (tmp) { + tmp->setLinger(-1); + tmp->terminate(); + delete tmp; + } +} + +bool JBSocket::recv(char* buffer, unsigned int& len) +{ + if (!valid()) + return false; + + int read = m_socket->recv(buffer,len); + if (read != Socket::socketError()) { #ifdef XDEBUG - if (m_engine->printXml() && m_engine->debugAt(DebugAll)) { + if (read) { + String s(buffer,read); + XDebug(m_engine,DebugAll,"Stream recv [%p]\r\n%s", + m_stream,s.c_str()); + } +#endif + len = read; + return true; + } + + len = 0; + if (!m_socket->canRetry()) { + Debug(m_engine,DebugWarn, + "Stream. Socket read error: %d: '%s' [%p]", + m_socket->error(),::strerror(m_socket->error()),m_stream); + return false; + } + return true; +} + +bool JBSocket::send(const char* buffer, unsigned int& len) +{ + if (!valid()) + return false; + + XDebug(m_engine,DebugAll,"Stream sending [%p]\r\n%s",m_stream,buffer); + int c = m_socket->send(buffer,len); + if (c != Socket::socketError()) { + len = c; + return true; + } + if (!m_socket->canRetry()) { + Debug(m_engine,DebugWarn,"Stream. Socket send error: %d: '%s' [%p]", + m_socket->error(),::strerror(m_socket->error()),m_stream); + return false; + } + len = 0; + DDebug(m_engine,DebugMild, + "Stream. Socket temporary unavailable to send: %d: '%s' [%p]", + m_socket->error(),::strerror(m_socket->error()),m_stream); + return true; +} + + +/** + * JBStream + */ +JBStream::JBStream(JBEngine* engine, + const JabberID& localJid, const JabberID& remoteJid, + const String& password, const SocketAddr& address, + bool autoRestart, unsigned int maxRestart, + u_int64_t incRestartInterval, bool outgoing, int type) + : m_password(password), + m_type(type), + m_state(Idle), + m_outgoing(outgoing), + m_autoRestart(autoRestart), + m_restart(maxRestart), + m_restartMax(maxRestart), + m_timeToFillRestart(Time::msecNow() + incRestartInterval), + m_fillRestartInterval(incRestartInterval), + m_local(localJid.node(),localJid.domain(),localJid.resource()), + m_remote(remoteJid.node(),remoteJid.domain(),remoteJid.resource()), + m_engine(engine), + m_socket(engine,this), + m_address(address), + m_lastEvent(0), + m_terminateEvent(0), + m_startEvent(0) +{ + if (m_type == -1 && engine) + m_type = engine->protocol(); + DDebug(m_engine,DebugAll,"Stream created type=%s local=%s remote=%s [%p]", + JBEngine::lookupProto(m_type),m_local.safe(),m_remote.safe(),this); +} + +JBStream::~JBStream() +{ +#ifdef XDEBUG + if (m_engine && m_engine->printXml() && m_engine->debugAt(DebugAll)) { String buffer, element; - for (; true; ) { + while (true) { XMLElement* e = m_parser.extract(); if (!e) break; @@ -74,763 +191,812 @@ JBComponentStream::~JBComponentStream() delete e; } m_parser.getBuffer(buffer); - Debug(m_engine,DebugAll, - "Stream. Incoming data:[%p]\r\nParser buffer: '%s'.\r\nParsed elements: %s", - this,buffer.c_str(),element?element.c_str():"None."); + if (buffer || element) + Debug(m_engine,DebugAll, + "~Stream [%p]\r\nUnparsed data: '%s'.\r\nParsed elements: %s", + this,buffer.c_str(),element?element.c_str():"None."); } #endif - Lock2 lock(*this,m_receiveMutex); - cleanup(false,0); - lock.drop(); - m_engine->removeStream(this,false); + XDebug(m_engine,DebugAll,"JBStream::~JBStream() [%p]",this); } -void JBComponentStream::connect() +// Close the stream. Release memory +void JBStream::destroyed() { - Lock2 lock(*this,m_receiveMutex); - if (m_state != Terminated) { + if (m_engine) { + Lock lock(m_engine); + m_engine->m_streams.remove(this,false); + } + terminate(false,0,XMPPError::NoError,0,false,true); + // m_terminateEvent shouldn't be valid: + // do that to print a DebugFail output for the stream inside the event + TelEngine::destruct(m_terminateEvent); + TelEngine::destruct(m_startEvent); + DDebug(m_engine,DebugAll,"Stream destroyed local=%s remote=%s [%p]", + m_local.safe(),m_remote.safe(),this); + RefObject::destroyed(); +} + +// Connect the stream +void JBStream::connect() +{ + Lock2 lck(m_socket.m_streamMutex,m_socket.m_receiveMutex); + if (state() != Idle) { Debug(m_engine,DebugNote, - "Stream::connect. Attempt to connect in non Terminated state. [%p]",this); + "Stream. Attempt to connect when not idle [%p]",this); return; } - m_state = WaitToConnect; + DDebug(m_engine,DebugInfo, + "Stream. Attempt to connect local=%s remote=%s addr=%s:%d count=%u [%p]", + m_local.safe(),m_remote.safe(),m_address.host().safe(),m_address.port(), + m_restart,this); + changeState(Connecting); // Check if we can restart - if (!m_engine->getStreamRestart(m_remoteName)) { - DDebug(m_engine,DebugNote, - "Stream::connect. Stream can't restart (restart counter is 0). [%p]",this); - terminate(true,false,0,false); + if (!m_restart) { + terminate(true,0,XMPPError::NoError,"restart counter is 0",false); return; } + m_restart--; // Reset data m_id = ""; m_parser.reset(); - // Re-create socket - m_socket = new Socket(PF_INET,SOCK_STREAM); - lock.drop(); - // Connect - bool res = m_socket->connect(m_remoteAddr); - // Lock again to update stream - lock.lock(*this,m_receiveMutex); - // Update restart counters - if (!m_socket) { - Debug(m_engine,DebugMild,"Stream::connect. Socket deleted. [%p]",this); + lck.drop(); + // Re-connect socket + bool terminated = false; + if (!m_socket.connect(m_address,terminated)) { + if (!terminated) + terminate(false,0,XMPPError::NoError,"connection failed",false); return; } - // Check connect result - if (!res) { - Debug(m_engine,DebugWarn, - "Stream::connect. Failed to connect socket to '%s:%d'. Error: '%s' (%d). [%p]", - m_remoteAddr.host().c_str(),m_remoteAddr.port(), - ::strerror(m_socket->error()),m_socket->error(),this); - terminate(false,false,0,false); + + Debug(m_engine,DebugAll,"Stream. local=%s remote=%s connected to %s:%d [%p]", + m_local.safe(),m_remote.safe(),m_address.host().safe(),m_address.port(),this); + + // Send declaration + static String declaration = ""; + if (m_engine->printXml() && m_engine->debugAt(DebugInfo)) + Debug(m_engine,DebugInfo,"Stream. Sending XML declaration %s [%p]", + declaration.c_str(),this); + unsigned int len = declaration.length(); + if (!m_socket.send(declaration.c_str(),len) || len != declaration.length()) { + terminate(false,0,XMPPError::Internal,"send declaration failed",false); return; } - Debug(m_engine,DebugAll,"Stream::connect. Connected to '%s:%d'. [%p]", - m_remoteAddr.host().c_str(),m_remoteAddr.port(),this); - // Connected - m_socket->setBlocking(false); - // Start - XMLElement* start = XMPPUtils::createElement(XMLElement::StreamStart, - XMPPNamespace::ComponentAccept); - start->setAttribute("xmlns:stream",s_ns[XMPPNamespace::Stream]); - start->setAttribute("to",m_localName); - m_state = Started; - sendStreamXML(start,Started); + + sendStreamXML(getStreamStart(),Started,false); } -void JBComponentStream::terminate(bool destroy, bool sendEnd, - XMLElement* error, bool sendError) +// Read data from socket and pass it to the parser +// Terminate stream on parser or socket error +bool JBStream::receive() { - Lock2 lock(*this,m_receiveMutex); - if (m_state == Destroy || m_state == Terminated) - return; - DDebug(m_engine,DebugAll,"JBComponentStream::terminate(%s, %s, %p, %s) [%p]", - String::boolText(destroy),String::boolText(sendEnd),error,String::boolText(sendError),this); - // Error is sent only if end stream is sent - XMLElement* eventError = 0; - if (sendEnd && sendError) { - //TODO: Make a copy of error element to be attached to the event - } - else { - eventError = error; - error = 0; - } - cleanup(sendEnd,error); - // Add event. Change state - if (destroy) { - addEvent(JBEvent::Destroy,eventError); - m_state = Destroy; - deref(); - } - else { - addEvent(JBEvent::Terminated,eventError); - m_state = Terminated; - } - Debug(m_engine,DebugInfo,"Stream. %s. [%p]",destroy?"Destroy":"Terminate",this); -} - -bool JBComponentStream::receive() -{ - char buf[1024]; - if (m_state == Destroy || m_state == Terminated || - m_state == WaitToConnect) + static char buf[1024]; + if (state() == Destroy || state() == Idle || state() == Connecting) return false; - u_int32_t len = sizeof(buf); + + XMPPError::Type error = XMPPError::NoError; + bool send = false; // Lock between start read and end consume to serialize input - m_receiveMutex.lock(); - bool read = (readSocket(buf,len) && len); - // Parse if received any data and no error - if (read && !m_parser.consume(buf,len)) { - Debug(m_engine,DebugNote, - "Stream::receive. Error parsing data: '%s'. [%p]", - m_parser.ErrorDesc(),this); - XDebug(m_engine,DebugAll,"Parser buffer: %s",buf); - XMLElement* e = XMPPUtils::createStreamError(XMPPError::Xml,m_parser.ErrorDesc()); - terminate(false,true,e,true); + m_socket.m_receiveMutex.lock(); + const char* text = 0; + unsigned int len = sizeof(buf); + if (m_socket.recv(buf,len)) { + if (len && !m_parser.consume(buf,len)) { + error = XMPPError::Xml; + text = m_parser.ErrorDesc(); + Debug(m_engine,DebugNote,"Stream. Parser error: '%s' [%p]",text,this); + send = true; + } } - m_receiveMutex.unlock(); + else { + error = XMPPError::HostGone; + text = "remote server not found"; + } + m_socket.m_receiveMutex.unlock(); + if (error != XMPPError::NoError) + terminate(false,0,error,text,send); return len != 0; } -JBComponentStream::Error JBComponentStream::sendStanza(XMLElement* stanza, - const char* senderId) +// Send a stanza +JBStream::Error JBStream::sendStanza(XMLElement* stanza, const char* senderId) { if (!stanza) return ErrorContext; - DDebug(m_engine,DebugAll,"Stream::sendStanza((%p): '%s'). Sender id: '%s'. [%p]", - stanza,stanza->name(),senderId,this); - XMLElementOut* e = new XMLElementOut(stanza,senderId); - return postXML(e); -} - -JBEvent* JBComponentStream::getEvent(u_int64_t time) -{ - Lock lock(this); - for (;;) { - if (m_lastEvent || m_terminateEvent || - m_state == Destroy || m_state == Terminated) { - if (m_lastEvent) - return 0; - break; - } - // Send pending elements. - // If not terminated check received elements - // Again, if not terminated, get event from queue - sendXML(); - if (m_terminateEvent) - break; - processIncomingXML(); - if (m_terminateEvent) - break; - // Get first event from queue - ObjList* obj = m_events.skipNull(); - if (!obj) - break; - m_lastEvent = static_cast(obj->get()); - m_events.remove(m_lastEvent,false); - break; - } - if (m_lastEvent || m_terminateEvent) { - if (!m_lastEvent) { - m_lastEvent = m_terminateEvent; - m_terminateEvent = 0; - } - DDebug(m_engine,DebugAll, - "Stream::getEvent. Raise event (%p): %u. [%p]", - m_lastEvent,m_lastEvent->type(),this); - return m_lastEvent; - } - //TODO: Keep alive ? - return 0; -} - -void JBComponentStream::cancelPending(bool raise, const String* id) -{ - Lock lock(this); - // Cancel elements with id. Raise event if requested - // Don't cancel the first element if partial data was sent: - // The remote parser will fail - if (id) { - XDebug(m_engine,DebugAll, - "Stream. Cancel pending elements with id '%s'. [%p]",id->c_str(),this); - ListIterator iter(m_outXML); - GenObject* obj; - bool first = true; - for (; (obj = iter.get());) { - XMLElementOut* e = static_cast(obj); - if (first) { - first = false; - if (e->dataCount()) - continue; - } - if (!e->id() || *id != e->id()) - continue; - if (raise) - addEventNotify(JBEvent::WriteFail,e); - else - m_outXML.remove(e,true); - } - return; - } - // Cancel all pending elements without id - XDebug(m_engine,DebugAll,"Stream. Cancel pending elements without id. [%p]",this); - ListIterator iter(m_outXML); - GenObject* obj; - for (; (obj = iter.get());) { - XMLElementOut* e = static_cast(obj); - if (!e->id()) - m_outXML.remove(e,true); - } -} - -void JBComponentStream::eventTerminated(const JBEvent* event) -{ - if (event && event == m_lastEvent) { - m_lastEvent = 0; - DDebug(m_engine,DebugAll, - "Stream::eventTerminated. Event: (%p): %u. [%p]", - event,event->type(),this); - } -} - -void JBComponentStream::cleanup(bool endStream, XMLElement* e) -{ - if (!m_socket) { - if (e) - delete e; - return; - } - DDebug(m_engine,DebugAll,"Stream::cleanup(%s, %p). [%p]", - String::boolText(endStream),e,this); - bool partialData = false; - // Remove first element from queue if partial data was sent - ObjList* obj = m_outXML.skipNull(); - XMLElementOut* first = obj ? static_cast(obj->get()) : 0; - if (first && first->dataCount()) { - addEventNotify(JBEvent::WriteFail,first); - partialData = true; - } - // Send stream terminate - // No need to do that if partial data was sent: - // the remote XML parser will fail anyway - if (!partialData && endStream) { - if (state() != WaitToConnect) { - sendStreamXML(new XMLElement(XMLElement::StreamEnd),m_state,e); - e = 0; - } - else - DDebug(m_engine,DebugAll, - "Stream::cleanup. No end tag sent: stream is waiting to connect. [%p]",this); - } - if (e) - delete e; - // Cancel outgoing elements without id - cancelPending(false,0); - // Destroy socket. Close in background - Socket* tmp = m_socket; - m_socket = 0; - if (!tmp) { - Debug(m_engine,DebugWarn,"Stream::cleanup. Socket deleted. [%p]",this); - return; - } - tmp->setLinger(-1); - tmp->terminate(); - delete tmp; -} - -JBComponentStream::Error JBComponentStream::postXML(XMLElementOut* element) -{ - Lock lock(this); - if (!element) - return ErrorNone; + Lock lock(m_socket.m_streamMutex); if (state() == Destroy) { - element->deref(); + delete stanza; return ErrorContext; } - DDebug(m_engine,DebugAll,"Stream::postXML((%p): '%s'). [%p]", - element->element(),element->element()->name(),this); + DDebug(m_engine,DebugAll,"Stream. Posting stanza (%p,%s) id='%s' [%p]", + stanza,stanza->name(),senderId,this); + XMLElementOut* e = new XMLElementOut(stanza,senderId); // List not empty: the return value will be ErrorPending // Else: element will be sent bool pending = (0 != m_outXML.skipNull()); - m_outXML.append(element); + m_outXML.append(e); // Send first element - Error result = sendXML(); + Error result = sendPending(); return pending ? ErrorPending : result; } -JBComponentStream::Error JBComponentStream::sendXML() +// Extract an element from parser and construct an event +JBEvent* JBStream::getEvent(u_int64_t time) { - // Get the first element from list - ObjList* obj = m_outXML.skipNull(); - XMLElementOut* e = obj ? static_cast(obj->get()) : 0; - if (!e) - return ErrorNone; - if (state() != Running) - return ErrorPending; - if (m_engine->printXml() && m_engine->debugAt(DebugInfo)) { - String eStr; - XMPPUtils::print(eStr,e->element()); - Debug(m_engine,DebugInfo,"Stream. Send XML. [%p]%s", - this,eStr.c_str()); - } - else - DDebug(m_engine,DebugInfo,"Stream::sendXML((%p): '%s'). [%p]", - e->element(),e->element()->name(),this); - // Prepare & send - u_int32_t len; - const char* data = e->getData(len); - if (!writeSocket(data,len)) { - // Write failed. Try to raise event. Remove from list - addEventNotify(JBEvent::WriteFail,e); - return ErrorNoSocket; - } - e->dataSent(len); - // Partial data sent ? - if (e->dataCount()) - return ErrorPending; - // All data was sent. Remove - m_outXML.remove(e,true); - return ErrorNone; -} + Lock lock(m_socket.m_streamMutex); -bool JBComponentStream::sendStreamXML(XMLElement* element, State newState, - XMLElement* before) -{ - if (!element) { - if (before) - delete before; - return false; + // Do nothing if destroying or connecting + if (state() == Destroy || state() == Connecting) + return 0; + + // Increase stream restart counter if it's time to + if (m_timeToFillRestart < time) { + if (m_restart < m_restartMax) + m_restart++; + m_timeToFillRestart = time + m_fillRestartInterval; } - if (m_engine->printXml() && m_engine->debugAt(DebugInfo)) { - String eStr; - if (before) - XMPPUtils::print(eStr,before); - XMPPUtils::print(eStr,element); - Debug(m_engine,DebugInfo,"Stream. Send XML. [%p]%s", - this,eStr.c_str()); + + if (state() == Idle) { + if (m_autoRestart && m_restart) + m_engine->connect(this); + return 0; } - else - DDebug(m_engine,DebugInfo,"Stream::sendStreamXML('%s'). [%p]", - element->name(),this); - String tmp, buff; - switch (element->type()) { - case XMLElement::StreamStart: - // Send declaration and the start tag - element->toString(buff,true); - tmp << s_declaration << buff; + + if (m_lastEvent) + return 0; + + while (true) { + if (m_terminateEvent) break; - case XMLElement::StreamEnd: - // Send 'before' and the end tag - if (before) - before->toString(tmp); - element->toString(buff,true); - tmp += buff; + + // Send pending elements and process the received ones + sendPending(); + if (m_terminateEvent) break; - default: - element->toString(tmp,false); - } - delete element; - if (before) - delete before; - u_int32_t len = tmp.length(); - bool result = (writeSocket(tmp,len) && len == tmp.length()); - if (result) - m_state = newState; - else - terminate(false); - return result; -} -JBComponentStream::Error JBComponentStream::sendIqError(XMLElement* stanza, - XMPPError::ErrorType eType, XMPPError::Type eCond, const char* eText) -{ - if (!stanza) - return ErrorContext; - String to = stanza->getAttribute("from"); - String from = stanza->getAttribute("to"); - String id = stanza->getAttribute("id"); - // Create 'iq' and add stanza - XMLElement* xml = XMPPUtils::createIq(XMPPUtils::IqError,from,to,id); - xml->addChild(stanza); - // Add 'error' - xml->addChild(XMPPUtils::createError(eType,eCond,eText)); - return sendStanza(xml); -} + // Process the received XML + XMLElement* xml = m_parser.extract(); + if (!xml) + break; -bool JBComponentStream::processIncomingXML() -{ - if (state() == Destroy || state() == Terminated - || state() == WaitToConnect) - return false; - for (bool noEvent = true; noEvent;) { - XMLElement* element = m_parser.extract(); - if (!element) - return false; + // Print it if (m_engine->printXml() && m_engine->debugAt(DebugInfo)) { - String eStr; - XMPPUtils::print(eStr,element); - Debug(m_engine,DebugInfo,"Stream. Received XML [%p]. %s", - this,eStr.c_str()); + String s; + XMPPUtils::print(s,xml); + Debug(m_engine,DebugInfo,"Stream. Received [%p]%s",this,s.c_str()); } else - DDebug(m_engine,DebugInfo,"Stream::processIncomingXML((%p): '%s'). [%p].", - element,element->name(),this); - // Check if we received a stream end or stream error - if (isStreamEnd(element)) + DDebug(m_engine,DebugInfo,"Stream. Received (%p,%s) [%p]",xml,xml->name(),this); + + // Check destination + if (!checkDestination(xml)) { + // TODO Respond if state is Started ? + if (state() == Started) + dropXML(xml); + else + invalidStreamXML(xml,XMPPError::BadAddressing,"unknown destination"); break; - // Process received element + } + + // Check if stream end was received (end tag or error) + if (xml->type() == XMLElement::StreamEnd || + xml->type() == XMLElement::StreamError) { + Debug(m_engine,DebugAll,"Stream. Remote closed in state %s [%p]", + lookupState(state()),this); + terminate(false,xml,XMPPError::NoError,xml->getText(),true); + break; + } + + XDebug(m_engine,DebugAll,"Stream. Processing (%p,%s) in state %s [%p]", + xml,xml->name(),lookupState(state()),this); + switch (state()) { case Running: - noEvent = !processStateRunning(element); - break; - case Started: - noEvent = !processStateStarted(element); + processRunning(xml); break; case Auth: - noEvent = !processStateAuth(element); + processAuth(xml); break; - default: - delete element; + case Securing: + processSecuring(xml); + break; + case Started: + // Set stream id if not already set + if (!m_id) { + if (xml->type() != XMLElement::StreamStart) { + dropXML(xml); + break; + } + m_id = xml->getAttribute("id"); + if (!m_id || m_engine->checkDupId(this)) { + invalidStreamXML(xml,XMPPError::InvalidId,"invalid stream id"); + break; + } + } + processStarted(xml); + break; + default: + Debug(m_engine,DebugStub,"Unhandled stream state %u '%s' [%p]", + state(),lookupState(state()),this); + delete xml; } + break; } - return true; -} -bool JBComponentStream::processStateStarted(XMLElement* e) -{ - XDebug(m_engine,DebugAll,"Stream::processStateStarted(%p) [%p].",e,this); - // Expect stream start tag - // Check if received element other then 'stream' - if (e->type() != XMLElement::StreamStart) - return unexpectedElement(e); - // Check attributes: namespaces, from, id - if (!e->hasAttribute("xmlns:stream",s_ns[XMPPNamespace::Stream])) - return invalidElement(e,XMPPError::InvalidNamespace); - if (!e->hasAttribute("xmlns",s_ns[XMPPNamespace::ComponentAccept])) - return invalidElement(e,XMPPError::InvalidNamespace); - if (!e->hasAttribute("from",m_localName)) - //TODO: Possible ejabberd support: check if we received remoteName - // if (!(e->hasAttribute("from",m_localName) || e->hasAttribute("from",m_remoteName))) - return invalidElement(e,XMPPError::HostUnknown); - m_id = e->getAttribute("id"); - if (!m_id.length() || m_engine->remoteIdExists(this)) - return invalidElement(e,XMPPError::InvalidId); - // Everything is OK: Reply - delete e; - // Get password from engine. Destroy if not accepted - if (!m_engine->acceptOutgoing(m_remoteAddr.host(),m_password)) { - Debug(m_engine,DebugNote, - "Stream::processStateStarted(%p). Not accepted. [%p]",e,this); - terminate(true,true,XMPPUtils::createStreamError(XMPPError::NotAuth), - true); - return true; + // Return terminate event if set + // Get events from queue if not set to terminate + if (m_terminateEvent) { + m_lastEvent = m_terminateEvent; + m_terminateEvent = 0; } - // Send auth - Debug(m_engine,DebugInfo, - "Stream::processStateStarted(%p). Accepted. Send auth. [%p]",e,this); - String handshake; - m_engine->createSHA1(handshake,m_id,m_password); - XMLElement* xml = new XMLElement(XMLElement::Handshake,0,handshake); - if (!sendStreamXML(xml,Auth)) - return true; - m_state = Auth; - return false; + else if (m_startEvent) { + m_lastEvent = m_startEvent; + m_startEvent = 0; + } + else { + ObjList* obj = m_events.skipNull(); + m_lastEvent = obj ? static_cast(obj->get()) : 0; + if (m_lastEvent) + m_events.remove(m_lastEvent,false); + } + + if (m_lastEvent) + DDebug(m_engine,DebugAll,"Stream. Raising event (%p,%s) [%p]", + m_lastEvent,m_lastEvent->name(),this); + return m_lastEvent; } -bool JBComponentStream::processStateAuth(XMLElement* e) +// Terminate stream. Send stream end tag or error. Remove pending stanzas without id +// Deref stream if destroying +void JBStream::terminate(bool destroy, XMLElement* recvStanza, XMPPError::Type error, + const char* reason, bool send, bool final) { - XDebug(m_engine,DebugAll,"Stream::processStateAuth(%p). [%p]",e,this); - // Expect handshake - if (e->type() != XMLElement::Handshake) - return unexpectedElement(e); - delete e; - Debug(m_engine,DebugInfo, - "Stream::processStateAuth(%p). Authenticated. [%p]",e,this); - m_state = Running; - return false; -} + Lock2 lock(m_socket.m_streamMutex,m_socket.m_receiveMutex); + if (state() == Destroy || state() == Idle) { + if (recvStanza) + delete recvStanza; + return; + } + if (error == XMPPError::NoError && m_engine->exiting()) { + error = XMPPError::Shutdown; + reason = 0; + } -bool JBComponentStream::processStateRunning(XMLElement* e) -{ - XDebug(m_engine,DebugAll,"Stream::processStateRunning(%p) [%p].",e,this); - switch (e->type()) { - case XMLElement::Iq: - return processIncomingIq(e); - case XMLElement::Presence: - case XMLElement::Message: - { - JBEvent::Type evType; - if (e->type() == XMLElement::Presence) - evType = JBEvent::Presence; + Debug(m_engine,DebugAll, + "Stream. Terminate state=%s destroy=%u error=%s reason=%s final=%u [%p]", + lookupState(state()),destroy,s_err[error],reason,final,this); + + // Send ending stream element + if (send && state() != Connecting) { + XMLElement* e; + if (error == XMPPError::NoError) + e = new XMLElement(XMLElement::StreamEnd); + else { + e = XMPPUtils::createStreamError(error,reason); + XMLElement* child = recvStanza; + // Preserve received element if an event will be generated + if (recvStanza) + if (final || m_terminateEvent) + recvStanza = 0; else - evType = JBEvent::Message; - // Create event - JBEvent* event = addEvent(evType,e); - event->m_stanzaType = e->getAttribute("type"); - event->m_from = e->getAttribute("from"); - event->m_to = e->getAttribute("to"); - event->m_id = e->getAttribute("id"); - } - return true; - default: ; + recvStanza = new XMLElement(*child); + e->addChild(child); + } + sendStreamXML(e,m_state,true); } - addEvent(JBEvent::Unhandled,e); - return true; + m_socket.terminate(); + + // Done if called from destructor + if (final) { + changeState(Destroy); + if (recvStanza) + delete recvStanza; + return; + } + + // Cancel all outgoing elements without id + removePending(false,0,true); + // Always set termination event, except when exiting + TelEngine::destruct(m_startEvent); + if (!m_terminateEvent) { + if (!recvStanza && error != XMPPError::NoError) + recvStanza = XMPPUtils::createStreamError(error,reason); + if (error != XMPPError::Shutdown) + m_terminateEvent = new JBEvent(destroy?JBEvent::Destroy:JBEvent::Terminated, + this,recvStanza); + } + else if (recvStanza) + delete recvStanza; + + // Change state + if (destroy) { + changeState(Destroy); + deref(); + } + else + changeState(Idle); } -bool JBComponentStream::processIncomingIq(XMLElement* e) +// Get the name of a stream state +const char* JBStream::lookupState(int state) { + return lookup(state,s_streamState); +} + +// Process received data while running +void JBStream::processRunning(XMLElement* xml) +{ + switch (xml->type()) { + case XMLElement::Message: + m_events.append(new JBEvent(JBEvent::Message,this,xml)); + return; + case XMLElement::Presence: + m_events.append(new JBEvent(JBEvent::Presence,this,xml)); + return; + case XMLElement::Iq: + break; + default: + m_events.append(new JBEvent(JBEvent::Unhandled,this,xml)); + return; + } + + XMPPError::Type error = XMPPError::NoError; + int iq = XMPPUtils::iqType(xml->getAttribute("type")); + JBEvent* ev = getIqEvent(xml,iq,error); + if (ev) { + m_events.append(ev); + return; + } + if (error == XMPPError::NoError) { + m_events.append(new JBEvent(JBEvent::Unhandled,this,xml)); + return; + } + + // Don't respond to error or result + if (iq == XMPPUtils::IqError || iq == XMPPUtils::IqResult) { + dropXML(xml); + return; + } + + // Send error + String to = xml->getAttribute("from"); + String from = xml->getAttribute("to"); + String id = xml->getAttribute("id"); + XMLElement* err = XMPPUtils::createIq(XMPPUtils::IqError,from,to,id); + err->addChild(xml); + err->addChild(XMPPUtils::createError(XMPPError::TypeModify,error)); + sendStanza(err); +} + +// Helper function to make the code simpler +inline bool checkChild(XMLElement* e, XMPPNamespace::Type ns, XMPPError::Type& error) +{ + if (!e) { + error = XMPPError::SBadRequest; + return false; + } + if (e->hasAttribute("xmlns",s_ns[ns])) + return true; + error = XMPPError::SFeatureNotImpl; + return false; +} + +// Create an iq event from a received iq stanza +JBEvent* JBStream::getIqEvent(XMLElement* xml, int iqType, XMPPError::Type& error) +{ + // Filter iq stanzas to generate an appropriate event // Get iq type : set/get, error, result // result: MAY have a first child with a response // set/get: MUST have a first child // error: MAY have a first child with the sent stanza // MUST have an 'error' child // Check type and the first child's namespace - DDebug(m_engine,DebugAll,"Stream::processIncomingIq(%p). [%p]",e,this); - XMPPUtils::IqType iq = XMPPUtils::iqType(e->getAttribute("type")); - JBEvent* event = 0; - // Get first child - XMLElement* child = e->findFirstChild(); + XMLElement* child = xml->findFirstChild(); // Create event - switch (iq) { - case XMPPUtils::IqResult: - // No child: This is a confirmation to a sent stanza - if (!child) { - event = addEvent(JBEvent::IqResult,e); + if (iqType == XMPPUtils::IqResult || iqType == XMPPUtils::IqSet || + iqType == XMPPUtils::IqGet) { + if (!child) { + if (iqType == XMPPUtils::IqResult) + return new JBEvent(JBEvent::IqResult,this,xml); + return new JBEvent(JBEvent::Iq,this,xml); + } + switch (child->type()) { + case XMLElement::Jingle: + if (!checkChild(child,XMPPNamespace::Jingle,error)) + return 0; + switch (iqType) { + case XMPPUtils::IqGet: + return new JBEvent(JBEvent::IqJingleGet,this,xml,child); + case XMPPUtils::IqSet: + return new JBEvent(JBEvent::IqJingleSet,this,xml,child); + case XMPPUtils::IqResult: + return new JBEvent(JBEvent::IqJingleRes,this,xml,child); + } break; - } - // Child non 0: Fall through to check the child - case XMPPUtils::IqSet: - case XMPPUtils::IqGet: - // Jingle ? - if (child->type() == XMLElement::Jingle) { - // Jingle stanza's type is never 'result' - if (iq == XMPPUtils::IqResult) { - sendIqError(e,XMPPError::TypeModify,XMPPError::SBadRequest); - return false; + case XMLElement::Query: + if (checkChild(child,XMPPNamespace::DiscoInfo,error)) + switch (iqType) { + case XMPPUtils::IqGet: + return new JBEvent(JBEvent::IqDiscoInfoGet,this,xml,child); + case XMPPUtils::IqSet: + return new JBEvent(JBEvent::IqDiscoInfoSet,this,xml,child); + case XMPPUtils::IqResult: + return new JBEvent(JBEvent::IqDiscoInfoRes,this,xml,child); + } + else if (checkChild(child,XMPPNamespace::DiscoItems,error)) + switch (iqType) { + case XMPPUtils::IqGet: + return new JBEvent(JBEvent::IqDiscoItemsGet,this,xml,child); + case XMPPUtils::IqSet: + return new JBEvent(JBEvent::IqDiscoItemsSet,this,xml,child); + case XMPPUtils::IqResult: + return new JBEvent(JBEvent::IqDiscoItemsRes,this,xml,child); + } + return 0; + case XMLElement::Command: + if (!checkChild(child,XMPPNamespace::Command,error)) + return 0; + switch (iqType) { + case XMPPUtils::IqGet: + return new JBEvent(JBEvent::IqCommandGet,this,xml,child); + case XMPPUtils::IqSet: + return new JBEvent(JBEvent::IqCommandSet,this,xml,child); + case XMPPUtils::IqResult: + return new JBEvent(JBEvent::IqCommandRes,this,xml,child); } - // Check namespace - if (!child->hasAttribute("xmlns",s_ns[XMPPNamespace::Jingle])) { - sendIqError(e,XMPPError::TypeModify,XMPPError::SFeatureNotImpl); - return false; - } - // Add event - if (iq == XMPPUtils::IqSet) - event = addEvent(JBEvent::IqJingleSet,e,child); - else - event = addEvent(JBEvent::IqJingleGet,e,child); - break; - } - // Query ? - if (child->type() == XMLElement::Query) { - // Check namespace - if (!(child->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoInfo]) || - child->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoItems]))) { - // Send error - sendIqError(e,XMPPError::TypeModify,XMPPError::SFeatureNotImpl); - return false; - } - // Add event - switch (iq) { - case XMPPUtils::IqGet: - event = addEvent(JBEvent::IqDiscoGet,e,child); - break; - case XMPPUtils::IqSet: - event = addEvent(JBEvent::IqDiscoSet,e,child); - break; - case XMPPUtils::IqResult: - event = addEvent(JBEvent::IqDiscoRes,e,child); - break; - default: ; - } - break; - } - // Command - if (child->type() == XMLElement::Command) { - // Check namespace - if (!(child->hasAttribute("xmlns",s_ns[XMPPNamespace::Command]))) { - // Send error - sendIqError(e,XMPPError::TypeModify,XMPPError::SFeatureNotImpl); - return false; - } - // Add event - switch (iq) { - case XMPPUtils::IqGet: - event = addEvent(JBEvent::IqCommandGet,e,child); - break; - case XMPPUtils::IqSet: - event = addEvent(JBEvent::IqCommandSet,e,child); - break; - case XMPPUtils::IqResult: - event = addEvent(JBEvent::IqCommandRes,e,child); - break; - default: ; - } - break; - } - // Unknown child - event = addEvent(JBEvent::Iq,e,child); - break; - case XMPPUtils::IqError: - // First child may be a sent stanza - if (child && child->type() != XMLElement::Error) - child = e->findNextChild(child); - // Check child type - if (!(child && child->type() == XMLElement::Error)) - child = 0; - event = addEvent(JBEvent::IqError,e,child); - break; - default: - event = addEvent(JBEvent::Iq,e,child); + default: ; + } + // Unhandled child + if (iqType != XMPPUtils::IqResult) + return new JBEvent(JBEvent::Iq,this,xml,child); + return new JBEvent(JBEvent::IqResult,this,xml,child); } - // Set event data from type, from, to and id attributes - event->m_stanzaType = e->getAttribute("type"); - event->m_from = e->getAttribute("from"); - event->m_to = e->getAttribute("to"); - event->m_id = e->getAttribute("id"); - return true; + else if (iqType == XMPPUtils::IqError) { + JBEvent::Type evType = JBEvent::IqError; + // First child may be a sent stanza + if (child && child->type() != XMLElement::Error) { + switch (child->type()) { + case XMLElement::Jingle: + evType = JBEvent::IqJingleErr; + break; + case XMLElement::Query: + if (xml->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoInfo])) + evType = JBEvent::IqDiscoInfoErr; + else if (xml->hasAttribute("xmlns",s_ns[XMPPNamespace::DiscoItems])) + evType = JBEvent::IqDiscoItemsErr; + break; + case XMLElement::Command: + evType = JBEvent::IqCommandErr; + break; + default: ; + } + child = xml->findNextChild(child); + } + if (!(child && child->type() == XMLElement::Error)) + child = 0; + return new JBEvent(evType,this,xml,child); + } + error = XMPPError::SBadRequest; + return 0; } -JBEvent* JBComponentStream::addEvent(JBEvent::Type type, - XMLElement* element, XMLElement* child) +// Send stream XML elements through the socket +bool JBStream::sendStreamXML(XMLElement* e, State newState, bool streamEnd) { - Lock2 lock(*this,m_receiveMutex); - JBEvent* ev = new JBEvent(type,this,element,child); - DDebug(m_engine,DebugAll,"Stream::addEvent((%p): %u). [%p]",ev,ev->type(),this); - // Append event - // If we already have a terminated event, ignore the new one - if (type == JBEvent::Destroy || type == JBEvent::Terminated) { - if (m_terminateEvent) { - DDebug(m_engine,DebugAll, - "Stream::addEvent. Ignoring terminating event ((%p): %u). Already set. [%p]", - ev,ev->type(),this); - ev->destruct(); + bool result = false; + Lock lock(m_socket.m_streamMutex); + while (e) { + if (state() == Idle || state() == Destroy) + break; + String tmp; + e->toString(tmp,true); + unsigned int len = tmp.length(); + result = (ErrorNone == sendXML(e,tmp.c_str(),len,true,streamEnd)); + break; + } + if (!result) + Debug(m_engine,DebugNote, + "Stream. Failed to send stream XML (%p,%s) in state=%s [%p]", + e,e?e->name():"",lookupState(state()),this); + if (e) + delete e; + if (result) + changeState(newState); + return result; +} + +// Terminate stream on receiving invalid elements +void JBStream::invalidStreamXML(XMLElement* xml, XMPPError::Type error, const char* reason) +{ + if (!xml) + return; + Debug(m_engine,DebugNote, + "Stream. Invalid XML (%p,%s) state=%s error='%s' reason='%s' [%p]", + xml,xml->name(),lookupState(state()),s_err[error],reason,this); + terminate(false,xml,error,reason,true); +} + +// Drop an unexpected or unhandled element +void JBStream::dropXML(XMLElement* xml, bool unexpected) +{ + if (!xml) + return; + Debug(m_engine,unexpected?DebugNote:DebugInfo, + "Stream. Dropping %s element (%p,%s) in state %s [%p]", + unexpected?"unexpected":"unhandled",xml,xml->name(), + lookupState(state()),this); + delete xml; +} + +// Change stream state +void JBStream::changeState(State newState) +{ + if (m_state == newState) + return; + Debug(m_engine,DebugInfo,"Stream. Changing state from %s to %s [%p]", + lookupState(m_state),lookupState(newState),this); + m_state = newState; + if (newState == Running && !m_startEvent) + m_startEvent = new JBEvent(JBEvent::Running,this,0); +} + +// Event termination notification +void JBStream::eventTerminated(const JBEvent* event) +{ + if (event && event == m_lastEvent) { + m_lastEvent = 0; + DDebug(m_engine,DebugAll, + "Stream. Event (%p,%s) terminated [%p]",event,event->name(),this); + } +} + +// Actually send XML elements through the socket +JBStream::Error JBStream::sendXML(XMLElement* e, const char* buffer, unsigned int& len, + bool stream, bool streamEnd) +{ + if (!(e && buffer && len)) + return ErrorNone; + + if (!stream && state() != Running) + return ErrorPending; + + Error ret = ErrorNone; + XMPPError::Type error = XMPPError::NoError; + + while (true) { + // Try to send any partial data remaining from the last sent stanza + // Don't send stream element on failure: remote XML parser will fail anyway + if (stream) { + ObjList* obj = m_outXML.skipNull(); + XMLElementOut* eout = obj ? static_cast(obj->get()) : 0; + if (eout && eout->dataCount()) + ret = sendPending(); + if (ret != ErrorNone) { + if (!streamEnd) + error = XMPPError::UndefinedCondition; + // Ignore partial data sent error: terminate stream + ret = ErrorNoSocket; + break; + } + } + + if (m_engine->printXml() && m_engine->debugAt(DebugInfo)) { + String s; + XMPPUtils::print(s,e); + Debug(m_engine,DebugInfo,"Stream. Sending [%p]%s",this,s.c_str()); } else - m_terminateEvent = ev; - return 0; - } - m_events.append(ev); - return ev; -} + DDebug(m_engine,DebugInfo,"Stream. Sending (%p,%s) [%p]",e,e->name(),this); -bool JBComponentStream::addEventNotify(JBEvent::Type type, - XMLElementOut* element) -{ - Lock lock(this); - XMLElement* e = 0; - bool raise = !element->id().null(); - if (raise) { - e = element->release(); - JBEvent* ev = new JBEvent(type,this,e,&(element->id())); - DDebug(m_engine,DebugAll, - "Stream::addEventNotify((%p): %u). [%p]",ev,ev->type(),this); - m_events.append(ev); - } - else - e = element->element(); - // Remove element - DDebug(m_engine,DebugAll, - "Stream::addEventNotify. Remove (%p): '%s' from outgoing queue. [%p]", - e,e ? e->name() : "",this); - m_outXML.remove(element,true); - return raise; -} - -bool JBComponentStream::invalidElement(XMLElement* e, XMPPError::Type type, - const char* text) -{ - Debug(m_engine,DebugNote, - "Stream. Received invalid element ((%p): '%s') in state %u. Error: '%s'. [%p]", - e,e->name(),state(),s_err[type],this); - delete e; - terminate(false,true,XMPPUtils::createStreamError(type,text),true); - return true; -} - -bool JBComponentStream::unexpectedElement(XMLElement* e) -{ - Debug(m_engine,DebugNote, - "Stream. Ignoring unexpected element ((%p): '%s') in state %u. [%p]", - e,e->name(),state(),this); - delete e; - return false; -} - -bool JBComponentStream::isStreamEnd(XMLElement* e) -{ - if (!e) - return false; - bool end = (e->type() == XMLElement::StreamEnd); - bool error = (e->type() == XMLElement::StreamError); - if (end || error) { - DDebug(m_engine,DebugAll,"Stream. Received stream %s in state %u. [%p]", - end?"end":"error",state(),this); - terminate(false,true,e,false); - return true; - } - return false; -} - -bool JBComponentStream::readSocket(char* data, u_int32_t& len) -{ - if (state() == Destroy) - return false; - // Check socket - if (!(m_socket && m_socket->valid())) { - terminate(false,false,errorHostGone(),false); - return false; - } - // Read socket - int read = m_socket->recv(data,len); - if (read == Socket::socketError()) { - len = 0; - if (!m_socket->canRetry()) { - Debug(m_engine,DebugWarn, - "Stream::readSocket. Socket error: %d: '%s'. [%p]", - m_socket->error(),::strerror(m_socket->error()),this); - terminate(false,false,errorHostGone(),false); - return false; + unsigned int tmp = len; + if (m_socket.send(buffer,len)) { + if (len != tmp) { + ret = !stream ? ErrorPending : ErrorNoSocket; + error = XMPPError::Internal; + } + break; } + ret = ErrorNoSocket; + error = XMPPError::HostGone; + break; } - else - len = read; -#ifdef XDEBUG - if (len) { - String s(data,len); - XDebug(m_engine,DebugAll,"Stream::readSocket [%p] Data:\r\n%s",this,s.c_str()); - } -#endif //XDEBUG - return true; + + if (stream && ret != ErrorNone && !streamEnd) + terminate(false,0,error,0,false); + return ret; } -bool JBComponentStream::writeSocket(const char* data, u_int32_t& len) +// Try to send the first element in pending outgoing stanzas list +// Terminate stream on socket error +JBStream::Error JBStream::sendPending() { - if (state() == Destroy) - return false; - // Check socket - if (!(m_socket && m_socket->valid())) { - terminate(false,false,errorHostGone(),false); - return false; + ObjList* obj = m_outXML.skipNull(); + XMLElementOut* eout = obj ? static_cast(obj->get()) : 0; + + if (!eout) + return ErrorNone; + if (!eout->element()) { + m_outXML.remove(eout,true); + return ErrorNone; } - // Write data - XDebug(m_engine,DebugAll,"Stream::writeSocket [%p] Data:\r\n%s",this,data); - int c = m_socket->send(data,len); - if (c == Socket::socketError()) { - c = 0; - if (!m_socket->canRetry()) { - Debug(m_engine,DebugWarn, - "Stream::writeSocket. Socket error: %d: '%s'. [%p]", - m_socket->error(),::strerror(m_socket->error()),this); - terminate(false,false,errorHostGone(),false); - return false; + if (state() != Running) + return ErrorPending; + + u_int32_t len; + const char* data = eout->getData(len); + Error ret = sendXML(eout->element(),data,len); + bool notify = false; + switch (ret) { + case ErrorNoSocket: + notify = true; + case ErrorNone: + break; + case ErrorPending: + eout->dataSent(len); + return ErrorPending; + default: ; + ret = ErrorContext; + } + +#ifdef DEBUG + if (eout->element()) + DDebug(m_engine,notify?DebugNote:DebugAll, + "Stream. Remove pending stanza (%p,%s) with id='%s'%s [%p]", + eout->element(),eout->element()->name(),eout->id().c_str(), + notify?". Failed to send":"",this); +#endif + + if (notify) { + if (eout->id()) { + JBEvent* ev = new JBEvent(JBEvent::WriteFail,this, + eout->release(),&(eout->id())); + m_events.append(ev); } - DDebug(m_engine,DebugMild, - "Stream::writeSocket. Socket temporary unavailable: %d: '%s'. [%p]", - m_socket->error(),::strerror(m_socket->error()),this); + terminate(false,0,XMPPError::HostGone,0,false); } - len = (u_int32_t)c; - return true; + m_outXML.remove(eout,true); + return ret; +} + +// Remove: +// Pending elements with id if id is not 0 +// All elements without id if id is 0 +void JBStream::removePending(bool notify, const String* id, bool force) +{ + ListIterator iter(m_outXML); + bool first = true; + for (GenObject* o = 0; (o = iter.get());) { + XMLElementOut* eout = static_cast(o); + // Check if the first element will be removed if partially sent + if (first) { + first = false; + if (eout->dataCount() && !force) + continue; + } + if (id) { + if (*id != eout->id()) + continue; + } + else if (eout->id()) + continue; + if (notify) + m_events.append(new JBEvent(JBEvent::WriteFail,this,eout->release(),id)); + m_outXML.remove(eout,true); + } +} + + +/** + * JBClientStream + */ +JBClientStream::JBClientStream(JBEngine* engine, const JabberID& jid, + const String& password, const SocketAddr& address, + unsigned int maxRestart, u_int64_t incRestartInterval, bool outgoing) + : JBStream(engine,jid,JabberID(0,jid.domain(),0),password,address, + true,maxRestart,incRestartInterval,outgoing,JBEngine::Client) +{ +} + +// Get the starting stream element to be sent after stream connected +XMLElement* JBClientStream::getStreamStart() +{ + Debug(engine(),DebugStub,"Please implement JBComponentStream::getStreamStart()"); + return 0; +} + +// Process a received element in Securing state +void JBClientStream::processSecuring(XMLElement* xml) +{ + Debug(engine(),DebugStub,"Please implement JBComponentStream::processSecuring()"); + dropXML(xml); +} + +// Process a received element in Auth state +void JBClientStream::processAuth(XMLElement* xml) +{ + Debug(engine(),DebugStub,"Please implement JBComponentStream::processAuth()"); + dropXML(xml); +} + +// Process a received element in Started state +void JBClientStream::processStarted(XMLElement* xml) +{ + Debug(engine(),DebugStub,"Please implement JBComponentStream::processStarted()"); + dropXML(xml); +} + + +/** + * JBComponentStream + */ +JBComponentStream::JBComponentStream(JBEngine* engine, + const JabberID& localJid, const JabberID& remoteJid, + const String& password, const SocketAddr& address, + bool autoRestart, unsigned int maxRestart, + u_int64_t incRestartInterval, bool outgoing) + : JBStream(engine,localJid,remoteJid,password,address, + autoRestart,maxRestart,incRestartInterval,outgoing,JBEngine::Component), + m_shaAuth(true) +{ +} + +// Create stream start element +XMLElement* JBComponentStream::getStreamStart() +{ + XMLElement* start = XMPPUtils::createElement(XMLElement::StreamStart, + XMPPNamespace::ComponentAccept); + start->setAttribute("xmlns:stream",s_ns[XMPPNamespace::Stream]); + start->setAttribute("to",local()); + return start; +} + +// Process a received element in Started state +void JBComponentStream::processStarted(XMLElement* xml) +{ + // Expect stream start tag + // Check if received element other then 'stream' + if (xml->type() != XMLElement::StreamStart) { + dropXML(xml); + return; + } + // Check attributes: namespaces, from + if (!(xml->hasAttribute("xmlns:stream",s_ns[XMPPNamespace::Stream]) && + xml->hasAttribute("xmlns",s_ns[XMPPNamespace::ComponentAccept]))) { + invalidStreamXML(xml,XMPPError::InvalidNamespace,0); + return; + } + // Check the from attribute + if (!engine()->checkComponentFrom(this,xml->getAttribute("from"))) { + invalidStreamXML(xml,XMPPError::HostUnknown,0); + return; + } + delete xml; + + String handshake; + if (m_shaAuth) { + SHA1 auth; + auth << id() << m_password; + handshake = auth.hexDigest(); + } + else { + MD5 auth; + auth << id() << m_password; + handshake = auth.hexDigest(); + } + xml = new XMLElement(XMLElement::Handshake,0,handshake); + sendStreamXML(xml,Auth,false); +} + +// Process a received element in Auth state +void JBComponentStream::processAuth(XMLElement* xml) +{ + // Expect handshake + if (xml->type() != XMLElement::Handshake) { + dropXML(xml); + return; + } + delete xml; + changeState(Running); } /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yjingle/jgengine.cpp b/libs/yjingle/jgengine.cpp index 34e8c981..1c51f2ec 100644 --- a/libs/yjingle/jgengine.cpp +++ b/libs/yjingle/jgengine.cpp @@ -28,117 +28,81 @@ using namespace TelEngine; /** * JGEngine */ -JGEngine::JGEngine(JBEngine* jb, const NamedList& params) - : JBClient(jb), - Mutex(true), - m_sessionIdMutex(true), - m_sessionId(1) +JGEngine::JGEngine(JBEngine* engine, const NamedList* params, int prio) + : JBService(engine,"jgengine",params,prio), + m_sessionIdMutex(true), + m_sessionId(1), + m_stanzaTimeout(10000), + m_useSidAttr(false) { - debugName("jgengine"); - initialize(params); - XDebug(this,DebugAll,"JGEngine. [%p]",this); } JGEngine::~JGEngine() { - XDebug(this,DebugAll,"~JGEngine. [%p]",this); + cancelThreads(); } +// Create private stream(s) to get events from sessions void JGEngine::initialize(const NamedList& params) { + debugLevel(params.getIntValue("debug_level",debugLevel())); + int timeout = params.getIntValue("stanza_timeout",10); + if (timeout < 10) + timeout = 10; + m_stanzaTimeout = timeout * 1000; + m_useSidAttr = params.getBoolValue("session_sid",false); + + if (debugAt(DebugInfo)) { + String s; + s << " stanza_timeout=" << (unsigned int)m_stanzaTimeout; + s << " session_sid=" << m_useSidAttr; + Debug(this,DebugInfo,"Jabber Jingle service initialized:%s [%p]", + s.c_str(),this); + } + + if (!m_initialized) { + m_initialized = true; + int c = params.getIntValue("private_process_threads",1); + for (int i = 0; i < c; i++) + JBThread::start(JBThread::Jingle,this,this,2,Thread::Normal); + } } +// Make an outgoing call JGSession* JGEngine::call(const String& localJID, const String& remoteJID, XMLElement* media, XMLElement* transport, const char* message) { - DDebug(this,DebugAll,"call. New outgoing call from '%s' to '%s'.", + DDebug(this,DebugAll,"New outgoing call from '%s' to '%s'", localJID.c_str(),remoteJID.c_str()); - JBComponentStream* stream = m_engine->getStream(); + + // Get a stream from the engine + JBStream* stream = 0; + if (engine()->protocol() == JBEngine::Component) + stream = engine()->getStream(); + else { + // Client: the stream must be already created + JabberID jid(localJID); + stream = engine()->getStream(&jid,false); + } + + // Create outgoing session if (stream) { - // Create outgoing session - JGSession* session = new JGSession(this,stream,localJID,remoteJID); + JGSession* session = new JGSession(this,stream,localJID,remoteJID, + media,transport,m_useSidAttr,message); if (session->state() != JGSession::Destroy) { - if (message) - session->sendMessage(message); - session->initiate(media,transport); m_sessions.append(session); return (session && session->ref() ? session : 0); } - session->deref(); + TelEngine::destruct(session); } - DDebug(this,DebugCall,"call. Outgoing call to '%s' failed. No stream.",remoteJID.c_str()); + + Debug(this,DebugNote,"Outgoing call from '%s' to '%s' failed: %s", + localJID.c_str(),remoteJID.c_str(), + stream?"can't create stream":"failed to send data"); return 0; } -bool JGEngine::receive() -{ - Lock lock(this); - JBEvent* event = m_engine->getEvent(Time::msecNow()); - if (!event) - return false; - bool checkSession = true; - // Check for new incoming session - if (event->type() == JBEvent::IqJingleSet && event->child()) { - const char* type = event->child()->getAttribute("type"); - JGSession::Action action = JGSession::action(type); - if (action == JGSession::ActInitiate) { - m_sessions.append(new JGSession(this,event)); - return true; - } - } - // Check if it's a message from a user with resource - else if (event->type() == JBEvent::Message) { - JabberID from(event->from()); - checkSession = !from.resource().null(); - } - // Add event to the appropriate session - if (checkSession) { - ObjList* obj = m_sessions.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGSession* session = static_cast(obj->get()); - if (session->receive(event)) - return true; - } - } - // Return unhandled event to the jabber engine - m_engine->returnEvent(event); - return false; -} - -void JGEngine::runReceive() -{ - while(1) { - if (!receive()) - Thread::msleep(2,true); - } -} - -bool JGEngine::process() -{ - bool ok = false; - for (;;) { - JGEvent* event = getEvent(Time::msecNow()); - if (!event) - break; - ok = true; - if (event->type() == JGEvent::Destroy) { - DDebug(this,DebugAll,"Deleting internal event(%p) 'Destroy'.",event); - delete event; - continue; - } - processEvent(event); - } - return ok; -} - -void JGEngine::runProcess() -{ - while(1) { - if (!process()) - Thread::msleep(2,true); - } -} - +// Get events from sessions JGEvent* JGEngine::getEvent(u_int64_t time) { JGEvent* event = 0; @@ -155,22 +119,135 @@ JGEvent* JGEngine::getEvent(u_int64_t time) continue; unlock(); if (0 != (event = s->getEvent(time))) - return event; + if (event->type() == JGEvent::Destroy) { + DDebug(this,DebugAll,"Deleting internal event (%p,Destroy)",event); + delete event; + } + else + return event; lock(); } unlock(); return 0; } +// Default event processor void JGEngine::defProcessEvent(JGEvent* event) { if (!event) return; - DDebug(this,DebugAll,"JGEngine::defprocessEvent. Deleting event(%p). Type %u.", + DDebug(this,DebugAll,"JGEngine::defprocessEvent. Deleting event (%p,%u)", event,event->type()); delete event; } +// Accept an event from the Jabber engine +bool JGEngine::accept(JBEvent* event, bool& processed, bool& insert) +{ + if (!(event && event->stream())) + return false; + XMLElement* child = event->child(); + XMPPError::Type error = XMPPError::NoError; + const char* errorText = 0; + Lock lock(this); + switch (event->type()) { + case JBEvent::IqJingleGet: + // Jingle stanzas should never have type='get' + Debug(this,DebugNote,"Received iq jingle stanza with type='get'"); + return false; + case JBEvent::IqJingleSet: + case JBEvent::IqJingleRes: + case JBEvent::IqJingleErr: + if (child) { + // Jingle clients may send the session id as 'id' or 'sid' + bool useSid = false; + String sid = child->getAttribute("id"); + if (!sid) { + sid = child->getAttribute("sid"); + useSid = false; + } + if (!sid) { + error = XMPPError::SBadRequest; + errorText = "Missing or empty session id"; + break; + } + // Check for a destination by SID + for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) { + JGSession* session = static_cast(o->get()); + if (sid == session->sid()) { + session->enqueue(event); + processed = true; + return true; + } + } + // Check if this an incoming session request + if (event->type() == JBEvent::IqJingleSet) { + const char* type = event->child()->getAttribute("type"); + int action = lookup(type,JGSession::s_actions,JGSession::ActCount); + if (action == JGSession::ActInitiate) { + if (!event->stream()->ref()) { + error = XMPPError::SInternal; + break; + } + DDebug(this,DebugAll,"New incoming call from '%s' to '%s'", + event->from().c_str(),event->to().c_str()); + m_sessions.append(new JGSession(this,event,sid,useSid)); + processed = true; + return true; + } + } + error = XMPPError::SRequest; + errorText = "Unknown session"; + } + else + error = XMPPError::SBadRequest; + break; + case JBEvent::IqResult: + case JBEvent::WriteFail: + // Sessions always set the id of sent stanzas to their local id + for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) { + JGSession* session = static_cast(o->get()); + if (event->id().startsWith(session->m_localSid)) { + session->enqueue(event); + processed = true; + return true; + } + } + break; + case JBEvent::Terminated: + case JBEvent::Destroy: + for (ObjList* o = m_sessions.skipNull(); o; o = o->skipNext()) { + JGSession* session = static_cast(o->get()); + if (event->stream() == session->stream()) + session->enqueue(new JBEvent((JBEvent::Type)event->type(), + event->stream(),0)); + } + break; + default: + return false; + } + if (error == XMPPError::NoError) + return false; + + // Send error + XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqError, + event->to(),event->from(),event->id()); + iq->addChild(event->releaseXML()); + iq->addChild(XMPPUtils::createError(XMPPError::TypeModify,error,errorText)); + event->stream()->sendStanza(iq); + TelEngine::destruct(event); + processed = true; + return true; +} + +// Process generated events +void JGEngine::processEvent(JGEvent* event) +{ + Debug(this,DebugStub,"JGEngine::processEvent. Calling default processor"); + defProcessEvent(event); +} + +// Create a local session id void JGEngine::createSessionId(String& id) { Lock lock(m_sessionIdMutex); @@ -179,56 +256,28 @@ void JGEngine::createSessionId(String& id) m_sessionId++; } -void JGEngine::processEvent(JGEvent* event) -{ - DDebug(this,DebugAll,"JGEngine::processEvent. Call default."); - defProcessEvent(event); -} - -void JGEngine::removeSession(JGSession* session) -{ - if (!session) - return; - Lock lock(this); - m_sessions.remove(session,false); -} /** * JGEvent */ -JGEvent::JGEvent(Type type, JGSession* session, XMLElement* element) - : m_type(type), - m_session(0), - m_element(element), - m_action(JGSession::ActCount) - -{ - XDebug(DebugAll,"JGEvent::JGEvent [%p].",this); - if (session && session->ref()) - m_session = session; -} - JGEvent::~JGEvent() { if (m_session) { m_session->eventTerminated(this); - m_session->deref(); + TelEngine::destruct(m_session); } if (m_element) delete m_element; - XDebug(DebugAll,"JGEvent::~JGEvent [%p].",this); + XDebug(DebugAll,"JGEvent::~JGEvent [%p]",this); } -bool JGEvent::final() +void JGEvent::init(JGSession* session) { -// Check: Terminated, Destroy - switch (type()) { - case Terminated: - case Destroy: - return true; - default: ; - } - return false; + XDebug(DebugAll,"JGEvent::JGEvent [%p]",this); + if (session && session->ref()) + m_session = session; + if (m_element) + m_id = m_element->getAttribute("id"); } /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yjingle/session.cpp b/libs/yjingle/session.cpp index 016409da..6dc6fbef 100644 --- a/libs/yjingle/session.cpp +++ b/libs/yjingle/session.cpp @@ -31,54 +31,96 @@ static XMPPError s_err; /** * JGAudio */ -XMLElement* JGAudio::createDescription() -{ - return XMPPUtils::createElement(XMLElement::Description, - XMPPNamespace::JingleAudio); -} - XMLElement* JGAudio::toXML() { XMLElement* p = new XMLElement(XMLElement::PayloadType); - p->setAttribute("id",m_id); - p->setAttributeValid("name",m_name); - p->setAttributeValid("clockrate",m_clockrate); - p->setAttributeValid("bitrate",m_bitrate); + p->setAttribute("id",id); + p->setAttributeValid("name",name); + p->setAttributeValid("clockrate",clockrate); + p->setAttributeValid("bitrate",bitrate); return p; } -void JGAudio::fromXML(XMLElement* element) +void JGAudio::fromXML(XMLElement* xml) { - element->getAttribute("id",m_id); - element->getAttribute("name",m_name); - element->getAttribute("clockrate",m_clockrate); - element->getAttribute("bitrate",m_bitrate); + if (!xml) { + set("","","","",""); + return; + } + xml->getAttribute("id",id); + xml->getAttribute("name",name); + xml->getAttribute("clockrate",clockrate); + xml->getAttribute("bitrate",bitrate); } -void JGAudio::set(const char* id, const char* name, const char* clockrate, - const char* bitrate) + +/** + * JGAudioList + */ +// Find a data payload by its synonym +JGAudio* JGAudioList::findSynonym(const String& value) { - m_id = id; - m_name = name; - m_clockrate = clockrate; - m_bitrate = bitrate; + for (ObjList* o = skipNull(); o; o = o->skipNext()) { + JGAudio* a = static_cast(o->get()); + if (value == a->synonym) + return a; + } + return 0; } +// Create a 'description' element and add payload children to it +XMLElement* JGAudioList::toXML(bool telEvent) +{ + XMLElement* desc = XMPPUtils::createElement(XMLElement::Description, + XMPPNamespace::JingleAudio); + for (ObjList* o = skipNull(); o; o = o->skipNext()) { + JGAudio* a = static_cast(o->get()); + desc->addChild(a->toXML()); + } + if (telEvent) { + JGAudio* te = new JGAudio("106","telephone-event","8000","",""); + desc->addChild(te->toXML()); + TelEngine::destruct(te); + } + return desc; +} + +// Fill this list from an XML element's children. Clear before attempting to fill +void JGAudioList::fromXML(XMLElement* xml) +{ + clear(); + XMLElement* m = xml ? xml->findFirstChild(XMLElement::PayloadType) : 0; + for (; m; m = xml->findNextChild(m,XMLElement::PayloadType)) + ObjList::append(new JGAudio(m)); +} + +// Create a list from data payloads +bool JGAudioList::createList(String& dest, bool synonym, const char* sep) +{ + dest = ""; + for (ObjList* o = skipNull(); o; o = o->skipNext()) { + JGAudio* a = static_cast(o->get()); + dest.append(synonym?a->synonym:a->name,sep); + } + return dest.length(); +} + + /** * JGTransport */ JGTransport::JGTransport(const JGTransport& src) { - m_name = src.m_name; - m_address = src.m_address; - m_port = src.m_port; - m_preference = src.m_preference; - m_username = src.m_username; - m_protocol = src.m_protocol; - m_generation = src.m_generation; - m_password = src.m_password; - m_type = src.m_type; - m_network = src.m_network; + name = src.name; + address = src.address; + port = src.port; + preference = src.preference; + username = src.username; + protocol = src.protocol; + generation = src.generation; + password = src.password; + type = src.type; + network = src.network; } XMLElement* JGTransport::createTransport() @@ -90,875 +132,655 @@ XMLElement* JGTransport::createTransport() XMLElement* JGTransport::toXML() { XMLElement* p = new XMLElement(XMLElement::Candidate); - p->setAttribute("name",m_name); - p->setAttribute("address",m_address); - p->setAttribute("port",m_port); - p->setAttributeValid("preference",m_preference); - p->setAttributeValid("username",m_username); - p->setAttributeValid("protocol",m_protocol); - p->setAttributeValid("generation",m_generation); - p->setAttributeValid("password",m_password); - p->setAttributeValid("type",m_type); - p->setAttributeValid("network",m_network); + p->setAttribute("name",name); + p->setAttribute("address",address); + p->setAttribute("port",port); + p->setAttributeValid("preference",preference); + p->setAttributeValid("username",username); + p->setAttributeValid("protocol",protocol); + p->setAttributeValid("generation",generation); + p->setAttributeValid("password",password); + p->setAttributeValid("type",type); + p->setAttributeValid("network",network); return p; } void JGTransport::fromXML(XMLElement* element) { - element->getAttribute("name",m_name); - element->getAttribute("address",m_address); - element->getAttribute("port",m_port); - element->getAttribute("preference",m_preference); - element->getAttribute("username",m_username); - element->getAttribute("protocol",m_protocol); - element->getAttribute("generation",m_generation); - element->getAttribute("password",m_password); - element->getAttribute("type",m_type); - element->getAttribute("network",m_network); + element->getAttribute("name",name); + element->getAttribute("address",address); + element->getAttribute("port",port); + element->getAttribute("preference",preference); + element->getAttribute("username",username); + element->getAttribute("protocol",protocol); + element->getAttribute("generation",generation); + element->getAttribute("password",password); + element->getAttribute("type",type); + element->getAttribute("network",network); } + /** * JGSession */ -String JGSession::s_dtmf = "0123456789#*ABCD"; + +TokenDict JGSession::s_states[] = { + {"Idle", Idle}, + {"Pending", Pending}, + {"Active", Active}, + {"Ending", Ending}, + {"Destroy", Destroy}, + {0,0} +}; TokenDict JGSession::s_actions[] = { - {"accept", ActAccept}, - {"initiate", ActInitiate}, - {"modify", ActModify}, - {"redirect", ActRedirect}, - {"reject", ActReject}, - {"terminate", ActTerminate}, - {"candidates", ActTransportCandidates}, - {"transport-info", ActTransportInfo}, - {"transport-accept", ActTransportAccept}, - {"content-info", ActContentInfo}, - {0,0} - }; + {"accept", ActAccept}, + {"initiate", ActInitiate}, + {"reject", ActReject}, + {"terminate", ActTerminate}, + {"candidates", ActTransportCandidates}, + {"transport-info", ActTransportInfo}, + {"transport-accept", ActTransportAccept}, + {"content-info", ActContentInfo}, + {0,0} +}; -JGSession::JGSession(JGEngine* engine, JBComponentStream* stream, - const String& callerJID, const String& calledJID) +// Create an outgoing session +JGSession::JGSession(JGEngine* engine, JBStream* stream, + const String& callerJID, const String& calledJID, + XMLElement* media, XMLElement* transport, + bool sid, const char* msg) : Mutex(true), - m_state(Idle), - m_transportType(TransportInfo), - m_engine(engine), - m_stream(stream), - m_incoming(false), - m_lastEvent(0), - m_terminateEvent(0), - m_private(0), - m_stanzaId(1), - m_timeout(0) + m_state(Idle), + m_transportType(TransportUnknown), + m_engine(engine), + m_stream(stream), + m_outgoing(true), + m_localJID(callerJID), + m_remoteJID(calledJID), + m_sidAttr(sid?"sid":"id"), + m_lastEvent(0), + m_private(0), + m_stanzaId(1) { m_engine->createSessionId(m_localSid); m_sid = m_localSid; - m_localJID.set(callerJID); - m_remoteJID.set(calledJID); - DDebug(m_engine,DebugAll,"Session. Outgoing. ID: '%s'. [%p]", - m_sid.c_str(),this); + DDebug(m_engine,DebugAll,"Call(%s). Outgoing [%p]",m_sid.c_str(),this); + if (msg) + sendMessage(msg); + XMLElement* xml = createJingle(ActInitiate,media,transport); + if (sendStanza(xml)) + changeState(Pending); + else + changeState(Destroy); } -JGSession::JGSession(JGEngine* engine, JBEvent* event) +// Create an incoming session +JGSession::JGSession(JGEngine* engine, JBEvent* event, const String& id, bool sid) : Mutex(true), - m_state(Idle), - m_transportType(TransportInfo), - m_engine(engine), - m_stream(0), - m_incoming(true), - m_lastEvent(0), - m_terminateEvent(0), - m_private(0), - m_stanzaId(1), - m_timeout(0) + m_state(Idle), + m_transportType(TransportUnknown), + m_engine(engine), + m_stream(event->stream()), + m_outgoing(false), + m_sid(id), + m_sidAttr(sid?"sid":"id"), + m_lastEvent(0), + m_private(0), + m_stanzaId(1) { - // This should never happen - if (!(event && event->stream() && event->stream()->ref() && - event->element() && event->child())) { - Debug(m_engine,DebugFail,"Session. Incoming. Invalid event. [%p]",this); - if (event) - event->deref(); - m_state = Destroy; - return; - } - // Keep stream and event - m_stream = event->stream(); - event->releaseStream(); m_events.append(event); - // Get attributes - event->child()->getAttribute("id",m_sid); - // Create local sid m_engine->createSessionId(m_localSid); - DDebug(m_engine,DebugAll,"Session. Incoming. ID: '%s'. [%p]", - m_sid.c_str(),this); + DDebug(m_engine,DebugAll,"Call(%s). Incoming [%p]",m_sid.c_str(),this); } +// Destructor: hangup, cleanup, remove from engine's list JGSession::~JGSession() { + XDebug(m_engine,DebugAll,"JGSession::~JGSession() [%p]",this); +} + +// Release this session and its memory +void JGSession::destroyed() +{ + lock(); // Cancel pending outgoing. Hangup. Cleanup if (m_stream) { - m_stream->cancelPending(false,&m_localSid); + m_stream->removePending(m_localSid,false); hangup(); - m_stream->deref(); + TelEngine::destruct(m_stream); } - lock(); m_events.clear(); - m_engine->removeSession(this); - if (m_terminateEvent) - delete m_terminateEvent; unlock(); - DDebug(m_engine,DebugAll,"~Session. [%p]",this); -} - -bool JGSession::sendMessage(const char* message) -{ - XMLElement* xml = XMPPUtils::createMessage(XMPPUtils::MsgChat, - m_localJID,m_remoteJID,0,message); - return sendXML(xml,false); -} - -bool JGSession::hangup(bool reject, const char* message) -{ - if (!(state() == Pending || state() == Active)) - return false; - Lock lock(this); - DDebug(m_engine,DebugAll,"Session. %s('%s'). [%p]", - reject?"Reject":"Hangup",message,this); - if (message) - sendMessage(message); - XMLElement* xml = createJingleSet(reject ? ActReject : ActTerminate); - // Clear sent stanzas list. We will wait for this element to be confirmed - m_sentStanza.clear(); - m_state = Ending; - m_timeout = Time::msecNow() + JGSESSION_ENDTIMEOUT; - return sendXML(xml); -} - -bool JGSession::sendTransport(JGTransport* transport, Action act) -{ - if (act != ActTransport && act != ActTransportAccept) - return false; - // Create transport - // For transport-info: A 'transport' child element - // For candidates: The 'session' element - if (act == ActTransportAccept) { - if (transport) - transport->deref(); - // No need to send transport-accept if type is candidates - if (m_transportType == TransportCandidates) - return true; - XMLElement* child = JGTransport::createTransport(); - return sendXML(createJingleSet(act,0,child)); - } - if (!transport) - return false; - // transport-info: send both transport types - if (m_transportType == TransportInfo) { - XMLElement* child = JGTransport::createTransport(); - transport->addTo(child); - if (!sendXML(createJingleSet(ActTransportInfo,0,child))) - return false; - } - XMLElement* child = transport->toXML(); - transport->deref(); - return sendXML(createJingleSet(ActTransportCandidates,0,child)); + // Remove from engine + Lock lock(m_engine); + m_engine->m_sessions.remove(this,false); + lock.drop(); + DDebug(m_engine,DebugAll,"Call(%s). Destroyed [%p]",m_sid.c_str(),this); } +// Accept a Pending incoming session bool JGSession::accept(XMLElement* description) { - if (state() != Pending) + Lock lock(this); + if (outgoing() || state() != Pending) return false; - XMLElement* jingle = createJingleSet(ActAccept,description, - JGTransport::createTransport()); - if (sendXML(jingle)) { - m_state = Active; - return true; - } - return false; + XMLElement* xml = createJingle(ActAccept,description,JGTransport::createTransport()); + if (!sendStanza(xml)) + return false; + changeState(Active); + return true; } -bool JGSession::sendResult(const char* id) +// Close a Pending or Active session +bool JGSession::hangup(bool reject, const char* msg) { - String tmp = id; - // Don't send if no id. - // It's useless: the remote peer can't match a response without id - if (tmp.null()) - return true; - XMLElement* result = XMPPUtils::createIq(XMPPUtils::IqResult, - m_localJID,m_remoteJID,tmp); - return sendXML(result,false); + Lock lock(this); + if (state() != Pending && state() != Active) + return false; + DDebug(m_engine,DebugAll,"Call(%s). %s('%s') [%p]",m_sid.c_str(), + reject?"Reject":"Hangup",msg,this); + if (msg) + sendMessage(msg); + // Clear sent stanzas list. We will wait for this element to be confirmed + m_sentStanza.clear(); + XMLElement* xml = createJingle(reject ? ActReject : ActTerminate); + bool ok = sendStanza(xml); + changeState(Ending); + return ok; } +// Confirm a received element. If the error is NoError a result stanza will be sent +// Otherwise, an error stanza will be created and sent +bool JGSession::confirm(XMLElement* xml, XMPPError::Type error, + const char* text, XMPPError::ErrorType type) +{ + if (!xml) + return false; + String id = xml->getAttribute("id"); + XMLElement* iq = 0; + if (error == XMPPError::NoError) { + iq = XMPPUtils::createIq(XMPPUtils::IqResult,m_localJID,m_remoteJID,id); + // The receiver will detect which stanza is confirmaed by id + // If missing, make a copy of the received element and attach it to the error + if (!id) { + XMLElement* copy = new XMLElement(*xml); + iq->addChild(copy); + } + } + else { + iq = XMPPUtils::createIq(XMPPUtils::IqError,m_localJID,m_remoteJID,id); + iq->addChild(xml); + iq->addChild(XMPPUtils::createError(type,error,text)); + } + return sendStanza(iq,false); +} + +// Send a dtmf character to remote peer bool JGSession::sendDtmf(char dtmf, bool buttonUp) { - if (!isDtmf(dtmf)) - return false; - String tmp = dtmf; - XMLElement* xml = XMPPUtils::createElement(XMLElement::Dtmf, - XMPPNamespace::Dtmf); + XMLElement* xml = XMPPUtils::createElement(XMLElement::Dtmf,XMPPNamespace::Dtmf); xml->setAttribute("action",buttonUp?"button-up":"button-down"); + String tmp = dtmf; xml->setAttribute("code",tmp); - return sendXML(createJingleSet(ActContentInfo,xml)); + return sendStanza(createJingle(ActContentInfo,xml)); } +// Send a dtmf method to remote peer bool JGSession::sendDtmfMethod(const char* method) { XMLElement* xml = XMPPUtils::createElement(XMLElement::DtmfMethod, XMPPNamespace::Dtmf); xml->setAttribute("method",method); - return sendXML(createJingleSet(ActContentInfo,xml)); + return sendStanza(createJingle(ActContentInfo,xml)); } +// Deny a dtmf method request from remote peer bool JGSession::denyDtmfMethod(XMLElement* element) { if (!element) return false; String id = element->getAttribute("id"); - XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqError, - m_localJID,m_remoteJID,id); + XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqError,m_localJID,m_remoteJID,id); iq->addChild(element); - XMLElement* err = XMPPUtils::createError(XMPPError::TypeCancel, - XMPPError::SFeatureNotImpl); - err->addChild(XMPPUtils::createElement(s_err[XMPPError::DtmfNoMethod], - XMPPNamespace::DtmfError)); + XMLElement* err = XMPPUtils::createError(XMPPError::TypeCancel,XMPPError::SFeatureNotImpl); + err->addChild(XMPPUtils::createElement(s_err[XMPPError::DtmfNoMethod],XMPPNamespace::DtmfError)); iq->addChild(err); - return sendXML(iq,false); + return sendStanza(iq,false); } -bool JGSession::sendError(XMLElement* element, XMPPError::Type error, - XMPPError::ErrorType type, const char* text) +// Enqueue a Jabber engine event +void JGSession::enqueue(JBEvent* event) { - if (!element) - return false; - String id = element->getAttribute("id"); - XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqError, - m_localJID,m_remoteJID,id); - XMLElement* err = XMPPUtils::createError(type,error,text); - iq->addChild(element); - iq->addChild(err); - return sendXML(iq,false); -} - -bool JGSession::receive(JBEvent* event) -{ - // Check stream - if (!(event && event->stream() && m_stream == event->stream())) - return false; - DDebug(m_engine,DebugAll, - "Session. Check event ((%p): %u) from Jabber. [%p]", - event,event->type(),this); - bool accepted = false; - bool retVal = false; - switch (event->type()) { - case JBEvent::Message: - accepted = receiveMessage(event,retVal); - break; - case JBEvent::IqResult: - case JBEvent::IqError: - accepted = receiveResult(event,retVal); - break; - case JBEvent::IqJingleGet: - case JBEvent::IqJingleSet: - accepted = receiveJingle(event,retVal); - break; - // WriteFail is identified by the local SID set when posting elements in stream - case JBEvent::WriteFail: - if (event->id() != m_localSid) - return false; - accepted = true; - break; - case JBEvent::Destroy: - accepted = receiveDestroy(event,retVal); - break; - default: - return false; - } - if (!accepted) { - if (retVal) - event->deref(); - return retVal; - } - // This session is the destination ! - // Delete event if in terminating state - if (state() == Destroy) { - DDebug(m_engine,DebugAll, - "Session. Received event ((%p). %u) from Jabber in terminating state. Deleting. [%p]", - event,event->type(),this); - event->deref(); - return true; - } - DDebug(m_engine,DebugAll, - "Session. Accepted event ((%p): %u) from Jabber. [%p]", - event,event->type(),this); - // Unlock stream events - event->releaseStream(); - // Keep event Lock lock(this); - m_events.append(event); - return true; + if (event->type() == JBEvent::Terminated || event->type() == JBEvent::Destroy) + m_events.insert(event); + else + m_events.append(event); + DDebug(m_engine,DebugAll,"Call(%s). Accepted event (%p,%s) [%p]", + m_sid.c_str(),event,event->name(),this); } +// Process received events. Generate Jingle events JGEvent* JGSession::getEvent(u_int64_t time) { Lock lock(this); - // Check last event, State if (m_lastEvent) return 0; - if (m_terminateEvent) { - JGEvent* event = m_terminateEvent; - m_terminateEvent = 0; - return raiseEvent(event); - } if (state() == Destroy) return 0; - ListIterator iter(m_events); - GenObject* obj; - for (; (obj = iter.get());) { - // Process the event - JBEvent* jbev = static_cast(obj); + // Deque and process event(s) + // Loop until a jingle event is generated or no more events in queue + JBEvent* jbev = 0; + while (true) { + TelEngine::destruct(jbev); + jbev = static_cast(m_events.remove(false)); + if (!jbev) + break; + DDebug(m_engine,DebugAll, - "Session. Process Jabber event ((%p): %u). [%p]", - jbev,jbev->type(),this); - JGEvent* event = processEvent(jbev,time); - // Remove jabber event - DDebug(m_engine,DebugAll, - "Session. Remove Jabber event ((%p): %u) from queue. [%p]", - jbev,jbev->type(),this); - m_events.remove(jbev,true); - // Raise ? - if (event) - return raiseEvent(event); - if (state() == Destroy) { - m_events.clear(); + "Call(%s). Dequeued Jabber event (%p,%s) in state %s [%p]", + m_sid.c_str(),jbev,jbev->name(),lookupState(state()),this); + + // Process Jingle 'set' stanzas + if (jbev->type() == JBEvent::IqJingleSet) { + // Filter some conditions in which we can't accept any jingle stanza + // Incoming pending sessions are waiting for the user to accept/reject them + // Outgoing idle sessions are waiting for the user to initiate them + if ((state() == Pending && !outgoing()) || + (state() == Idle && outgoing())) { + confirm(jbev->releaseXML(),XMPPError::SRequest); + continue; + } + + m_lastEvent = decodeJingle(jbev); + if (!m_lastEvent) { + // Destroy incoming session if session initiate stanza contains errors + if (!outgoing() && state() == Idle) { + m_lastEvent = new JGEvent(JGEvent::Destroy,this,0,"failure"); + break; + } + continue; + } + // Check for termination events + if (m_lastEvent->final()) + break; + + bool error = false; + bool fatal = false; + switch (state()) { + case Active: + if (m_lastEvent->action() == ActAccept || + m_lastEvent->action() == ActInitiate) + error = true; + break; + case Pending: + // Accept session-accept or transport stanzas + switch (m_lastEvent->action()) { + case ActAccept: + changeState(Active); + break; + case ActTransportAccept: + case ActTransport: + case ActTransportInfo: + case ActTransportCandidates: + case ActContentInfo: + break; + default: + error = true; + } + break; + case Idle: + // Update data. Terminate if not a session initiating event + if (m_lastEvent->action() == ActInitiate) { + m_localJID.set(jbev->to()); + m_remoteJID.set(jbev->from()); + changeState(Pending); + } + else + error = fatal = true; + break; + default: + error = true; + } + + if (!error) { + // Automatically confirm some actions + // Don't confirm actions that need session user's interaction: + // transport and dtmf method negotiation + if (m_lastEvent->action() != ActTransport && + m_lastEvent->action() != ActDtmfMethod) + confirm(m_lastEvent->element()); + } + else { + confirm(m_lastEvent->releaseXML(),XMPPError::SRequest); + delete m_lastEvent; + m_lastEvent = 0; + if (fatal) + m_lastEvent = new JGEvent(JGEvent::Destroy,this); + else + continue; + } break; } - } - // No event: check timeout - if (timeout(time)) { - JGEvent* event = new JGEvent(JGEvent::Terminated,this); - event->m_reason = "timeout"; - return raiseEvent(event); - } - return 0; -} -JGEvent* JGSession::badRequest(JGEvent* event) -{ - XDebug(m_engine,DebugAll,"Session::badRequest. [%p]",this); - sendEBadRequest(event->releaseXML()); - delete event; - return 0; -} - -JGEvent* JGSession::processEvent(JBEvent* jbev, u_int64_t time) -{ - JGEvent* event = 0; - // Process state Ending - if (state() == Ending) { - bool response = isResponse(jbev) != 0; - if (response || time > m_timeout) { - DDebug(m_engine,DebugAll, - "Session. Terminated in state Ending. Reason: '%s'. [%p]", - response ? "confirmation" : "timeout",this); - event = new JGEvent(JGEvent::Destroy,this); - } - } - else - event = createEvent(jbev); - if (!event) - return 0; - if (event->final()) { - confirmIq(event->element()); - m_state = Destroy; - return event; - } - switch (state()) { - case Pending: - return processStatePending(jbev,event); - case Active: - return processStateActive(jbev,event); - case Idle: - return processStateIdle(jbev,event); - default: ; - } - return 0; -} - -JGEvent* JGSession::processStatePending(JBEvent* jbev, JGEvent* event) -{ - XDebug(m_engine,DebugAll,"Session::processStatePending. [%p]",this); - // Check event type - if (event->type() != JGEvent::Jingle) { - confirmIq(event->element()); - return event; - } - // Check forbidden Jingle actions in this state - // Change state - switch (event->action()) { - case ActAccept: - // Incoming sessions should never receive an accept - if (incoming()) - return badRequest(event); - // Outgoing session received accept: change state - m_state = Active; - break; - case ActInitiate: - // Session initiate not allowed - return badRequest(event); - default: ; - } - confirmIqSelect(event); - return event; -} - -JGEvent* JGSession::processStateActive(JBEvent* jbev, JGEvent* event) -{ - XDebug(m_engine,DebugAll,"Session::processStateActive. [%p]",this); - if (event->type() == JGEvent::Terminated) - m_state = Destroy; - confirmIqSelect(event); - return event; -} - -JGEvent* JGSession::processStateIdle(JBEvent* jbev, JGEvent* event) -{ - XDebug(m_engine,DebugAll,"Session::processStateIdle. [%p]",this); - if (!incoming()) - return badRequest(event); - if (event->action() != ActInitiate) { - m_state = Destroy; - return badRequest(event); - } - m_localJID.set(jbev->to()); - m_remoteJID.set(jbev->from()); - confirmIq(event->element()); - m_state = Pending; - return event; -} - -bool JGSession::decodeJingle(JGEvent* event) -{ - // Get action - event->element()->getAttribute("id",event->m_id); - XMLElement* child = event->element()->findFirstChild(); - event->m_action = action(child->getAttribute("type")); - if (event->m_action == ActCount) { - sendEServiceUnavailable(event->releaseXML()); - return false; - } - // Check session id - if (m_sid != child->getAttribute("id")) { - sendEBadRequest(event->releaseXML()); - return false; - } - switch (event->m_action) { - // Check termination - case ActTerminate: - case ActReject: - event->m_type = JGEvent::Terminated; - if (event->m_action == ActTerminate) - event->m_reason = "hangup"; - else - event->m_reason = "rejected"; - return true; - case ActContentInfo: - return processContentInfo(event); - default: ; - } - // Update media & transport - if (!updateMedia(event)) - return false; - if (!updateTransport(event)) - return false; - // OK ! - event->m_type = JGEvent::Jingle; - return true; -} - -bool JGSession::processContentInfo(JGEvent* event) -{ - // Check dtmf - XMLElement* child = event->element()->findFirstChild(XMLElement::Dtmf); - if (child) { - event->m_reason = child->getAttribute("action"); - event->m_text = child->getAttribute("code"); - bool checked = true; - for (int i = 0; event->m_text[i]; i++) - if (!isDtmf(event->m_text[i])) { - checked = false; + // Check for responses or failures + bool response = jbev->type() == JBEvent::IqJingleRes || + jbev->type() == JBEvent::IqJingleErr || + jbev->type() == JBEvent::IqResult || + jbev->type() == JBEvent::WriteFail; + while (response) { + bool notSent = true; + // Don't use iterator: we stop searching the list at first item removal + for (ObjList* o = m_sentStanza.skipNull(); o; o = o->skipNext()) { + JGSentStanza* sent = static_cast(o->get()); + if (jbev->id() == *sent) { + m_sentStanza.remove(sent,true); + notSent = false; + if (state() == Ending) + m_lastEvent = new JGEvent(JGEvent::Destroy,this); + break; + } + } + // Ignore it if this event is not a result of a known sent stanza + // We didn't expect a result anyway + if (notSent) + break; + // Write fail: Terminate if failed stanza is a Jingle one + // Ignore all other write failures + if (jbev->type() == JBEvent::WriteFail) { + // Check if failed stanza is a jingle one + XMLElement* e = jbev->element() ? jbev->element()->findFirstChild() : 0; + if (e && e->hasAttribute("xmlns",s_ns[XMPPNamespace::Jingle])) { + Debug(m_engine,DebugInfo, + "Call(%s). Write stanza failure. Terminating [%p]", + m_sid.c_str(),this); + m_lastEvent = new JGEvent(JGEvent::Terminated,this,0,"noconn"); + } break; } - if ((event->m_reason != "button-up" && event->m_reason != "button-down") || - event->m_text.null() || !checked) { - sendEBadRequest(event->releaseXML()); - return false; - } - event->m_action = ActDtmf; - return true; - } - // Check dtmf method - child = event->element()->findFirstChild(XMLElement::DtmfMethod); - if (child) { - event->m_text = child->getAttribute("method"); - if (event->m_text != "rtp" && event->m_reason != "xmpp") { - sendEBadRequest(event->releaseXML()); - return false; - } - event->m_action = ActDtmfMethod; - return true; - } - return true; -} -bool JGSession::updateMedia(JGEvent* event) -{ - XMLElement* child = event->element()->findFirstChild(); - XMLElement* descr = child->findFirstChild(XMLElement::Description); - if (descr) { - // Check namespace - if (!descr->hasAttribute("xmlns",s_ns[XMPPNamespace::JingleAudio])) { - sendEServiceUnavailable(event->releaseXML()); - return false; +#ifdef DEBUG + String error; + if (jbev->text()) + error << ". Error: " << jbev->text(); + Debug(m_engine,DebugAll, + "Call(%s). Sent element with id '%s' confirmed%s [%p]", + m_sid.c_str(),jbev->id().c_str(),error.safe(),this); +#endif + + // Terminate pending outgoing sessions if session initiate stanza received error + if (state() == Pending && outgoing() && jbev->type() == JBEvent::IqJingleErr) + m_lastEvent = new JGEvent(JGEvent::Terminated,this,jbev->releaseXML(), + jbev->text()?jbev->text().c_str():"failure"); + + break; } - // Get payloads - XMLElement* p = descr->findFirstChild(XMLElement::PayloadType); - for (; p; p = descr->findNextChild(p,XMLElement::PayloadType)) { - JGAudio* a = new JGAudio(p); - event->m_audio.append(a); + if (response) + if (!m_lastEvent) + continue; + else + break; + + // Silently ignore temporary stream down + if (jbev->type() == JBEvent::Terminated) { + DDebug(m_engine,DebugInfo, + "Call(%s). Stream disconnected in state %s [%p]", + m_sid.c_str(),lookupState(state()),this); + continue; + } + + // Terminate on stream destroy + if (jbev->type() == JBEvent::Destroy) { + Debug(m_engine,DebugInfo, + "Call(%s). Stream destroyed in state %s [%p]", + m_sid.c_str(),lookupState(state()),this); + m_lastEvent = new JGEvent(JGEvent::Terminated,this,0,"noconn"); + break; + } + + Debug(m_engine,DebugStub,"Call(%s). Unhandled event type %u '%s' [%p]", + m_sid.c_str(),jbev->type(),jbev->name(),this); + continue; + } + TelEngine::destruct(jbev); + + // No event: check first sent stanza's timeout + if (!m_lastEvent) { + ObjList* o = m_sentStanza.skipNull(); + if (o) { + JGSentStanza* tmp = static_cast(o->get()); + if (tmp->timeout(time)) { + Debug(m_engine,DebugNote,"Call(%s). Sent stanza ('%s') timed out [%p]", + m_sid.c_str(),tmp->c_str(),this); + m_lastEvent = new JGEvent(JGEvent::Terminated,this,0,"timeout"); + } } } - return true; -} -bool JGSession::updateTransport(JGEvent* event) -{ - // Detect transport type - if (event->m_action == ActTransportCandidates) { - m_transportType = TransportCandidates; - event->m_action = ActTransport; + if (m_lastEvent) { + // Deref the session for final events + if (m_lastEvent->final()) { + changeState(Destroy); + deref(); + } DDebug(m_engine,DebugAll, - "Session. Set transport type: 'candidates'. [%p]",this); + "Call(%s). Raising event (%p,%u) action=%u final=%s [%p]", + m_sid.c_str(),m_lastEvent,m_lastEvent->type(), + m_lastEvent->action(),String::boolText(m_lastEvent->final()),this); + return m_lastEvent; } - else if (event->m_action == ActTransportInfo || - event->m_action == ActTransportAccept) { - m_transportType = TransportInfo; - // Don't set action for transport-accept - // Use it only to get transport info if any - if (event->m_action == ActTransportInfo) - event->m_action = ActTransport; - DDebug(m_engine,DebugAll, - "Session. Set transport type: 'transport-info'. [%p]",this); - } - else - return true; - // Get candidates parent: - // For transport-info: A 'transport' child element - // For candidates: The 'session' element - XMLElement* candidates = event->element()->findFirstChild(); - if (m_transportType == TransportInfo) { - candidates = candidates->findFirstChild(XMLElement::Transport); - if (candidates && - !candidates->hasAttribute("xmlns",s_ns[XMPPNamespace::JingleTransport])) { - sendEServiceUnavailable(event->releaseXML()); - return false; - } - } - if (!candidates) { - sendEBadRequest(event->releaseXML()); - return false; - } - // Get transports - XMLElement* t = candidates->findFirstChild(XMLElement::Candidate); - for (; t; t = candidates->findNextChild(t,XMLElement::Candidate)) { - JGTransport* tr = new JGTransport(t); - event->m_transport.append(tr); - } - return true; -} - -JGEvent* JGSession::decodeMessage(JGEvent* event) -{ - event->element()->getAttribute("id",event->m_id); - XMLElement* child = event->element()->findFirstChild(XMLElement::Body); - if (child) - event->m_text = child->getText(); - event->m_type = JGEvent::Message; - return event; -} - -bool JGSession::decodeError(JGEvent* event) -{ - event->element()->getAttribute("id",event->m_id); - event->m_type = JGEvent::Error; - XMLElement* element = event->element(); - if (!element) - return (event != 0); - element = element->findFirstChild("error"); - if (!element) - return (event != 0); - XMLElement* tmp = element->findFirstChild(); - if (tmp) { - event->m_reason = tmp->name(); - tmp = element->findNextChild(tmp); - if (tmp) - event->m_text = tmp->getText(); - } - return (event != 0); -} - -JGEvent* JGSession::createEvent(JBEvent* jbev) -{ - JGSentStanza* sent; - JGEvent* event = new JGEvent(JGEvent::Unexpected,this,jbev->releaseXML()); - if (!event->element()) - return 0; - // Decode the event - switch (jbev->type()) { - case JBEvent::IqResult: - DDebug(m_engine,DebugAll, - "Session. Received confirmation. ID: '%s'. [%p]", - jbev->id().c_str(),this); - sent = isResponse(jbev); - if (sent) - m_sentStanza.remove(sent,true); - break; - case JBEvent::IqJingleGet: - case JBEvent::IqJingleSet: - if (decodeJingle(event)) - return event; - break; - case JBEvent::IqError: - DDebug(m_engine,DebugAll, - "Session. Received error. ID: '%s'. [%p]", - jbev->id().c_str(),this); - sent = isResponse(jbev); - if (sent) - m_sentStanza.remove(sent,true); - if (decodeError(event)) - return event; - break; - case JBEvent::Message: - return decodeMessage(event); - case JBEvent::WriteFail: - sent = isResponse(jbev); - if (sent) - m_sentStanza.remove(sent,true); - event->m_reason = "noconn"; - event->m_type = JGEvent::Terminated; - return event; - default: ; - } - delete event; return 0; } -JGEvent* JGSession::raiseEvent(JGEvent* event) +// Send a stanza to the remote peer +bool JGSession::sendStanza(XMLElement* stanza, bool confirmation) { - if (m_lastEvent) - Debug(m_engine,DebugGoOn,"Session::raiseEvent. Last event already set to %p. [%p]", - m_lastEvent,this); - m_lastEvent = event; - // Do specific actions: change state, deref() ... - switch (event->type()) { - case JGEvent::Terminated: - m_state = Destroy; - deref(); - break; - case JGEvent::Destroy: - deref(); - break; - default: ; - } - DDebug(m_engine,DebugAll,"Session. Raising event((%p): %u). Action: %u. [%p]", - event,event->type(),event->action(),this); - return event; -} - -bool JGSession::initiate(XMLElement* media, XMLElement* transport) -{ - if (incoming() || state() != Idle) - return false; - DDebug(m_engine,DebugAll,"Session. Initiate from '%s' to '%s'. [%p]", - m_localJID.c_str(),m_remoteJID.c_str(),this); - XMLElement* xml = createJingleSet(ActInitiate,media,transport); - if (sendXML(xml)) - m_state = Pending; - return (m_state == Pending); -} - -bool JGSession::sendXML(XMLElement* e, bool addId) -{ - if (!e) - return false; Lock lock(this); - DDebug(m_engine,DebugAll,"Session::sendXML((%p): '%s'). [%p]",e,e->name(),this); - if (addId) { + if (!(state() != Ending && state() != Destroy && stanza && m_stream)) { + Debug(m_engine,DebugNote, + "Call(%s). Can't send stanza (%p,'%s') in state %s [%p]", + m_sid.c_str(),stanza,stanza->name(),lookupState(m_state),this); + delete stanza; + return false; + } + DDebug(m_engine,DebugAll,"Call(%s). Sending stanza (%p,'%s') [%p]", + m_sid.c_str(),stanza,stanza->name(),this); + // Check if the stanza should be added to the list of stanzas requiring confirmation + if (confirmation && stanza->type() == XMLElement::Iq) { // Create id String id = m_localSid; id << "_" << (unsigned int)m_stanzaId; m_stanzaId++; - e->setAttribute("id",id); - appendSent(e); + stanza->setAttribute("id",id); + // Append to sent stanzas + m_sentStanza.append(new JGSentStanza(id,m_engine->stanzaTimeout() + Time::msecNow())); } // Send. If it fails leave it in the sent items to timeout - JBComponentStream::Error res = m_stream->sendStanza(e,m_localSid); - if (res == JBComponentStream::ErrorNoSocket || - res == JBComponentStream::ErrorContext) + JBStream::Error res = m_stream->sendStanza(stanza,m_localSid); + if (res == JBStream::ErrorNoSocket || res == JBStream::ErrorContext) return false; return true; } -XMLElement* JGSession::createJingleSet(Action action, - XMLElement* element1, XMLElement* element2) +// Decode a jingle stanza +JGEvent* JGSession::decodeJingle(JBEvent* jbev) { - // Create 'iq' and 'jingle' - XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqSet, - m_localJID,m_remoteJID,0); + XMLElement* jingle = jbev->child(); + if (!jingle) { + confirm(jbev->releaseXML(),XMPPError::SBadRequest); + return 0; + } + + Action act = (Action)lookup(jingle->getAttribute("type"),s_actions,ActCount); + if (act == ActCount) { + confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable, + "Unknown jingle type"); + return 0; + } + + // *** ActTerminate or ActReject + if (act == ActTerminate || act == ActReject) + return new JGEvent(JGEvent::Terminated,this,jbev->releaseXML(), + act==ActTerminate?"hangup":"rejected"); + + // *** ActContentInfo: ActDtmf or ActDtmfMethod + if (act == ActContentInfo) { + // Check dtmf + XMLElement* tmp = jingle->findFirstChild(XMLElement::Dtmf); + if (tmp) { + String reason = tmp->getAttribute("action"); + String text = tmp->getAttribute("code"); + if (!text || (reason != "button-up" && reason != "button-down")) { + confirm(jbev->releaseXML(),XMPPError::SBadRequest,"Unknown action"); + return 0; + } + return new JGEvent(ActDtmf,this,jbev->releaseXML(),reason,text); + } + // Check dtmf method + tmp = jingle->findFirstChild(XMLElement::DtmfMethod); + if (tmp) { + String text = tmp->getAttribute("method"); + if (text != "rtp" && text != "xmpp") { + confirm(jbev->releaseXML(),XMPPError::SBadRequest,"Unknown method"); + return 0; + } + return new JGEvent(ActDtmfMethod,this,jbev->releaseXML(),0,text); + } + confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable); + return 0; + } + + // *** ActAccept ActInitiate ActModify + // *** ActTransport: ActTransportInfo/ActTransportCandidates + // *** ActTransportAccept + + // Detect transport type + if (act == ActTransportCandidates) { + m_transportType = TransportCandidates; + act = ActTransport; + DDebug(m_engine,DebugInfo,"Call(%s). Set transport='candidates' [%p]", + m_sid.c_str(),this); + } + else if (act == ActTransportInfo || act == ActTransportAccept) { + m_transportType = TransportInfo; + // Don't set action for transport-accept. Use it only to get transport info if any + if (act == ActTransportInfo) + act = ActTransport; + DDebug(m_engine,DebugInfo,"Call(%s). Set transport='transport-info' [%p]", + m_sid.c_str(),this); + } + + // Get transport candidates parent: + // transport-info: A 'transport' child element + // candidates: The 'session' element + XMLElement* trans = jingle; + if (m_transportType == TransportInfo) { + trans = trans->findFirstChild(XMLElement::Transport); + if (trans && !trans->hasAttribute("xmlns",s_ns[XMPPNamespace::JingleTransport])) { + confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable); + return 0; + } + } + // Get media description + XMLElement* media = jingle->findFirstChild(XMLElement::Description); + if (media && !media->hasAttribute("xmlns",s_ns[XMPPNamespace::JingleAudio])) { + confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable); + return 0; + } + + // Create event, update transport and media + JGEvent* event = new JGEvent(act,this,jbev->releaseXML()); + + XMLElement* t = trans ? trans->findFirstChild(XMLElement::Candidate) : 0; + for (; t; t = trans->findNextChild(t,XMLElement::Candidate)) + event->m_transport.append(new JGTransport(t)); + event->m_audio.fromXML(media); + return event; +} + +// Create an 'iq' stanza with a 'jingle' child +XMLElement* JGSession::createJingle(Action action, XMLElement* element1, XMLElement* element2) +{ + XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqSet,m_localJID,m_remoteJID,0); XMLElement* jingle = XMPPUtils::createElement(XMLElement::Jingle, XMPPNamespace::Jingle); if (action < ActCount) - jingle->setAttribute("type",actionText(action)); - jingle->setAttribute("initiator",initiator()); -// jingle->setAttribute("responder",incoming()?m_localJID:m_remoteJID); - jingle->setAttribute("id",m_sid); + jingle->setAttribute("type",lookup(action,s_actions)); + jingle->setAttribute("initiator",outgoing()?m_localJID:m_remoteJID); +// jingle->setAttribute("responder",outgoing()?m_remoteJID:m_localJID); + jingle->setAttribute(m_sidAttr,m_sid); jingle->addChild(element1); jingle->addChild(element2); iq->addChild(jingle); return iq; } -void JGSession::confirmIq(XMLElement* element) +// Send a transport related element to the remote peer +bool JGSession::sendTransport(JGTransport* transport, Action act) { - if (!(element && element->type() == XMLElement::Iq)) - return; - XMPPUtils::IqType type = XMPPUtils::iqType(element->getAttribute("type")); - if (type == XMPPUtils::IqResult || type == XMPPUtils::IqError) - return; - String id = element->getAttribute("id"); - sendResult(id); -} - -void JGSession::confirmIqSelect(JGEvent* event) -{ - if (!event) - return; - // Skip transport - if (event->type() == JGEvent::Jingle) - switch (event->action()) { - case ActTransport: - case ActTransportInfo: - case ActTransportCandidates: - case ActDtmfMethod: - return; - default: ; - } - confirmIq(event->element()); + if (act != ActTransport && act != ActTransportAccept) + return false; + // Accept received transport + if (act == ActTransportAccept) { + TelEngine::destruct(transport); + // Clients negotiating transport as 'candidates' don't expect transport-accept + if (m_transportType == TransportCandidates) + return true; + XMLElement* child = JGTransport::createTransport(); + return sendStanza(createJingle(ActTransportAccept,0,child)); + } + // Sent transport + if (!transport) + return false; + // TransportUnknown: send both transport types + // TransportInfo: A 'transport' child element of the session element + // TransportCandidates: Transport candidates are direct children of the 'session' element + XMLElement* child = 0; + bool ok = false; + switch (m_transportType) { + case TransportUnknown: + // Fallthrough to send both transport types + case TransportInfo: + child = JGTransport::createTransport(); + transport->addTo(child); + ok = sendStanza(createJingle(ActTransportInfo,0,child)); + if (!ok || m_transportType == TransportInfo) + break; + // Fallthrough to send candidates if unknown and succedded + case TransportCandidates: + child = transport->toXML(); + ok = sendStanza(createJingle(ActTransportCandidates,0,child)); + } + TelEngine::destruct(transport); + return ok; } +// Event termination notification void JGSession::eventTerminated(JGEvent* event) { lock(); if (event == m_lastEvent) { - DDebug(m_engine,DebugAll, - "Session. Event((%p): %u) terminated. [%p]",event,event->type(),this); + DDebug(m_engine,DebugAll,"Call(%s). Event (%p,%u) terminated [%p]", + m_sid.c_str(),event,event->type(),this); m_lastEvent = 0; } else if (m_lastEvent) - Debug(m_engine,DebugNote,"Event((%p): %u) replaced while processed. [%p]", - event,event->type(),this); + Debug(m_engine,DebugNote, + "Call(%s). Event (%p,%u) replaced while processed [%p]", + m_sid.c_str(),event,event->type(),this); unlock(); } -JGSentStanza* JGSession::isResponse(const JBEvent* jbev) +// Change session state +void JGSession::changeState(State newState) { - Lock lock(this); - ObjList* obj = m_sentStanza.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGSentStanza* tmp = static_cast(obj->get()); - if (tmp->isResponse(jbev)) { - DDebug(m_engine,DebugAll, - "Session. Sent element with id '%s' confirmed. [%p]", - tmp->m_id.c_str(),this); - return tmp; - } - } - return 0; -} - -bool JGSession::timeout(u_int64_t time) -{ - Lock lock(this); - ObjList* obj = m_sentStanza.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGSentStanza* tmp = static_cast(obj->get()); - if (tmp->timeout(time)) { - DDebug(m_engine,DebugAll, - "Session. Sent element with id '%s' timed out. [%p]", - tmp->m_id.c_str(),this); - return true; - } - } - return false; -} - -void JGSession::appendSent(XMLElement* element) -{ - if (!(element && element->type() == XMLElement::Iq)) + if (m_state == newState) return; - String id = element->getAttribute("id"); - if (id) - m_sentStanza.append(new JGSentStanza(id)); -} - -bool JGSession::receiveMessage(const JBEvent* event, bool& retValue) -{ - if (!(event->to() == local() && event->from() == remote())) - return false; - //TODO: Make a copy of message: return false - retValue = true; - return true; -} - -bool JGSession::receiveResult(const JBEvent* event, bool& retValue) -{ - if (!(event->to() == local() && event->from() == remote())) - return false; - Lock lock(this); - JGSentStanza* sent = isResponse(event); - if (!sent) - return false; - // Keep the event if: state is Ending: Will raise a Terminated event - // event is IqError: Will be sent to the upper layer - if (state() == Ending || event->type() == JBEvent::IqError) - return true; - // Event is a result. Consume it - m_sentStanza.remove(sent,true); - retValue = true; - return false; -} - -bool JGSession::receiveJingle(const JBEvent* event, bool& retValue) -{ - // Jingle stanzas must match source, destination and session id - if (!(event->to() == local() && event->from() == remote() && - event->child() && event->child()->hasAttribute("id",m_sid))) - return false; - return true; -} - -bool JGSession::receiveDestroy(const JBEvent* event, bool& retValue) -{ - Lock lock(this); - // Ignore if session is already ending or destroying - if (state() != Ending && state() != Destroy && !m_terminateEvent) { - DDebug(m_engine,DebugAll, - "Session. Terminate on stream destroy. [%p]",this); - m_state = Destroy; - m_terminateEvent = new JGEvent(JGEvent::Terminated,this); - m_terminateEvent->m_reason = "noconn"; - } - retValue = false; - return false; + Debug(m_engine,DebugInfo,"Call(%s). Changing state from %s to %s [%p]", + m_sid.c_str(),lookup(m_state,s_states),lookup(newState,s_states),this); + m_state = newState; } /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/yjingle/xmlparser.cpp b/libs/yjingle/xmlparser.cpp index 51d1950f..635d0561 100644 --- a/libs/yjingle/xmlparser.cpp +++ b/libs/yjingle/xmlparser.cpp @@ -61,6 +61,16 @@ XMLElement::XMLElement() // XDebug(DebugAll,"XMLElement::XMLElement [%p]. Name: '%s'",this,name()); } +XMLElement::XMLElement(const XMLElement& src) + : m_type(Invalid), m_owner(true), m_element(0) +{ + TiXmlElement* e = src.get(); + if (!e) + return; + m_element = new TiXmlElement(*e); + setType(); +} + XMLElement::XMLElement(const char* name, NamedList* attributes, const char* text) : m_type(Unknown), m_owner(true), m_element(0) @@ -164,6 +174,8 @@ void XMLElement::addChild(XMLElement* element) if (tiElement) m_element->LinkEndChild(tiElement); } + if (element) + delete element; } XMLElement* XMLElement::findFirstChild(const char* name) diff --git a/libs/yjingle/xmlparser.h b/libs/yjingle/xmlparser.h index c59d1e77..99839b13 100644 --- a/libs/yjingle/xmlparser.h +++ b/libs/yjingle/xmlparser.h @@ -55,13 +55,16 @@ class XMLParser; class XMLElementOut; /** - * This class holds an XML element. - * @short An XML element. + * This class holds an XML element + * @short An XML element */ class YJINGLE_API XMLElement { friend class XMLParser; public: + /** + * Element type as enumeration + */ enum Type { // *** Stream related elements StreamStart, // stream:stream @@ -98,82 +101,87 @@ public: /** * Constructor. - * Constructs an StreamEnd element. + * Constructs a StreamEnd element */ XMLElement(); + /** + * Copy constructor + */ + XMLElement(const XMLElement& src); + /** * Constructor. * Constructs an XML element with a TiXmlElement element with the given name. - * Used for outgoing elements. - * @param name The element's name. - * @param attributes Optional list of attributes. - * @param text Optional text for the XML element. + * Used for outgoing elements + * @param name The element's name + * @param attributes Optional list of attributes + * @param text Optional text for the XML element */ XMLElement(const char* name, NamedList* attributes = 0, const char* text = 0); /** * Constructor. * Constructs an XML element with a TiXmlElement element with the given type's name. - * Used for outgoing elements. - * @param type The element's type. - * @param attributes Optional list of attributes. - * @param text Optional text for the XML element. + * Used for outgoing elements + * @param type The element's type + * @param attributes Optional list of attributes + * @param text Optional text for the XML element */ XMLElement(Type type, NamedList* attributes = 0, const char* text = 0); /** - * Destructor. Deletes the underlying TiXmlElement if owned. + * Destructor. Deletes the underlying TiXmlElement if owned */ virtual ~XMLElement(); /** - * Get the type of this object. - * @return The type of this object as enumeration. + * Get the type of this object + * @return The type of this object as enumeration */ inline Type type() const { return m_type; } /** - * Get the TiXmlElement's name. - * @return The name of the TiXmlElement object or 0. + * Get the TiXmlElement's name + * @return The name of the TiXmlElement object or 0 */ inline const char* name() const { return valid() ? m_element->Value() : 0; } /** - * Check if the TiXmlElement's name is the given text. - * @param text Text to compare with. - * @return False if text is 0 or not equal to name. + * Check if the TiXmlElement's name is the given text + * @param text Text to compare with + * @return False if text is 0 or not equal to name */ inline bool nameIs(const char* text) const { return (text && name() && (0 == ::strcmp(name(),text))); } /** - * Get the validity of this object. - * @return True if m_element is non null. + * Get the validity of this object + * @return True if m_element is non null */ inline bool valid() const { return m_element != 0; } /** - * Put the element in a buffer. - * @param dest Destination string. - * @param unclose True to leave the tag unclosed. + * Put the element in a buffer + * @param dest Destination string + * @param unclose True to leave the tag unclosed */ void toString(String& dest, bool unclose = false) const; /** - * Set the value of an existing attribute or adds a new one. - * @param name Attribute's name. - * @param value Attribute's value. + * Set the value of an existing attribute or adds a new one + * @param name Attribute's name + * @param value Attribute's value */ void setAttribute(const char* name, const char* value); /** - * Set the value of an existing attribute or adds a new one if the value's length is not 0. - * @param name Attribute's name. - * @param value Attribute's value. + * Set the value of an existing attribute or adds a new one if the value's length is not 0 + * @param name Attribute's name + * @param value Attribute's value */ inline void setAttributeValid(const char* name, const String& value) { if (value) @@ -181,9 +189,9 @@ public: } /** - * Set the value of an existing attribute or adds a new one from an integer. - * @param name Attribute's name. - * @param value Attribute's value. + * Set the value of an existing attribute or adds a new one from an integer + * @param name Attribute's name + * @param value Attribute's value */ inline void setAttribute(const char* name, int value) { String s(value); @@ -191,17 +199,17 @@ public: } /** - * Get the value of an attribute. - * @param name Attribute's name. - * @return Attribute's value. May be 0 if doesn't exists or empty. + * Get the value of an attribute + * @param name Attribute's name + * @return Attribute's value. May be 0 if doesn't exists or empty */ const char* getAttribute(const char* name); /** - * Get the value of an attribute. - * @param name Attribute's name. - * @param value Destination string. - * @return True if attribute with the given name exists and is not empty. + * Get the value of an attribute + * @param name Attribute's name + * @param value Destination string + * @return True if attribute with the given name exists and is not empty */ inline bool getAttribute(const char* name, String& value) { value = getAttribute(name); @@ -209,79 +217,77 @@ public: } /** - * Check if an attribute with the given name and value exists. - * @param name Attribute's name. - * @param value Attribute's value. - * @return True/False. + * Check if an attribute with the given name and value exists + * @param name Attribute's name + * @param value Attribute's value + * @return True/False */ bool hasAttribute(const char* name, const char* value); /** - * Get the text of this XML element. - * @return Pointer to the text of this XML element or 0. + * Get the text of this XML element + * @return Pointer to the text of this XML element or 0 */ const char* getText(); /** - * Add a child to this object. Release the received element. - * On exit 'element' will be invalid if the operation succeedded. - * To succeed, 'element' MUST own his 'm_element'. - * @param element XMLElement to add. + * Add a child to this object. Release the received element + * @param element XMLElement to add */ void addChild(XMLElement* element); /** - * Find the first child element. - * @param name Optional name of the child. - * @return Pointer to an XMLElement or 0 if not found. + * Find the first child element + * @param name Optional name of the child + * @return Pointer to an XMLElement or 0 if not found */ XMLElement* findFirstChild(const char* name = 0); /** - * Find the first child element of the given type. - * @param type Child's type to find. - * @return Pointer to an XMLElement or 0 if not found. + * Find the first child element of the given type + * @param type Child's type to find + * @return Pointer to an XMLElement or 0 if not found */ inline XMLElement* findFirstChild(Type type) { return findFirstChild(typeName(type)); } /** - * Find the next child element. - * @param element Starting XMLElement. O to find from the beginning. - * @param name Optional name of the child. - * @return Pointer to an XMLElement or 0 if not found. + * Find the next child element + * @param element Starting XMLElement. O to find from the beginning + * @param name Optional name of the child + * @return Pointer to an XMLElement or 0 if not found */ XMLElement* findNextChild(const XMLElement* element, const char* name = 0); /** - * Find the next child element of the given type. - * @param element Starting XMLElement. O to find from the beginning. - * @param type Child's type to find. - * @return Pointer to an XMLElement or 0 if not found. + * Find the next child element of the given type + * @param element Starting XMLElement. O to find from the beginning + * @param type Child's type to find + * @return Pointer to an XMLElement or 0 if not found */ inline XMLElement* findNextChild(const XMLElement* element, Type type) { return findNextChild(element,typeName(type)); } /** - * Find the first attribute. - * @return Pointer to the first attribute or 0. + * Find the first attribute + * @return Pointer to the first attribute or 0 */ inline const TiXmlAttribute* firstAttribute() const { return valid() ? m_element->FirstAttribute() : 0; } /** - * Get the name associated with the given type. - * @param type Element type as enumeration. - * @return Pointer to the name or 0. + * Get the name associated with the given type + * @param type Element type as enumeration + * @return Pointer to the name or 0 */ static inline const char* typeName(Type type) { return lookup(type,s_names); } /** - * check if the given text is equal to the one associated with the given type. - * @param txt Text to compare. - * @param type Element type as enumeration. - * @return True if txt equals the text associated with the given type. + * check if the given text is equal to the one associated with the given type + * @param txt Text to compare + * @param type Element type as enumeration + * @return True if txt equals the text associated with the given type */ static inline bool isType(const char* txt, Type type) { const char* s = typeName(type); @@ -294,28 +300,28 @@ protected: * Constructs an XML element from a TiXmlElement. * Used to extract elements from parser and access the children. * When extracting elements from parser the object will own the TiXmlElement. - * When accessing the children, the object will not own the TiXmlElement. - * @param element Pointer to a valid TiXmlElement. - * @param owner Owner flag. + * When accessing the children, the object will not own the TiXmlElement + * @param element Pointer to a valid TiXmlElement + * @param owner Owner flag */ XMLElement(TiXmlElement* element, bool owner); /** - * Get the underlying TiXmlElement. - * @return The underlying TiXmlElement object or 0. + * Get the underlying TiXmlElement + * @return The underlying TiXmlElement object or 0 */ inline TiXmlElement* get() const { return m_element; } /** - * Release the ownership of the underlying TiXmlElement - * and returns it if the object owns it. - * @return The underlying TiXmlElement object or 0 if not owned or 0. + * Release the ownership of the underlying TiXmlElement + * and returns it if the object owns it + * @return The underlying TiXmlElement object or 0 if not owned or 0 */ TiXmlElement* releaseOwnership(); /** - * Associations between XML element name and type. + * Associations between XML element name and type */ static TokenDict s_names[]; @@ -331,22 +337,22 @@ private: /** * This class is responsable of parsing incoming data. - * Keeps the resulting XML elements and the input buffer. - * @short An XML parser. + * Keeps the resulting XML elements and the input buffer + * @short An XML parser */ class YJINGLE_API XMLParser : public TiXmlDocument, public Mutex { public: /** * Constructor. - * Constructs an XML parser. + * Constructs an XML parser */ inline XMLParser() : TiXmlDocument(), Mutex(true), m_findstart(true) {} /** - * Destructor. + * Destructor */ virtual ~XMLParser() {} @@ -354,40 +360,40 @@ public: /** * Add data to buffer. Parse the buffer. * On success, the already parsed data is removed from buffer. - * This method is thread safe. - * @param data Pointer to the data to consume. - * @param len Data length. - * @return True on successfully parsed. + * This method is thread safe + * @param data Pointer to the data to consume + * @param len Data length + * @return True on successfully parsed */ bool consume(const char* data, u_int32_t len); /** * Extract the first XML element from document. * Remove non-element children of the document (e.g. declaration). - * This method is thread safe. - * @return Pointer to an XMLElement or 0 if the document is empty. + * This method is thread safe + * @return Pointer to an XMLElement or 0 if the document is empty */ XMLElement* extract(); /** - * Get a copy of the parser's buffer. - * @param dest Destination string. + * Get a copy of the parser's buffer + * @param dest Destination string */ inline void getBuffer(String& dest) const { dest = m_buffer; } /** - * Clear the parser's input buffer and already parsed elements. Reset data. + * Clear the parser's input buffer and already parsed elements. Reset data */ void reset(); /** - * The maximum allowed buffer length. + * The maximum allowed buffer length */ static u_int32_t s_maxDataBuffer; /** - * The XML encoding. + * The XML encoding */ static TiXmlEncoding s_xmlEncoding; @@ -397,24 +403,24 @@ private: }; /** - * This class holds an XML element to be sent through a stream. - * @short An outgoing XML element. + * This class holds an XML element to be sent through a stream + * @short An outgoing XML element */ class YJINGLE_API XMLElementOut : public RefObject { public: /** - * Constructor. - * @param element The XML element. - * @param senderID Optional sender id. + * Constructor + * @param element The XML element + * @param senderID Optional sender id */ inline XMLElementOut(XMLElement* element, const char* senderID = 0) : m_element(element), m_offset(0), m_id(senderID) {} /** - * Destructor. - * Delete m_element if not 0. + * Destructor + * Delete m_element if not 0 */ virtual ~XMLElementOut() { if (m_element) @@ -422,37 +428,37 @@ public: } /** - * Get the underlying element. - * @return The underlying element. + * Get the underlying element + * @return The underlying element */ inline XMLElement* element() const { return m_element; } /** - * Get the data buffer. - * @return The data buffer. + * Get the data buffer + * @return The data buffer */ inline String& buffer() { return m_buffer; } /** - * Get the id member. - * @return The id member. + * Get the id member + * @return The id member */ inline const String& id() const { return m_id; } /** - * Get the remainig byte count to send. - * @return The unsent number of bytes. + * Get the remainig byte count to send + * @return The unsent number of bytes */ inline u_int32_t dataCount() { return m_buffer.length() - m_offset; } /** - * Get the remainig data to send. Set the buffer if not already set. - * @param nCount The number of unsent bytes. - * @return Pointer to the remaining data or 0. + * Get the remainig data to send. Set the buffer if not already set + * @param nCount The number of unsent bytes + * @return Pointer to the remaining data or 0 */ inline const char* getData(u_int32_t& nCount) { if (!m_buffer) @@ -462,8 +468,8 @@ public: } /** - * Increase the offset with nCount bytes. - * @param nCount The number of bytes sent. + * Increase the offset with nCount bytes + * @param nCount The number of bytes sent */ inline void dataSent(u_int32_t nCount) { m_offset += nCount; @@ -472,9 +478,9 @@ public: } /** - * Release the ownership of m_element. - * The caller is responsable of returned pointer. - * @return XMLElement pointer or 0. + * Release the ownership of m_element + * The caller is responsable of returned pointer + * @return XMLElement pointer or 0 */ inline XMLElement* release() { XMLElement* e = m_element; @@ -483,14 +489,14 @@ public: } /** - * Fill a buffer with the XML element to send. - * @param buffer The buffer to fill. + * Fill a buffer with the XML element to send + * @param buffer The buffer to fill */ inline void toBuffer(String& buffer) { if (m_element) m_element->toString(buffer); } /** - * Fill the buffer with the XML element to send. + * Fill the buffer with the XML element to send */ inline void prepareToSend() { toBuffer(m_buffer); } diff --git a/libs/yjingle/xmpputils.cpp b/libs/yjingle/xmpputils.cpp index a2cad4f7..49115298 100644 --- a/libs/yjingle/xmpputils.cpp +++ b/libs/yjingle/xmpputils.cpp @@ -48,7 +48,6 @@ TokenDict XMPPNamespace::s_value[] = { {"http://jabber.org/protocol/jingle/info/dtmf#errors", DtmfError}, {"http://jabber.org/protocol/commands", Command}, {"http://www.google.com/xmpp/protocol/voice/v1", CapVoiceV1}, - {0,0} }; @@ -144,11 +143,11 @@ void JabberID::set(const char* node, const char* domain, const char* resource) if (resource != m_resource.c_str()) m_resource = resource; clear(); - if (m_node.length()) + if (m_node) *this << m_node << "@"; *this << m_domain; m_bare = *this; - if (m_node.length() && m_resource.length()) + if (m_node && m_resource) *this << "/" << m_resource; } @@ -186,7 +185,7 @@ void JabberID::parse() } // Set bare JID m_bare = ""; - if (m_node.length()) + if (m_node) m_bare << m_node << "@"; m_bare << m_domain; } @@ -229,33 +228,6 @@ bool JIDIdentity::fromXML(const XMLElement* element) return true; } -/** - * JIDFeatureList - */ -JIDFeature* JIDFeatureList::get(XMPPNamespace::Type feature) -{ - ObjList* obj = m_features.skipNull(); - for (; obj; obj = obj->skipNext()) { - JIDFeature* f = static_cast(obj->get()); - if (*f == feature) - return f; - } - return 0; -} - -XMLElement* JIDFeatureList::addTo(XMLElement* element) -{ - if (!element) - return 0; - ObjList* obj = m_features.skipNull(); - for (; obj; obj = obj->skipNext()) { - JIDFeature* f = static_cast(obj->get()); - XMLElement* feature = new XMLElement(XMLElement::Feature); - feature->setAttribute("var",s_ns[*f]); - element->addChild(feature); - } - return element; -} /** * XMPPUtils @@ -402,6 +374,29 @@ XMLElement* XMPPUtils::createStreamError(XMPPError::Type error, const char* text return element; } +void XMPPUtils::decodeError(XMLElement* element, String& error, String& text) +{ + if (!element) + return; + if (element->type() != XMLElement::StreamError && element->type() != XMLElement::Error) + return; + + error = ""; + text = ""; + + element = element->findFirstChild("error"); + if (!element) + return; + XMLElement* child = element->findFirstChild(); + if (!child) + return; + error = child->name(); + child = element->findFirstChild("text"); + const char* t = child ? child->getText() : 0; + if (t) + text = t; +} + void XMPPUtils::print(String& xmlStr, XMLElement* element, const char* indent) { #define STARTLINE(indent) "\r\n" << indent @@ -463,22 +458,17 @@ bool XMPPUtils::split(NamedList& dest, const char* src, const char sep, { if (!src) return false; - u_int32_t index = 1; - for (u_int32_t i = 0; src[i];) { - // Skip separator(s) - for (; src[i] && src[i] == sep; i++) ; - // Find first separator - u_int32_t start = i; - for (; src[i] && src[i] != sep; i++) ; - // Get part - if (start != i) { - String tmp(src + start,i - start); - if (nameFirst) - dest.addParam(tmp,String((int)index++)); - else - dest.addParam(String((int)index++),tmp); - } + unsigned int index = 1; + String s = src; + ObjList* obj = s.split(sep,false); + for (ObjList* o = obj->skipNull(); o; o = o->skipNext(), index++) { + String* tmp = static_cast(o->get()); + if (nameFirst) + dest.addParam(*tmp,String(index)); + else + dest.addParam(String(index),*tmp); } + TelEngine::destruct(obj); return true; } diff --git a/libs/yjingle/xmpputils.h b/libs/yjingle/xmpputils.h index d1a9f6a7..13e9f3a4 100644 --- a/libs/yjingle/xmpputils.h +++ b/libs/yjingle/xmpputils.h @@ -48,18 +48,113 @@ */ namespace TelEngine { -class XMPPNamespace; -class XMPPErrorCode; -class XMPPError; -class JabberID; -class JIDIdentity; -class JIDFeature; -class JIDFeatureList; -class XMPPUtils; +class XMPPServerInfo; // Server info class +class XMPPNamespace; // XMPP namespaces +class XMPPError; // XMPP errors +class JabberID; // A Jabber ID (JID) +class JIDIdentity; // A JID's identity +class JIDFeature; // A JID's feature +class JIDFeatureList; // Feature list +class XMPPUtils; // Utilities + /** - * This class holds the XMPP/JabberComponent/Jingle namespace enumerations and the associated strings. - * @short XMPP namespaces. + * This class holds informations about a server + * @short Server info class + */ +class YJINGLE_API XMPPServerInfo : public RefObject +{ +public: + /** + * Constructor. Construct a full server info object + * @param name Server domain name + * @param address IP address + * @param port IP port + * @param password Component only: Password used for authentication + * @param identity Component only: the stream identity used when connecting + * @param fullidentity Component only: the user identity + * @param roster Component only: Keep the user roster + * @param autoRestart Component only: Auto restart stream when connection is down + */ + inline XMPPServerInfo(const char* name, const char* address, int port, + const char* password, const char* identity, const char* fullidentity, + bool roster, bool autoRestart) + : m_name(name), m_address(address), m_port(port), m_password(password), + m_identity(identity), m_fullIdentity(fullidentity), m_roster(roster) + {} + + /** + * Constructor. Construct a partial server info object + * @param name Server domain name + * @param port IP port + */ + inline XMPPServerInfo(const char* name, int port) + : m_name(name), m_port(port), m_roster(false) + {} + + /** + * Get the server's address + * @return The server's address + */ + inline const String& address() const + { return m_address; } + + /** + * Get the server's domain name + * @return The server's domain name + */ + inline const String& name() const + { return m_name; } + + /** + * Get the server's port used to connect to + * @return The server's port used to connect to + */ + inline const int port() const + { return m_port; } + + /** + * Get the server's port used to connect to + * @return The server's port used to connect to + */ + inline const String& password() const + { return m_password; } + + /** + * Get the server's identity + * @return The server's identity + */ + inline const String& identity() const + { return m_identity; } + + /** + * Get the server's full identity + * @return The server's full identity + */ + inline const String& fullIdentity() const + { return m_fullIdentity; } + + /** + * Check if someone should keep the roster for this server + * @return True if someone should keep the roster for this server + */ + inline bool roster() const + { return m_roster; } + +private: + String m_name; // Domain name + String m_address; // IP address + int m_port; // Port + String m_password; // Authentication data + String m_identity; // Identity. Used for Jabber Component protocol + String m_fullIdentity; // Full identity for this server + bool m_roster; // Keep roster for this server +}; + + +/** + * This class holds the XMPP/JabberComponent/Jingle namespace enumerations and the associated strings + * @short XMPP namespaces */ class YJINGLE_API XMPPNamespace { @@ -83,11 +178,20 @@ public: Count, }; + /** + * Get the string representation of a namespace value + */ inline const char* operator[](Type index) { return lookup(index,s_value); } + /** + * Check if a text is a known namespace + */ static bool isText(Type index, const char* txt); + /** + * Get the type associated with a given namespace text + */ static inline Type type(const char* txt) { int tmp = lookup(txt,s_value,Count); return tmp ? (Type)tmp : Count; @@ -97,6 +201,7 @@ private: static TokenDict s_value[]; // Namespace list }; + /** * This class holds the XMPP error type, error enumerations and associated strings * @short XMPP errors. @@ -104,19 +209,13 @@ private: class YJINGLE_API XMPPError { public: - // Error type - enum ErrorType { - TypeCancel = 0, // do not retry (the error is unrecoverable) - TypeContinue, // proceed (the condition was only a warning) - TypeModify, // retry after changing the data sent - TypeAuth, // retry after providing credentials - TypeWait, // retry after waiting (the error is temporary) - TypeCount, - }; - + /** + * Error condition enumeration + */ enum Type { + NoError = 0, // Stream errors - BadFormat = TypeCount + 1, // bad-format + BadFormat, // bad-format BadNamespace, // bad-namespace-prefix ConnTimeout, // connection-timeout HostGone, // host-gone @@ -167,11 +266,32 @@ public: Count, }; + /** + * Error type enumeration + */ + enum ErrorType { + TypeCancel = 1000, // do not retry (the error is unrecoverable) + TypeContinue, // proceed (the condition was only a warning) + TypeModify, // retry after changing the data sent + TypeAuth, // retry after providing credentials + TypeWait, // retry after waiting (the error is temporary) + TypeCount, + }; + + /** + * Get the text representation of a given error value + */ inline const char* operator[](int index) { return lookup(index,s_value); } + /** + * Check if a given text is a valid error + */ static bool isText(int index, const char* txt); + /** + * Get the type associated with a given error text + */ static inline int type(const char* txt) { return lookup(txt,s_value,Count); } @@ -179,53 +299,52 @@ private: static TokenDict s_value[]; // Error list }; + /** - * This class holds a Jabber ID in form "node@domain/resource" or "node@domain". - * @short A Jabber ID. + * This class holds a Jabber ID in form "node@domain/resource" or "node@domain" + * @short A Jabber ID */ class YJINGLE_API JabberID : public String { public: /** - * Constructor. + * Constructor */ inline JabberID() {} /** - * Constructor. - * Constructs a JID from a given string. - * @param jid The JID string. + * Constructor. Constructs a JID from a given string + * @param jid The JID string */ inline JabberID(const char* jid) { set(jid); } /** - * Constructor. - * Constructs a JID from user, domain, resource. - * @param node The node. - * @param domain The domain. - * @param resource The resource. + * Constructor. Constructs a JID from user, domain, resource + * @param node The node + * @param domain The domain + * @param resource The resource */ JabberID(const char* node, const char* domain, const char* resource = 0) { set(node,domain,resource); } /** - * Get the node part of the JID. - * @return The node part of the JID. + * Get the node part of the JID + * @return The node part of the JID */ inline const String& node() const { return m_node; } /** - * Get the bare JID: "node@domain". - * @return The bare JID. + * Get the bare JID: "node@domain" + * @return The bare JID */ inline const String& bare() const { return m_bare; } /** - * Get the domain part of the JID. - * @return The domain part of the JID. + * Get the domain part of the JID + * @return The domain part of the JID */ inline const String& domain() const { return m_domain; } @@ -238,51 +357,90 @@ public: { set(m_node.c_str(),d,m_resource.c_str()); } /** - * Get the resource part of the JID. - * @return The resource part of the JID. + * Get the resource part of the JID + * @return The resource part of the JID */ inline const String& resource() const { return m_resource; } + /** + * Check if this is a full JID + * @return True if this is a full JID + */ + inline bool isFull() const + { return m_node && m_domain && m_resource; } + /** * Try to match another JID to this one. If src has a resource compare it too - * (case sensitive). Otherwise compare just the bare JID (case insensitive). - * @param src The JID to match. - * @return True if matched. + * (case sensitive). Otherwise compare just the bare JID (case insensitive) + * @param src The JID to match + * @return True if matched */ inline bool match(const JabberID& src) const { return (src.resource().null() || (resource() == src.resource())) && (bare() &= src.bare()); } /** - * Set the resource part of the JID. - * @param res The new resource part of the JID. + * Equality operator. Do a case senitive resource comparison and a case insensitive bare jid comparison + * @param src The JID to compare with + * @return True if equal + */ + inline bool operator==(const JabberID& src) const + { return (resource() == src.resource()) && (bare() &= src.bare()); } + + /** + * Equality operator. Build a temporary JID and compare with it + * @param src The string to compare with + * @return True if equal + */ + inline bool operator==(const String& src) const + { JabberID tmp(src); return operator==(tmp); } + + /** + * Inequality operator + * @param src The JID to compare with + * @return True if not equal + */ + inline bool operator!=(const JabberID& src) const + { return !operator==(src); } + + /** + * Inequality operator + * @param src The string to compare with + * @return True if not equal + */ + inline bool operator!=(const String& src) const + { return !operator==(src); } + + /** + * Set the resource part of the JID + * @param res The new resource part of the JID */ inline void resource(const char* res) { set(m_node.c_str(),m_domain.c_str(),res); } /** - * Set the data. - * @param jid The JID string to assign. + * Set the data + * @param jid The JID string to assign */ void set(const char* jid); /** - * Set the data. - * @param node The node. - * @param domain The domain. - * @param resource The resource. + * Set the data + * @param node The node + * @param domain The domain + * @param resource The resource */ void set(const char* node, const char* domain, const char* resource = 0); /** - * Check if the given string contains valid characters. - * @param value The string to check. - * @return True if value is valid or 0. False if value is a non empty invalid string. + * Check if the given string contains valid characters + * @param value The string to check + * @return True if value is valid or 0. False if value is a non empty invalid string */ static bool valid(const String& value); /** - * Keep the regexp used to check the validity of a string. + * Keep the regexp used to check the validity of a string */ static Regexp s_regExpValid; @@ -295,13 +453,17 @@ private: String m_bare; // The bare JID: node@domain }; + /** - * This class holds an identity for a JID. - * @short JID identity. + * This class holds an identity for a JID + * @short A JID identity */ -class YJINGLE_API JIDIdentity : public String, public RefObject +class YJINGLE_API JIDIdentity : public RefObject, virtual public String { public: + /** + * JID category enumeration + */ enum Category { Account, // account Client, // client @@ -310,6 +472,9 @@ public: CategoryUnknown }; + /** + * JID type enumeration + */ enum Type { AccountRegistered, // registered ClientPhone, // phone @@ -319,26 +484,59 @@ public: TypeUnknown }; + /** + * Constructor. Build a JID identity + * @param c The JID's category + * @param t The JID's type + * @param name The name of this identity + */ inline JIDIdentity(Category c, Type t, const char* name = 0) : String(name), m_category(c), m_type(t) {} + /** + * Destructor + */ virtual ~JIDIdentity() {} + /** + * Build an XML element from this identity + * @return A valid XML element + */ XMLElement* toXML(); + /** + * Build this identity from an XML element + * @return True on succes + */ bool fromXML(const XMLElement* element); + /** + * Lookup for a text associated with a given category + * @return The category's name + */ static inline const char* categoryText(Category c) { return lookup(c,s_category); } + /** + * Lookup for a value associated with a given category name + * @return The category's value + */ static inline Category categoryValue(const char* c) { return (Category)lookup(c,s_category,CategoryUnknown); } + /** + * Lookup for a text associated with a given category type + * @return The category's type name + */ static inline const char* typeText(Type t) { return lookup(t,s_type); } + /** + * Lookup for a value associated with a given category type + * @return The category's type value + */ static inline Type typeValue(const char* t) { return (Type)lookup(t,s_category,TypeUnknown); } @@ -350,28 +548,29 @@ private: Type m_type; // Type }; + /** - * This class holds a JID feature. - * @short JID feature. + * This class holds a JID feature + * @short JID feature */ class YJINGLE_API JIDFeature : public RefObject { public: /** - * Constructor. + * Constructor */ inline JIDFeature(XMPPNamespace::Type feature) : m_feature(feature) {} /** - * Destructor. + * Destructor */ virtual ~JIDFeature() {} /** - * XMPPNamespace::Type conversion operator. + * XMPPNamespace::Type conversion operator */ inline operator XMPPNamespace::Type() { return m_feature; } @@ -380,17 +579,18 @@ private: XMPPNamespace::Type m_feature; // The feature }; + /** - * This class holds a list of features. - * @short Feature list. + * This class holds a list of features + * @short Feature list */ class YJINGLE_API JIDFeatureList { public: /** - * Add a feature to the list. - * @param feature The feature to add. - * @return False if the given feature already exists. + * Add a feature to the list + * @param feature The feature to add + * @return False if the given feature already exists */ inline bool add(XMPPNamespace::Type feature) { if (get(feature)) @@ -400,23 +600,23 @@ public: } /** - * Remove a feature from the list. - * @param feature The feature to remove. + * Remove a feature from the list + * @param feature The feature to remove */ inline void remove(XMPPNamespace::Type feature) { m_features.remove(get(feature),true); } /** - * Get a feature from the list. - * @param feature The feature to get. - * @return Pointer to the feature or 0 if it doesn't exists. + * Get a feature from the list + * @param feature The feature to get + * @return Pointer to the feature or 0 if it doesn't exists */ JIDFeature* get(XMPPNamespace::Type feature); /** - * Add 'feature' children to the given element. - * @param element The target XMLElement. - * @return The given element. + * Add 'feature' children to the given element + * @param element The target XMLElement + * @return The given element */ XMLElement* addTo(XMLElement* element); @@ -425,14 +625,14 @@ private: }; /** - * This class is a general XMPP utilities. - * @short General XMPP utilities. + * This class is a general XMPP utilities + * @short General XMPP utilities */ class YJINGLE_API XMPPUtils { public: /** - * Iq type enumeration. + * Iq type enumeration */ enum IqType { IqSet, // set @@ -443,7 +643,7 @@ public: }; /** - * Message type enumeration. + * Message type enumeration */ enum MsgType { MsgChat, // chat @@ -451,7 +651,7 @@ public: }; /** - * Command action enumeration. + * Command action enumeration */ enum CommandAction { CommExecute, @@ -462,7 +662,7 @@ public: }; /** - * Command status enumeration. + * Command status enumeration */ enum CommandStatus { CommExecuting, @@ -471,144 +671,152 @@ public: }; /** - * Create an XML element with an 'xmlns' attribute. - * @param name Element's name. - * @param ns 'xmlns' attribute. - * @param text Optional text for the element. - * @return A valid XMLElement pointer. + * Create an XML element with an 'xmlns' attribute + * @param name Element's name + * @param ns 'xmlns' attribute + * @param text Optional text for the element + * @return A valid XMLElement pointer */ static XMLElement* createElement(const char* name, XMPPNamespace::Type ns, const char* text = 0); /** - * Create an XML element with an 'xmlns' attribute. - * @param type Element's type. - * @param ns 'xmlns' attribute. - * @param text Optional text for the element. - * @return A valid XMLElement pointer. + * Create an XML element with an 'xmlns' attribute + * @param type Element's type + * @param ns 'xmlns' attribute + * @param text Optional text for the element + * @return A valid XMLElement pointer */ static XMLElement* createElement(XMLElement::Type type, XMPPNamespace::Type ns, const char* text = 0); /** - * Create a 'message' element. + * Create a 'message' element * @param type Message type as enumeration - * @param from The 'from' attribute. - * @param to The 'to' attribute. - * @param id The 'id' attribute. - * @param message The message body. - * @return A valid XMLElement pointer. + * @param from The 'from' attribute + * @param to The 'to' attribute + * @param id The 'id' attribute + * @param message The message body + * @return A valid XMLElement pointer */ static XMLElement* createMessage(MsgType type, const char* from, const char* to, const char* id, const char* message); /** - * Create an 'iq' element. + * Create an 'iq' element * @param type Iq type as enumeration - * @param from The 'from' attribute. - * @param to The 'to' attribute. - * @param id The 'id' attribute. - * @return A valid XMLElement pointer. + * @param from The 'from' attribute + * @param to The 'to' attribute + * @param id The 'id' attribute + * @return A valid XMLElement pointer */ static XMLElement* createIq(IqType type, const char* from, const char* to, const char* id); /** - * Create an 'iq' element with a 'bind' child containing the resources. - * @param from The 'from' attribute. - * @param to The 'to' attribute. - * @param id The 'id' attribute. - * @param resources The resources to bind (strings). - * @return A valid XMLElement pointer. + * Create an 'iq' element with a 'bind' child containing the resources + * @param from The 'from' attribute + * @param to The 'to' attribute + * @param id The 'id' attribute + * @param resources The resources to bind (strings) + * @return A valid XMLElement pointer */ static XMLElement* createIqBind(const char* from, const char* to, const char* id, const ObjList& resources); /** * Create a 'command' element - * @param action The command action. - * @param node The command. - * @param sessionId Optional session ID for the command. - * @return A valid XMLElement pointer. + * @param action The command action + * @param node The command + * @param sessionId Optional session ID for the command + * @return A valid XMLElement pointer */ static XMLElement* createCommand(CommandAction action, const char* node, const char* sessionId = 0); /** - * Create an 'identity' element. - * @param category The 'category' attribute. - * @param type The 'type' attribute. - * @param name The 'name' attribute. - * @return A valid XMLElement pointer. + * Create an 'identity' element + * @param category The 'category' attribute + * @param type The 'type' attribute + * @param name The 'name' attribute + * @return A valid XMLElement pointer */ static XMLElement* createIdentity(const char* category, const char* type, const char* name); /** - * Create an 'iq' of type 'get' element with a 'query' child. - * @param from The 'from' attribute. - * @param to The 'to' attribute. - * @param id The 'id' attribute. - * @param info True to create a query info request. False to create a query items request. - * @return A valid XMLElement pointer. + * Create an 'iq' of type 'get' element with a 'query' child + * @param from The 'from' attribute + * @param to The 'to' attribute + * @param id The 'id' attribute + * @param info True to create a query info request. False to create a query items request + * @return A valid XMLElement pointer */ static XMLElement* createIqDisco(const char* from, const char* to, const char* id, bool info = true); /** - * Create a 'error' element. - * @param type Error type. - * @param error The error. - * @param text Optional text to add to the error element. - * @return A valid XMLElement pointer. + * Create a 'error' element + * @param type Error type + * @param error The error + * @param text Optional text to add to the error element + * @return A valid XMLElement pointer */ static XMLElement* createError(XMPPError::ErrorType type, XMPPError::Type error, const char* text = 0); /** - * Create a 'stream:error' element. - * @param error The XMPP defined condition. - * @param text Optional text to add to the error. - * @return A valid XMLElement pointer. + * Create a 'stream:error' element + * @param error The XMPP defined condition + * @param text Optional text to add to the error + * @return A valid XMLElement pointer */ static XMLElement* createStreamError(XMPPError::Type error, const char* text = 0); /** - * Print an XMLElement to a string. - * @param xmlStr The destination string. - * @param element The element to print. - * @param indent The indent. 0 if it is the root element. + * Decode a received stream error or stanza error + * @param element The received element + * @param error The error condition + * @param text The stanza's error or error text + */ + static void decodeError(XMLElement* element, String& error, String& text); + + /** + * Print an XMLElement to a string + * @param xmlStr The destination string + * @param element The element to print + * @param indent The indent. 0 if it is the root element */ static void print(String& xmlStr, XMLElement* element, const char* indent = 0); /** - * Split a string at a delimiter character and fills a named list with its parts. - * Skip empty parts. - * @param dest The destination NamedList. - * @param src Pointer to the string. - * @param sep The delimiter. + * Split a string at a delimiter character and fills a named list with its parts + * Skip empty parts + * @param dest The destination NamedList + * @param src Pointer to the string + * @param sep The delimiter * @param nameFirst True to add the parts as name and index as value. - * False to do the other way. + * False to do the other way */ static bool split(NamedList& dest, const char* src, const char sep, bool nameFirst); /** - * Get the type of an 'iq' stanza as enumeration. - * @param text The text to check. - * @return Iq type as enumeration. + * Get the type of an 'iq' stanza as enumeration + * @param text The text to check + * @return Iq type as enumeration */ static inline IqType iqType(const char* text) { return (IqType)lookup(text,s_iq,IqCount); } /** - * Keep the types of 'iq' stanzas. + * Keep the types of 'iq' stanzas */ static TokenDict s_iq[]; /** - * Keep the types of 'message' stanzas. + * Keep the types of 'message' stanzas */ static TokenDict s_msg[]; diff --git a/libs/yjingle/yatejabber.h b/libs/yjingle/yatejabber.h index bb9d07ad..57abe62a 100644 --- a/libs/yjingle/yatejabber.h +++ b/libs/yjingle/yatejabber.h @@ -32,134 +32,170 @@ */ namespace TelEngine { -class JBEvent; -class JBComponentStream; -class JBServerInfo; -class JBEngine; -class JBClient; -class JBPresence; -class JIDResource; -class JIDResourceList; -class XMPPUser; -class XMPPUserRoster; +class JBEvent; // A Jabber event +class JBStream; // A Jabber stream +class JBComponentStream; // A Jabber Component stream +class JBClientStream; // A Jabber client to server stream +class JBThread; // Base class for private threads +class JBThreadList; // A list of threads +class JBEngine; // A Jabber engine +class JBService; // A Jabber service +class JBPresence; // A Jabber presence service +class JIDResource; // A JID resource +class JIDResourceList; // Resource list +class XMPPUser; // An XMPP user (JID, resources, capabilities etc) +class XMPPUserRoster; // An XMPP user's roster /** - * This class holds a Jabber Component stream event. - * @short A Jabber Component event. + * This class holds a Jabber stream event. Stream events are raised by streams + * and sent by the engine to the proper service + * @short A Jabber stream event */ class YJINGLE_API JBEvent : public RefObject { - friend class JBComponentStream; + friend class JBStream; public: + /** + * Event type enumeration + */ enum Type { // Stream events Terminated = 1, // Stream terminated. Try to connect Destroy = 2, // Stream is destroying + Running = 3, // Stream is running (stable state: can send/recv stanzas) // Result events - WriteFail = 10, // Write failed - // m_element is the element - // m_id is the id set by the sender - // Stanza events + WriteFail = 10, // Write failed. m_element is the element, m_id is the id set by the sender + // Stanza events: m_element is always valid Presence = 20, // m_element is a 'presence' stanza Message = 30, // m_element is a 'message' stanza - Iq = 50, // m_element is an unknown 'iq' element - // m_child may be an unexpected element - IqError = 51, // m_element is an 'iq' error - // m_child is the 'error' child if any - IqResult = 52, // m_element is an 'iq' result - IqDiscoGet = 60, // m_element is an 'iq' get - // m_child is a 'query' element qualified by - // XMPPNamespace::DiscoInfo namespace - IqDiscoSet = 61, // m_element is an 'iq' set - // m_child is a 'query' element qualified by - // XMPPNamespace::DiscoInfo namespace - IqDiscoRes = 62, // m_element is an 'iq' result - // m_child is a 'query' element qualified by - // XMPPNamespace::DiscoInfo namespace - IqCommandGet = 70, // m_element is an 'iq' of type get - // m_child is a 'command' element qualified by - // XMPPNamespace::Command namespace - IqCommandSet = 71, // m_element is an 'iq' of type set - // m_child is a 'command' element qualified by - // XMPPNamespace::Command namespace - IqCommandRes = 72, // m_element is an 'iq' result - // m_child is a 'command' element qualified by - // XMPPNamespace::Command namespace - IqJingleGet = 100, // m_element is an 'iq' get - // m_child is a 'jingle' element - IqJingleSet = 101, // m_element is an 'iq' set - // m_child is a 'jingle' element + Iq = 50, // m_element is an 'iq' set/get, m_child is it's first child + IqError = 51, // m_element is an 'iq' error, m_child is the 'error' child if any + IqResult = 52, // m_element is an 'iq' result, m_child is it's first child + // Disco: m_child is a 'query' element qualified by DiscoInfo/DiscoItems namespaces + // IqDisco error: m_child is the 'error' child, m_element has a 'query' child + IqDiscoInfoGet = 60, + IqDiscoInfoSet = 61, + IqDiscoInfoRes = 62, + IqDiscoInfoErr = 63, + IqDiscoItemsGet = 64, + IqDiscoItemsSet = 65, + IqDiscoItemsRes = 66, + IqDiscoItemsErr = 67, + // Command: m_child is a 'command' element qualified by Command namespace + // IqCommandError: m_child is the 'error' child, m_element has a 'command' child + IqCommandGet = 70, + IqCommandSet = 71, + IqCommandRes = 72, + IqCommandErr = 73, + // Jingle: m_child is a 'jingle' element qualified by Jingle namespace + // IqJingleError: m_child is the 'error' child, m_element has a 'jingle' child + IqJingleGet = 80, + IqJingleSet = 81, + IqJingleRes = 82, + IqJingleErr = 83, // Invalid Unhandled = 200, // m_element is an unhandled element Invalid = 500, // m_element is 0 }; /** - * Destructor. - * Delete the XML element if valid. + * Constructor. Constructs an event from a stream + * @param type Type of this event + * @param stream The stream that generated the event + * @param element Element that generated the event + * @param child Optional type depending element's child + */ + JBEvent(Type type, JBStream* stream, XMLElement* element, XMLElement* child = 0); + + /** + * Constructor. Constructs a WriteSuccess/WriteFail event from a stream + * @param type Type of this event + * @param stream The stream that generated the event + * @param element Element that generated the event + * @param senderID Sender's id + */ + JBEvent(Type type, JBStream* stream, XMLElement* element, const String& senderID); + + /** + * Destructor. Delete the XML element if valid */ virtual ~JBEvent(); /** - * Get the event type. - * @return the type of this event as enumeration. + * Get the event type + * @return The type of this event as enumeration */ - inline Type type() const + inline int type() const { return m_type; } /** - * Get the element's 'type' attribute if any. - * @return The element's 'type' attribute. + * Get the event name + * @return The name of this event + */ + inline const char* name() const + { return lookup(type()); } + + /** + * Get the element's 'type' attribute if any + * @return The element's 'type' attribute */ inline const String& stanzaType() const { return m_stanzaType; } /** - * Get the 'from' data. - * @return The 'from' data. + * Get the 'from' attribute of a received stanza + * @return The 'from' attribute */ inline const String& from() const { return m_from; } /** - * Get the 'to' data. - * @return The 'to' data. + * Get the 'to' attribute of a received stanza + * @return The 'to' attribute */ inline const String& to() const { return m_to; } /** - * Get the 'id' data. - * @return The 'id' data. + * Get the sender's id for Write... events or the 'id' attribute if the + * event carries a received stanza + * @return The event id */ inline const String& id() const { return m_id; } /** - * Get the stream. - * @return The stream. + * The stanza's text or termination reason for Terminated/Destroy events + * @return The event's text */ - inline JBComponentStream* stream() const + inline const String& text() const + { return m_text; } + + /** + * Get the stream that generated this event + * @return The stream that generated this event + */ + inline JBStream* stream() const { return m_stream; } /** - * Get the underlying XMLElement. - * @return XMLElement pointer or 0. + * Get the underlying XMLElement + * @return XMLElement pointer or 0 */ inline XMLElement* element() const { return m_element; } /** - * Get the first child of the underlying element if any. - * @return XMLElement pointer or 0. + * Get the first child of the underlying element if any + * @return XMLElement pointer or 0 */ inline XMLElement* child() const { return m_child; } /** * Get the underlying XMLElement. Release the ownership. - * The caller is responsable of returned pointer. - * @return XMLElement pointer or 0. + * The caller is responsable of returned pointer + * @return XMLElement pointer or 0 */ inline XMLElement* releaseXML() { XMLElement* tmp = m_element; @@ -168,47 +204,24 @@ public: } /** - * Release the link with the stream to let the stream continue with events. + * Release the link with the stream to let the stream continue with events */ void releaseStream(); -protected: /** - * Constructor. - * Constructs an internal stream event. - * @param type Type of this event. - * @param stream The stream that generated the event. + * Get the name of an event type + * @return The name an event type */ - JBEvent(Type type, JBComponentStream* stream); - - /** - * Constructor. - * Constructs an event from a stream. - * @param type Type of this event. - * @param stream The stream that generated the event. - * @param element Element that generated the event. - * @param child Optional type depending element's child. - */ - JBEvent(Type type, JBComponentStream* stream, - XMLElement* element, XMLElement* child = 0); - - /** - * Constructor. - * Constructs a WriteSuccess/WriteFail event from a stream. - * @param type Type of this event. - * @param stream The stream that generated the event. - * @param element Element that generated the event. - * @param senderID Sender's id. - */ - JBEvent(Type type, JBComponentStream* stream, XMLElement* element, - const String& senderID); + inline static const char* lookup(int type) + { return TelEngine::lookup(type,s_type); } private: + static TokenDict s_type[]; // Event names JBEvent() {} // Don't use it! - bool init(JBComponentStream* stream, XMLElement* element); + bool init(JBStream* stream, XMLElement* element); Type m_type; // Type of this event - JBComponentStream* m_stream; // The stream that generated this event + JBStream* m_stream; // The stream that generated this event bool m_link; // Stream link state XMLElement* m_element; // Received XML element, if any XMLElement* m_child; // The first child element for 'iq' elements @@ -217,446 +230,678 @@ private: String m_to; // Stanza's 'to' attribute String m_id; // Sender's id for Write... events // 'id' attribute if the received stanza has one + String m_text; // The stanza's text or termination reason for + // Terminated/Destroy events +}; + + +/** + * A socket used used to transport data for a Jabber stream + * @short A Jabber streams's socket + */ +class YJINGLE_API JBSocket +{ + friend class JBStream; +public: + /** + * Constructor + * @param engine The Jabber engine + * @param stream The stream owning this socket + */ + inline JBSocket(JBEngine* engine, JBStream* stream) + : m_engine(engine), m_stream(stream), m_socket(0), + m_streamMutex(true), m_receiveMutex(true) + {} + + /** + * Destructor. Close the socket + */ + inline ~JBSocket() + { terminate(); } + + /** + * Check if the socket is valid + * @return True if the socket is valid. + */ + inline bool valid() const + { return m_socket && m_socket->valid(); } + + /** + * Connect the socket + * @param addr Remote address to connect to + * @param terminated True if false is returned and the socket was terminated + * while connecting + * @return False on failure + */ + bool connect(const SocketAddr& addr, bool& terminated); + + /** + * Terminate the socket + */ + void terminate(); + + /** + * Read data from socket + * @param buffer Destination buffer + * @param len The number of bytes to read. On exit contains the number of + * bytes actually read + * @return False on socket error + */ + bool recv(char* buffer, unsigned int& len); + + /** + * Write data to socket + * @param buffer Source buffer + * @param len The number of bytes to send + * @return False on socket error + */ + bool send(const char* buffer, unsigned int& len); + +private: + JBEngine* m_engine; // The Jabber engine + JBStream* m_stream; // Stream owning this socket + Socket* m_socket; // The socket + Mutex m_streamMutex; // Lock stream + Mutex m_receiveMutex; // Lock receive }; /** - * This class holds a Jabber Component stream (implements the Jabber Component Protocol). - * @short A Jabber Component stream. + * Base class for all Jabber streams. Basic stream data processing: send/receive + * XML elements, keep stream state, generate events + * @short A Jabber stream */ -class YJINGLE_API JBComponentStream : public RefObject, public Mutex +class YJINGLE_API JBStream : public RefObject { -public: friend class JBEngine; + friend class JBEvent; public: /** * Stream state enumeration. */ enum State { - WaitToConnect, // Outgoing stream is waiting for the socket to connect - Started, // Stream start sent - Auth, // Authentication (handshake) sent - Running, // Authenticated. Allow any XML element to pass over the stream - Terminated, // Stream is terminated. Wait to be restarted or destroyed - Destroy, // Stream is destroying. No more traffic allowed + Idle = 0, // Stream is waiting to be connected or destroyed + Connecting = 1, // Stream is waiting for the socket to connect + Started = 2, // Stream start tag sent + Securing = 3, // Stream is currently negotiating the TLS + Auth = 4, // Stream is currently authenticating + Running = 5, // Established. Allow XML stanzas to pass over the stream + Destroy = 6, // Stream is destroying. No more traffic allowed }; /** * Values returned by send() methods. */ enum Error { - ErrorNone = 0, // No error - ErrorContext, // Invalid stream context (state) or parameters. - ErrorPending, // The operation is pending in the stream's queue. - ErrorNoSocket, // Unrecoverable socket error. - // The stream will be terminated. + ErrorNone = 0, // No error (stanza enqueued/sent) + ErrorContext, // Invalid stream context (state) or parameters + ErrorPending, // The operation is pending in the stream's queue + ErrorNoSocket, // Unrecoverable socket error. The stream will be terminated }; /** - * Destructor. - * Close the stream and the socket. + * Constructor + * @param engine The engine that owns this stream + * @param localJid Local party's JID + * @param remoteJid Remote party's JID + * @param password Password used for authentication + * @param address The remote address to connect to + * @param autoRestart True to auto restart the stream + * @param maxRestart The maximum restart attempts allowed + * @param incRestartInterval The interval to increase the restart counter + * @param outgoing Stream direction + * @param type Stream type. Set to -1 to use the engine's protocol type */ - virtual ~JBComponentStream(); + JBStream(JBEngine* engine, const JabberID& localJid, const JabberID& remoteJid, + const String& password, const SocketAddr& address, + bool autoRestart, unsigned int maxRestart, + u_int64_t incRestartInterval, bool outgoing, int type = -1); /** - * Get the stream state. + * Destructor. + * Gracefully close the stream and the socket + */ + virtual ~JBStream(); + + /** + * Get the type of this stream. See the protocol enumeration of the engine + * @return The type of this stream + */ + inline int type() const + { return m_type; } + + /** + * Get the stream state * @return The stream state as enumeration. */ inline State state() const { return m_state; } /** - * Get the local name. - * @return The local name. + * Get the stream direction + * @return True if the stream is an outgoing one */ - inline const String& localName() const - { return m_localName; } + inline bool outgoing() const + { return m_outgoing; } /** - * Get the remote server name. - * @return The remote server name. - */ - inline const String& remoteName() const - { return m_remoteName; } - - /** - * Get the local name. - * @return The local name. - */ - inline const SocketAddr& remoteAddr() const - { return m_remoteAddr; } - - /** - * Get the stream id. - * @return The stream id. + * Get the stream id + * @return The stream id */ inline const String& id() const { return m_id; } /** - * Get the stream's connection. - * @return The stream connection's pointer. + * Get the stream's owner + * @return Pointer to the engine owning this stream */ inline JBEngine* engine() const { return m_engine; } /** - * Connect the stream to the server. + * Get the JID of the local side of this stream + * @return The JID of the local side of this stream + */ + inline const JabberID& local() const + { return m_local; } + + /** + * Get the JID of the remote side of this stream + * @return The JID of the remote side of this stream + */ + inline const JabberID& remote() const + { return m_remote; } + + /** + * Get the remote perr's address + * @return The remote perr's address + */ + inline const SocketAddr& addr() const + { return m_address; } + + /** + * Connect the stream. Send stream start tag on success + * This method is thread safe */ void connect(); /** - * Cleanup the stream. Terminate/Destroy it. Raise the appropriate event. - * This method is thread safe. - * @param destroy True to destroy. False to terminate. - * @param sendEnd True to send stream termination tag. - * @param error Optional XML element to send before termination tag. - * @param sendError True to send the error element. - */ - void terminate(bool destroy, bool sendEnd = false, - XMLElement* error = 0, bool sendError = false); - - /** - * Read data from socket and pass it to the parser. - * Raise event on bad XML. - * This method is thread safe. - * @return True if data was received. + * Read data from socket and pass it to the parser. Terminate stream on + * socket or parser error. + * This method is thread safe + * @return True if data was received */ bool receive(); /** * Send a stanza. - * This method is thread safe. - * @param stanza Element to send. - * @param senderId Optional sender's id. Used for notification events. - * @return The result of posting the stanza. + * This method is thread safe + * @param stanza Element to send + * @param senderId Optional sender's id. Used for notification events + * @return The result of posting the stanza */ Error sendStanza(XMLElement* stanza, const char* senderId = 0); /** + * Stream state and data processor. Increase restart counter. + * Restart stream if idle and auto restart. * Extract an element from parser and construct an event. - * This method is thread safe. - * @param time Current time. - * @return XMPPEvent pointer or 0. + * This method is thread safe + * @param time Current time + * @return JBEvent pointer or 0 */ JBEvent* getEvent(u_int64_t time); /** - * Cancel pending outgoing elements. - * @param raise True to raise WriteFail events. - * @param id Optional 'id' to cancel. 0 to cancel all elements without id. + * Terminate stream. Send stream end tag or error. + * Remove pending stanzas without id. Deref stream if destroying. + * This method is thread safe + * @param destroy True to destroy. False to terminate + * @param recvStanza Received stanza, if any + * @param error Termination reason. Set it to NoError to send stream end tag + * @param reason Optional text to be added to the error stanza + * @param send True to send the error element (ignored if error is NoError) + * @param final True if called from destructor */ - void cancelPending(bool raise, const String* id = 0); + void terminate(bool destroy, XMLElement* recvStanza, XMPPError::Type error, const char* reason, + bool send, bool final = false); /** - * Event termination notification. - * @param event The notifier. Must be m_lastEvent. + * Remove pending stanzas with a given id. + * This method is thread safe + * @param id The id of stanzas to remove + * @param notify True to raise an event for each removed stanza */ - void eventTerminated(const JBEvent* event); + inline void removePending(const String& id, bool notify = false) { + Lock lock(m_socket.m_streamMutex); + removePending(notify,&id,false); + } + + /** + * Get the name of a stream state + * @param state The requested state number + * @return The name of the requested state + */ + static const char* lookupState(int state); protected: /** - * Constructor. - * Constructs an outgoing stream. - * @param engine The engine that owns this stream. - * @param remoteName The remote domain name. - * @param remoteAddr The remote address to connect. + * Default constructor */ - JBComponentStream(JBEngine* engine, const String& remoteName, - const SocketAddr& remoteAddr); + inline JBStream() + : m_socket(0,0) + {} /** - * Send data without passing it through the outgoing queue. - * Change state if operation succeeds. Terminate stream if it fails. - * This method is thread safe. - * @param element The XML element to send. - * @param newState The new state if the operation succeeds. - * @param before Optional XML element to send before element. - * @return False if the write operation fails. + * Close the stream. Release memory */ - bool sendStreamXML(XMLElement* element, State newState, - XMLElement* before = 0); + virtual void destroyed(); /** - * Send a 'stream:error' element. Terminate the stream. - * @param error The XMPP defined condition. - * @param text Optional text to add to the error. + * Check the 'to' attribute of a received element + * @param xml The received element + * @return False to reject it. If the stream is not in Running state, + * it will be terminated */ - inline void sendStreamError(XMPPError::Type error, const char* text = 0) - { terminate(false,true,XMPPUtils::createStreamError(error,text)); } + virtual bool checkDestination(XMLElement* xml) + { return true; } /** - * Send an 'iq' of type 'error'. - * @param stanza Element that generated the error. - * @param eType The error type. - * @param eCond The error condition. - * @param eText Optional text to add to the error stanza. - * @return The result of posting the stanza. + * Get the starting stream element to be sent after stream connected + * @return XMLElement pointer */ - Error sendIqError(XMLElement* stanza, XMPPError::ErrorType eType, - XMPPError::Type eCond, const char* eText = 0); + virtual XMLElement* getStreamStart() = 0; /** - * Cleanup stream. - * Remove the first element from the outgoing list if partially sent. - * Send stream termination tag if requested. - * Cancel all outgoing elements without id. Destroy the socket. - * This method is thread safe. - * @param endStream True to send stream termination tag. - * @param element Optional element to send before. + * Process a received stanza in Running state + * @param xml Valid XMLElement pointer */ - void cleanup(bool endStream, XMLElement* element = 0); + virtual void processRunning(XMLElement* xml); /** - * Post an XMLElement in the outgoing queue. Send the first element in the queue. - * This method is thread safe. - * @param element The XMLElementOut to post. - * @return ErrorNone, ErrorContext, ErrorPending, ErrorNoSocket. + * Process a received element in Auth state. Descendants MUST consume the data + * @param xml Valid XMLElement pointer */ - Error postXML(XMLElementOut* element); + virtual void processAuth(XMLElement* xml) + { dropXML(xml,false); } /** - * Send the first element from the outgoing queue if state is Running. - * Raise WriteFail event if operation fails. Adjust buffer on partial writes. - * @return ErrorNone, ErrorContext, ErrorPending, ErrorNoSocket. + * Process a received element in Securing state. Descendants MUST consume the data + * @param xml Valid XMLElement pointer */ - Error sendXML(); + virtual void processSecuring(XMLElement* xml) + { dropXML(xml,false); } /** - * Process the received XML elements. Validate them. Add events. - * @return True if generated any event(s). + * Process a received element in Started state. Descendants MUST consume the data + * @param xml Valid XMLElement pointer */ - bool processIncomingXML(); + virtual void processStarted(XMLElement* xml) + { dropXML(xml,false); } /** - * Process a received element in state Started. - * @param e The element to process. - * @return True if generated any event(s). + * Create an iq event from a received iq stanza + * @param xml Received element + * @param iqType The iq type + * @param error Error type if 0 is returned + * @return JBEvent pointer or 0 */ - bool processStateStarted(XMLElement* e); + JBEvent* getIqEvent(XMLElement* xml, int iqType, XMPPError::Type& error); /** - * Process a received element in state Auth. - * @param e The element to process. - * @return True if generated any event(s). + * Send stream XML elements through the socket + * @param e The element to send + * @param newState The new stream state on success + * @param streamEnd Stream element is a stream ending one: end tag or stream error + * @return False if send failed (stream termination was initiated) */ - bool processStateAuth(XMLElement* e); + bool sendStreamXML(XMLElement* e, State newState, bool streamEnd); /** - * Process a received element in state Running. - * @param e The element to process. - * @return True if generated any event(s). + * Terminate stream on receiving invalid elements + * @param xml Received element + * @param error Termination reason + * @param reason Optional text to be added to the error stanza */ - bool processStateRunning(XMLElement* e); + void invalidStreamXML(XMLElement* xml, XMPPError::Type error, const char* reason); /** - * Process a received 'iq' element. - * @param e The element to process. - * @return True if generated any event(s). + * Drop an unexpected or unhandled element + * @param xml Received element + * @param unexpected True if unexpected */ - bool processIncomingIq(XMLElement* e); + void dropXML(XMLElement* xml, bool unexpected = true); /** - * Add an event to the list. - * @param type Event type. - * @param element Optional XML element. - * @param child Optional child element. - * @return The added event. + * Change stream's state + * @param newState the new stream state */ - JBEvent* addEvent(JBEvent::Type type, XMLElement* element = 0, - XMLElement* child = 0); + void changeState(State newState); /** - * Add a notification event to the list if the element has an id. - * Remove the element from the outgoing queue. - * @param type Event type. - * @param element XMLElementOut element that generated the event. - * @return True if the event was added. + * The password used for authentication */ - bool addEventNotify(JBEvent::Type type, XMLElementOut* element); - - /** - * Actions to take when an invalid element is received. - * Delete element. Send a stream error to remote peer. Raise a Terminated event. - * @param e The invalid element. - * @param type Stream error type. - * @param text Optional text to add to the stream error. - * @return True. - */ - bool invalidElement(XMLElement* e, XMPPError::Type type, - const char* text = 0); - - /** - * Actions to take when an unexpected element is received. - * Delete element. - * @param e The unexpected element. - * @return False. - */ - bool unexpectedElement(XMLElement* e); - - /** - * Check if a given element is a termination one (stream error or stream end). - * Raise a Terminated event if so. - * If true is returned the element is still valid. - * @param e The element to check. - * @return True if a Terminated event was raised. - */ - bool isStreamEnd(XMLElement* e); - - /** - * Read data from socket and pass it to the parser. Terminate on error. - * @param data The destination buffer. - * @param len The maximum number of bytes to read. Bytes read on exit. - * @return True if data was received. - */ - bool readSocket(char* data, u_int32_t& len); - - /** - * Write data to socket. Terminate on error. - * @param data The source buffer. - * @param len The buffer length on input. Bytes written on output. - * @return False on socket error. State is Terminated on unrecoverable socket error. - */ - bool writeSocket(const char* data, u_int32_t& len); + String m_password; private: - JBComponentStream() {} // Don't use it! + // Event termination notification + // @param event The notifier. Ignored if it's not m_lastEvent + void eventTerminated(const JBEvent* event); + // Try to send the first element in pending outgoing stanzas list + // If ErrorNoSocket is returned, stream termination was initiated + Error sendPending(); + // Send XML elements through the socket + // @param e The element to send (used to print it to output) + // @param stream True if this is a stream element. If true, try to send any partial data + // remaining from the last stanza transmission + // @param streamEnd Stream element is a stream ending one: end tag or stream error + // @return The result of the transmission. For stream elements, stream termination was initiated + // if returned value is not ErrorNone + Error sendXML(XMLElement* e, const char* buffer, unsigned int& len, + bool stream = false, bool streamEnd = false); + // Remove pending elements with id if id is not 0 + // Remove all elements without id if id is 0 + // Set force to true to remove the first element even if partially sent + void removePending(bool notify, const String* id, bool force); - // State + int m_type; // Stream type State m_state; // Stream state - // Info + bool m_outgoing; // Stream direction + bool m_autoRestart; // Auto restart flag + unsigned int m_restart; // Remaining restart attempts + unsigned int m_restartMax; // Max restart attempts + u_int64_t m_timeToFillRestart; // Next time to increase the restart counter + u_int64_t m_fillRestartInterval; // Interval to increase the restart counter String m_id; // Stream id - String m_localName; // Local name received from the remote peer - String m_remoteName; // Remote peer's domain name - SocketAddr m_remoteAddr; // Socket's address - String m_password; // Password used for authentication - // Data + JabberID m_local; // Local peer's jid + JabberID m_remote; // Remote peer's jid JBEngine* m_engine; // The owner of this stream - Socket* m_socket; // The socket used by this stream + JBSocket m_socket; // The socket used by this stream + SocketAddr m_address; // Remote peer's address XMLParser m_parser; // XML parser - Mutex m_receiveMutex; // Ensure serialization of received data ObjList m_outXML; // Outgoing XML elements ObjList m_events; // Event queue JBEvent* m_lastEvent; // Last generated event JBEvent* m_terminateEvent; // Destroy/Terminate event + JBEvent* m_startEvent; // Running event }; /** - * This class holds info about a component server used by the Jabber engine. - * @short Server info. + * This class holds a Jabber Component stream (implements the Jabber Component Protocol). + * @short A Jabber Component stream */ -class YJINGLE_API JBServerInfo : public RefObject +class YJINGLE_API JBComponentStream : public JBStream { -public: - inline JBServerInfo(const char* name, const char* address, int port, - const char* password, const char* identity, const char* fullidentity, - bool roster, bool autoRestart, u_int32_t restartCount) - : m_name(name), m_address(address), m_port(port), m_password(password), - m_identity(identity), m_fullIdentity(fullidentity), m_roster(roster), - m_autoRestart(autoRestart), m_restartCount(restartCount) - {} - virtual ~JBServerInfo() {} - inline const String& address() const - { return m_address; } - inline const String& name() const - { return m_name; } - inline const int port() const - { return m_port; } - inline const String& password() const - { return m_password; } - inline const String& identity() const - { return m_identity; } - inline const String& fullIdentity() const - { return m_fullIdentity; } - inline bool roster() const - { return m_roster; } - inline bool autoRestart() const - { return m_autoRestart; } - inline u_int32_t restartCount() const - { return m_restartCount; } - inline void incRestart() - { m_restartCount++; } - inline bool getRestart() { - if (!restartCount()) - return false; - m_restartCount--; - return true; - } -private: - String m_name; // Domain name - String m_address; // IP address - int m_port; // Port - String m_password; // Authentication data - String m_identity; // Identity. Used for Jabber Component protocol - String m_fullIdentity; // Full identity for this server - bool m_roster; // Keep roster for this server - bool m_autoRestart; // Automatically restart stream - u_int32_t m_restartCount; // Restart counter -}; - -/** - * This class holds a Jabber engine. - * @short A Jabber engine. - */ -class YJINGLE_API JBEngine : public DebugEnabler, public Mutex, public RefObject -{ - friend class JBEvent; - friend class JBComponentStream; - friend class JBClient; - friend class JBPresence; + friend class JBEngine; public: /** - * Jabber protocol type. + * Destructor */ - enum Protocol { - Component, // Use Jabber Component protocol + virtual ~JBComponentStream() + {} + +protected: + /** + * Constructor + * @param engine The engine that owns this stream + * @param localJid Local party's JID + * @param remoteJid Remote party's JID + * @param password Password used for authentication + * @param address The remote address to connect to + * @param autoRestart True to auto restart the stream + * @param maxRestart The maximum restart attempts allowed + * @param incRestartInterval The interval to increase the restart counter + * @param outgoing Stream direction + */ + JBComponentStream(JBEngine* engine, + const JabberID& localJid, const JabberID& remoteJid, + const String& password, const SocketAddr& address, + bool autoRestart, unsigned int maxRestart, + u_int64_t incRestartInterval, bool outgoing = true); + + /** + * Get the starting stream element to be sent after stream connected + * @return XMLElement pointer + */ + virtual XMLElement* getStreamStart(); + + /** + * Process a received element in Auth state + * @param xml Valid XMLElement pointer + */ + virtual void processAuth(XMLElement* xml); + + /** + * Process a received element in Started state + * @param xml Valid XMLElement pointer + */ + virtual void processStarted(XMLElement* xml); + +private: + // Default constructor is private to avoid unwanted use + JBComponentStream() {} + + bool m_shaAuth; // Use SHA1/MD5 digest authentication +}; + +/** + * This class holds a Jabber client stream used to connect an user to its server + * @short A Jabber client to server stream + */ +class YJINGLE_API JBClientStream : public JBStream +{ + friend class JBEngine; +public: + /** + * Destructor + */ + virtual ~JBClientStream() + {} + +protected: + /** + * Constructor + * @param engine The engine that owns this stream + * @param jid User's JID + * @param password Password used for authentication + * @param address The remote address to connect to + * @param maxRestart The maximum restart attempts allowed + * @param incRestartInterval The interval to increase the restart counter + * @param outgoing Stream direction + */ + JBClientStream(JBEngine* engine, const JabberID& jid, + const String& password, const SocketAddr& address, + unsigned int maxRestart, u_int64_t incRestartInterval, + bool outgoing = true); + + /** + * Get the starting stream element to be sent after stream connected + * @return XMLElement pointer + */ + virtual XMLElement* getStreamStart(); + + /** + * Process a received element in Securing state + * @param xml Valid XMLElement pointer + */ + virtual void processSecuring(XMLElement* xml); + + /** + * Process a received element in Auth state + * @param xml Valid XMLElement pointer + */ + virtual void processAuth(XMLElement* xml); + + /** + * Process a received element in Started state + * @param xml Valid XMLElement pointer + */ + virtual void processStarted(XMLElement* xml); + +private: + // Default constructor is private to avoid unwanted use + JBClientStream() {} +}; + + +/** + * This class holds encapsulates a private library thread + * @short A Jabber thread that can be added to a list of threads + */ +class YJINGLE_API JBThread : public GenObject +{ +public: + /** + * Thread type enumeration. Used to do a specific client processing + */ + enum Type { + StreamConnect, // Asynchronously connect a stream's socket + EngineReceive, // Read all streams sockets + EngineProcess, // Get events from sockets and send them to + // registered services + Presence, // Presence service processor + Jingle, // Jingle service processor + Message // Message service processor }; /** - * Constructor. - * Constructs a Jabber engine. + * Destructor. Remove itself from the owner's list */ - JBEngine(); + virtual ~JBThread(); /** - * Destructor. + * Cancel (terminate) this thread + * @param hard Kill the thread the hard way rather than just setting + * an exit check marker + */ + virtual void cancelThread(bool hard = false) = 0; + + /** + * Create and start a private thread + * @param type Thread type + * @param list The list owning this thread + * @param client The client to process + * @param sleep Time to sleep if there is nothing to do + * @param prio Thread priority + * @return False if failed to start the requested thread + */ + static bool start(Type type, JBThreadList* list, void* client, int sleep, int prio); + +protected: + /** + * Constructor. Append itself to the owner's list + * @param type Thread type + * @param owner The list owning this thread + * @param client The client to process + * @param sleep Time to sleep if there is nothing to do + */ + JBThread(Type type, JBThreadList* owner, void* client, int sleep = 2); + + /** + * Process the client + */ + void runClient(); + +private: + Type m_type; // Thread type + JBThreadList* m_owner; // List owning this thread + void* m_client; // The client to process + int m_sleep; // Time to sleep if there is nothing to do +}; + + +/** + * This class holds a list of private threads for an object that wants to terminate them on destroy + * @short A list of private threads + */ +class YJINGLE_API JBThreadList +{ + friend class JBThread; +public: + /** + * Cancel all threads + * This method is thread safe + * @param wait True to wait for the threads to terminate + * @param hard Kill the threads the hard way rather than just setting an exit check marker + */ + void cancelThreads(bool wait = true, bool hard = false); + +protected: + /** + * Constructor. Set the auto delete flag of the list to false + */ + JBThreadList() + : m_mutex(true), m_cancelling(false) + { m_threads.setDelete(false); } + +private: + Mutex m_mutex; // Lock list operations + ObjList m_threads; // Private threads list + bool m_cancelling; // Cancelling threads operation in progress +}; + + +/** + * This class holds a Jabber engine + * @short A Jabber engine + */ +class YJINGLE_API JBEngine : public DebugEnabler, public Mutex, + public GenObject, public JBThreadList +{ + friend class JBStream; +public: + /** + * Jabber protocol type + */ + enum Protocol { + Component = 1, // Use Jabber Component protocol + Client = 2, // Use client streams + }; + + /** + * Service type enumeration + */ + enum Service { + ServiceJingle = 0, // Receive Jingle events + ServiceIq = 1, // Receive generic Iq events + ServiceMessage = 2, // Receive Message events + ServicePresence = 3, // Receive Presence events + ServiceCommand = 4, // Receive Command events + ServiceDisco = 5, // Receive Disco events + ServiceStream = 6, // Receive stream Terminated or Destroy events + ServiceWriteFail = 7, // Receive WriteFail events + ServiceCount = 8 + }; + + /** + * Constructor + * @param proto The protocol used by the streams belonging to this engine + */ + JBEngine(Protocol proto); + + /** + * Destructor */ virtual ~JBEngine(); /** - * Get the Jabber protocol this engine is using. - * @return The Jabber protocol as enumeration. + * Get the Jabber protocol this engine is using + * @return The Jabber protocol as enumeration */ - inline Protocol jabberProtocol() const - { return Component; } + inline Protocol protocol() const + { return m_protocol; } /** - * Initialize the engine's parameters. - * Parameters: - * stream_restartupdateinterval : int Interval to update (increase) the stream restart counter. Defaults to 15000. - * stream_restartcount : int Max stream restart counter. Defaults to 4. - * xmlparser_maxbuffer : int The maximum allowed xml buffer length. Defaults to 8192. - * @param params Engine's parameters. + * Check if a sender or receiver of XML elements should print them to output + * @return True to print XML element to output */ - void initialize(const NamedList& params); + inline bool printXml() const + { return m_printXml; } /** - * Terminate all stream. + * Get the default component server + * @return The default component server */ - void cleanup(); - - /** - * Set the default component server to use. - * The domain must be in the server list. - * Choose the first one from the server list if the given one doesn't exists. - * @param domain Domain name of the server. - */ - void setComponentServer(const char* domain); - - /** - * Get the default component server. - * @return The default component server. - */ - const String& componentServer() + inline const JabberID& componentServer() const { return m_componentDomain; } /** @@ -667,11 +912,11 @@ public: { m_alternateDomain = domain; } /** - * Get the default stream restart count. - * @return The default stream restart count. + * Get the alternate domain name + * @return the alternate domain name */ - inline u_int32_t restartCount() const - { return m_restartCount; } + inline const JabberID& getAlternateDomain() const + { return m_alternateDomain; } /** * Get the default resource name. @@ -681,293 +926,352 @@ public: { return m_defaultResource; } /** - * Check if a sender or receiver of XML elements should print them to output. - * @return True to print XML element to output. + * Get the stream list + * @return The list of streams belonging to this engine */ - inline bool printXml() const - { return m_printXml; } + inline const ObjList& streams() const + { return m_streams; } /** - * Set/reset print XML elements to output permission. - * @param print True to allow XML elements printing to output. + * Cleanup streams. Stop all threads owned by this engine. Release memory */ - inline void printXml(bool print) - { m_printXml = print; } + virtual void destruct(); /** - * Check if a stream to the given server exists. - * If the stream doesn't exists creates it. + * Initialize the engine's parameters. Start private streams if requested + * @param params Engine's parameters + */ + virtual void initialize(const NamedList& params); + + /** + * Terminate all streams + */ + void cleanup(); + + /** + * Set the default component server to use. The domain must be in the server list. + * Choose the first one from the server list if the given one doesn't exists. + * Do nothing if the protocol is not Component + * @param domain Domain name of the server + */ + void setComponentServer(const char* domain); + + /** + * Get a stream. Create it not found and requested. + * For the component protocol, the jid parameter may contain the domain to find, + * otherwise, the default component will be used. + * For the client protocol, the jid parameter must contain the full user's + * jid (including the resource). * This method is thread safe. - * @param domain The domain name to check. 0 to use the default server. - * @param create True to create a stream to the specified domain if none exists. - * @return Pointer to a JBComponentStream or 0. + * @param jid Optional jid to use to find or create the stream + * @param create True to create a stream if don't exist + * @param pwd Password used to authenticate the user if the client protocol + * is used and a new stream is going to be created. Set it to empty string + * to create the stream without password + * @return Referenced JBStream pointer or 0 */ - JBComponentStream* getStream(const char* domain = 0, bool create = true); + JBStream* getStream(const JabberID* jid = 0, bool create = true, const char* pwd = 0); /** - * Keep calling receive() for each stream until no data is received. - * @return True if data was received. + * Try to get a stream if stream parameter is 0 + * @param stream Stream to check + * @param release Set to true on exit if the caller must deref the stream + * @param pwd Password used to authenticate the user if the client protocol + * is used and a new stream is going to be created. Set it to empty string + * to create the stream without password + * @return True if stream is valid + */ + bool getStream(JBStream*& stream, bool& release, const char* pwd = 0); + + /** + * Keep calling receive() for each stream until no data is received or the + * thread is terminated + * @return True if data was received */ bool receive(); /** - * Keep calling receive(). + * Get events from the streams owned by this engine and send them to a service. + * Delete them if not processed by a service + * @param time Current time + * @return True if an event was generated by any stream */ - void runReceive(); + bool process(u_int64_t time); /** - * Get events from the streams owned by this engine. - * This method is thread safe. - * @param time Current time. - * @return JBEvent pointer or 0. + * Check if an outgoing stream exists with the same id and remote peer + * @param stream The calling stream + * @return True if found */ - JBEvent* getEvent(u_int64_t time); + bool checkDupId(const JBStream* stream); /** - * Check if an outgoing stream exists with the same id and remote peer. - * @param stream The calling stream. - * @return True if found. + * Check the 'from' attribute received by a Component stream at startup + * @param stream The calling stream + * @param from The from attribute to check + * @return True if valid */ - bool remoteIdExists(const JBComponentStream* stream); + bool checkComponentFrom(JBComponentStream* stream, const char* from); /** - * Create an SHA1 value from 'id' + 'password'. - * @param sha Destination string. - * @param id First element (stream id). - * @param password The second element (stream password). + * Asynchronously call the connect method of the given stream if the stream is idle + * @param stream The stream to connect */ - void createSHA1(String& sha, const String& id, const String& password); + virtual void connect(JBStream* stream); /** - * Check if a received value is correct. - * @param sha Destination string. - * @param id First element (stream id). - * @param password The second element (stream password). - * @return True if equal. + * Check if this engine is exiting + * @return True is terminating */ - bool checkSHA1(const String& sha, const String& id, const String& password); - - /** - * Called to update time dependent values - * @param time Current time. - */ - virtual void timerTick(u_int64_t time); - - /** - * Call the connect method of the given stream. - * @param stream The stream to connect. - * @return False if stream is 0. - */ - virtual bool connect(JBComponentStream* stream); - - /** - * Return a non processed event to this engine. - * @param event The returned event. - */ - virtual void returnEvent(JBEvent* event); - - /** - * Accept an outgoing stream. If accepted, deliver a password. - * @param remoteAddr The remote address. - * @param password Password to use. - * @return True if accepted. False to terminate the stream. - */ - virtual bool acceptOutgoing(const String& remoteAddr, String& password); - - /** - * Deliver a port for an outgoing connection. - * @param remoteAddr The remote address. - * @return True if accepted. False to terminate the stream. - */ - virtual int getPort(const String& remoteAddr); - - /** - * Check if the process is terminating. - * @return True if the process is terminating. - */ - virtual bool exiting() + virtual bool exiting() const { return false; } /** - * Append a servr info element to the list. - * @param server The object to add. - * @param open True to open the stream. + * Append a server info element to the list + * @param server The object to add + * @param open True to open the stream, if in component mode */ - void appendServer(JBServerInfo* server, bool open); + void appendServer(XMPPServerInfo* server, bool open); /** - * Find server info object. - * @param token The search string. If 0 the default server will be used. - * @param domain True to find by domain name. False to find by address. - * @return Referenced JBServerInfo pointer or 0. + * Get the identity of the given server + * @param destination The destination buffer + * @param full True to get the full identity + * @param token The search string. If 0 and the component protocol is used, + * the default server will be used + * @param domain True to find by domain name. False to find by address + * @return False if server doesn't exists */ - JBServerInfo* getServer(const char* token = 0, bool domain = true); - - /** - * Get the identity of the given server for a component server. - * @param destination The destination. - * @param token The search string. If 0 the default server will be used. - * @param domain True to find by domain name. False to find by address. - * @return False if server doesn't exists. - */ - bool getServerIdentity(String& destination, const char* token = 0, + bool getServerIdentity(String& destination, bool full, const char* token = 0, bool domain = true); /** - * Get the full identity of the given server. - * @param destination The destination. - * @param token The search string. If 0 the default server will be used. - * @param domain True to find by domain name. False to find by address. - * @return False if server doesn't exists. + * Find server info object + * @param token The search string. If 0 and the Component protocol is used, + * the default component server will be used + * @param domain True to find by domain name. False to find by address + * @return XMPPServerInfo pointer or 0 if not found */ - bool getFullServerIdentity(String& destination, const char* token = 0, - bool domain = true); + XMPPServerInfo* findServerInfo(const char* token, bool domain); /** - * Get the name of the alternate domain - if any - * @return Alternate domain name, empty string if not set + * Attach a service to this engine. + * This method is thread safe + * @param service The service to attach + * @param type Service type + * @param prio Service priority. Set to -1 to use the givent service's priority. + * A lower values indicates a service with higher priority */ - inline const String& getAlternateDomain() const - { return m_alternateDomain; } + void attachService(JBService* service, Service type, int prio = -1); /** - * Check if a stream to a remote server can be restarted. - * If true is returned, the stream restart counter has been decreased. - * @param token The remote server name or address. - * @param domain True to find by domain name. False to find by address. - * @return False if server doesn't exists. + * Remove a service from all event handlers of this engine. + * This method is thread safe + * @param service The service to detach */ - bool getStreamRestart(const char* token, bool domain = true); - -protected: - /** - * Process a DiscoInfo event. - * @param event The received event. - * @return True if processed. - */ - bool processDiscoInfo(JBEvent* event); + void detachService(JBService* service); /** - * Process a Command event. - * @param event The received event. - * @return True if processed. + * Get the name of a protocol + * @return The name of the requested protocol or the default value */ - bool processCommand(JBEvent* event); + inline static const char* lookupProto(int proto, const char* def = 0) + { return lookup(proto,s_protoName,def); } /** - * Check if a stream with a given remote address exists. - * @param remoteName The remote address to find. - * @return Stream pointer or 0. + * Get the value associated with a protocol name + * @return The value associated with a protocol name */ - JBComponentStream* findStream(const String& remoteName); - - /** - * Remove a stream from the list. - * @param stream The stream to remove. - * @param del Delete flag. If true the stream will be deleted. - */ - void removeStream(JBComponentStream* stream, bool del); - - /** - * Add a client to this engine if not in it. - * @param client The client to add. - */ - void addClient(JBClient* client); - - /** - * Remove a client to this engine if not in it. - * @param client The client to remove. - */ - void removeClient(JBClient* client); - - /** - * Clear the server list. - */ - inline void clearServerList() - { Lock lock(m_serverMutex); m_server.clear(); } - - /** - * Process a message event. - * @param event The event to process. Always a valid message event. - * @return True if the event was processed (kept). False to destroy it. - */ - virtual bool processMessage(JBEvent* event); + inline static int lookupProto(const char* proto, int def = 0) + { return lookup(proto,s_protoName,def); } private: - void processEventNew(JBEvent* event); - void processEventAuth(JBEvent* event); - bool getServerPassword(String& destination, const char* token = 0, - bool domain = true); - bool getServerPort(int& destination, const char* token = 0, - bool domain = true); - void setPresenceServer(JBPresence* presence); - void unsetPresenceServer(JBPresence* presence); + // Process a Disco... events + bool processDisco(JBEvent* event); + // Process a Command events + bool processCommand(JBEvent* event); + // Pass events to services + bool received(Service service, JBEvent* event); + static TokenDict s_protoName[]; // Protocol names - ObjList m_streams; // Streams belonging to this engine - Mutex m_clientsMutex; // Lock clients list - ObjList m_clients; // XMPP clients list - JBPresence* m_presence; // The presence server - JIDIdentity* m_identity; // Engine's identity - JIDFeatureList m_features; // Engine's features - u_int64_t m_restartUpdateTime; // Time to update the restart counter of all streams + Protocol m_protocol; // The protocol to use u_int32_t m_restartUpdateInterval; // Update interval for restart counter of all streams u_int32_t m_restartCount; // The default restart counter value bool m_printXml; // Print XML data to output - // ID generation data - u_int64_t m_streamID; // Stream id counter - // Server list - String m_componentDomain; // Default server domain name + ObjList m_streams; // Streams belonging to this engine + JIDIdentity* m_identity; // Engine's identity + JIDFeatureList m_features; // Engine's features + JabberID m_componentDomain; // Default server domain name String m_componentAddr; // Default server address - ObjList m_server; // Server list - Mutex m_serverMutex; // Lock server list - String m_alternateDomain; // Alternate acceptable domain - // Misc + int m_componentCheckFrom; // The behaviour when checking the 'from' attribute for a component stream + // 0: no check 1: local identity 2: remote identity + JabberID m_alternateDomain; // Alternate acceptable domain String m_defaultResource; // Default name for missing resources + Mutex m_serverMutex; // Lock server info list + ObjList m_server; // Server info list + Mutex m_servicesMutex; // Lock service list + ObjList m_services[ServiceCount]; // Services list + bool m_initialized; // True if already initialized }; + /** - * This class is the base class for a Jabber client who wants - * to deliver protocol specific data to the engine. - * @short An Jabber client. + * This class is the base class for a Jabber service who wants + * to get specific protocol data from the Jabber engine + * @short A Jabber service */ -class YJINGLE_API JBClient : public RefObject +class YJINGLE_API JBService : public DebugEnabler, public Mutex, public GenObject { public: /** - * Constructor. - * @param engine The Jabber engine. + * Constructor + * @param engine The Jabber engine + * @param name This service's name + * @param params Service's parameters + * @param prio The priority of this service */ - JBClient(JBEngine* engine); + JBService(JBEngine* engine, const char* name, const NamedList* params, int prio); /** - * Destructor. + * Destructor. Remove from engine */ - virtual ~JBClient(); + virtual ~JBService(); /** - * Get the Jabber engine. - * @return The Jabber engine. + * Get the Jabber engine + * @return The Jabber engine */ - JBEngine* engine() + inline JBEngine* engine() { return m_engine; } + /** + * Get the Jabber engine + * @return The Jabber engine + */ + inline int priority() const + { return m_priority; } + + /** + * Accept an event from the engine. If accepted, the event is enqueued + * and the stream that generated the event is notified on event + * terminated to allow it to process other data. + * This method is thread safe + * @param event The event to accept + * @return False if not accepted, let the engine try another service + */ + bool received(JBEvent* event); + + /** + * Initialize the service + * @param params Service's parameters + */ + virtual void initialize(const NamedList& params) + {} + + /** + * Remove from engine. Release memory + */ + virtual void destruct(); + protected: - JBEngine* m_engine; // The Jabber Component engine + /** + * Accept an event from the engine + * @param event The event to accept + * @param processed Set to true on exit to signal that the event was + * already processed + * @param insert Set to true if accepted to insert on top of the event queue + * @return False if not accepted, let the engine try another service + */ + virtual bool accept(JBEvent* event, bool& processed, bool& insert); + + /** + * Get an event from queue + * @return JBEvent pointer or 0 if queue is empty + */ + JBEvent* deque(); + + /** + * True if already initialized + */ + bool m_initialized; private: - inline JBClient() {} // Don't use it ! + inline JBService() {} // Don't use it ! + JBEngine* m_engine; // The Jabber Component engine + int m_priority; // Service priority + ObjList m_events; // Events received from engine }; + /** - * This class is the presence server for Jabber. - * @short A Jabber presence server. + * This class is a message receiver service for the Jabber engine + * @short A Jabber message service */ -class YJINGLE_API JBPresence : public DebugEnabler, public JBClient, public Mutex +class YJINGLE_API JBMessage : public JBService, public JBThreadList +{ +public: + /** + * Constructor. Constructs a Jabber message service + * @param engine The Jabber engine + * @param params Service's parameters + * @param prio The priority of this service + */ + inline JBMessage(JBEngine* engine, const NamedList* params, int prio = 0) + : JBService(engine,"jbmsgrecv",params,prio), m_syncProcess(true) + {} + + /** + * Destructor. Cancel private thread(s) + */ + virtual ~JBMessage() + { cancelThreads(); } + + /** + * Initialize the service + * @param params Service's parameters + */ + virtual void initialize(const NamedList& params); + + /** + * Get a message from queue + * @return JBEvent pointer or 0 if no messages + */ + inline JBEvent* getMessage() + { return deque(); } + + /** + * Message processor. The derived classes must override this method + * to process received messages + * @param event The event to process + */ + virtual void processMessage(JBEvent* event); + +protected: + /** + * Accept an event from the engine and process it if configured to do that + * @param event The event to accept + * @param processed Set to true on exit to signal that the event was already processed + * @param insert Set to true if accepted to insert on top of the event queue + * @return False if not accepted, let the engine try another service + */ + virtual bool accept(JBEvent* event, bool& processed, bool& insert); + +private: + bool m_syncProcess; // Process messages on accept +}; + + +/** + * This class is a presence service for Jabber engine. Handle presence stanzas and + * iq query info or items with destination containing a node and a valid domain + * @short A Jabber presence service + */ +class YJINGLE_API JBPresence : public JBService, public JBThreadList { friend class XMPPUserRoster; public: /** - * Presence enumeration. + * Presence type enumeration */ enum Presence { Error, // error @@ -981,356 +1285,344 @@ public: }; /** - * Constructor. - * Constructs an Jabber Component presence server. - * @param engine The Jabber Component engine. - * @param params Engine's parameters. + * Constructor. Constructs a Jabber Component presence service + * @param engine The Jabber engine + * @param params Service's parameters + * @param prio The priority of this service */ - JBPresence(JBEngine* engine, const NamedList& params); + JBPresence(JBEngine* engine, const NamedList* params, int prio = 0); /** - * Destructor. + * Destructor */ virtual ~JBPresence(); /** - * Get the auto subscribe parameter. - * @return The auto subscribe parameter. + * Get the auto subscribe parameter + * @return The auto subscribe parameter */ inline int autoSubscribe() const { return m_autoSubscribe; } /** - * Check if the unavailable resources must be deleted. - * @return The delete unavailable parameter. + * Check if the unavailable resources must be deleted + * @return The delete unavailable parameter */ inline bool delUnavailable() const { return m_delUnavailable; } /** - * Check if this server should add new users when receiving subscribe stanzas. - * @return True if should add a new user. + * Check if this server should add new users when receiving subscribe stanzas + * @return True if should add a new user when receiving subscribe stanzas */ inline bool addOnSubscribe() const { return m_addOnSubscribe; } /** - * Check if this server should add new users when receiving presence probes. - * @return True if should add a new user. + * Check if this server should add new users when receiving presence probes + * @return True if should add a new user when receiving presence probes */ inline bool addOnProbe() const { return m_addOnProbe; } /** - * Check if this server should add new users when receiving presence. - * @return True if should add a new user + * Check if this server should add new users when receiving presence + * @return True if should add a new user when receiving presence stanzas */ inline bool addOnPresence() const { return m_addOnPresence; } /** - * Get the probe interval. Time to send a probe if nothing was received from that user. - * @return The probe interval. + * Check if this server should add new users when receiving presence, probe or subscribe + * @return True if should add a new user when receiving presence, probe or subscribe + */ + inline bool autoRoster() const + { return m_autoRoster; } + + /** + * Get the probe interval. Time to send a probe if nothing was received from that user + * @return The probe interval */ inline u_int32_t probeInterval() { return m_probeInterval; } /** - * Get the expire after probe interval. - * @return The expire after probe interval. + * Get the expire after probe interval + * @return The expire after probe interval */ inline u_int32_t expireInterval() { return m_expireInterval; } /** - * Initialize the engine. - * @param params Engine's parameters. + * Initialize the presence service + * @param params Service's parameters */ - void initialize(const NamedList& params); + virtual void initialize(const NamedList& params); /** - * Add an event to the list. - * This method is thread safe. - * @param event The event to add. - * @return False if the event is not a Presence one. + * Process an event from the receiving list + * This method is thread safe + * @return False if the list is empty */ - bool receive(JBEvent* event); + virtual bool process(); /** - * Process an event from the receiving list. - * This method is thread safe. - * @return False if the list is empty. + * Check presence timeout + * This method is thread safe + * @param time Current time */ - bool process(); + virtual void checkTimeout(u_int64_t time); /** - * Keep calling process(). - */ - void runProcess(); - - /** - * Process disco info elements. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process disco info elements + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processDisco(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Process a presence error element. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process a presence error element + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processError(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Process a presence probe element. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process a presence probe element + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processProbe(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Process a presence subscribe element. - * @param event The event with the element. - * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process a presence subscribe element + * @param event The event with the element + * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processSubscribe(JBEvent* event, Presence presence, const JabberID& local, const JabberID& remote); /** - * Process a presence unavailable element. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process a presence unavailable element + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processUnavailable(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Process a presence element. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. + * Process a presence element + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user */ virtual void processPresence(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Notify on probe request with users we don't know about. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. - * @return False to send item-not-found error. + * Notify on probe request with users we don't know about + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user + * @return False to send item-not-found error */ virtual bool notifyProbe(JBEvent* event, const JabberID& local, const JabberID& remote); /** - * Notify on subscribe event with users we don't know about. - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. - * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed. - * @return False to send item-not-found error. + * Notify on subscribe event with users we don't know about + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user + * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed + * @return False to send item-not-found error */ virtual bool notifySubscribe(JBEvent* event, const JabberID& local, const JabberID& remote, Presence presence); /** - * Notify on subscribe event. - * @param user The user that received the event. - * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed. + * Notify on subscribe event + * @param user The user that received the event + * @param presence Presence type: Subscribe,Subscribed,Unsubscribe,Unsubscribed */ virtual void notifySubscribe(XMPPUser* user, Presence presence); /** * Notify on presence event with users we don't know about or presence unavailable - * received without resource (the remote user is entirely unavailable). - * @param event The event with the element. - * @param local The local (destination) user. - * @param remote The remote (source) user. - * @param available The availability of the remote user. - * @return False to send item-not-found error. + * received without resource (the remote user is entirely unavailable) + * @param event The event with the element + * @param local The local (destination) user + * @param remote The remote (source) user + * @param available The availability of the remote user + * @return False to send item-not-found error */ virtual bool notifyPresence(JBEvent* event, const JabberID& local, const JabberID& remote, bool available); /** - * Notify on state/capabilities change. - * @param user The user that received the event. - * @param resource The resource that changet its state or capabilities. + * Notify on state/capabilities change + * @param user The user that received the event + * @param resource The resource that changet its state or capabilities */ virtual void notifyPresence(XMPPUser* user, JIDResource* resource); /** - * Notify when a new user is added. - * Used basically to add a local resource. - * @param user The new user. + * Notify when a new user is added + * Used basically to add a local resource + * @param user The new user */ virtual void notifyNewUser(XMPPUser* user); /** * Get a roster. Add a new one if requested. - * This method is thread safe. - * @param jid The user's jid. - * @param add True to add the user if doesn't exists. - * @param added Optional parameter to be set if a new user was added. - * @return Referenced pointer or 0 if none. + * This method is thread safe + * @param jid The user's jid + * @param add True to add the user if doesn't exists + * @param added Optional parameter to be set if a new user was added + * @return Referenced pointer or 0 if none */ XMPPUserRoster* getRoster(const JabberID& jid, bool add, bool* added); /** * Get a remote peer of a local one. Add a new one if requested. - * This method is thread safe. - * @param local The local peer. - * @param remote The remote peer. - * @param addLocal True to add the local user if doesn't exists. - * @param addedLocal Optional parameter to be set if a new local user was added. - * @param addRemote True to add the remote user if doesn't exists. - * @param addedRemote Optional parameter to be set if a new remote user was added. - * @return Referenced pointer or 0 if none. + * This method is thread safe + * @param local The local peer + * @param remote The remote peer + * @param addLocal True to add the local user if doesn't exists + * @param addedLocal Optional parameter to be set if a new local user was added + * @param addRemote True to add the remote user if doesn't exists + * @param addedRemote Optional parameter to be set if a new remote user was added + * @return Referenced pointer or 0 if none */ XMPPUser* getRemoteUser(const JabberID& local, const JabberID& remote, bool addLocal, bool* addedLocal, bool addRemote, bool* addedRemote); /** - * Remove a remote peer of a local one. - * This method is thread safe. - * @param local The local peer. - * @param remote The remote peer. + * Remove a remote peer of a local one + * This method is thread safe + * @param local The local peer + * @param remote The remote peer */ void removeRemoteUser(const JabberID& local, const JabberID& remote); /** - * Check if the given domain is a valid (known) one. - * @param domain The domain name to check. - * @return True if the given domain is a valid one. + * Check if the given domain is a valid (known) one + * @param domain The domain name to check + * @return True if the given domain is a valid one */ bool validDomain(const String& domain); - /** - * Try to get a stream from Jabber engine if stream parameter is 0. - * @param stream Stream to check. - * @param release Set to true on exit if the caller must deref the stream. - * @return True if stream is valid. - */ - bool getStream(JBComponentStream*& stream, bool& release); - /** * Send an element through the given stream. * If the stream is 0 try to get one from the engine. - * In any case the element is consumed (deleted). - * @param element Element to send. - * @param stream The stream to send through. - * @return The result of send operation. False if element is 0. + * In any case the element is consumed (deleted) + * @param element Element to send + * @param stream The stream to send through + * @return The result of send operation. False if element is 0 */ - bool sendStanza(XMLElement* element, JBComponentStream* stream); + bool sendStanza(XMLElement* element, JBStream* stream); /** * Send an error. Error type is 'modify'. - * If id is 0 sent element will be of type 'presence'. Otherwise: 'iq'. - * @param type The error. - * @param from The from attribute. - * @param to The to attribute. - * @param element The element that generated the error. - * @param stream Optional stream to use. - * @param id Optional id. If present (even if empty) the error element will be of type 'iq'. - * @return The result of send operation. + * If id is 0 sent element will be of type 'presence'. Otherwise: 'iq' + * @param type The error + * @param from The from attribute + * @param to The to attribute + * @param element The element that generated the error + * @param stream Optional stream to use + * @param id Optional id. If present (even if empty) the error element will be of type 'iq' + * @return The result of send operation */ bool sendError(XMPPError::Type type, const String& from, const String& to, - XMLElement* element, JBComponentStream* stream = 0, const String* id = 0); + XMLElement* element, JBStream* stream = 0, const String* id = 0); /** - * Check timeout. - * This method is thread safe. - * @param time Current time. - */ - void checkTimeout(u_int64_t time); - - /** - * Keep calling checkTimeout(). - */ - void runCheckTimeout(); - - /** - * Create an 'presence' element. - * @param from The 'from' attribute. - * @param to The 'to' attribute. - * @param type Presence type as enumeration. - * @return A valid XMLElement pointer. + * Create an 'presence' element + * @param from The 'from' attribute + * @param to The 'to' attribute + * @param type Presence type as enumeration + * @return A valid XMLElement pointer */ static XMLElement* createPresence(const char* from, const char* to, Presence type = None); /** - * Decode an error element. - * @param element The XML element. - * @param code The 'code' attribute. - * @param type The 'type' attribute. - * @param error The name of the 'error' child. - * @return False if 'element' is 0 or is not a presence one. + * Decode an error element + * @param element The XML element + * @param code The 'code' attribute + * @param type The 'type' attribute + * @param error The name of the 'error' child + * @return False if 'element' is 0 or is not a presence one */ static bool decodeError(const XMLElement* element, String& code, String& type, String& error); /** - * Get the type of a 'presence' stanza as enumeration. - * @param text The text to check. - * @return Presence type as enumeration. + * Get the type of a 'presence' stanza as enumeration + * @param text The text to check + * @return Presence type as enumeration */ static inline Presence presenceType(const char* text) { return (Presence)lookup(text,s_presence,None); } /** - * Get the text from a presence type. - * @param presence The presence type. - * @return The associated text or 0. + * Get the text from a presence type + * @param presence The presence type + * @return The associated text or 0 */ static inline const char* presenceText(Presence presence) { return lookup(presence,s_presence,0); } /** - * Cleanup rosters. + * Cleanup rosters */ void cleanup(); protected: /** - * Check if the given jid has a valid domain. Send error if not. - * @param event The event with element. - * @param jid The destination jid. - * @return True if jid has a valid domain. + * Accept an event from the engine + * @param event The event to accept + * @param processed Set to true on exit to signal that the event was already processed + * @param insert Set to true if accepted to insert on top of the event queue + * @return False if not accepted, let the engine try another service */ - bool checkDestination(JBEvent* event, const JabberID& jid); + virtual bool accept(JBEvent* event, bool& processed, bool& insert); static TokenDict s_presence[]; // Keep the types of 'presence' int m_autoSubscribe; // Auto subscribe state bool m_delUnavailable; // Delete unavailable user or resource + bool m_autoRoster; // True if this service make an automatically roster management bool m_addOnSubscribe; // Add new user on subscribe request bool m_addOnProbe; // Add new user on probe request bool m_addOnPresence; // Add new user on presence bool m_autoProbe; // Automatically respond to probe requests u_int32_t m_probeInterval; // Interval to probe a remote user u_int32_t m_expireInterval; // Expire interval after probe - ObjList m_events; // Incoming events from Jabber engine ObjList m_rosters; // The rosters private: + // Wrapper for getRemoteUser() used when receiving presence + // Show a debug message if not found + XMPPUser* recvGetRemoteUser(const char* type, const JabberID& local, const JabberID& remote, + bool addLocal = false, bool* addedLocal = 0, + bool addRemote = false, bool* addedRemote = 0); void addRoster(XMPPUserRoster* ur); void removeRoster(XMPPUserRoster* ur); }; + /** - * This class holds a JID resource (name,presence,capabilities). - * @short A JID resource. + * This class holds a JID resource (name,presence,capabilities) + * @short A JID resource */ class YJINGLE_API JIDResource : public RefObject { @@ -1344,7 +1636,7 @@ public: }; /** - * Resource presence enumeration. + * Resource presence enumeration */ enum Presence { Unknown = 0, // unknown @@ -1353,7 +1645,7 @@ public: }; /** - * Values of the 'show' child of a presence element. + * Values of the 'show' child of a presence element */ enum Show { ShowAway, // away : Temporarily away @@ -1364,10 +1656,10 @@ public: }; /** - * Constructor. Set data members. - * @param name The resource name. - * @param presence The resource presence. - * @param capability The resource capability. + * Constructor. Set data members + * @param name The resource name + * @param presence The resource presence + * @param capability The resource capability */ inline JIDResource(const char* name, Presence presence = Unknown, u_int32_t capability = CapChat) @@ -1376,114 +1668,114 @@ public: {} /** - * Destructor. + * Destructor */ inline virtual ~JIDResource() {} /** - * Get the resource name. - * @return The resource name. + * Get the resource name + * @return The resource name */ inline const String& name() const { return m_name; } /** - * Get the presence attribute. - * @return The presence attribute. + * Get the presence attribute + * @return The presence attribute */ inline Presence presence() const { return m_presence; } /** - * Check if the resource is available. - * @return True if the resource is available. + * Check if the resource is available + * @return True if the resource is available */ inline bool available() const { return (m_presence == Available); } /** - * Get the show attribute as enumeration. - * @return The show attribute as enumeration. + * Get the show attribute as enumeration + * @return The show attribute as enumeration */ inline Show show() const { return m_show; } /** - * Set the show attribute. - * @param s The new show attribute. + * Set the show attribute + * @param s The new show attribute */ inline void show(Show s) { m_show = s; } /** - * Get the status of this resource. - * @return The status of this resource. + * Get the status of this resource + * @return The status of this resource */ inline const String& status() const { return m_status; } /** - * Set the status of this resource. - * @param s The new status of this resource. + * Set the status of this resource + * @param s The new status of this resource */ inline void status(const char* s) { m_status = s; } /** - * Set the presence information. - * @param value True if available, False if not. - * @return True if presence changed. + * Set the presence information + * @param value True if available, False if not + * @return True if presence changed */ bool setPresence(bool value); /** - * Check if the resource has the required capability. - * @param capability The required capability. - * @return True if the resource has the required capability. + * Check if the resource has the required capability + * @param capability The required capability + * @return True if the resource has the required capability */ inline bool hasCap(Capability capability) const { return (m_capability & capability) != 0; } /** - * Update resource from a presence element. - * @param element A presence element. - * @return True if presence or capability changed changed. + * Update resource from a presence element + * @param element A presence element + * @return True if presence or capability changed changed */ bool fromXML(XMLElement* element); /** - * Add capabilities to a presence element. - * @param element The target presence element. + * Add capabilities to a presence element + * @param element The target presence element */ void addTo(XMLElement* element); /** - * Get the 'show' child of a presence element. - * @param element The XML element. - * @return The text or 0. + * Get the 'show' child of a presence element + * @param element The XML element + * @return The text or 0 */ static const char* getShow(XMLElement* element); /** - * Get the 'show' child of a presence element. - * @param element The XML element. - * @return The text or 0. + * Get the 'show' child of a presence element + * @param element The XML element + * @return The text or 0 */ static const char* getStatus(XMLElement* element); /** - * Get the type of a 'show' element as enumeration. - * @param text The text to check. - * @return Show type as enumeration. + * Get the type of a 'show' element as enumeration + * @param text The text to check + * @return Show type as enumeration */ static inline Show showType(const char* text) { return (Show)lookup(text,s_show,ShowNone); } /** - * Get the text from a show type. - * @param show The type to get text for. - * @return The associated text or 0. + * Get the text from a show type + * @param show The type to get text for + * @return The associated text or 0 */ static inline const char* showText(Show show) { return lookup(show,s_show,0); } @@ -1499,16 +1791,17 @@ private: String m_status; // Status attribute }; + /** - * This class holds a resource list. - * @short A resource list. + * This class holds a resource list + * @short A resource list */ class YJINGLE_API JIDResourceList : public Mutex { friend class XMPPUser; public: /** - * Constructor. + * Constructor */ inline JIDResourceList() : Mutex(true) @@ -1516,54 +1809,55 @@ public: /** * Add a resource to the list if a resource with the given name - * doesn't exists. - * @param name The resource name. - * @return False if the the resource already exists in the list. + * doesn't exists + * @param name The resource name + * @return False if the the resource already exists in the list */ bool add(const String& name); /** - * Add a resource to the list if a resource with the same doesn't already - * exists. Destroy the received resource if not added. - * @param resource The resource to add. - * @return False if the the resource already exists in the list. + * Add a resource to the list if not already there. + * Destroy the received resource if not added + * @param resource The resource to add + * @return False if the the resource already exists in the list */ bool add(JIDResource* resource); /** - * Remove a resource from the list. - * @param resource The resource to remove. - * @param del True to delete the resource. + * Remove a resource from the list + * @param resource The resource to remove + * @param del True to delete the resource */ inline void remove(JIDResource* resource, bool del = true) - { m_resources.remove(resource,del); } + { Lock lock(this); m_resources.remove(resource,del); } /** - * Clear the list. + * Clear the list */ inline void clear() - { m_resources.clear(); } + { Lock lock(this); m_resources.clear(); } /** - * Get a resource with the given name. - * @param name The resource name. - * @return A pointer to the resource or 0. + * Get a resource with the given name + * @param name The resource name + * @return A pointer to the resource or 0 */ JIDResource* get(const String& name); /** - * Get the first resource from the list. - * @return A pointer to the resource or 0. + * Get the first resource from the list + * @return A pointer to the resource or 0 */ inline JIDResource* getFirst() { + Lock lock(this); ObjList* obj = m_resources.skipNull(); return obj ? static_cast(obj->get()) : 0; } /** - * Get the first resource with audio capability. - * @param availableOnly True to get only if available. - * @return A pointer to the resource or 0. + * Get the first resource with audio capability + * @param availableOnly True to get only if available + * @return A pointer to the resource or 0 */ JIDResource* getAudio(bool availableOnly = true); @@ -1571,6 +1865,7 @@ private: ObjList m_resources; // The resources list }; + /** * This class holds a remote XMPP user along with his resources and subscribe state. * @short An XMPP remote user. @@ -1707,7 +2002,7 @@ public: * @param time Probe time. * @return True if send succeedded. */ - bool probe(JBComponentStream* stream, u_int64_t time = Time::msecNow()); + bool probe(JBStream* stream, u_int64_t time = Time::msecNow()); /** * Send subscription to remote peer. @@ -1716,7 +2011,7 @@ public: * @param stream Optional stream to use to send the data. * @return True if send succeedded. */ - bool sendSubscribe(JBPresence::Presence type, JBComponentStream* stream); + bool sendSubscribe(JBPresence::Presence type, JBStream* stream); /** * Send unavailable to remote peer. @@ -1724,7 +2019,7 @@ public: * @param stream Optional stream to use to send the data. * @return True if send succeedded. */ - bool sendUnavailable(JBComponentStream* stream); + bool sendUnavailable(JBStream* stream); /** * Send presence to remote peer. @@ -1734,7 +2029,7 @@ public: * @param force True to send even if we've already sent presence from this resource. * @return True if send succeedded. */ - bool sendPresence(JIDResource* resource, JBComponentStream* stream = 0, bool force = false); + bool sendPresence(JIDResource* resource, JBStream* stream = 0, bool force = false); /** * Check if this user sent us any presence data for a given interval. @@ -1754,7 +2049,7 @@ public: * @param force True to send even if we've already sent presence from this resource. */ void notifyResource(bool remote, const String& name, - JBComponentStream* stream = 0, bool force = false); + JBStream* stream = 0, bool force = false); /** * Notify the state of all resources. @@ -1764,7 +2059,7 @@ public: * @param stream Optional stream to use to send the data if remote is false. * @param force True to send even if we've already sent presence from a resource. */ - void notifyResources(bool remote, JBComponentStream* stream = 0, bool force = false); + void notifyResources(bool remote, JBStream* stream = 0, bool force = false); /** * Get the string associated with a subscription enumeration value. @@ -1789,7 +2084,7 @@ protected: * @param value True if subscribed. False is unsubscribed. * @param stream Optional stream to use to send presence if subscription from remote user changed to true. */ - void updateSubscription(bool from, bool value, JBComponentStream* stream); + void updateSubscription(bool from, bool value, JBStream* stream); /** * Update user timeout data. diff --git a/libs/yjingle/yatejingle.h b/libs/yjingle/yatejingle.h index 26c4f3b5..50acfb18 100644 --- a/libs/yjingle/yatejingle.h +++ b/libs/yjingle/yatejingle.h @@ -32,172 +32,233 @@ */ namespace TelEngine { -class JGAudio; -class JGTransport; -class JGSession; -class JGEvent; -class JGEngine; -class JGSentStanza; +class JGAudio; // A Jingle data payload +class JGAudioList; // A List of Jingle data payloads +class JGTransport; // A Jingle transport description +class JGSession; // A Jingle session +class JGEvent; // An event generated by a Jingle session +class JGEngine; // The Jingle engine +class JGSentStanza; // Sent stanza timeout info -// Time to wait before destroing a session after hangup -#define JGSESSION_ENDTIMEOUT 2000 -// Time to wait for a response -#define JGSESSION_STANZATIMEOUT 10000 /** - * This class holds a Jingle audio payload description. - * @short A Jingle audio payload. + * This class holds a Jingle data payload description + * @short A Jingle data payload */ -class YJINGLE_API JGAudio : public RefObject +class YJINGLE_API JGAudio : public GenObject { public: /** - * Constructor. - * Fill this object from the given attributes. - * @param id The 'id' attribute. - * @param name The 'name' attribute. - * @param clockrate The 'clockrate' attribute. - * @param bitrate The 'bitrate' attribute. + * Constructor. Fill this object from the given attributes + * @param _id The 'id' attribute + * @param _name The 'name' attribute + * @param _clockrate The 'clockrate' attribute + * @param _bitrate The 'bitrate' attribute + * @param _synonym The 'synonym' attribute */ - inline JGAudio(const char* id, const char* name, const char* clockrate, - const char* bitrate = 0) - { set(id,name,clockrate,bitrate); } + inline JGAudio(const char* _id, const char* _name, const char* _clockrate, + const char* _bitrate, const char* _synonym) + { set(_id,_name,_clockrate,_bitrate,_synonym); } /** - * Copy constructor. + * Constructor. Fill this object from an XML element + * @param xml The element to fill from + */ + inline JGAudio(XMLElement* xml) + { fromXML(xml); } + + /** + * Copy constructor */ inline JGAudio(const JGAudio& src) - { set(src.m_id,src.m_name,src.m_clockrate,src.m_bitrate); } + { set(src.id,src.name,src.clockrate,src.bitrate,src.synonym); } /** - * Constructor. - * Fill this object from an XML element. - * @param element The element to fill from. + * Set the data + * @param _id The 'id' attribute + * @param _name The 'name' attribute + * @param _clockrate The 'clockrate' attribute + * @param _bitrate The 'bitrate' attribute + * @param _synonym The 'synonym' attribute */ - inline JGAudio(XMLElement* element) - { fromXML(element); } + inline void set(const char* _id, const char* _name, const char* _clockrate, + const char* _bitrate, const char* _synonym) { + id = _id; + name = _name; + clockrate = _clockrate; + bitrate = _bitrate; + synonym = _synonym; + } /** - * Destructor. + * Get the string repreasentation (id) of this payload + * @return The string repreasentation (id) of this payload */ - virtual ~JGAudio() - {} + virtual const String& toString() const + { return id; } /** - * Create a 'description' element. - * @return Valid XMLElement pointer. - */ - static XMLElement* createDescription(); - - /** - * Create a 'payload-type' element from this object. - * @return Valid XMLElement pointer. + * Create a 'payload-type' element from this object + * @return Valid XMLElement pointer */ XMLElement* toXML(); /** - * Fill this object from a given element. - * @param element The element. + * Fill this object from a given element + * @param xml The element */ - void fromXML(XMLElement* element); + void fromXML(XMLElement* xml); /** - * Create and add a 'payload-type' child to the given element. - * @param description The element. + * The numeric id of this payload */ - inline void addTo(XMLElement* description) - { if (description) description->addChild(toXML()); } + String id; /** - * Set the data. - * @param id The 'id' attribute. - * @param name The 'name' attribute. - * @param clockrate The 'clockrate' attribute. - * @param bitrate The 'bitrate' attribute. + * The Jingle name of this payload */ - void set(const char* id, const char* name, const char* clockrate, - const char* bitrate = 0); + String name; - // Attributes - String m_id; - String m_name; - String m_clockrate; - String m_bitrate; + /** + * The clockrate of this payload + */ + String clockrate; + + /** + * The bitrate of this payload + */ + String bitrate; + + /** + * A synonym of this payload's name + */ + String synonym; }; + /** - * This class holds a Jingle transport method. - * @short A Jingle transport. + * Hold a list of data payloads + * @short A List of Jingle data payloads + */ +class JGAudioList : public ObjList +{ +public: + /** + * Append a new data payload + * @param id The payload's id + * @param name The payload's name + * @param clockrate The payload's clockrate + * @param bitrate The payload's bitrate + * @param synonym The payload's synonym + */ + inline void add(const char* id, const char* name, const char* clockrate, + const char* bitrate, const char* synonym) + { append(new JGAudio(id,name,clockrate,bitrate,synonym)); } + + /** + * Find a data payload by its synonym + * @param value The value to compare with + * @return JGAudio pointer or 0 if not found + */ + JGAudio* findSynonym(const String& value); + + /** + * Create a 'description' element and add payload children to it + * @param telEvent True to append a telephone event data payload + * @return Valid XMLElement pointer + */ + XMLElement* toXML(bool telEvent = true); + + /** + * Fill this list from an XML element's children. Clear before attempting to fill + * @param xml The source XML element + */ + void fromXML(XMLElement* xml); + + /** + * Create a list from data payloads + * @param dest Destination string + * @param synonym True to create from synonyms, false to create from names + * @param sep List item separator + * @return False if the list is empty + */ + bool createList(String& dest, bool synonym, const char* sep = ","); +}; + + +/** + * This class holds a Jingle transport description (protocol, address, port ...) + * @short A Jingle transport description */ class YJINGLE_API JGTransport : public RefObject { public: /** - * Constructor. + * Constructor */ inline JGTransport() {} /** - * Copy constructor. + * Copy constructor */ JGTransport(const JGTransport& src); /** - * Constructor. - * Fill this object from an XML element. - * @param element The element to fill from. + * Constructor. Fill this object from an XML element + * @param element The element to fill from */ inline JGTransport(XMLElement* element) { fromXML(element); } /** - * Destructor. + * Destructor */ virtual ~JGTransport() {} /** - * Create a 'transport' element. - * @return Valid XMLElement pointer. + * Create a 'transport' element + * @return Valid XMLElement pointer */ static XMLElement* createTransport(); /** - * Create a 'candidate' element from this object. - * @return Valid XMLElement pointer. + * Create a 'candidate' element from this object + * @return Valid XMLElement pointer */ XMLElement* toXML(); /** - * Fill this object from a given element. - * @param element The element. + * Fill this object from a given element + * @param element The element */ void fromXML(XMLElement* element); /** - * Create and add a 'candidate' child to the given element. - * @param transport The element. + * Create and add a 'candidate' child to the given element + * @param transport The element */ inline void addTo(XMLElement* transport) { if (transport) transport->addChild(toXML()); } // Attributes - String m_name; - String m_address; - String m_port; - String m_preference; - String m_username; - String m_protocol; - String m_generation; - String m_password; - String m_type; - String m_network; + String name; + String address; + String port; + String preference; + String username; + String protocol; + String generation; + String password; + String type; + String network; }; + /** - * This class does the management of a Jingle session. - * @short A Jingle session. + * This class does the management of a Jingle session + * @short A Jingle session */ class YJINGLE_API JGSession : public RefObject, public Mutex { @@ -205,24 +266,22 @@ class YJINGLE_API JGSession : public RefObject, public Mutex friend class JGEngine; public: /** - * Session state enumeration. + * Session state enumeration */ enum State { - Idle, // Outgoing stream is waiting for - Pending, // Session is pending, session-initiate sent/received - Active, // Session is active, session-accept sent/received - Ending, // Session terminated: Wait for write result - Destroy, // The session will be destroyed + Idle = 0, // Outgoing stream is waiting for + Pending = 1, // Session is pending, session-initiate sent/received + Active = 2, // Session is active, session-accept sent/received + Ending = 3, // Session terminated: Wait for write result + Destroy = 4, // The session will be destroyed }; /** - * Jingle action enumeration. + * Jingle action enumeration */ enum Action { ActAccept, // accept ActInitiate, // initiate - ActModify, // modify - ActRedirect, // redirect ActReject, // reject ActTerminate, // terminate ActTransport, // Used to set/get transport info @@ -236,55 +295,47 @@ public: }; /** - * Jingle client type. + * Jingle transport type enumeration */ enum TransportType { + TransportUnknown, // Detect transport type on incoming, sent both on outgoing TransportInfo, // transport-info TransportCandidates, // candidates }; /** - * Destructor. - * Send SessionTerminate if Pending or Active. - * Notify the owner of termination. Deref the owner. + * Destructor. Hangup if Pending or Active */ virtual ~JGSession(); /** - * Get the session direction. - * @return True if it is an incoming session. + * Get the session direction + * @return True if it is an outgoing session */ - inline bool incoming() const - { return m_incoming; } + inline bool outgoing() const + { return m_outgoing; } /** - * Get the session id. - * @return The session id. + * Get the session id + * @return The session id */ const String& sid() const { return m_sid; } /** - * Get the local peer's JID. - * @return The local peer's JID. + * Get the local peer's JID + * @return The local peer's JID */ const JabberID& local() const { return m_localJID; } /** - * Get the remote peer's JID. - * @return The remote peer's JID. + * Get the remote peer's JID + * @return The remote peer's JID */ const JabberID& remote() const { return m_remoteJID; } - /** - * Get the initiator of this session. - * @return The initiator of this session. - */ - const String& initiator() const - { return m_incoming ? m_remoteJID : m_localJID; } - /** * Get the session state. * @return The session state as enumeration. @@ -292,424 +343,244 @@ public: inline State state() const { return m_state; } - inline const JBComponentStream* stream() const + /** + * Get the stream this session is bound to + * @return The stream this session is bound to + */ + inline const JBStream* stream() const { return m_stream; } /** - * Get the arbitrary user data of this session. - * @return The arbitrary user data of this session. + * Get the arbitrary user data of this session + * @return The arbitrary user data of this session */ - inline void* jingleConn() + inline void* userData() { return m_private; } /** - * Set the arbitrary user data of this session. - * @param jingleconn The new arbitrary user data's value. + * Set the arbitrary user data of this session + * @param userdata The new arbitrary user data's value */ - inline void jingleConn(void* jingleconn) - { m_private = jingleconn; } + inline void userData(void* userdata) + { m_private = userdata; } /** - * Send a message to the remote peer. - * This method is thread safe. - * @param message The message to send. - * @return False on socket error. + * Accept a Pending incoming session. + * This method is thread safe + * @param description The media description element to send + * @return False if send failed */ - bool sendMessage(const char* message); + bool accept(XMLElement* description); /** - * Send an 'iq' of type 'result' to the remote peer. - * This method is thread safe. - * @param id The element's id attribute. - * @return False if send failed. + * Close a Pending or Active session + * This method is thread safe + * @param reject True to reject. False to terminate gracefully + * @param msg Optional message to send before hangup + * @return False if send failed */ - bool sendResult(const char* id); + bool hangup(bool reject = false, const char* msg = 0); /** - * Send a dtmf character to remote peer. - * This method is thread safe. - * @param dtmf The dtmf character. - * @param buttonUp True to send button-up action. False to send button-down. - * @return False if send failed. + * Confirm a received element. If the error is NoError a result stanza will be sent. + * Otherwise, an error stanza will be created and sent and the received element is + * consumed (attached to the sent error stanza) + * @param xml The element to confirm + * @param error The error condition + * @param text Optional text to add to the error element + * @param type Error type + * @return False if send failed or element is 0 + */ + bool confirm(XMLElement* xml, XMPPError::Type error = XMPPError::NoError, + const char* text = 0, XMPPError::ErrorType type = XMPPError::TypeModify); + + /** + * Send a dtmf character to remote peer + * @param dtmf The dtmf character + * @param buttonUp True to send button-up action. False to send button-down + * @return False if send failed */ bool sendDtmf(char dtmf, bool buttonUp = true); /** - * Send a dtmf method to remote peer. - * This method is thread safe. - * @param method The method to send. - * @return False if send failed. + * Send a dtmf method to remote peer + * @param method The method to send + * @return False if send failed */ bool sendDtmfMethod(const char* method); /** - * Deny a dtmf method request from remote peer. - * This method is thread safe. - * @param element The received 'iq' element with the method. - * @return False if send failed. + * Deny a dtmf method request from remote peer + * @param element The received 'iq' element with the method + * @return False if send failed */ bool denyDtmfMethod(XMLElement* element); - /** - * Create and sent an 'error' element. - * This method is thread safe. - * @param element The element to respond to. - * @param error The error. - * @param type Error type. - * @param text Optional text to add to the error element. - * @return False if send failed or element is 0. - */ - bool sendError(XMLElement* element, XMPPError::Type error, - XMPPError::ErrorType type = XMPPError::TypeModify, - const char* text = 0); - - /** - * Send a 'transport-info' element to the remote peer. - * This method is thread safe. - * @param transport The transport data. - * @return False if send failed. - */ - inline bool requestTransport(JGTransport* transport) - { return sendTransport(transport,ActTransport); } - /** * Send a 'transport-accept' element to the remote peer. - * This method is thread safe. - * @param transport Optional transport data to send. - * @return False if send failed. + * This method is thread safe + * @param transport Optional transport data to send + * @return False if send failed */ inline bool acceptTransport(JGTransport* transport = 0) { return sendTransport(transport,ActTransportAccept); } /** - * Accept a session. - * This method is thread safe. - * @param description The media description element to send. - * @return False if send failed. + * Send a 'transport-info' element to the remote peer. + * This method is thread safe + * @param transport The transport data + * @return False if send failed */ - bool accept(XMLElement* description); + inline bool sendTransport(JGTransport* transport) + { return sendTransport(transport,ActTransport); } /** - * Send a session terminate element. - * Requirements: . - * This method is thread safe. - * @param reject True to reject. - * @param message Optional message to send before hangup. - * @return False if the requirements are not met. + * Send a message to the remote peer. + * This method is thread safe + * @param msg The message to send + * @return False on socket error */ - bool hangup(bool reject = false, const char* message = 0); + inline bool sendMessage(const char* msg) { + return sendStanza(XMPPUtils::createMessage(XMPPUtils::MsgChat, + m_localJID,m_remoteJID,0,msg),false); + } + +protected: + /** + * Constructor. Create an outgoing session + * @param engine The engine that owns this session + * @param stream The stream this session is bound to + * @param callerJID The caller's full JID + * @param calledJID The called party's full JID + * @param media The session media description + * @param transport The session transport method(s) + * @param sid True to used 'sid' instead of 'id' as session id attribute + * @param msg Optional message to be sent before session initiate + */ + JGSession(JGEngine* engine, JBStream* stream, + const String& callerJID, const String& calledJID, + XMLElement* media, XMLElement* transport, + bool sid, const char* msg = 0); /** - * Check if this session is a destination for an event. - * Process it if it is. - * This method is thread safe. - * @param event The event event to process. - * @return False if the event doesn't belong to this session. + * Constructor. Create an incoming session. + * @param engine The engine that owns this session + * @param event A valid Jabber Jingle event with action session initiate + * @param id Session id + * @param sid True to used 'sid' instead of 'id' as session id attribute */ - bool receive(JBEvent* event); + JGSession(JGEngine* engine, JBEvent* event, const String& id, bool sid); + + /** + * Release this session and its memory + */ + virtual void destroyed(); + + /** + * Enqueue a Jabber engine event. + * This method is thread safe + * @param event The event event to process + */ + void enqueue(JBEvent* event); /** * Get a Jingle event from the queue. - * This method is thread safe. - * @param time Current time in miliseconds. - * @return JGEvent pointer or 0. + * This method is thread safe + * @param time Current time in miliseconds + * @return JGEvent pointer or 0 */ JGEvent* getEvent(u_int64_t time); /** - * Get the jingle action as enumeration from the given text. - * @param txt Text to check. - * @return The jingle action as enumeration from the given text. + * Send a stanza to the remote peer + * @param stanza The stanza to send + * @param confirmation True if the stanza needs confirmation (add 'id' attribute) + * @return True on success */ - static inline Action action(const char* txt) - { return (Action)lookup(txt,s_actions,ActCount); } + bool sendStanza(XMLElement* stanza, bool confirmation = true); /** - * Get the text associated with an action. - * @param action The action to find. - * @return Pointer to the text or 0. + * Decode a valid jingle set event. Set the event's data on success + * @param jbev The event to decode + * @return JGEvent pointer or 0 */ - static inline const char* actionText(Action action) - { return lookup(action,s_actions); } + JGEvent* decodeJingle(JBEvent* jbev); /** - * Check if the given parameter is a valid dtmf character. - * @param dtmf Character to check. - * @return True if the given parameter is a valid dtmf character. + * Create an 'iq' of type 'set' with a 'jingle' child + * @param action The action of the Jingle stanza + * @param element1 Optional child element + * @param element2 Optional child element + * @return Valid XMLElement pointer */ - static inline bool isDtmf(char dtmf) - { return -1 != s_dtmf.find(dtmf); } + XMLElement* createJingle(Action action, XMLElement* element1 = 0, XMLElement* element2 = 0); /** - * Keeps the dtmf chars. - */ - static String s_dtmf; - -protected: - /** - * Constructor. - * Create an outgoing session. - * @param engine The engine that owns this session. - * @param stream The stream this session is bound to. - * @param callerJID The caller's full JID. - * @param calledJID The called party's full JID. - */ - JGSession(JGEngine* engine, JBComponentStream* stream, - const String& callerJID, const String& calledJID); - - /** - * Constructor. - * Create an incoming session. - * @param engine The engine that owns this session. - * @param event A valid Jabber Jingle event with action session initiate. - */ - JGSession(JGEngine* engine, JBEvent* event); - - /** - * Send a bad-request error. Delete event. - * @param event An already generated event. - * @return 0. - */ - JGEvent* badRequest(JGEvent* event); - - /** - * Process a received event. - * @param jbev The Jabber Component event to process. - * @param time Current time. - * @return The Jingle event or 0. - */ - JGEvent* processEvent(JBEvent* jbev, u_int64_t time); - - /** - * Process received elements in state Pending. - * @param jbev The Jabber Component event to process. - * @param event The jingle event to construct. - * @return The event parameter on success. 0 on failure. - */ - JGEvent* processStatePending(JBEvent* jbev, JGEvent* event); - - /** - * Process received elements in state Active. - * @param jbev The Jabber Component event to process. - * @param event The jingle event to construct. - * @return The event parameter on success. 0 on failure. - */ - JGEvent* processStateActive(JBEvent* jbev, JGEvent* event); - - /** - * Process received elements in state Idle. - * @param jbev The Jabber Component event to process. - * @param event The jingle event to construct. - * @return The event parameter on success. 0 on failure. - */ - JGEvent* processStateIdle(JBEvent* jbev, JGEvent* event); - - /** - * Check if a given event is a valid Jingle one. Send an error if not. - * Set the event's data on success. - * @param event The event to check. - * @return True on success. - */ - bool decodeJingle(JGEvent* event); - - /** - * Process a content-info jingle element. - * Set the event's data on success. - * @param event The event to check. - * @return True on success. - */ - bool processContentInfo(JGEvent* event); - - /** - * Update media payloads from a Jingle event. - * @param event The event to process. - * @return True on success. - */ - bool updateMedia(JGEvent* event); - - /** - * Update transport candidates from a Jingle event. - * @param event The event to process. - * @return True on success. - */ - bool updateTransport(JGEvent* event); - - /** - * Set the message text of the the given event. - * @param event The event to process. - * @return The event parameter. - */ - JGEvent* decodeMessage(JGEvent* event); - - /** - * Check if a given event is a valid Error one. Send an error if not. - * Set the event's data on success. - * @param event The event to check. - * @return True on success. - */ - bool decodeError(JGEvent* event); - - /** - * Send a 'service-unavailable' error to the remote peer. - * @param element The element that generated the error. - * @return True on success. - */ - inline bool sendEServiceUnavailable(XMLElement* element) - { return sendError(element,XMPPError::SServiceUnavailable); } - - /** - * Send a 'bad-request' error to the remote peer. - * @param element The element that generated the error. - * @return True on success. - */ - inline bool sendEBadRequest(XMLElement* element) - { return sendError(element,XMPPError::SBadRequest); } - - /** - * Send a transport related element to the remote peer. - * @param transport Transport data to send. - * @param act The element's type (info, accept, etc). - * @return True on success. + * Send a transport related element to the remote peer + * @param transport Transport data to send + * @param act The element's type (info, accept, etc) + * @return True on success */ bool sendTransport(JGTransport* transport, Action act); /** - * Initiate an outgoing call. - * @param media Media description element. - * @param transport Transport description element. - * @return True on success. + * Get the name of a session state + * @return The name of a session state */ - bool initiate(XMLElement* media, XMLElement* transport); - - /** - * Send an XML element to remote peer. - * @param e The element to send. - * @param addId True to add 'id' attribute. - * @return True on success. - */ - bool sendXML(XMLElement* e, bool addId = true); - - /** - * Create an event. - * @param jbev Optional Jabber event that generated the event. - * @return Valid JGEvent pointer. - */ - JGEvent* createEvent(JBEvent* jbev); - - /** - * Set last event. Change state. Deref this session if event type is Destroy. - * @param event Event to raise. - * @return Valid JGEvent pointer. - */ - JGEvent* raiseEvent(JGEvent* event); - - /** - * Create an 'iq' of type 'set' or 'get' with a 'jingle' child. - * @param action The action of the Jingle stanza. - * @param element1 Optional child element. - * @param element2 Optional child element. - * @return Valid XMLElement pointer. - */ - XMLElement* createJingleSet(Action action, - XMLElement* element1 = 0, XMLElement* element2 = 0); - - /** - * Confirm the given element if has to. - * @param element The element to confirm. - */ - void confirmIq(XMLElement* element); - - /** - * Selectively confirm an 'iq' (skip confirmation for transport-info). - * @param event The event containing the element to confirm. - */ - void confirmIqSelect(JGEvent* event); - - /** - * Terminate notification from an event. Reset the last generated event. - * @param event The notifier. - */ - void eventTerminated(JGEvent* event); - - /** - * Check if a received event contains a confirmation of a sent stanza. - * @param jbev The received Jabber Component event. - * @return The confirmed element or 0. - */ - JGSentStanza* isResponse(const JBEvent* jbev); - - /** - * Check if any element timed out. - * @return True if timeout. - */ - bool timeout(u_int64_t time); - - /** - * Keeps the associations between jingle actions and their text. - */ - static TokenDict s_actions[]; + static const char* lookupState(int state) + { return lookup(state,s_states); } private: + static TokenDict s_states[]; // Session state names + static TokenDict s_actions[]; // Action names + JGSession() {} // Don't use it - void appendSent(XMLElement* element); - // Accept the appropriate events. Called in receive() - // @param retValue The value receive() will return if the message was not accepted - // @return True if the message is accepted. False if not - bool receiveMessage(const JBEvent* event, bool& retValue); - bool receiveResult(const JBEvent* event, bool& retValue); - bool receiveJingle(const JBEvent* event, bool& retValue); - bool receiveDestroy(const JBEvent* event, bool& retValue); + // Terminate notification from an event. Reset the last generated event. + void eventTerminated(JGEvent* event); + // Change session state + void changeState(State newState); // State info State m_state; // Session state - TransportType m_transportType; // Remote client type + TransportType m_transportType; // Remote client transport type // Links JGEngine* m_engine; // The engine that owns this session - JBComponentStream* m_stream; // The stream this session is bound to + JBStream* m_stream; // The stream this session is bound to // Session info - bool m_incoming; // Session direction + bool m_outgoing; // Session direction String m_sid; // Session id JabberID m_localJID; // Local peer's JID JabberID m_remoteJID; // Remote peer's JID + String m_sidAttr; // Session id attribute name // Session data ObjList m_events; // Incoming events from Jabber engine JGEvent* m_lastEvent; // Last generated event - JGEvent* m_terminateEvent; // Terminate event void* m_private; // Arbitrary user data // Sent stanzas id generation String m_localSid; // Local session id (used to generate element's id) u_int32_t m_stanzaId; // Sent stanza id counter - // Timeout - u_int64_t m_timeout; // Timeout period for Ending state or for sent stanzas ObjList m_sentStanza; // Sent stanzas' id }; /** - * This class holds an event generated by a Jingle session. + * This class holds an event generated by a Jingle session * @short A Jingle event */ class YJINGLE_API JGEvent { friend class JGSession; public: + /** + * Jingle events enumeration + */ enum Type { Jingle, // Actions: // ActAccept // ActInitiate - // ActModify - // ActRedirect - // ActReject Never: See Terminated event - // ActTerminate Never: See Terminated event // ActTransport Transport candidade(s) - // ActTransportInfo Never - // ActTransportCandidates Never // ActTransportAccept - // ActContentInfo Never // ActDtmf m_reason is button-up/button-down. m_text is the dtmf // ActDtmfMethod m_text is the dtmf method: rtp/xmpp - Message, // m_text is the message body - Error, - Unexpected, // Unexpected or invalid element // Final Terminated, // m_element is the element that caused the termination // m_reason contains the reason @@ -717,77 +588,76 @@ public: }; /** - * Destructor. - * Deref the session. Delete the XML element. + * Destructor. Deref the session. Delete the XML element */ virtual ~JGEvent(); /** - * Get the type of this event. - * @return The type of this event as enumeration. + * Get the type of this event + * @return The type of this event as enumeration */ inline Type type() const { return m_type; } /** - * Get the session that generated this event. - * @return The session that generated this event. + * Get the session that generated this event + * @return The session that generated this event */ inline JGSession* session() const { return m_session; } /** - * Get the XML element that generated this event. - * @return The XML element that generated this event. + * Get the XML element that generated this event + * @return The XML element that generated this event */ inline XMLElement* element() const { return m_element; } /** - * Get the jingle action as enumeration. - * @return The jingle action as enumeration. + * Get the jingle action as enumeration + * @return The jingle action as enumeration */ inline JGSession::Action action() const { return m_action; } /** - * Get the audio payloads list. - * @return The audio payloads list. + * Get the audio payloads list + * @return The audio payloads list */ - inline ObjList& audio() + inline JGAudioList& audio() { return m_audio; } /** - * Get the transports list. - * @return The transports list. + * Get the transports list + * @return The transports list */ inline ObjList& transport() { return m_transport; } /** - * Get the id. - * @return The id. + * Get the id + * @return The id */ - inline const String& id() + inline const String& id() const { return m_id; } /** - * Get the reason. - * @return The reason. + * Get the reason + * @return The reason */ - inline const String& reason() + inline const String& reason() const { return m_reason; } /** - * Get the text. - * @return The text. + * Get the text + * @return The text */ - inline const String& text() + inline const String& text() const { return m_text; } /** - * Get the XML element that generated this event and set it to 0. - * @return The XML element that generated this event. + * Get the XML element that generated this event and set it to 0 + * @return The XML element that generated this event */ inline XMLElement* releaseXML() { XMLElement* tmp = m_element; @@ -796,191 +666,172 @@ public: } /** - * Check if this event is a final one (Terminated or Destroy). - * @return True if it is. + * Check if this event is a final one (Terminated or Destroy) + * @return True if it is */ - bool final(); + inline bool final() const + { return m_type == Terminated || m_type == Destroy; } protected: /** - * Constructor. - * @param type Event type. - * @param session The session that generated this event. - * @param element Optional XML element that generated this event. + * Constructor. Set the id parameter if the element is valid + * @param type Event type + * @param session The session that generated this event + * @param element Optional XML element that generated this event + * @param reason Optional reason data + * @param text Optional text data */ - JGEvent(Type type, JGSession* session, XMLElement* element = 0); + inline JGEvent(Type type, JGSession* session, XMLElement* element = 0, + const char* reason = 0, const char* text = 0) + : m_type(type), m_session(0), m_element(element), m_action(JGSession::ActCount), + m_reason(reason), m_text(text) + { init(session); } + + /** + * Constructor. Create a Jingle event. Set the id parameter if the element is valid + * @param act The jingle action + * @param session The session that generated this event + * @param element XML element that generated this event + * @param reason Optional reason data + * @param text Optional text data + */ + inline JGEvent(JGSession::Action act, JGSession* session, XMLElement* element, + const char* reason = 0, const char* text = 0) + : m_type(Jingle), m_session(0), m_element(element), m_action(act), + m_reason(reason), m_text(text) + { init(session); } private: JGEvent() {} // Don't use it + void init(JGSession* session); Type m_type; // The type of this event JGSession* m_session; // Jingle session that generated this event XMLElement* m_element; // XML element that generated this event // Event specific JGSession::Action m_action; // The action if type is Jingle - ObjList m_audio; // The received audio payloads + JGAudioList m_audio; // The received audio payloads ObjList m_transport; // The received transport data String m_id; // The element's id attribute String m_reason; // The reason if type is Error or Terminated - String m_text; // The text if type is Error or any other text + String m_text; // Dtmf text }; /** - * This class holds the Jingle engine. - * @short The Jingle engine. + * This class holds a Jingle service for the Jabber engine. Handle jingle stanzas, + * stanza write fail events and stream termination events + * @short A Jingle engine */ -class YJINGLE_API JGEngine : public JBClient, public DebugEnabler, public Mutex +class YJINGLE_API JGEngine : public JBService, public JBThreadList { friend class JGSession; public: /** - * Constructor. - * Constructs a Jingle engine. - * @param jb The JBEngine. - * @param params Engine's parameters. + * Constructor. Constructs a Jingle service + * @param engine The Jabber engine + * @param params Service's parameters + * @param prio The priority of this service */ - JGEngine(JBEngine* jb, const NamedList& params); + JGEngine(JBEngine* engine, const NamedList* params, int prio = 0); /** - * Destructor. - * Terminates all active sessions. Delete the XMPP engine. + * Destructor. Terminates all active sessions */ virtual ~JGEngine(); /** - * Initialize this engine. - * Parameters: None - * @param params Engine's parameters. + * Get the timeout interval of a sent stanza + * @return The timeout interval of a sent stanza */ - void initialize(const NamedList& params); + inline u_int64_t stanzaTimeout() const + { return m_stanzaTimeout; } /** - * Get events from the Jabber engine. - * This method is thread safe. - * @return True if data was received. + * Initialize this service + * @param params Service's parameters */ - bool receive(); + virtual void initialize(const NamedList& params); /** - * Keep calling receive(). - */ - void runReceive(); - - /** - * Keep calling getEvent() for each session list until no more event is generated. - * Call processEvent if needded. - * @return True if at least one event was generated. - */ - bool process(); - - /** - * Keep calling process(). - */ - void runProcess(); - - /** - * Call getEvent() for each session list until an event is generated or the end is reached. - * This method is thread safe. - * @param time Current time in miliseconds. - * @return The first generated event. + * Call getEvent() for each session list until an event is generated or the end is reached + * This method is thread safe + * @param time Current time in miliseconds + * @return The first generated event */ JGEvent* getEvent(u_int64_t time); /** * Make an outgoing call. - * 'media' and 'transport' will be invalid on exit. Don't delete them. - * @param callerName The local peer's username. - * @param remoteJID The remote peer's JID. - * @param media A valid 'description' XML element. - * @param transport A valid 'transport' XML element. - * @param message Optional message to send before call. - * @return Valid JGSession pointer (referenced) on success. + * 'media' and 'transport' will be invalid on exit. Don't delete them + * @param callerName The local peer's username + * @param remoteJID The remote peer's JID + * @param media A valid 'description' XML element + * @param transport A valid 'transport' XML element + * @param msg Optional message to send before call + * @return Valid JGSession pointer (referenced) on success */ JGSession* call(const String& callerName, const String& remoteJID, - XMLElement* media, XMLElement* transport, const char* message = 0); + XMLElement* media, XMLElement* transport, const char* msg = 0); /** - * Default event processor. - * Action: Delete event. - * @param event The event to process. + * Default event processor. Delete event. + * @param event The event to process */ void defProcessEvent(JGEvent* event); /** - * Create a session id for an outgoing one. - * @param id Destination string. - */ - void createSessionId(String& id); - -protected: - /** - * Process events from the sessions. - * Default action: Delete event. - * Descendants must override this method. - * @param event The event to process. + * Process events from the sessions. Default action: Delete event. + * Descendants must override this method to process generated events + * @param event The event to process */ virtual void processEvent(JGEvent* event); +protected: /** - * Remove a session from the list. - * @param session Session to remove. + * Accept an event from the Jabber engine + * @param event The event to accept + * @param processed Set to true on exit to signal that the event was already processed + * @param insert Set to true if accepted to insert on top of the event queue + * @return False if not accepted, let the engine try another service */ - void removeSession(JGSession* session); + virtual bool accept(JBEvent* event, bool& processed, bool& insert); private: + // Create a local session id + void createSessionId(String& id); + ObjList m_sessions; // List of sessions Mutex m_sessionIdMutex; // Session id counter lock u_int32_t m_sessionId; // Session id counter + u_int64_t m_stanzaTimeout; // The timeout of a sent stanza + bool m_useSidAttr; // Use 'sid' for session id attribute name for outgoing calls }; + /** - * This class holds sent stanzas info used for timeout checking. - * @short Timeout info. + * This class holds sent stanzas info used for timeout checking + * @short Send stanza timeout info */ -class YJINGLE_API JGSentStanza : public RefObject +class YJINGLE_API JGSentStanza : public String { - friend class JGSession; public: /** - * Constructor. - * @param id The sent stanza's id. - * @param time The sent time. + * Constructor + * @param id The sent stanza's id + * @param time The sent time */ - JGSentStanza(const char* id, u_int64_t time = Time::msecNow()) - : m_id(id), m_time(time + JGSESSION_STANZATIMEOUT) + JGSentStanza(const char* id, u_int64_t time) + : String(id), m_time(time) {} /** - * Destructor. - */ - virtual ~JGSentStanza() - {} - - /** - * Check if a received element is an iq result or error with the given id or - * a sent element failed to be written to socket. - * @param jbev The received Jabber Component event. - * @return False if the given element is not a response one or is 0. - */ - bool isResponse(const JBEvent* jbev) { - if (jbev && - (jbev->type() == JBEvent::IqResult || - jbev->type() == JBEvent::IqError || - jbev->type() == JBEvent::WriteFail) && - m_id == jbev->id()) - return true; - return false; - } - - /** - * Check if this element timed out. - * @return True if timeout. + * Check if this element timed out + * @return True if timeout */ inline bool timeout(u_int64_t time) const { return time > m_time; } private: - String m_id; // Sent stanza's id u_int64_t m_time; // Timeout }; diff --git a/modules/yjinglechan.cpp b/modules/yjinglechan.cpp index 9985234b..2667bc5f 100644 --- a/modules/yjinglechan.cpp +++ b/modules/yjinglechan.cpp @@ -38,46 +38,26 @@ using namespace TelEngine; namespace { // anonymous -class YJBEngine; // Jabber engine -class YJBPresence; // Jabber presence engine -class YJGEngine; // Jingle engine -class YJGTransport; // Handle the transport for a connection +class YJBEngine; // Jabber engine. Initiate protocol from Yate run mode +class YJGEngine; // Jingle service +class YJBMessage; // Message service +class YJBStreamService; // Stream start/stop event service +class YJBPresence; // Presence service +class YJGData; // Handle the transport and formats for a connection class YJGConnection; // Jingle channel -class YJGLibThread; // Library thread class ResNotifyHandler; // resource.notify handler class ResSubscribeHandler; // resource.subscribe handler class YJGDriver; // The driver -// Yate Payloads -static TokenDict dict_payloads[] = { - { "mulaw", 0 }, - { "alaw", 8 }, - { "gsm", 3 }, - { "lpc10", 7 }, - { "slin", 11 }, - { "g726", 2 }, - { "g722", 9 }, - { "g723", 4 }, - { "g728", 15 }, - { "g729", 18 }, - { "ilbc", 98 }, - { "ilbc20", 98 }, - { "ilbc30", 98 }, - { "h261", 31 }, - { "h263", 34 }, - { "mpv", 32 }, - { 0, 0 }, -}; +// TODO: +// Negotiate DTMF method. Accept remote peer's method; + // Username/Password length for transport #define JINGLE_AUTHSTRINGLEN 16 -// Timeout value to override "maxcall" in call.execute -#define JINGLE_CONN_TIMEOUT 10000 -// Default caller if none for outgoing calls -#define JINGLE_ANONYMOUS_CALLER "unk_caller" // Messages /* MODULE_MSG_NOTIFY - protocol MODULE_NAME + protocol s_name subscription true/false: subscription state 'to' --> 'from' status online/offline/subscribed/unsubscribed or any other string from node@domain @@ -85,14 +65,12 @@ static TokenDict dict_payloads[] = { */ #define MODULE_MSG_NOTIFY "resource.notify" /* MODULE_MSG_SUBSCRIBE - protocol MODULE_NAME + protocol s_name operation probe/subscribe/unsubscribe from node@domain to node@domain */ #define MODULE_MSG_SUBSCRIBE "resource.subscribe" -// Module name -static const String MODULE_NAME("jingle"); /** * YJBEngine @@ -100,19 +78,58 @@ static const String MODULE_NAME("jingle"); class YJBEngine : public JBEngine { public: - inline YJBEngine() {} - virtual ~YJBEngine() {} - // Overloaded methods - virtual bool connect(JBComponentStream* stream); + inline YJBEngine(Protocol proto) : JBEngine(proto) + {} virtual bool exiting() { return Engine::exiting(); } - // Start thread members - // @param read Reading socket thread count. - void startThreads(u_int16_t read); - // Process a message event. - // @param event The event to process. Always a valid message event. - // @return True if the event was processed (kept). False to destroy it. - virtual bool processMessage(JBEvent* event); + void initialize(); +}; + +/** + * YJGEngine + */ +class YJGEngine : public JGEngine +{ +public: + inline YJGEngine(YJBEngine* engine, int prio) + : JGEngine(engine,0,prio), m_requestSubscribe(true) + {} + inline bool requestSubscribe() const + { return m_requestSubscribe; } + void initialize(); + virtual void processEvent(JGEvent* event); +private: + bool m_requestSubscribe; // Request subscribe before making a call +}; + +/** + * YJBMessage + */ +class YJBMessage : public JBMessage +{ +public: + inline YJBMessage(YJBEngine* engine, int prio) + : JBMessage(engine,0,prio) + {} + void initialize(); + virtual void processMessage(JBEvent* event); +}; + +/** + * YJBStreamService + */ +class YJBStreamService : public JBService +{ +public: + YJBStreamService(JBEngine* engine, int prio) + : JBService(engine,"jabberstreamservice",0,prio) + {} + virtual ~YJBStreamService() + {} + void initialize(); +protected: + // Process stream termination events + virtual bool accept(JBEvent* event, bool& processed, bool& insert); }; /** @@ -122,8 +139,10 @@ class YJBPresence : public JBPresence { friend class YUserPresence; public: - YJBPresence(JBEngine* engine, const NamedList& params); - virtual ~YJBPresence(); + inline YJBPresence(JBEngine* engine, int prio) + : JBPresence(engine,0,prio) + {} + void initialize(); // Overloaded methods virtual bool notifyProbe(JBEvent* event, const JabberID& local, const JabberID& remote); @@ -134,10 +153,6 @@ public: const JabberID& remote, bool available); virtual void notifyPresence(XMPPUser* user, JIDResource* resource); virtual void notifyNewUser(XMPPUser* user); - // Start thread members - // @param process Event processor thread count. - // @param timeout Check user timeout. - void startThreads(u_int16_t process, u_int16_t timeout); protected: // Create & enqueue a message from received presence parameter. // Add status/operation/subscription parameters @@ -146,63 +161,33 @@ protected: }; /** - * YJGEngine + * YJGData */ -class YJGEngine : public JGEngine +class YJGData : public JGTransport, virtual public JGAudioList { + friend class YJGConnection; public: - inline YJGEngine(YJBEngine* jb, const NamedList& jgParams) - : JGEngine(jb,jgParams), m_requestSubscribe(true) {} - virtual ~YJGEngine() {} - virtual void processEvent(JGEvent* event); - void requestSubscribe(bool value) - { m_requestSubscribe = value; } - bool requestSubscribe() - { return m_requestSubscribe; } - // Start thread members - // @param read Reading events from the Jabber engine thread count. - // @param process Event processor thread count. - void startThreads(u_int16_t read, u_int16_t process); -private: - bool m_requestSubscribe; -}; - -/** - * YJGTransport - */ -class YJGTransport : public JGTransport, public Mutex -{ -public: - YJGTransport(YJGConnection* connection, Message* msg = 0); - virtual ~YJGTransport(); - inline const JGTransport* remote() const - { return m_remote; } - inline bool transportReady() const - { return m_transportReady; } - // Init local address/port - bool initLocal(); - // Update media - bool updateMedia(ObjList& media); - // Update transport + // Init data and format list + YJGData(YJGConnection* conn, Message* msg = 0); + // Release remote transport info + virtual ~YJGData(); + // Create media description XML element + inline XMLElement* mediaXML() + { return JGAudioList::toXML(); } + // Reserve RTP address and port or start the RTP session + bool rtp(bool start); + // Update media from received data. Return false if already updated media or failed to negotiate a format + // Hangup the connection if failed to negotiate audio formats + bool updateMedia(JGAudioList& media); + // Check received transports and try to accept one if not already negotiated one + // Return true if accepted bool updateTransport(ObjList& transport); - // Start RTP - bool start(); - // Send transport through the given session - inline bool send(JGSession* session) - { return session->requestTransport(new JGTransport(*this)); } - // Create a media description element - XMLElement* createDescription(); - // Create a media string from the list - void createMediaString(String& dest); protected: + YJGConnection* m_conn; // Connection owning this object bool m_mediaReady; // Media ready (updated) flag bool m_transportReady; // Transport ready (both parties) flag bool m_started; // True if chan.stun already sent JGTransport* m_remote; // The remote transport info - ObjList m_formats; // The media formats - YJGConnection* m_connection; // The connection - RefObject* m_rtpData; - String m_rtpId; }; /** @@ -217,9 +202,10 @@ public: Active, Terminated, }; - YJGConnection(YJGEngine* jgEngine, Message& msg, const char* caller, - const char* called, bool available); - YJGConnection(YJGEngine* jgEngine, JGEvent* event); + // Outgoing constructor + YJGConnection(Message& msg, const char* caller, const char* called, bool available); + // Incoming contructor + YJGConnection(JGEvent* event); virtual ~YJGConnection(); inline State state() { return m_state; } @@ -227,6 +213,7 @@ public: { return m_local; } inline const JabberID& remote() const { return m_remote; } + // Overloaded methods from Channel virtual void callAccept(Message& msg); virtual void callRejected(const char* error, const char* reason, const Message* msg); virtual bool callRouted(Message& msg); @@ -235,37 +222,44 @@ public: virtual bool msgUpdate(Message& msg); virtual bool msgText(Message& msg, const char* text); virtual bool msgTone(Message& msg, const char* tone); + inline bool disconnect(const char* reason) { + setReason(reason); + return Channel::disconnect(m_reason); + } + // Route an incoming call bool route(); + // Process Jingle and Terminated events void handleEvent(JGEvent* event); void hangup(bool reject, const char* reason = 0); - // Process presence info - //@return True to disconnect - bool processPresence(bool available, const char* error = 0); + // Process remote user's presence changes. + // Make the call if outgoing and in Pending (waiting for presence information) state + // Hangup if the remote user is unavailbale + // Return true to disconnect + bool presenceChanged(bool available); + inline void updateResource(const String& resource) { if (!m_remote.resource() && resource) m_remote.resource(resource); } + inline void getRemoteAddr(String& dest) { if (m_session && m_session->stream()) - dest = m_session->stream()->remoteAddr().host(); + dest = m_session->stream()->addr().host(); } - inline bool disconnect() - { return Channel::disconnect(m_reason); } -protected: - void handleJingle(JGEvent* event); - void handleError(JGEvent* event); - void handleTransport(JGEvent* event); - bool call(); + inline void setReason(const char* reason) { + if (!m_reason) + m_reason = reason; + } + private: + Mutex m_mutex; // Lock transport and session State m_state; // Connection state - YJGEngine* m_jgEngine; // Jingle engine - JGSession* m_session; // Jingle session for this connection - JabberID m_local; - JabberID m_remote; - String m_callerPrompt; - YJGTransport* m_transport; // Transport - Mutex m_connMutex; // Lock transport and session + JGSession* m_session; // Jingle session attached to this connection + JabberID m_local; // Local user's JID + JabberID m_remote; // Remote user's JID + String m_callerPrompt; // Text to be sent to called before calling it + YJGData* m_data; // Transport and data format(s) // Termination bool m_hangup; // Hang up flag: True - already hung up String m_reason; // Hangup reason @@ -273,32 +267,6 @@ private: u_int64_t m_timeout; // Timeout for not answered outgoing connections }; -/** - * YJGLibThread - * Thread class for library asynchronous operations - */ -class YJGLibThread : public Thread -{ -public: - // Action to run - enum Action { - JBReader, // m_jb->runReceive() - JBConnect, // m_jb->connect(m_stream) - JGReader, // m_jg->runReceive() - JGProcess, // m_jg->runProcess() - JBPresence, // m_presence->runProcess() - JBPresenceTimeout, // m_presence->runCheckTimeout() - }; - YJGLibThread(Action action, const char* name = 0, - Priority prio = Normal); - YJGLibThread(JBComponentStream* stream, const char* name = 0, - Priority prio = Normal); - virtual void run(); -protected: - Action m_action; // Action - JBComponentStream* m_stream; // The stream if action is JBConnect -}; - /** * resource.notify message handler */ @@ -330,19 +298,17 @@ public: class YJGDriver : public Driver { public: + // Additional driver status commands + enum StatusCommands { + StatusStreams = 0, // Show all streams + StatusCmdCount = 1 + }; YJGDriver(); virtual ~YJGDriver(); virtual void initialize(); virtual bool msgExecute(Message& msg, String& dest); + // Message handler: Disconnect channels, destroy streams, clear rosters virtual bool received(Message& msg, int id); - inline u_int32_t pendingTimeout() - { return m_pendingTimeout; } - inline const String& anonymousCaller() const - { return m_anonymousCaller; } - // Split 'src' into parts separated by 'sep'. - // Puts the parts as names in dest if nameFirst is true - // Puts the parts as values in dest if nameFirst is false - bool getParts(NamedList& dest, const char* src, const char sep, bool nameFirst); // Try to create a JID from a message. // First try to get the 'username' parameter of the message. Then the 'from' parmeter // @param checkDomain True to check if jid's domain is valid @@ -353,7 +319,7 @@ public: // Return false if node or domain are 0 or domain is invalid bool decodeJid(JabberID& jid, Message& msg, const char* param, bool checkDomain = false); - // Create the presence notification command. + // Create the presence notification command XMLElement* getPresenceCommand(JabberID& from, JabberID& to, bool available); // Create a random string of JINGLE_AUTHSTRINGLEN length void createAuthRandomString(String& dest); @@ -364,89 +330,288 @@ public: void createMediaString(String& dest, ObjList& formats, char sep); // Find a connection by local and remote jid, optionally ignore local resource (always ignore if local has no resource) YJGConnection* find(const JabberID& local, const JabberID& remote, bool anyResource = false); - protected: - void initCodecLists(); - void initJB(const NamedList& sect); - void initPresence(const NamedList& sect); - void initJG(const NamedList& sect); -public: - YJBEngine* m_jb; // Jabber component engine - YJBPresence* m_presence; // Jabber component presence server - YJGEngine* m_jg; // Jingle engine - ObjList m_allCodecs; // List of all codecs (JGAudio) - ObjList m_usedCodecs; // List of used codecs (JGAudio) - bool m_sendCommandOnNotify; // resource.notify: Send command if true. Send presence if false + // Handle command complete requests + virtual bool commandComplete(Message& msg, const String& partLine, + const String& partWord); + // Additional driver status commands + static String s_statusCmd[StatusCmdCount]; private: + // Check and build caller and called for Component run mode + // Caller: Set user if missing. Get default server identity for Yate Component + // Try to get an available resource for the called party + bool setComponentCall(JabberID& caller, JabberID& called, const char* cr, + const char* cd, bool& available, String& error); + bool m_init; - u_int32_t m_pendingTimeout; - String m_anonymousCaller; + String m_statusCmd; // status jingle }; + /** * Local data */ -static Configuration s_cfg; // The configuration: yjinglechan.conf -static YJGDriver iplugin; // The driver -static String s_localAddress; // The local machine's address +static Configuration s_cfg; // The configuration file +static const String s_name = "jingle"; // Module's name +static JGAudioList s_knownCodecs; // List of all known codecs (JGAudio) +static JGAudioList s_usedCodecs; // List of used codecs (JGAudio) +static String s_localAddress; // The local machine's address +static unsigned int s_pendingTimeout = 10000; // Outgoing call pending timeout +static String s_anonymousCaller = "unk_caller"; // Caller name when missing +static YJBEngine* s_jabber = 0; +static YJGEngine* s_jingle = 0; +static YJBMessage* s_message = 0; +static YJBPresence* s_presence = 0; +static YJBStreamService* s_stream = 0; +static YJGDriver plugin; // The driver + + +// Get the number of private threads of a given type +// Force to 1 for client run mode +// Force at least 1 otherwise +inline int threadCount(const NamedList& params, const char* param) +{ + if (s_jabber->protocol() == JBEngine::Client) + return 1; + int t = params.getIntValue(param); + return t < 1 ? 1 : t; +} + /** * YJBEngine */ -bool YJBEngine::connect(JBComponentStream* stream) +void YJBEngine::initialize() { - if (!stream) - return false; - (new YJGLibThread(stream,"JBConnect thread"))->startup(); - return true; -} + debugChain(&plugin); + NamedList dummy(""); + NamedList* sect = s_cfg.getSection("general"); + if (!sect) + sect = &dummy; + // Force private processing. Force 1 thread for client run mode + sect->setParam("private_process_threads",String(threadCount(*sect,"private_process_threads"))); + sect->setParam("private_receive_threads",String(threadCount(*sect,"private_receive_threads"))); + JBEngine::initialize(*sect); -void YJBEngine::startThreads(u_int16_t read) -{ - // Reader(s) - if (!read) - Debug(this,DebugWarn,"No reading socket threads(s)!"); - for (; read; read--) - (new YJGLibThread(YJGLibThread::JBReader,"JBReader thread"))->startup(); -} + String defComponent; + // Set server list + unsigned int count = s_cfg.sections(); + for (unsigned int i = 0; i < count; i++) { + const NamedList* comp = s_cfg.getSection(i); + String name = comp ? comp->c_str() : ""; + if (!name || name == "general" || name == "codecs") + continue; -bool YJBEngine::processMessage(JBEvent* event) -{ - const char* text = 0; - if (event->element()) { - XMLElement* body = event->element()->findFirstChild(XMLElement::Body); - if (body) - text = body->getText(); + // Process server list for client mode + if (protocol() == Client) { + int port = comp->getIntValue("port",0); + if (port <= 0) + port = 5222; + appendServer(new XMPPServerInfo(name,port),false); + continue; + } + + // Process server list for component mode + const char* address = comp->getValue("address"); + String tmp = comp->getValue("port"); + int port = tmp.toInteger(0); + if (!(address && port)) { + Debug(this,DebugNote, + "Invalid address=%s or port=%s in configuration for %s", + address,tmp.c_str(),name.c_str()); + continue; + } + const char* password = comp->getValue("password"); + // Check identity. Construct the full identity + String identity = comp->getValue("identity"); + if (!identity) + identity = name; + String fullId; + bool keepRoster = false; + if (identity == name) { + String subdomain = comp->getValue("subdomain",s_cfg.getValue( + "general","default_resource",defaultResource())); + identity = subdomain; + identity << '.' << name; + fullId = name; + } + else { + keepRoster = true; + fullId << '.' << name; + if (identity.endsWith(fullId)) { + if (identity.length() == fullId.length()) { + Debug(this,DebugNote,"Invalid identity=%s in configuration for %s", + identity.c_str(),name.c_str()); + continue; + } + fullId = identity; + } + else { + fullId = identity; + fullId << '.' << name; + } + identity = fullId; + } + if (!identity) + continue; + bool startup = comp->getBoolValue("startup"); + bool restart = comp->getBoolValue("auto_restart",true); + if (!defComponent || comp->getBoolValue("default")) + defComponent = name; + XMPPServerInfo* server = new XMPPServerInfo(name,address,port, + password,identity,fullId,keepRoster,restart); + DDebug(this,DebugAll, + "Add server '%s' addr=%s port=%d ident=%s full-ident=%s roster=%s restart=%s startup=%s", + name.c_str(),address,port,identity.c_str(),fullId.c_str(), + String::boolText(keepRoster),String::boolText(restart),String::boolText(startup)); + appendServer(server,startup); } - YJGConnection* conn = iplugin.find(event->to().c_str(),event->from().c_str()); - DDebug(this,DebugInfo, - "Message '%s' from: '%s' to: '%s' conn=%p", - text,event->from().c_str(),event->to().c_str(),conn); + // Set default component server + if (protocol() != Client) + setComponentServer(defComponent); +} + + +/** + * YJGEngine + */ +void YJGEngine::initialize() +{ + debugChain(&plugin); + NamedList dummy(""); + NamedList* sect = s_cfg.getSection("general"); + if (!sect) + sect = &dummy; + // Force private processing + sect->setParam("private_process_threads",String(threadCount(*sect,"private_process_threads"))); + JGEngine::initialize(*sect); + // Init data + m_requestSubscribe = sect->getBoolValue("request_subscribe",true); +} + +// Process jingle events +void YJGEngine::processEvent(JGEvent* event) +{ + if (!event) + return; + JGSession* session = event->session(); + // This should never happen !!! + if (!session) { + Debug(this,DebugWarn,"Received event without session"); + delete event; + return; + } + YJGConnection* conn = static_cast(session->userData()); + if (conn) { + conn->handleEvent(event); + if (event->final()) + conn->disconnect(event->reason()); + } + else { + if (event->type() == JGEvent::Jingle && + event->action() == JGSession::ActInitiate) { + if (event->session()->ref()) { + conn = new YJGConnection(event); + // Constructor failed ? + if (conn->state() == YJGConnection::Pending) + conn->deref(); + else if (!conn->route()) + event->session()->userData(0); + } + else { + Debug(this,DebugWarn,"Session ref failed for new connection"); + event->session()->hangup(false,"failure"); + } + } + else + DDebug(this,DebugAll,"Invalid (non initiate) event for new session"); + } + delete event; +} + + +/** + * YJBMessage + */ +void YJBMessage::initialize() +{ + debugChain(&plugin); + NamedList dummy(""); + NamedList* sect = s_cfg.getSection("general"); + if (!sect) + sect = &dummy; + // Force sync (not enqueued) message processing + sect->setParam("sync_process","true"); + JBMessage::initialize(*sect); +} + +// Process a Jabber message +void YJBMessage::processMessage(JBEvent* event) +{ + if (!(event && event->text())) + return; + + if (engine()->protocol() == JBEngine::Client) { + //TODO: Enqueue message in Yate + Debug(this,DebugStub,"Please implement YJBMessage::processMessage() for Client protocol"); + delete event; + return; + } + + YJGConnection* conn = plugin.find(event->to().c_str(),event->from().c_str()); + DDebug(this,DebugInfo,"Message from=%s to=%s conn=%p '%s' [%p]", + event->from().c_str(),event->to().c_str(),conn,event->text().c_str(),this); if (conn) { Message* m = conn->message("chan.text"); - m->addParam("text",text); + m->addParam("text",event->text()); Engine::enqueue(m); } + delete event; +} + + +/** + * YJBStreamService + */ +void YJBStreamService::initialize() +{ + debugChain(&plugin); +} + +// Process stream termination events +bool YJBStreamService::accept(JBEvent* event, bool& processed, bool& insert) +{ + if (event->type() != JBEvent::Terminated && + event->type() != JBEvent::Running && + event->type() != JBEvent::Destroy) + return false; + + //TODO: Enqueue message in Yate. Notify user unavailable + Debug(this,DebugStub,"Please implement YJBStreamService::accept() for Client protocol"); return false; } + /** * YJBPresence */ -YJBPresence::YJBPresence(JBEngine* engine, const NamedList& params) - : JBPresence(engine,params) -{ -} - -YJBPresence::~YJBPresence() +void YJBPresence::initialize() { + debugChain(&plugin); + NamedList dummy(""); + NamedList* sect = s_cfg.getSection("general"); + if (!sect) + sect = &dummy; + // Force private processing + sect->setParam("private_process_threads",String(threadCount(*sect,"private_process_threads"))); + JBPresence::initialize(*sect); } bool YJBPresence::notifyProbe(JBEvent* event, const JabberID& local, const JabberID& remote) { - XDebug(this,DebugAll, - "notifyProbe. Local: '%s'. Remote: '%s'.",local.c_str(),remote.c_str()); + XDebug(this,DebugAll,"notifyProbe local=%s remote=%s [%p]", + local.c_str(),remote.c_str(),this); // Enqueue message return message(JBPresence::Probe,remote.bare(),local.bare(),0); } @@ -454,19 +619,17 @@ bool YJBPresence::notifyProbe(JBEvent* event, const JabberID& local, bool YJBPresence::notifySubscribe(JBEvent* event, const JabberID& local, const JabberID& remote, Presence presence) { - XDebug(this,DebugAll, - "notifySubscribe(%s). Local: '%s'. Remote: '%s'.", - presenceText(presence),local.c_str(),remote.c_str()); + XDebug(this,DebugAll,"notifySubscribe(%s) local=%s remote=%s [%p]", + presenceText(presence),local.c_str(),remote.c_str(),this); // Respond if auto subscribe - if ((presence == JBPresence::Subscribe || presence == JBPresence::Unsubscribe) && - (autoSubscribe() & XMPPUser::From)) { + if (event->stream() && 0 != (autoSubscribe() & XMPPUser::From) && + (presence == JBPresence::Subscribe || presence == JBPresence::Unsubscribe)) { if (presence == JBPresence::Subscribe) presence = JBPresence::Subscribed; else presence = JBPresence::Unsubscribed; XMLElement* xml = createPresence(local.bare(),remote.bare(),presence); - if (event->stream()) - event->stream()->sendStanza(xml); + event->stream()->sendStanza(xml); return true; } // Enqueue message @@ -477,8 +640,9 @@ void YJBPresence::notifySubscribe(XMPPUser* user, Presence presence) { if (!user) return; - XDebug(this,DebugAll, - "notifySubscribe(%s). User: (%p).",presenceText(presence),user); + XDebug(this,DebugAll,"notifySubscribe(%s) local=%s remote=%s [%p]", + presenceText(presence),user->local()->jid().bare().c_str(), + user->jid().bare().c_str(),this); // Enqueue message message(presence,user->jid().bare(),user->local()->jid().bare(),0); } @@ -486,9 +650,6 @@ void YJBPresence::notifySubscribe(XMPPUser* user, Presence presence) bool YJBPresence::notifyPresence(JBEvent* event, const JabberID& local, const JabberID& remote, bool available) { - XDebug(this,DebugAll, - "notifyPresence(%s). Local: '%s'. Remote: '%s'.", - available?"available":"unavailable",local.c_str(),remote.c_str()); // Check audio properties and availability for received resource bool capAudio = false; if (event && event->element()) { @@ -497,26 +658,27 @@ bool YJBPresence::notifyPresence(JBEvent* event, const JabberID& local, capAudio = res->hasCap(JIDResource::CapAudio); available = res->available(); } - res->deref(); + TelEngine::destruct(res); } - // Notify presence to module - iplugin.processPresence(local,remote,available,capAudio); - // Enqueue message + Debug(this,DebugAll,"notifyPresence local=%s remote=%s available=%s [%p]", + local.c_str(),remote.c_str(),String::boolText(available),this); + // Notify presence to module and enqueue message in engine + plugin.processPresence(local,remote,available,capAudio); return message(available ? JBPresence::None : JBPresence::Unavailable, remote.bare(),local.bare(),0); } +// Notify plugin and enqueue message in engine void YJBPresence::notifyPresence(XMPPUser* user, JIDResource* resource) { if (!(user && resource)) return; - XDebug(this,DebugAll,"notifyPresence. User: (%p). Resource: (%p).", - user,resource); - // Notify plugin JabberID remote(user->jid().node(),user->jid().domain(),resource->name()); - iplugin.processPresence(user->local()->jid(),remote,resource->available(), + Debug(this,DebugAll,"notifyPresence local=%s remote=%s available=%s [%p]", + user->local()->jid().c_str(),remote.c_str(), + String::boolText(resource->available()),this); + plugin.processPresence(user->local()->jid(),remote,resource->available(), resource->hasCap(JIDResource::CapAudio)); - // Enqueue message message(resource->available() ? JBPresence::None : JBPresence::Unavailable, user->jid().bare(),user->local()->jid().bare(), String::boolText(user->subscribedTo())); @@ -526,27 +688,13 @@ void YJBPresence::notifyNewUser(XMPPUser* user) { if (!user) return; - DDebug(this,DebugInfo,"Added new user '%s' for '%s'.", - user->jid().c_str(),user->local()->jid().c_str()); + DDebug(this,DebugAll,"notifyNewUser local=%s remote=%s. Adding default resource [%p]", + user->local()->jid().bare().c_str(),user->jid().bare().c_str(),this); // Add local resource - user->addLocalRes(new JIDResource(iplugin.m_jb->defaultResource(),JIDResource::Available, + user->addLocalRes(new JIDResource(s_jabber->defaultResource(),JIDResource::Available, JIDResource::CapAudio)); } -void YJBPresence::startThreads(u_int16_t process, u_int16_t timeout) -{ - // Process the received events - if (!process) - Debug(m_engine,DebugWarn,"No threads(s) to process events!"); - for (; process; process--) - (new YJGLibThread(YJGLibThread::JBPresence,"JBPresence thread"))->startup(); - // Process timeout - if (!timeout) - Debug(m_engine,DebugWarn,"No threads(s) to check user timeout!"); - for (; timeout; timeout--) - (new YJGLibThread(YJGLibThread::JBPresenceTimeout,"JBPresenceTimeout thread"))->startup(); -} - bool YJBPresence::message(Presence presence, const char* from, const char* to, const char* subscription) { @@ -587,7 +735,7 @@ bool YJBPresence::message(Presence presence, const char* from, const char* to, return 0; } m = new Message(type); - m->addParam("module",MODULE_NAME); + m->addParam("module",s_name); if (operation) m->addParam("operation",operation); if (subscription) @@ -599,345 +747,230 @@ bool YJBPresence::message(Presence presence, const char* from, const char* to, return Engine::enqueue(m); } -/** - * YJGEngine - */ -void YJGEngine::processEvent(JGEvent* event) -{ - if (!event) - return; - JGSession* session = event->session(); - // This should never happen !!! - if (!session) { - Debug(this,DebugWarn,"processEvent. Received event without session."); - delete event; - return; - } - YJGConnection* connection = 0; - if (session->jingleConn()) { - connection = static_cast(session->jingleConn()); - connection->handleEvent(event); - // Disconnect if final - if (event->final()) - connection->disconnect(); - } - else { - if (event->type() == JGEvent::Jingle && - event->action() == JGSession::ActInitiate) { - if (event->session()->ref()) { - connection = new YJGConnection(this,event); - // Constructor failed ? - if (connection->state() == YJGConnection::Pending) - connection->deref(); - else if (!connection->route()) - event->session()->jingleConn(0); - } - else { - Debug(this,DebugWarn, - "processEvent. Session ref failed for new connection."); - event->session()->hangup(false,"failure"); - } - } - else - DDebug(this,DebugAll, - "processEvent. Invalid (non initiate) event for new session."); - - } - delete event; -} - -void YJGEngine::startThreads(u_int16_t read, u_int16_t process) -{ - // Read events from Jabber engine - if (!read) - Debug(this,DebugWarn,"No threads(s) to get events from JBEngine!"); - for (; read; read--) - (new YJGLibThread(YJGLibThread::JGReader,"JGReader thread"))->startup(); - // Process the received events - if (!process) - Debug(this,DebugWarn,"No threads(s) to process events!"); - for (; process; process--) - (new YJGLibThread(YJGLibThread::JGProcess,"JGProcess thread"))->startup(); -} /** - * YJGTransport + * YJGData */ -YJGTransport::YJGTransport(YJGConnection* connection, Message* msg) - : Mutex(true), - m_mediaReady(false), - m_transportReady(false), - m_started(false), - m_remote(0), - m_connection(connection), - m_rtpData(0) +// Init data and format list +YJGData::YJGData(YJGConnection* conn, Message* msg) + : m_conn(conn), + m_mediaReady(false), + m_transportReady(false), + m_started(false), + m_remote(0) { - Lock lock(this); - if (!m_connection) - return; // Set data members - m_name = "rtp"; - m_protocol = "udp"; - m_type = "local"; - m_network = "0"; - m_preference = "1"; - m_generation = "0"; - iplugin.createAuthRandomString(m_username); - iplugin.createAuthRandomString(m_password); - // *** MEDIA - // Get formats from message. Fill with all supported if none received - NamedList nl(""); - const char* formats = msg ? msg->getValue("formats") : 0; - if (formats) { - // 'formats' parameter is empty ? Add 'alaw','mulaw' - if (!iplugin.getParts(nl,formats,',',true)) { - nl.setParam("alaw","1"); - nl.setParam("mulaw","2"); - } - } - else - for (int i = 0; dict_payloads[i].token; i++) - nl.addParam(dict_payloads[i].token,String(i+1)); - // Parse the used codecs list - // If the current element is in the received list, keep it - ObjList* obj = iplugin.m_usedCodecs.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGAudio* a = static_cast(obj->get()); - // Get name for this id - const char* payload = lookup(a->m_id.toInteger(),dict_payloads); - // Append if received a format - if (nl.getValue(payload)) - m_formats.append(new JGAudio(*a)); + name = "rtp"; + protocol = "udp"; + type = "local"; + network = "0"; + preference = "1"; + generation = "0"; + plugin.createAuthRandomString(username); + plugin.createAuthRandomString(password); + // Get formats from message. Fill with all supported if none + String f = msg ? msg->getValue("formats") : 0; + if (!f) + s_usedCodecs.createList(f,true); + ObjList* formats = f.split(','); + // Create the formats list. Validate formats against the used codecs list + for (ObjList* o = formats->skipNull(); o; o = o->skipNext()) { + String* format = static_cast(o->get()); + JGAudio* a = s_usedCodecs.findSynonym(*format); + if (a) + ObjList::append(new JGAudio(*a)); } + TelEngine::destruct(formats); // Not outgoing: Ready - if (m_connection->isIncoming()) + if (m_conn->isIncoming()) return; - // *** TRANSPORT - //TODO: Transport from message if RTP forward + //TODO: Get transport data from message if RTP forward } -YJGTransport::~YJGTransport() +YJGData::~YJGData() { - if (m_remote) - m_remote->deref(); + TelEngine::destruct(m_remote); } -bool YJGTransport::initLocal() +// Reserve RTP address and port or start the RTP session +bool YJGData::rtp(bool start) { - Lock lock(this); - if (!m_connection) + if (start) { + if (m_started || !(m_mediaReady && m_transportReady)) + return false; + } + else if (m_started) return false; - // Set data + + Debug(m_conn,DebugInfo,"%s RTP local='%s:%s' remote='%s:%s' [%p]", + start ? "Starting" : "Initializing",address.c_str(),port.c_str(), + m_remote?m_remote->address.c_str():"",m_remote?m_remote->port.c_str():"", + m_conn); + Message m("chan.rtp"); - m.userData(static_cast(m_connection)); - m_connection->complete(m); + m.userData(static_cast(m_conn)); + m_conn->complete(m); m.addParam("direction","bidir"); m.addParam("media","audio"); - m.addParam("anyssrc","true"); m.addParam("getsession","true"); - m_address = s_localAddress; - if (m_address) - m.setParam("localip",m_address); + if (start) { + ObjList* obj = JGAudioList::skipNull(); + if (obj) + m.addParam("format",(static_cast(obj->get()))->synonym); + m.addParam("localip",address); + m.addParam("localport",port); + m.addParam("remoteip",m_remote->address); + m.addParam("remoteport",m_remote->port); + //m.addParam("autoaddr","false"); + m.addParam("rtcp","false"); + } else { - String s; - if (m_connection) - m_connection->getRemoteAddr(s); - m.setParam("remoteip",s); + m.addParam("anyssrc","true"); + if (s_localAddress) + address = s_localAddress; + else + m_conn->getRemoteAddr(address); + if (address) + m.addParam("localip",address); } + if (!Engine::dispatch(m)) { - DDebug(m_connection,DebugAll,"Transport. Init RTP failed. [%p]", - m_connection); + Debug(m_conn,DebugNote,"Failed to %s RTP [%p]", + start?"start":"initialize",m_conn); return false; } - m_address = m.getValue("localip",m_address); - m_port = m.getValue("localport","-1"); + + if (start) { + // Start STUN + Message* msg = new Message("chan.stun"); + msg->userData(m.userData()); + msg->addParam("localusername",m_remote->username + username); + msg->addParam("remoteusername",username + m_remote->username); + msg->addParam("remoteip",m_remote->address); + msg->addParam("remoteport",m_remote->port); + msg->addParam("userid",m.getValue("rtpid")); + Engine::enqueue(msg); + m_started = true; + } + else { + address = m.getValue("localip",address); + port = m.getValue("localport","-1"); + } return true; } -bool YJGTransport::start() +// Update media from received data. Return false if already updated media or failed to negotiate a format +// Hangup the connection if failed to negotiate audio formats +bool YJGData::updateMedia(JGAudioList& media) { - Lock lock(this); - if (m_started || !(m_connection && m_mediaReady && m_transportReady)) - return false; - Debug(m_connection,DebugCall, - "Transport. Start. Local: '%s:%s'. Remote: '%s:%s'. [%p]", - m_address.c_str(),m_port.c_str(), - m_remote->m_address.c_str(),m_remote->m_port.c_str(),m_connection); - // Start RTP - Message* m = new Message("chan.rtp"); - m->userData(static_cast(m_connection)); - m_connection->complete(*m); - m->addParam("direction","bidir"); - m->addParam("media","audio"); -#if 0 - String formats; - createMediaString(formats); - m->addParam("formats",formats); -#endif - const char* format = 0; - ObjList* obj = m_formats.skipNull(); - if (obj) { - JGAudio* audio = static_cast(obj->get()); - format = lookup(audio->m_id.toInteger(),dict_payloads); - } - m->addParam("format",format); - m->addParam("localip",m_address); - m->addParam("localport",m_port); - m->addParam("remoteip",m_remote->m_address); - m->addParam("remoteport",m_remote->m_port); - //m.addParam("autoaddr","false"); - m->addParam("rtcp","false"); - m->addParam("getsession","true"); - if (!Engine::dispatch(m)) { - Debug(m_connection,DebugCall,"Transport. Start RTP failed. [%p]", - m_connection); - m->destruct(); - return false; - } - // Start STUN - Message* msg = new Message("chan.stun"); - msg->userData(m->userData()); - msg->addParam("localusername",m_remote->m_username + m_username); - msg->addParam("remoteusername",m_username + m_remote->m_username); - msg->addParam("remoteip",m_remote->m_address); - msg->addParam("remoteport",m_remote->m_port); - msg->addParam("userid",m->getValue("rtpid")); - m->destruct(); - Engine::enqueue(msg); - m_started = true; - return true; -} - -bool YJGTransport::updateMedia(ObjList& media) -{ - Lock lock(this); - if (m_mediaReady || !m_connection) + if (m_mediaReady) return false; // Check if we received any media - if (0 == media.skipNull()) { - Debug(m_connection,DebugWarn, - "Transport. The remote party has no media. [%p]",m_connection); - m_connection->hangup(false,"nomedia"); + if (!media.skipNull()) { + Debug(m_conn,DebugNote,"Remote party has no media [%p]",m_conn); + m_conn->hangup(false,"nomedia"); return false; } - ListIterator iter_local(m_formats); - for (GenObject* go; (go = iter_local.get());) { + + // Fill a string with our capabilities for debug purposes + String caps; + if (m_conn->debugAt(DebugNote)) + JGAudioList::createList(caps,false); + + ListIterator iter(*(JGAudioList*)this); + for (GenObject* go; (go = iter.get());) { JGAudio* local = static_cast(go); // Check if incoming media contains local media (compare 'id' and 'name') - ObjList* obj = media.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGAudio* remote = static_cast(obj->get()); - if (local->m_id == remote->m_id && local->m_name == remote->m_name) + ObjList* o = media.skipNull(); + for (; o; o = o->skipNext()) { + JGAudio* remote = static_cast(o->get()); + if (local->id == remote->id && local->name == remote->name) break; } // obj is 0. Current element from m_formats is not in received media. Remove it - if (!obj) - m_formats.remove(local,true); + if (!o) + JGAudioList::remove(local,true); } + // Check if both parties have common media - if (0 == m_formats.skipNull()) { - String recvFormat; - iplugin.createMediaString(recvFormat,media,','); - Debug(m_connection,DebugWarn, - "Transport. Unable to negotiate media (no common formats). Received: '%s'. [%p]", - recvFormat.c_str(),m_connection); - m_connection->hangup(false,"nomedia"); + if (!skipNull()) { + if (m_conn->debugAt(DebugNote)) { + String recvCaps; + media.createList(recvCaps,false); + Debug(m_conn,DebugNote,"No common format(s) local=%s remote=%s [%p]", + caps.c_str(),recvCaps.c_str(),m_conn); + } + m_conn->hangup(false,"nomedia"); return false; } m_mediaReady = true; - if (iplugin.debugAt(DebugCall)) { - String s; - createMediaString(s); - Debug(m_connection,DebugCall,"Transport. Media is ready ('%s'). [%p]", - s.c_str(),m_connection); + if (m_conn->debugAt(DebugAll)) { + createList(caps,true); + Debug(m_conn,DebugAll,"Media is ready: %s [%p]",caps.c_str(),m_conn); } return true; } -bool YJGTransport::updateTransport(ObjList& transport) +// Check received transports and try to accept one if not already negotiated one +bool YJGData::updateTransport(ObjList& transport) { - Lock lock(this); - if (m_transportReady || !m_connection) + if (m_transportReady) return false; JGTransport* remote = 0; // Find a transport we'd love to use - ObjList* obj = transport.skipNull(); - for (; obj; obj = obj->skipNext()) { - remote = static_cast(obj->get()); + for (ObjList* o = transport.skipNull(); o; o = o->skipNext()) { + remote = static_cast(o->get()); // Check: generation, name, protocol, type, network - if (m_generation == remote->m_generation && - m_name == remote->m_name && - m_protocol == remote->m_protocol && - m_type == remote->m_type) + if (generation == remote->generation && + name == remote->name && + protocol == remote->protocol && + type == remote->type) break; // We hate it: reset and skip - DDebug(m_connection,DebugInfo, - "Transport. Unacceptable transport. Name: '%s'. Protocol: '%s. Type: '%s'. Generation: '%s'. [%p]", - m_remote->m_name.c_str(),m_remote->m_protocol.c_str(), - m_remote->m_type.c_str(),m_remote->m_generation.c_str(), - m_connection); + DDebug(m_conn,DebugInfo, + "Skipping transport name=%s protocol=%s type=%s generation=%s [%p]", + remote->name.c_str(),remote->protocol.c_str(), + remote->type.c_str(),remote->generation.c_str(),m_conn); remote = 0; } if (!remote) return false; // Ok: keep it ! - if (m_remote) - m_remote->deref(); - m_remote = new JGTransport(*remote); + TelEngine::destruct(m_remote); + transport.remove(remote,false); + m_remote = remote; m_transportReady = true; - Debug(m_connection,DebugCall, - "Transport. Transport is ready. Local: '%s:%s'. Remote: '%s:%s'. [%p]", - m_address.c_str(),m_port.c_str(), - m_remote->m_address.c_str(),m_remote->m_port.c_str(),m_connection); + Debug(m_conn,DebugAll,"Transport is ready: local='%s:%s' remote='%s:%s' [%p]", + address.c_str(),port.c_str(),m_remote->address.c_str(), + m_remote->port.c_str(),m_conn); return true; } -XMLElement* YJGTransport::createDescription() -{ - Lock lock(this); - XMLElement* descr = JGAudio::createDescription(); - ObjList* obj = m_formats.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGAudio* a = static_cast(obj->get()); - a->addTo(descr); - } - JGAudio* te = new JGAudio("106","telephone-event","8000",""); - te->addTo(descr); - te->deref(); - return descr; -} - -void YJGTransport::createMediaString(String& dest) -{ - Lock lock(this); - iplugin.createMediaString(dest,m_formats,','); -} /** * YJGConnection */ // Outgoing call -YJGConnection::YJGConnection(YJGEngine* jgEngine, Message& msg, const char* caller, - const char* called, bool available) - : Channel(&iplugin,0,true), - m_state(Pending), - m_jgEngine(jgEngine), - m_session(0), - m_local(caller), - m_remote(called), - m_transport(0), - m_connMutex(true), - m_hangup(false), - m_timeout(0) +YJGConnection::YJGConnection(Message& msg, const char* caller, const char* called, + bool available) + : Channel(&plugin,0,true), + m_mutex(true), + m_state(Pending), + m_session(0), + m_local(caller), + m_remote(called), + m_callerPrompt(msg.getValue("callerprompt")), + m_data(0), + m_hangup(false), + m_timeout(0) { - Debug(this,DebugCall,"Outgoing. Caller: '%s'. Called: '%s'. [%p]", - caller,called,this); - m_callerPrompt = msg.getValue("callerprompt"); + Debug(this,DebugCall,"Outgoing. caller='%s' called='%s' [%p]",caller,called,this); // Init transport - m_transport = new YJGTransport(this,&msg); + m_data = new YJGData(this,&msg); // Set timeout + //TODO: Do that only if not available m_timeout = msg.getIntValue("maxcall",0) * (u_int64_t)1000; - u_int64_t pendingTimeout = iplugin.pendingTimeout() * (u_int64_t)1000; + u_int64_t pendingTimeout = s_pendingTimeout * (u_int64_t)1000; u_int64_t timenow = Time::now(); if (m_timeout && pendingTimeout >= m_timeout) { maxcall(timenow + m_timeout); @@ -948,8 +981,7 @@ YJGConnection::YJGConnection(YJGEngine* jgEngine, Message& msg, const char* call if (m_timeout) m_timeout += timenow - pendingTimeout; } - XDebug(this,DebugInfo, - "YJGConnection. Time: " FMT64 ". Maxcall set to " FMT64 " us. [%p]", + XDebug(this,DebugInfo,"Time: " FMT64 ". Maxcall set to " FMT64 " us. [%p]", Time::now(),maxcall(),this); // Startup Message* m = message("chan.startup",msg); @@ -961,31 +993,30 @@ YJGConnection::YJGConnection(YJGEngine* jgEngine, Message& msg, const char* call Engine::enqueue(m); // Make the call if (available) - processPresence(true); + presenceChanged(true); } // Incoming call -YJGConnection::YJGConnection(YJGEngine* jgEngine, JGEvent* event) - : Channel(&iplugin,0,false), - m_state(Active), - m_jgEngine(jgEngine), - m_session(event->session()), - m_local(event->session()->local()), - m_remote(event->session()->remote()), - m_transport(0), - m_connMutex(true), - m_hangup(false), - m_timeout(0) +YJGConnection::YJGConnection(JGEvent* event) + : Channel(&plugin,0,false), + m_mutex(true), + m_state(Active), + m_session(event->session()), + m_local(event->session()->local()), + m_remote(event->session()->remote()), + m_data(0), + m_hangup(false), + m_timeout(0) { - Debug(this,DebugCall,"Incoming. Caller: '%s'. Called: '%s'. [%p]", + Debug(this,DebugCall,"Incoming. caller='%s' called='%s' [%p]", m_remote.c_str(),m_local.c_str(),this); // Set session - m_session->jingleConn(this); + m_session->userData(this); // Init transport - m_transport = new YJGTransport(this); - if (!m_transport->updateMedia(event->audio())) + m_data = new YJGData(this); + if (!m_data->updateMedia(event->audio())) m_state = Pending; - m_transport->updateTransport(event->transport()); + m_data->updateTransport(event->transport()); // Startup Message* m = message("chan.startup"); m->setParam("direction",status()); @@ -994,22 +1025,16 @@ YJGConnection::YJGConnection(YJGEngine* jgEngine, JGEvent* event) Engine::enqueue(m); } +// Release data YJGConnection::~YJGConnection() { hangup(false); disconnected(true,m_reason); - Lock lock(m_connMutex); - if (m_session) { - m_session->deref(); - m_session = 0; - } - if (m_transport) { - m_transport->deref(); - m_transport = 0; - } - Debug(this,DebugCall,"Terminated. [%p]",this); + TelEngine::destruct((RefObject*)m_data); + Debug(this,DebugCall,"Destroyed [%p]",this); } +// Route an incoming call bool YJGConnection::route() { Message* m = message("call.preroute",false,true); @@ -1017,77 +1042,70 @@ bool YJGConnection::route() m->addParam("called",m_local.node()); m->addParam("caller",m_remote.node()); m->addParam("callername",m_remote.bare()); - m_connMutex.lock(); - if (m_transport && m_transport->remote()) { - m->addParam("ip_host",m_transport->remote()->m_address); - m->addParam("ip_port",m_transport->remote()->m_port); + m_mutex.lock(); + if (m_data->m_remote) { + m->addParam("ip_host",m_data->m_remote->address); + m->addParam("ip_port",m_data->m_remote->port); } - m_connMutex.unlock(); + m_mutex.unlock(); return startRouter(m); } +// Call accepted +// Init RTP. Accept session and transport. Send transport void YJGConnection::callAccept(Message& msg) { - // Init local transport - // Accept session and transport - // Request transport - // Try to start transport - Debug(this,DebugCall,"callAccept. [%p]",this); - m_connMutex.lock(); - if (m_transport && m_session) { - m_transport->initLocal(); - m_session->accept(m_transport->createDescription()); - m_session->acceptTransport(0); - m_transport->send(m_session); + Debug(this,DebugCall,"callAccept [%p]",this); + m_mutex.lock(); + if (m_session) { + m_data->rtp(false); + m_session->accept(m_data->JGAudioList::toXML()); + m_session->acceptTransport(); + m_session->sendTransport(new JGTransport(*m_data)); } - m_connMutex.unlock(); + m_mutex.unlock(); Channel::callAccept(msg); } void YJGConnection::callRejected(const char* error, const char* reason, const Message* msg) { + Debug(this,DebugCall,"callRejected. error=%s reason=%s [%p]",error,reason,this); + hangup(false,error?error:reason); Channel::callRejected(error,reason,msg); - if (error) - m_reason = error; - else - m_reason = reason; - Debug(this,DebugCall,"callRejected. Reason: '%s'. [%p]",m_reason.c_str(),this); - hangup(false); } bool YJGConnection::callRouted(Message& msg) { - DDebug(this,DebugCall,"callRouted. [%p]",this); + DDebug(this,DebugCall,"callRouted [%p]",this); return true; } void YJGConnection::disconnected(bool final, const char* reason) { - Debug(this,DebugCall,"disconnected. Final: %u. Reason: '%s'. [%p]", + Debug(this,DebugCall,"disconnected. final=%u reason=%s [%p]", final,reason,this); - Lock lock(m_connMutex); - if (m_reason.null() && reason) - m_reason = reason; + setReason(reason); Channel::disconnected(final,m_reason); } bool YJGConnection::msgAnswered(Message& msg) { - DDebug(this,DebugCall,"msgAnswered. [%p]",this); + DDebug(this,DebugCall,"msgAnswered [%p]",this); return true; } bool YJGConnection::msgUpdate(Message& msg) { - DDebug(this,DebugCall,"msgUpdate. [%p]",this); + DDebug(this,DebugCall,"msgUpdate [%p]",this); return true; } +// Send message to remote peer bool YJGConnection::msgText(Message& msg, const char* text) { - DDebug(this,DebugCall,"msgText. '%s'. [%p]",text,this); - Lock lock(m_connMutex); + DDebug(this,DebugCall,"msgText. '%s' [%p]",text,this); + Lock lock(m_mutex); if (m_session) { m_session->sendMessage(text); return true; @@ -1095,271 +1113,186 @@ bool YJGConnection::msgText(Message& msg, const char* text) return false; } +// Send tones to remote peer bool YJGConnection::msgTone(Message& msg, const char* tone) { - DDebug(this,DebugCall,"msgTone. '%s'. [%p]",tone,this); - Lock lock(m_connMutex); + DDebug(this,DebugCall,"msgTone. '%s' [%p]",tone,this); + Lock lock(m_mutex); if (m_session) while (tone && *tone) m_session->sendDtmf(*tone++); return true; } +// Hangup the call. Send session terminate if not already done void YJGConnection::hangup(bool reject, const char* reason) { - Lock lock(m_connMutex); - if (m_hangup) // Already hung up + Lock lock(m_mutex); + if (m_hangup) return; m_hangup = true; m_state = Terminated; - if (!m_reason) { - if (reason) - m_reason = reason; - else - m_reason = Engine::exiting() ? "Server shutdown" : "Hangup"; - } + setReason(reason?reason:(Engine::exiting()?"shutdown":"hangup")); Message* m = message("chan.hangup",true); m->setParam("status","hangup"); m->setParam("reason",m_reason); Engine::enqueue(m); if (m_session) { - m_session->jingleConn(0); + m_session->userData(0); m_session->hangup(reject,m_reason); + TelEngine::destruct(m_session); } - Debug(this,DebugCall,"hangup. Reason: '%s'. [%p]",m_reason.c_str(),this); + Debug(this,DebugCall,"Hangup. reason=%s [%p]",m_reason.c_str(),this); } +// Handle Jingle events void YJGConnection::handleEvent(JGEvent* event) { if (!event) return; - Lock lock(m_connMutex); - switch (event->type()) { - case JGEvent::Jingle: - handleJingle(event); - break; - case JGEvent::Terminated: - m_reason = event->reason(); - Debug(this,DebugCall,"handleEvent((%p): %u). Terminated. Reason: '%s'. [%p]", - event,event->type(),m_reason.c_str(),this); - break; - case JGEvent::Error: - handleError(event); - break; - case JGEvent::Message: { - DDebug(this,DebugCall, - "handleEvent((%p): %u). Message: '%s'. [%p]", - event,event->type(),event->text().c_str(),this); - Message* m = message("chan.text"); - m->addParam("text",event->text()); - Engine::enqueue(m); - } - break; - default: - Debug(this,DebugNote,"handleEvent((%p): %u). Unexpected. [%p]", - event,event->type(),event); + Lock lock(m_mutex); + if (m_hangup) { + Debug(this,DebugInfo,"Ignoring event (%p,%u). Already hung up [%p]", + event,event->type(),this); + return; } -} -void YJGConnection::handleJingle(JGEvent* event) -{ + if (event->type() == JGEvent::Terminated) { + Debug(this,DebugInfo,"Remote terminated with reason='%s' [%p]", + event->reason().c_str(),this); + setReason(event->reason()); + return; + } + + if (event->type() != JGEvent::Jingle) { + Debug(this,DebugNote,"Received unexpected event (%p,%u) [%p]", + event,event->type(),this); + return; + } + + // Process jingle events switch (event->action()) { case JGSession::ActDtmf: - Debug(this,DebugInfo,"handleJingle. Dtmf(%s): '%s'. [%p]", + Debug(this,DebugInfo,"Received dtmf(%s) '%s' [%p]", event->reason().c_str(),event->text().c_str(),this); - if (event->reason() == "button-up") { + if (event->reason() == "button-up" && event->text()) { Message* m = message("chan.dtmf"); m->addParam("text",event->text()); Engine::enqueue(m); } break; case JGSession::ActDtmfMethod: - Debug(this,DebugInfo,"handleJingle. Dtmf method: '%s'. [%p]", + Debug(this,DebugAll,"Received dtmf method='%s' [%p]", event->text().c_str(),this); - // TODO: method can be 'rtp' or 'xmpp' - m_session->sendResult(event->id()); + // Method can be 'rtp' or 'xmpp': accept both + m_session->confirm(event->element()); break; case JGSession::ActTransport: - { - bool accept = !m_transport->transportReady() && - m_transport->updateTransport(event->transport()); - DDebug(this,DebugInfo,"handleJingle. Transport-info. %s. [%p]", - accept?"Accepted":"Not accepted",this); - if (!accept) { - XMPPError::ErrorType errType = XMPPError::TypeCancel; - if (!m_transport->transportReady()) - errType = XMPPError::TypeModify; - m_session->sendError(event->releaseXML(), - XMPPError::SNotAcceptable,errType); - } - else { - m_session->sendResult(event->id()); - if (isOutgoing()) - m_session->acceptTransport(0); - } + if (m_data->m_transportReady) { + DDebug(this,DebugAll,"Received transport while ready [%p]",this); + m_session->confirm(event->releaseXML(),XMPPError::SNotAcceptable, + 0,XMPPError::TypeCancel); + break; } - m_transport->start(); + m_data->updateTransport(event->transport()); + if (m_data->m_transportReady) { + m_session->confirm(event->element()); + if (isOutgoing()) + m_session->acceptTransport(); + m_data->rtp(true); + } + else + m_session->confirm(event->releaseXML(),XMPPError::SNotAcceptable); break; case JGSession::ActTransportAccept: - Debug(this,DebugInfo,"handleJingle. Transport-accept. [%p]",this); + Debug(this,DebugAll,"Remote peer accepted transport [%p]",this); break; case JGSession::ActAccept: if (isAnswered()) break; // Update media - Debug(this,DebugCall,"handleJingle. Accept. [%p]",this); - m_transport->updateMedia(event->audio()); - m_transport->start(); - // Notify engine + Debug(this,DebugCall,"Remote peer answered the call [%p]",this); + m_state = Active; + m_data->updateMedia(event->audio()); + m_data->rtp(true); maxcall(0); status("answered"); Engine::enqueue(message("call.answered",false,true)); break; - case JGSession::ActModify: - Debug(this,DebugWarn,"handleJingle. Modify: not implemented. [%p]",this); - break; - case JGSession::ActRedirect: - Debug(this,DebugWarn,"handleJingle. Redirect: not implemented. [%p]",this); - break; - default: ; - DDebug(this,DebugWarn, - "handleJingle. Event (%p). Action: %u. Unexpected. [%p]", + default: + Debug(this,DebugNote, + "Received unexpected Jingle event (%p) with action=%u [%p]", event,event->action(),this); } } -void YJGConnection::handleError(JGEvent* event) +// Process remote user's presence notifications +// Make the call if outgoing and in Pending (waiting for presence information) state +// Hangup if the remote user is unavailbale +// Return true to disconnect +bool YJGConnection::presenceChanged(bool available) { - DDebug(this,DebugCall, - "handleError. Error. Id: '%s'. Reason: '%s'. Text: '%s'. [%p]", - event->id().c_str(),event->reason().c_str(), - event->text().c_str(),this); -} - -bool YJGConnection::processPresence(bool available, const char* error) -{ - Lock lock(m_connMutex); - if (m_state == Terminated) { - DDebug(this,DebugInfo, - "processPresence. Received presence in Terminated state. [%p]",this); + Lock lock(m_mutex); + if (m_state == Terminated) return false; - } - // Check if error or unavailable in any other state - if (!(error || available)) - error = "offline"; - if (error) { - Debug(this,DebugCall,"processPresence. Hangup (%s). [%p]",error,this); - hangup(false,error); + // Check if unavailable in any other states + if (!available) { + if (!m_hangup) { + DDebug(this,DebugCall,"Remote user is unavailable [%p]",this); + hangup(false,"offline"); + } return true; } // Check if we are in pending state and remote peer is present - if (!(m_state == Pending && available)) + if (!(isOutgoing() && m_state == Pending && available)) return false; // Make the call - m_state = Active; - Debug(this,DebugCall,"call. Caller: '%s'. Called: '%s'. [%p]", + Debug(this,DebugCall,"Calling. caller=%s called=%s [%p]", m_local.c_str(),m_remote.c_str(),this); - // Make the call - m_session = iplugin.m_jg->call(m_local,m_remote, - m_transport->createDescription(), + m_state = Active; + m_session = s_jingle->call(m_local,m_remote,m_data->JGAudioList::toXML(), JGTransport::createTransport(),m_callerPrompt); if (!m_session) { - hangup(false,"create session failed"); + hangup(false,"noconn"); return true; } - // Adjust timeout + m_session->userData(this); maxcall(m_timeout); - XDebug(this,DebugInfo,"processPresence. Maxcall set to " FMT64 " us. [%p]",maxcall(),this); - // Send prompt Engine::enqueue(message("call.ringing",false,true)); - m_session->jingleConn(this); - // Send transport - m_transport->initLocal(); - m_transport->send(m_session); + // Init & send transport + m_data->rtp(false); + m_session->sendTransport(new JGTransport(*m_data)); return false; } -/** - * YJGLibThread - */ -YJGLibThread::YJGLibThread(Action action, const char* name, Priority prio) - : Thread(name,prio), m_action(action), m_stream(0) -{ -} - -YJGLibThread::YJGLibThread(JBComponentStream* stream, const char* name, - Priority prio) - : Thread(name,prio), m_action(JBConnect), m_stream(0) -{ - if (stream && stream->ref()) - m_stream = stream; -} - -void YJGLibThread::run() -{ - switch (m_action) { - case JBReader: - DDebug(iplugin.m_jb,DebugAll,"%s started.",name()); - iplugin.m_jb->runReceive(); - break; - case JBConnect: - if (m_stream) { - DDebug(iplugin.m_jb,DebugAll, - "%s started. Stream (%p). Remote: '%s'.", - name(),m_stream,m_stream->remoteName().c_str()); - m_stream->connect(); - m_stream->deref(); - m_stream = 0; - } - break; - case JGReader: - DDebug(iplugin.m_jg,DebugAll,"%s started.",name()); - iplugin.m_jg->runReceive(); - break; - case JGProcess: - DDebug(iplugin.m_jg,DebugAll,"%s started.",name()); - iplugin.m_jg->runProcess(); - break; - case JBPresence: - DDebug(iplugin.m_jb,DebugAll,"%s started.",name()); - iplugin.m_presence->runProcess(); - break; - case JBPresenceTimeout: - DDebug(iplugin.m_jb,DebugAll,"%s started.",name()); - iplugin.m_presence->runCheckTimeout(); - break; - } - DDebug(iplugin.m_jb,DebugAll,"%s end of run.",name()); -} - /** * resource.notify message handler */ bool ResNotifyHandler::received(Message& msg) { // Avoid loopback message (if the same module: it's a message sent by this module) - if (MODULE_NAME == msg.getValue("module")) + if (s_name == msg.getValue("module")) return false; JabberID from,to; // *** Check from/to - if (!iplugin.getJidFrom(from,msg,true)) + if (!plugin.getJidFrom(from,msg,true)) return true; - if (iplugin.m_sendCommandOnNotify) + if (s_presence && !s_presence->autoRoster()) to = msg.getValue("to"); - else if (!iplugin.decodeJid(to,msg,"to")) + else if (!plugin.decodeJid(to,msg,"to")) return true; // *** Check status String status = msg.getValue("status"); if (status.null()) { - Debug(&iplugin,DebugNote, - "Received '%s' from '%s' with missing 'status' parameter.", + Debug(&plugin,DebugNote, + "Received '%s' from '%s' with missing 'status' parameter", MODULE_MSG_NOTIFY,from.c_str()); return true; } // *** Everything is OK. Process the message - XDebug(&iplugin,DebugAll,"Received '%s' from '%s' with status '%s'.", + XDebug(&plugin,DebugAll,"Received '%s' from '%s' with status '%s'", MODULE_MSG_NOTIFY,from.c_str(),status.c_str()); - if (!iplugin.m_sendCommandOnNotify && iplugin.m_presence->addOnPresence()) + if (s_presence->addOnPresence()) process(from,to,status,msg.getBoolValue("subscription",false)); else sendPresence(from,to,status); @@ -1369,13 +1302,13 @@ bool ResNotifyHandler::received(Message& msg) void ResNotifyHandler::process(const JabberID& from, const JabberID& to, const String& status, bool subFrom) { - XMPPUserRoster* roster = iplugin.m_presence->getRoster(from,true,0); + XMPPUserRoster* roster = s_presence->getRoster(from,true,0); XMPPUser* user = roster->getUser(to,false,0); // Add new user and local resource if (!user) { user = new XMPPUser(roster,to.node(),to.domain(), subFrom ? XMPPUser::From : XMPPUser::None,false,false); - iplugin.m_presence->notifyNewUser(user); + s_presence->notifyNewUser(user); if (!user->ref()) { roster->deref(); return; @@ -1437,8 +1370,8 @@ void ResNotifyHandler::sendPresence(JabberID& from, JabberID& to, else if (status == "offline") jbPresence = JBPresence::Unavailable; else { - if (iplugin.m_sendCommandOnNotify) { - XDebug(&iplugin,DebugNote,"Can't send command for status='%s'.",status.c_str()); + if (!s_presence->autoRoster()) { + XDebug(&plugin,DebugNote,"Can't send command for status='%s'",status.c_str()); return; } if (status == "subscribed") @@ -1446,23 +1379,23 @@ void ResNotifyHandler::sendPresence(JabberID& from, JabberID& to, else if (status == "unsubscribed") jbPresence = JBPresence::Unsubscribed; else { - XDebug(&iplugin,DebugNote,"Can't send presence for status='%s'.",status.c_str()); + XDebug(&plugin,DebugNote,"Can't send presence for status='%s'",status.c_str()); return; } } // Check if we can get a stream - JBComponentStream* stream = iplugin.m_jb->getStream(); + JBStream* stream = s_jabber->getStream(); if (!stream) return; // Create XML element to be sent bool available = (jbPresence == JBPresence::None); XMLElement* stanza = 0; - if (iplugin.m_sendCommandOnNotify) { + if (!s_presence->autoRoster()) { if (to.domain().null()) - to.domain(iplugin.m_jb->componentServer().c_str()); - DDebug(&iplugin,DebugAll,"Sending presence %s from: %s to: %s", + to.domain(s_jabber->componentServer().c_str()); + DDebug(&plugin,DebugAll,"Sending presence %s from: %s to: %s", String::boolText(available),from.c_str(),to.c_str()); - stanza = iplugin.getPresenceCommand(from,to,available); + stanza = plugin.getPresenceCommand(from,to,available); } else { stanza = JBPresence::createPresence(from,to,jbPresence); @@ -1485,13 +1418,13 @@ void ResNotifyHandler::sendPresence(JabberID& from, JabberID& to, bool ResSubscribeHandler::received(Message& msg) { // Avoid loopback message (if the same module: it's a message sent by this module) - if (MODULE_NAME == msg.getValue("module")) + if (s_name == msg.getValue("module")) return false; JabberID from,to; // *** Check from/to - if (!iplugin.decodeJid(from,msg,"from",true)) + if (!plugin.decodeJid(from,msg,"from",true)) return true; - if (!iplugin.decodeJid(to,msg,"to")) + if (!plugin.decodeJid(to,msg,"to")) return true; // *** Check operation String tmpParam = msg.getValue("operation"); @@ -1503,13 +1436,13 @@ bool ResSubscribeHandler::received(Message& msg) else if (tmpParam == "unsubscribe") presence = JBPresence::Unsubscribe; else { - Debug(&iplugin,DebugNote, - "Received '%s' with missing or unknown parameter: operation=%s.", + Debug(&plugin,DebugNote, + "Received '%s' with missing or unknown parameter: operation=%s", MODULE_MSG_SUBSCRIBE,msg.getValue("operation")); return true; } // *** Everything is OK. Process the message - XDebug(&iplugin,DebugAll,"Accepted '%s'.",MODULE_MSG_SUBSCRIBE); + XDebug(&plugin,DebugAll,"Accepted '%s'.",MODULE_MSG_SUBSCRIBE); process(from,to,presence); return true; } @@ -1518,10 +1451,10 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, JBPresence::Presence presence) { // Don't automatically add - if ((presence == JBPresence::Probe && !iplugin.m_presence->addOnProbe()) || + if ((presence == JBPresence::Probe && !s_presence->addOnProbe()) || ((presence == JBPresence::Subscribe || presence == JBPresence::Unsubscribe) && - !iplugin.m_presence->addOnSubscribe())) { - JBComponentStream* stream = iplugin.m_jb->getStream(); + !s_presence->addOnSubscribe())) { + JBStream* stream = s_jabber->getStream(); if (!stream) return; stream->sendStanza(JBPresence::createPresence(from,to,presence)); @@ -1529,13 +1462,13 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, return; } // Add roster/user - XMPPUserRoster* roster = iplugin.m_presence->getRoster(from,true,0); + XMPPUserRoster* roster = s_presence->getRoster(from,true,0); XMPPUser* user = roster->getUser(to,false,0); // Add new user and local resource if (!user) { user = new XMPPUser(roster,to.node(),to.domain(),XMPPUser::From, false,false); - iplugin.m_presence->notifyNewUser(user); + s_presence->notifyNewUser(user); if (!user->ref()) { roster->deref(); return; @@ -1548,7 +1481,7 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, if (presence == JBPresence::Subscribe) { // Already subscribed: notify. NO: send request if (user->subscribedTo()) - iplugin.m_presence->notifySubscribe(user,JBPresence::Subscribed); + s_presence->notifySubscribe(user,JBPresence::Subscribed); else { user->sendSubscribe(JBPresence::Subscribe,0); user->probe(0); @@ -1558,7 +1491,7 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, if (presence == JBPresence::Unsubscribe) { // Already unsubscribed: notify. NO: send request if (!user->subscribedTo()) - iplugin.m_presence->notifySubscribe(user,JBPresence::Unsubscribed); + s_presence->notifySubscribe(user,JBPresence::Unsubscribed); else { user->sendSubscribe(JBPresence::Unsubscribe,0); user->probe(0); @@ -1574,7 +1507,7 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, // No audio resource for remote user: send probe // Send probe fails: Assume remote user unavailable if (!user->probe(0)) - iplugin.m_presence->notifyPresence(0,to,from,false); + s_presence->notifyPresence(0,to,from,false); break; } user->unlock(); @@ -1584,22 +1517,24 @@ void ResSubscribeHandler::process(const JabberID& from, const JabberID& to, /** * YJGDriver */ +String YJGDriver::s_statusCmd[StatusCmdCount] = {"streams"}; + YJGDriver::YJGDriver() - : Driver(MODULE_NAME,"varchans"), - m_jb(0), m_presence(0), m_jg(0), m_sendCommandOnNotify(true), m_init(false), m_pendingTimeout(0) + : Driver(s_name,"varchans"), + m_init(false) { Output("Loaded module YJingle"); + m_statusCmd << "status " << s_name; } YJGDriver::~YJGDriver() { Output("Unloading module YJingle"); - if (m_presence) - m_presence->deref(); - if (m_jg) - m_jg->deref(); - if (m_jb) - m_jb->deref(); + TelEngine::destruct(s_jingle); + TelEngine::destruct(s_message); + TelEngine::destruct(s_presence); + TelEngine::destruct(s_stream); + TelEngine::destruct(s_jabber); } void YJGDriver::initialize() @@ -1607,277 +1542,236 @@ void YJGDriver::initialize() Output("Initializing module YJingle"); s_cfg = Engine::configFile("yjinglechan"); s_cfg.load(); - if (m_init) { - if (m_jb) - m_jb->printXml(s_cfg.getBoolValue("general","printxml",false)); - return; - } + NamedList dummy(""); NamedList* sect = s_cfg.getSection("general"); - if (!sect) { - Debug(this,DebugNote,"Section [general] missing - no initialization."); - return; + if (!sect) + sect = &dummy; + + if (!m_init) { + m_init = true; + + // Init all known codecs + s_knownCodecs.add("0", "PCMU", "8000", "", "mulaw"); + s_knownCodecs.add("2", "G726-32", "8000", "", "g726"); + s_knownCodecs.add("3", "GSM", "8000", "", "gsm"); + s_knownCodecs.add("4", "G723", "8000", "", "g723"); + s_knownCodecs.add("7", "LPC", "8000", "", "lpc10"); + s_knownCodecs.add("8", "PCMA", "8000", "", "alaw"); + s_knownCodecs.add("9", "G722", "8000", "", "g722"); + s_knownCodecs.add("11", "L16", "8000", "", "slin"); + s_knownCodecs.add("15", "G728", "8000", "", "g728"); + s_knownCodecs.add("18", "G729", "8000", "", "g729"); + s_knownCodecs.add("31", "H261", "90000", "", "h261"); + s_knownCodecs.add("32", "MPV", "90000", "", "mpv"); + s_knownCodecs.add("34", "H263", "90000", "", "h263"); + s_knownCodecs.add("98", "iLBC", "8000", "", "ilbc"); + s_knownCodecs.add("98", "iLBC", "8000", "", "ilbc20"); + s_knownCodecs.add("98", "iLBC", "8000", "", "ilbc30"); + + // Create Jabber engine and services + int p = JBEngine::lookupProto(sect->getValue("protocol"),-1); + JBEngine::Protocol proto; + if (p == -1) + if (Engine::mode() == Engine::Client) + proto = JBEngine::Client; + else + proto = JBEngine::Component; + else + proto = (JBEngine::Protocol)p; + s_jabber = new YJBEngine(proto); + s_jingle = new YJGEngine(s_jabber,0); + s_message = new YJBMessage(s_jabber,1); + s_presence = new YJBPresence(s_jabber,0); + // We don't need stream notifications for Component protocol + if (s_jabber->protocol() != JBEngine::Component) + s_stream = new YJBStreamService(s_jabber,1); + + // Attach services to the engine + s_jabber->attachService(s_jingle,JBEngine::ServiceJingle); + s_jabber->attachService(s_jingle,JBEngine::ServiceWriteFail); + s_jabber->attachService(s_jingle,JBEngine::ServiceIq); + s_jabber->attachService(s_jingle,JBEngine::ServiceStream); + s_jabber->attachService(s_message,JBEngine::ServiceMessage); + s_jabber->attachService(s_presence,JBEngine::ServicePresence); + s_jabber->attachService(s_presence,JBEngine::ServiceDisco); + if (s_stream) + s_jabber->attachService(s_stream,JBEngine::ServiceStream); + + // Driver setup + installRelay(Halt); + Engine::install(new ResNotifyHandler); + Engine::install(new ResSubscribeHandler); + setup(); } - m_init = true; - s_localAddress = sect->getValue("localip"); - m_anonymousCaller = sect->getValue("anonymous_caller",JINGLE_ANONYMOUS_CALLER); - m_pendingTimeout = sect->getIntValue("pending_timeout",JINGLE_CONN_TIMEOUT); + lock(); - initCodecLists(); // Init codec list - if (debugAt(DebugAll)) { - String s; - s << "\r\nlocalip=" << s_localAddress; - s << "\r\npending_timeout=" << (unsigned int)m_pendingTimeout; - s << "\r\nanonymous_caller=" << m_anonymousCaller; - String media; - createMediaString(media,m_usedCodecs,' '); - s << "\r\ncodecs=" << media; - Debug(this,DebugAll,"Initialized:%s",s.c_str()); + + // Initialize Jabber engine and services + s_jabber->initialize(); + s_jingle->initialize(); + s_message->initialize(); + s_presence->initialize(); + if (s_stream) + s_stream->initialize(); + + s_localAddress = sect->getValue("localip"); + s_anonymousCaller = sect->getValue("anonymous_caller","unk_caller"); + s_pendingTimeout = sect->getIntValue("pending_timeout",10000); + // Init codecs in use. Check each codec in known codecs list against the configuration + s_usedCodecs.clear(); + bool defcodecs = s_cfg.getBoolValue("codecs","default",true); + for (ObjList* o = s_knownCodecs.skipNull(); o; o = o->skipNext()) { + JGAudio* crt = static_cast(o->get()); + bool enable = defcodecs && DataTranslator::canConvert(crt->synonym); + if (s_cfg.getBoolValue("codecs",crt->synonym,enable)) + s_usedCodecs.append(new JGAudio(*crt)); } - if (s_localAddress.null()) - Debug(this,DebugNote,"No local address set."); - // Initialize - initJB(*sect); // Init Jabber Component engine - initPresence(*sect); // Init Jabber Component presence - initJG(*sect); // Init Jingle engine + + int dbg = DebugInfo; + if (!s_localAddress) + dbg = DebugNote; + if (!s_usedCodecs.count()) + dbg = DebugWarn; + + if (debugAt(dbg)) { + String s; + s << " localip=" << s_localAddress ? s_localAddress.c_str() : "MISSING"; + s << " pending_timeout=" << s_pendingTimeout; + s << " anonymous_caller=" << s_anonymousCaller; + String media; + if (!s_usedCodecs.createList(media,true)) + media = "MISSING"; + s << " codecs=" << media; + Debug(this,dbg,"Module initialized:%s",s.c_str()); + } + unlock(); - // Driver setup - installRelay(Halt); - Engine::install(new ResNotifyHandler); - Engine::install(new ResSubscribeHandler); - setup(); } -bool YJGDriver::getParts(NamedList& dest, const char* src, const char sep, - bool nameFirst) +// Make an outgoing calls +// Build peers' JIDs and check if the destination is available +bool YJGDriver::msgExecute(Message& msg, String& dest) { - if (!src) - return false; - u_int32_t index = 1; - for (u_int32_t i = 0; src[i];) { - // Skip separator(s) - for (; src[i] && src[i] == sep; i++) ; - // Find first separator - u_int32_t start = i; - for (; src[i] && src[i] != sep; i++) ; - // Get part - if (start != i) { - String tmp(src + start,i - start); - if (nameFirst) - dest.setParam(tmp,String((int)index++)); - else - dest.setParam(String((int)index++),tmp); + // Construct JIDs + JabberID caller; + JabberID called; + bool available = true; + String error; + while (true) { + if (!msg.userData()) { + error = "No data channel"; + break; } + // Component: prepare caller/called. check availability + // Client: just check if caller/called are full JIDs + if (s_jabber->protocol() == JBEngine::Component) + setComponentCall(caller,called,msg.getValue("caller"),dest,available,error); + else if (!(caller.isFull() && called.isFull())) + error << "Incomplete caller=" << caller << " or called=" << called; + break; } + if (error) { + Debug(this,DebugNote,"Jingle call failed. %s",error.c_str()); + msg.setParam("error","failure"); + return false; + } + // Parameters OK. Create connection and init channel + Debug(this,DebugAll,"msgExecute. caller='%s' called='%s' available=%u", + caller.c_str(),called.c_str(),available); + YJGConnection* conn = new YJGConnection(msg,caller,called,available); + Channel* ch = static_cast(msg.userData()); + if (ch && conn->connect(ch,msg.getValue("reason"))) { + msg.setParam("peerid",conn->id()); + msg.setParam("targetid",conn->id()); + } + conn->deref(); return true; } -void YJGDriver::initCodecLists() +// Handle command complete requests +bool YJGDriver::commandComplete(Message& msg, const String& partLine, + const String& partWord) { - // Init all supported codecs if not already done - if (!m_allCodecs.skipNull()) { - m_allCodecs.append(new JGAudio("0", "PCMU", "8000", "")); - m_allCodecs.append(new JGAudio("2", "G726-32", "8000", "")); - m_allCodecs.append(new JGAudio("3", "GSM", "8000", "")); - m_allCodecs.append(new JGAudio("4", "G723", "8000", "")); - m_allCodecs.append(new JGAudio("7", "LPC", "8000", "")); - m_allCodecs.append(new JGAudio("8", "PCMA", "8000", "")); - m_allCodecs.append(new JGAudio("9", "G722", "8000", "")); - m_allCodecs.append(new JGAudio("11", "L16", "8000", "")); - m_allCodecs.append(new JGAudio("15", "G728", "8000", "")); - m_allCodecs.append(new JGAudio("18", "G729", "8000", "")); - m_allCodecs.append(new JGAudio("31", "H261", "90000", "")); - m_allCodecs.append(new JGAudio("32", "MPV", "90000", "")); - m_allCodecs.append(new JGAudio("34", "H263", "90000", "")); - m_allCodecs.append(new JGAudio("98", "iLBC", "8000", "")); - } - // Init codecs in use - m_usedCodecs.clear(); - bool defcodecs = s_cfg.getBoolValue("codecs","default",true); - for (int i = 0; dict_payloads[i].token; i++) { - // Skip if duplicate id - // TODO: Enforce checking: Equal IDs may not be neighbours - if (dict_payloads[i].value == dict_payloads[i+1].value) - continue; - const char* payload = dict_payloads[i].token; - bool enable = defcodecs && DataTranslator::canConvert(payload); - // If enabled, add the codec to the used codecs list - if (s_cfg.getBoolValue("codecs",payload,enable)) { - // Use codec if exists in m_allCodecs - ObjList* obj = m_allCodecs.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGAudio* a = static_cast(obj->get()); - if (a->m_id == dict_payloads[i].value) { - XDebug(this,DebugAll,"Add '%s' to used codecs",payload); - m_usedCodecs.append(new JGAudio(*a)); - break; - } - } - } - } - if (!m_usedCodecs.skipNull()) - Debug(this,DebugWarn,"No audio format(s) available."); -} + bool status = partLine.startsWith("status"); + bool drop = !status && partLine.startsWith("drop"); + if (!(status || drop)) + return Driver::commandComplete(msg,partLine,partWord); -void YJGDriver::initJB(const NamedList& sect) -{ - if (m_jb) - return; - // Create the engine - m_jb = new YJBEngine(); - m_jb->debugChain(this); - // Initialize - m_jb->initialize(sect); - String defComponent; - // Set server list - unsigned int count = s_cfg.sections(); - for (unsigned int i = 0; i < count; i++) { - const NamedList* comp = s_cfg.getSection(i); - if (!comp) - continue; - String name = *comp; - if (name.null() || (name == "general") || (name == "codecs")) - continue; - const char* address = comp->getValue("address"); - int port = comp->getIntValue("port",0); - if (!(address && port)) { - Debug(this,DebugNote,"Missing address or port in configuration for %s", - name.c_str()); - continue; - } - const char* password = comp->getValue("password"); - // Check identity - String identity = comp->getValue("identity"); - if (identity.null()) - identity = name; - String fullId; - bool keepRoster = false; - if (identity == name) { - String subdomain = comp->getValue("subdomain",s_cfg.getValue( - "general","default_resource",m_jb->defaultResource())); - identity = subdomain; - identity << '.' << name; - fullId = name; - } - else { - keepRoster = true; - fullId << '.' << name; - if (identity.endsWith(fullId)) { - if (identity.length() == fullId.length()) { - Debug(this,DebugNote,"Invalid identity=%s in configuration for %s", - identity.c_str(),name.c_str()); - continue; - } - fullId = identity; - } - else { - fullId = identity; - fullId << '.' << name; - } - identity = fullId; - } - bool startup = comp->getBoolValue("startup"); - u_int32_t restartCount = m_jb->restartCount(); - if (!(address && port && !identity.null())) - continue; - if (defComponent.null() || comp->getBoolValue("default")) - defComponent = name; - JBServerInfo* server = new JBServerInfo(name,address,port, - password,identity,fullId,keepRoster,startup,restartCount); - DDebug(this,DebugAll, - "Add server '%s' addr=%s port=%d pass=%s ident=%s full-ident=%s roster=%s startup=%s restartcount=%u.", - name.c_str(),address,port,password,identity.c_str(),fullId.c_str(), - String::boolText(keepRoster),String::boolText(startup),restartCount); - m_jb->appendServer(server,startup); + // 'status' command + Lock lock(this); + // line='status jingle': add additional commands + if (partLine == m_statusCmd) { + for (unsigned int i = 0; i < StatusCmdCount; i++) + if (!partWord || s_statusCmd[i].startsWith(partWord)) + msg.retValue().append(s_statusCmd[i],"\t"); + return true; } - // Set default server - m_jb->setComponentServer(defComponent); - // Init threads - int read = 1; - m_jb->startThreads(read); -} -void YJGDriver::initPresence(const NamedList& sect) -{ - // Already initialized ? - if (m_presence) - m_presence->initialize(sect); - else { - m_presence = new YJBPresence(m_jb,sect); - m_presence->debugChain(this); - // Init threads - int process = 1; - int timeout = 1; - m_presence->startThreads(process,timeout); - m_sendCommandOnNotify = !(m_presence->addOnSubscribe() && - m_presence->addOnProbe() && m_presence->addOnPresence()); - } -} + if (partLine != "status" && partLine != "drop") + return false; -void YJGDriver::initJG(const NamedList& sect) -{ - if (m_jg) - m_jg->initialize(sect); - else { - m_jg = new YJGEngine(m_jb,sect); - m_jg->debugChain(this); - // Init threads - int read = 1; - int process = 1; - m_jg->startThreads(read,process); - } - m_jg->requestSubscribe(sect.getBoolValue("request_subscribe",true)); - if (debugAt(DebugAll)) { - String s; - s << "\r\nrequest_subscribe=" << String::boolText(m_jg->requestSubscribe()); - Debug(m_jg,DebugAll,"Initialized:%s",s.c_str()); - } -} - -bool YJGDriver::msgExecute(Message& msg, String& dest) -{ - if (!msg.userData()) { - Debug(this,DebugNote,"Jingle call failed. No data channel."); - msg.setParam("error","failure"); + // Empty partial word or name start with it: add name and prefix + if (!partWord || name().startsWith(partWord)) { + msg.retValue().append(name(),"\t"); + if (channels().skipNull()) + msg.retValue().append(prefix(),"\t"); return false; } - // Assume Jabber Component !!! + + // Partial word starts with module prefix: add channels + if (partWord.startsWith(prefix())) { + for (ObjList* o = channels().skipNull(); o; o = o->skipNext()) { + CallEndpoint* c = static_cast(o->get()); + if (c->id().startsWith(partWord)) + msg.retValue().append(c->id(),"\t"); + } + return true; + } + return false; +} + +// Check and build caller and called for Component run mode +// Caller: Set user if missing. Get default server identity for Yate Component +// Try to get an available resource for the called party +bool YJGDriver::setComponentCall(JabberID& caller, JabberID& called, + const char* cr, const char* cd, bool& available, String& error) +{ // Get identity for default server - String identity; - if (!iplugin.m_jb->getFullServerIdentity(identity)) { - Debug(this,DebugNote,"Jingle call failed. No default server."); - msg.setParam("error","failure"); + String domain; + if (!s_jabber->getServerIdentity(domain,true)) { + error = "No default server"; return false; } - String c = msg.getValue("caller"); - if (!c) - c = iplugin.anonymousCaller(); - if (!(c && JabberID::valid(c))) { - Debug(this,DebugNote,"Jingle call failed. Missing or invalid caller name."); - msg.setParam("error","failure"); + if (!cr) + cr = s_anonymousCaller; + // Validate caller's JID + if (!(cr && JabberID::valid(cr))) { + error << "Invalid caller=" << cr; return false; } - JabberID caller(c,identity); - JabberID called(dest); + caller.set(cr,domain); + called.set(cd); + // Get an available resource for the remote user if we keep the roster - bool available = true; - if (iplugin.m_presence->addOnSubscribe() || - iplugin.m_presence->addOnProbe() || - iplugin.m_presence->addOnPresence()) { + // Send subscribe and probe if not + if (s_presence->autoRoster()) { // Get remote user bool newPresence = false; - XMPPUser* remote = iplugin.m_presence->getRemoteUser(caller,called,true,0, + XMPPUser* remote = s_presence->getRemoteUser(caller,called,true,0, true,&newPresence); + if (!remote) { + error = "Remote user is unavailable"; + return false; + } // Get a resource for the caller JIDResource* res = remote->getAudio(true,true); if (!res) { - iplugin.m_presence->notifyNewUser(remote); + s_presence->notifyNewUser(remote); res = remote->getAudio(true,true); // This should never happen !!! if (!res) { remote->deref(); - Debug(this,DebugNote, - "Jingle call failed. Unable to get a resource for the caller."); - msg.setParam("error","failure"); + error = "Unable to get a resource for the caller"; return false; } } @@ -1886,10 +1780,9 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) res = remote->getAudio(false,true); available = (res != 0); if (!(newPresence || available)) { - if (!iplugin.m_jg->requestSubscribe()) { + if (!s_jingle->requestSubscribe()) { remote->deref(); - Debug(this,DebugNote,"Jingle call failed. Remote peer is unavailable."); - msg.setParam("error","offline"); + error = "Remote peer is unavailable"; return false; } remote->sendSubscribe(JBPresence::Subscribe,0); @@ -1904,15 +1797,14 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) else { available = false; // Get stream for default component - JBComponentStream* stream = m_jb->getStream(); + JBStream* stream = s_jabber->getStream(); if (!stream) { - Debug(this,DebugNote,"Jingle call failed. No stream for called=%s.",called.c_str()); - msg.setParam("error","failure"); + error << "No stream for called=" << called; return false; } // Send subscribe request and probe XMLElement* xml = 0; - if (iplugin.m_jg->requestSubscribe()) { + if (s_jingle->requestSubscribe()) { xml = JBPresence::createPresence(caller.bare(),called.bare(),JBPresence::Subscribe); stream->sendStanza(xml); } @@ -1920,35 +1812,62 @@ bool YJGDriver::msgExecute(Message& msg, String& dest) stream->sendStanza(xml); stream->deref(); } - // Set a resource for caller - if (caller.resource().null()) - caller.resource(iplugin.m_jb->defaultResource()); - // Parameters OK. Create connection and init channel - DDebug(this,DebugAll,"msgExecute. Caller: '%s'. Called: '%s'.", - caller.c_str(),called.c_str()); - YJGConnection* conn = new YJGConnection(m_jg,msg,caller,called,available); - Channel* ch = static_cast(msg.userData()); - if (ch && conn->connect(ch,msg.getValue("reason"))) { - msg.setParam("peerid",conn->id()); - msg.setParam("targetid",conn->id()); - } - conn->deref(); return true; } +// Message handler: Disconnect channels, destroy streams, clear rosters bool YJGDriver::received(Message& msg, int id) { - if (id == Timer) - m_jb->timerTick(msg.msgTime().msec()); - else - if (id == Halt) { - dropAll(msg); - lock(); - channels().clear(); - unlock(); - m_presence->cleanup(); - m_jb->cleanup(); + if (id == Status) { + String target = msg.getValue("module"); + // Target is the driver or channel + if (!target || target == name() || target.startsWith(prefix())) + return Driver::received(msg,id); + + // Check additional commands + if (!target.startSkip(name(),false)) + return false; + target.trimBlanks(); + int cmd = 0; + for (; cmd < StatusCmdCount; cmd++) + if (s_statusCmd[cmd] == target) + break; + + // Show streams + if (cmd == StatusStreams && s_jabber) { + msg.retValue().clear(); + msg.retValue() << "name=" << name(); + msg.retValue() << ",type=" << type(); + msg.retValue() << ",format=Type|State|Local|Remote"; + s_jabber->lock(); + msg.retValue() << ";count=" << s_jabber->streams().count(); + for (ObjList* o = s_jabber->streams().skipNull(); o; o = o->skipNext()) { + JBStream* stream = static_cast(o->get()); + msg.retValue() << ";" << JBEngine::lookupProto(stream->type()); + msg.retValue() << "|" << JBStream::lookupState(stream->state()); + msg.retValue() << "|" << stream->local(); + msg.retValue() << "|" << stream->remote(); + } + s_jabber->unlock(); + msg.retValue() << "\r\n"; + return true; } + } + else if (id == Halt) { + dropAll(msg); + lock(); + channels().clear(); + unlock(); + s_presence->cleanup(); + s_jabber->cleanup(); + s_jabber->cancelThreads(); + s_jingle->cancelThreads(); + s_presence->cancelThreads(); + s_jabber->detachService(s_presence); + s_jabber->detachService(s_jingle); + s_jabber->detachService(s_message); + s_jabber->detachService(s_stream); + } return Driver::received(msg,id); } @@ -1958,8 +1877,8 @@ bool YJGDriver::getJidFrom(JabberID& jid, Message& msg, bool checkDomain) if (username.null()) return decodeJid(jid,msg,"from",checkDomain); String domain; - iplugin.m_jb->getFullServerIdentity(domain); - jid.set(username,domain,iplugin.m_jb->defaultResource()); + s_jabber->getServerIdentity(domain,true); + jid.set(username,domain,s_jabber->defaultResource()); return true; } @@ -1972,7 +1891,7 @@ bool YJGDriver::decodeJid(JabberID& jid, Message& msg, const char* param, msg.c_str(),param,jid.c_str()); return false; } - if (checkDomain && !m_presence->validDomain(jid.domain())) { + if (checkDomain && !s_presence->validDomain(jid.domain())) { Debug(this,DebugNote,"'%s'. Parameter '%s'='%s' has invalid (unknown) domain.", msg.c_str(),param,jid.c_str()); return false; @@ -2007,7 +1926,7 @@ XMLElement* YJGDriver::getPresenceCommand(JabberID& from, JabberID& to, // 'iq' stanza String id = idCrt++; String domain; - if (iplugin.m_jb->getServerIdentity(domain)) + if (s_jabber->getServerIdentity(domain,false)) from.domain(domain); XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqSet,from,to,id); iq->addChild(command); @@ -2045,8 +1964,8 @@ void YJGDriver::processPresence(const JabberID& local, const JabberID& remote, (remote.bare() |= conn->remote().bare())) continue; conn->updateResource(remote.resource()); - if (conn->processPresence(true)) - conn->disconnect(); + if (conn->presenceChanged(true)) + conn->disconnect(0); } unlock(); return; @@ -2060,26 +1979,12 @@ void YJGDriver::processPresence(const JabberID& local, const JabberID& remote, if ((!broadcast && local.bare() != conn->local().bare()) || !conn->remote().match(remote)) continue; - if (conn->processPresence(false)) - conn->disconnect(); + if (conn->presenceChanged(false)) + conn->disconnect(0); } unlock(); } -void YJGDriver::createMediaString(String& dest, ObjList& formats, char sep) -{ - dest = ""; - String s = sep; - ObjList* obj = formats.skipNull(); - for (; obj; obj = obj->skipNext()) { - JGAudio* a = static_cast(obj->get()); - const char* payload = lookup(a->m_id.toInteger(),dict_payloads); - if (!payload) - continue; - dest.append(payload,s); - } -} - YJGConnection* YJGDriver::find(const JabberID& local, const JabberID& remote, bool anyResource) { String bareJID = local.bare();