/** * session.cpp * Yet Another Jingle Stack * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2006 Null Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include using namespace TelEngine; static XMPPNamespace s_ns; 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); return p; } void JGAudio::fromXML(XMLElement* element) { element->getAttribute("id",m_id); element->getAttribute("name",m_name); element->getAttribute("clockrate",m_clockrate); element->getAttribute("bitrate",m_bitrate); } void JGAudio::set(const char* id, const char* name, const char* clockrate, const char* bitrate) { m_id = id; m_name = name; m_clockrate = clockrate; m_bitrate = bitrate; } /** * 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; } XMLElement* JGTransport::createTransport() { return XMPPUtils::createElement(XMLElement::Transport, XMPPNamespace::JingleTransport); } 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); 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); } /** * JGSession */ String JGSession::s_dtmf = "0123456789#*ABCD"; 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} }; JGSession::JGSession(JGEngine* engine, JBComponentStream* stream, const String& callerJID, const String& calledJID) : 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_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); } JGSession::JGSession(JGEngine* engine, JBEvent* event) : 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) { // 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); } JGSession::~JGSession() { // Cancel pending outgoing. Hangup. Cleanup if (m_stream) { m_stream->cancelPending(false,&m_localSid); hangup(); m_stream->deref(); } 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)); } bool JGSession::accept(XMLElement* description) { if (state() != Pending) return false; XMLElement* jingle = createJingleSet(ActAccept,description, JGTransport::createTransport()); if (sendXML(jingle)) { m_state = Active; return true; } return false; } bool JGSession::sendResult(const char* id) { 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); } bool JGSession::sendDtmf(char dtmf, bool buttonUp) { if (!isDtmf(dtmf)) return false; String tmp = dtmf; XMLElement* xml = XMPPUtils::createElement(XMLElement::Dtmf, XMPPNamespace::Dtmf); xml->setAttribute("action",buttonUp?"button-up":"button-down"); xml->setAttribute("code",tmp); return sendXML(createJingleSet(ActContentInfo,xml)); } bool JGSession::sendDtmfMethod(const char* method) { XMLElement* xml = XMPPUtils::createElement(XMLElement::DtmfMethod, XMPPNamespace::Dtmf); xml->setAttribute("method",method); return sendXML(createJingleSet(ActContentInfo,xml)); } 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); iq->addChild(element); 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); } bool JGSession::sendError(XMLElement* element, XMPPError::Type error, XMPPError::ErrorType type, const char* text) { 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; } 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); 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(); 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); 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; 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; } // 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); } } return true; } bool JGSession::updateTransport(JGEvent* event) { // Detect transport type if (event->m_action == ActTransportCandidates) { m_transportType = TransportCandidates; event->m_action = ActTransport; DDebug(m_engine,DebugAll, "Session. Set transport type: 'candidates'. [%p]",this); } 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) { 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) { // Create id String id = m_localSid; id << "_" << (unsigned int)m_stanzaId; m_stanzaId++; e->setAttribute("id",id); appendSent(e); } // 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) return false; return true; } XMLElement* JGSession::createJingleSet(Action action, XMLElement* element1, XMLElement* element2) { // Create 'iq' and 'jingle' 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->addChild(element1); jingle->addChild(element2); iq->addChild(jingle); return iq; } void JGSession::confirmIq(XMLElement* element) { 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()); } void JGSession::eventTerminated(JGEvent* event) { lock(); if (event == m_lastEvent) { DDebug(m_engine,DebugAll, "Session. Event((%p): %u) terminated. [%p]",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); unlock(); } JGSentStanza* JGSession::isResponse(const JBEvent* jbev) { 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)) 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; } /* vi: set ts=8 sw=4 sts=4 noet: */