| /* |
| * libjingle |
| * Copyright 2004--2005, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "talk/p2p/base/sessionmanager.h" |
| #include "talk/base/common.h" |
| #include "talk/base/helpers.h" |
| #include "talk/p2p/base/constants.h" |
| #include "talk/xmpp/constants.h" |
| #include "talk/xmpp/jid.h" |
| |
| namespace cricket { |
| |
| SessionManager::SessionManager(PortAllocator *allocator, |
| talk_base::Thread *worker) { |
| allocator_ = allocator; |
| signaling_thread_ = talk_base::Thread::Current(); |
| if (worker == NULL) { |
| worker_thread_ = talk_base::Thread::Current(); |
| } else { |
| worker_thread_ = worker; |
| } |
| timeout_ = 50; |
| } |
| |
| SessionManager::~SessionManager() { |
| // Note: Session::Terminate occurs asynchronously, so it's too late to |
| // delete them now. They better be all gone. |
| ASSERT(session_map_.empty()); |
| //TerminateAll(); |
| } |
| |
| void SessionManager::AddClient(const std::string& session_type, |
| SessionClient* client) { |
| ASSERT(client_map_.find(session_type) == client_map_.end()); |
| client_map_[session_type] = client; |
| } |
| |
| void SessionManager::RemoveClient(const std::string& session_type) { |
| ClientMap::iterator iter = client_map_.find(session_type); |
| ASSERT(iter != client_map_.end()); |
| client_map_.erase(iter); |
| } |
| |
| SessionClient* SessionManager::GetClient(const std::string& session_type) { |
| ClientMap::iterator iter = client_map_.find(session_type); |
| return (iter != client_map_.end()) ? iter->second : NULL; |
| } |
| |
| Session *SessionManager::CreateSession(const std::string& name, |
| const std::string& session_type) { |
| return CreateSession(name, SessionID(name, CreateRandomId()), session_type, |
| false); |
| } |
| |
| Session *SessionManager::CreateSession( |
| const std::string &name, const SessionID& id, |
| const std::string& session_type, bool received_initiate) { |
| SessionClient* client = GetClient(session_type); |
| ASSERT(client != NULL); |
| |
| Session *session = new Session(this, name, id, session_type, client); |
| session_map_[session->id()] = session; |
| session->SignalRequestSignaling.connect( |
| this, &SessionManager::OnRequestSignaling); |
| session->SignalOutgoingMessage.connect( |
| this, &SessionManager::OnOutgoingMessage); |
| session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage); |
| SignalSessionCreate(session, received_initiate); |
| session->client()->OnSessionCreate(session, received_initiate); |
| return session; |
| } |
| |
| void SessionManager::DestroySession(Session *session) { |
| if (session != NULL) { |
| SessionMap::iterator it = session_map_.find(session->id()); |
| if (it != session_map_.end()) { |
| SignalSessionDestroy(session); |
| session->client()->OnSessionDestroy(session); |
| session_map_.erase(it); |
| delete session; |
| } |
| } |
| } |
| |
| Session *SessionManager::GetSession(const SessionID& id) { |
| SessionMap::iterator it = session_map_.find(id); |
| if (it != session_map_.end()) |
| return it->second; |
| return NULL; |
| } |
| |
| void SessionManager::TerminateAll() { |
| while (session_map_.begin() != session_map_.end()) { |
| Session *session = session_map_.begin()->second; |
| session->Terminate(); |
| } |
| } |
| |
| bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) { |
| if (stanza->Name() != buzz::QN_IQ) |
| return false; |
| if (!stanza->HasAttr(buzz::QN_TYPE)) |
| return false; |
| if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) |
| return false; |
| |
| const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); |
| if (!session) |
| return false; |
| if (!session->HasAttr(buzz::QN_TYPE)) |
| return false; |
| if (!session->HasAttr(buzz::QN_ID) || !session->HasAttr(QN_INITIATOR)) |
| return false; |
| |
| return true; |
| } |
| |
| Session* SessionManager::FindSessionForStanza(const buzz::XmlElement* stanza, |
| bool incoming) { |
| const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION); |
| ASSERT(session_xml != NULL); |
| |
| SessionID id; |
| id.set_id_str(session_xml->Attr(buzz::QN_ID)); |
| id.set_initiator(session_xml->Attr(QN_INITIATOR)); |
| |
| // Pass this message to the session in question. |
| SessionMap::iterator iter = session_map_.find(id); |
| if (iter == session_map_.end()) |
| return NULL; |
| |
| Session* session = iter->second; |
| |
| // match on "from"? or "to"? |
| buzz::QName attr = buzz::QN_TO; |
| if (incoming) { |
| attr = buzz::QN_FROM; |
| } |
| buzz::Jid remote(session->remote_name()); |
| buzz::Jid match(stanza->Attr(attr)); |
| if (remote == match) { |
| return session; |
| } |
| return NULL; |
| } |
| |
| void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) { |
| ASSERT(stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET); |
| |
| Session* session = FindSessionForStanza(stanza, true); |
| if (session) { |
| session->OnIncomingMessage(stanza); |
| return; |
| } |
| |
| const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION); |
| ASSERT(session_xml != NULL); |
| if (session_xml->Attr(buzz::QN_TYPE) == "initiate") { |
| std::string session_type = FindClient(session_xml); |
| if (session_type.size() == 0) { |
| SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", |
| "unknown session description type", NULL); |
| } else { |
| SessionID id; |
| id.set_id_str(session_xml->Attr(buzz::QN_ID)); |
| id.set_initiator(session_xml->Attr(QN_INITIATOR)); |
| |
| session = CreateSession(stanza->Attr(buzz::QN_TO), |
| id, |
| session_type, true); |
| session->OnIncomingMessage(stanza); |
| |
| // If we haven't rejected, and we haven't selected a transport yet, |
| // let's do it now. |
| if ((session->state() != Session::STATE_SENTREJECT) && |
| (session->transport() == NULL)) { |
| session->ChooseTransport(stanza); |
| } |
| } |
| return; |
| } |
| |
| SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", |
| "unknown session", NULL); |
| } |
| |
| void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza, |
| const buzz::XmlElement* response_stanza) { |
| // We don't do anything with the response now. If we need to we can forward |
| // it to the session. |
| return; |
| } |
| |
| void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza, |
| const buzz::XmlElement* error_stanza) { |
| Session* session = FindSessionForStanza(orig_stanza, false); |
| if (session) { |
| scoped_ptr<buzz::XmlElement> synthetic_error; |
| if (!error_stanza) { |
| // A failed send is semantically equivalent to an error response, so we |
| // can just turn the former into the latter. |
| synthetic_error.reset( |
| CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND, |
| "cancel", "Recipient did not respond", NULL)); |
| error_stanza = synthetic_error.get(); |
| } |
| |
| session->OnFailedSend(orig_stanza, error_stanza); |
| } |
| } |
| |
| std::string SessionManager::FindClient(const buzz::XmlElement* session) { |
| for (const buzz::XmlElement* elem = session->FirstElement(); |
| elem != NULL; |
| elem = elem->NextElement()) { |
| if (elem->Name().LocalPart() == "description") { |
| ClientMap::iterator iter = client_map_.find(elem->Name().Namespace()); |
| if (iter != client_map_.end()) |
| return iter->first; |
| } |
| } |
| return ""; |
| } |
| |
| void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza, |
| const buzz::QName& name, |
| const std::string& type, |
| const std::string& text, |
| const buzz::XmlElement* extra_info) { |
| scoped_ptr<buzz::XmlElement> msg( |
| CreateErrorMessage(stanza, name, type, text, extra_info)); |
| SignalOutgoingMessage(msg.get()); |
| } |
| |
| buzz::XmlElement* SessionManager::CreateErrorMessage( |
| const buzz::XmlElement* stanza, |
| const buzz::QName& name, |
| const std::string& type, |
| const std::string& text, |
| const buzz::XmlElement* extra_info) { |
| buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ); |
| iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM)); |
| iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID)); |
| iq->SetAttr(buzz::QN_TYPE, "error"); |
| |
| for (const buzz::XmlElement* elem = stanza->FirstElement(); |
| elem != NULL; |
| elem = elem->NextElement()) { |
| iq->AddElement(new buzz::XmlElement(*elem)); |
| } |
| |
| buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR); |
| error->SetAttr(buzz::QN_TYPE, type); |
| iq->AddElement(error); |
| |
| // If the error name is not in the standard namespace, we have to first add |
| // some error from that namespace. |
| if (name.Namespace() != buzz::NS_STANZA) { |
| error->AddElement( |
| new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION)); |
| } |
| error->AddElement(new buzz::XmlElement(name)); |
| |
| if (extra_info) |
| error->AddElement(new buzz::XmlElement(*extra_info)); |
| |
| if (text.size() > 0) { |
| // It's okay to always use English here. This text is for debugging |
| // purposes only. |
| buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT); |
| text_elem->SetAttr(buzz::QN_XML_LANG, "en"); |
| text_elem->SetBodyText(text); |
| error->AddElement(text_elem); |
| } |
| |
| // TODO: Should we include error codes as well for SIP compatibility? |
| |
| return iq; |
| } |
| |
| void SessionManager::OnOutgoingMessage(Session* session, |
| const buzz::XmlElement* stanza) { |
| SignalOutgoingMessage(stanza); |
| } |
| |
| void SessionManager::OnErrorMessage(Session* session, |
| const buzz::XmlElement* stanza, |
| const buzz::QName& name, |
| const std::string& type, |
| const std::string& text, |
| const buzz::XmlElement* extra_info) { |
| SendErrorMessage(stanza, name, type, text, extra_info); |
| } |
| |
| void SessionManager::OnSignalingReady() { |
| for (SessionMap::iterator it = session_map_.begin(); |
| it != session_map_.end(); |
| ++it) { |
| it->second->OnSignalingReady(); |
| } |
| } |
| |
| void SessionManager::OnRequestSignaling(Session* session) { |
| SignalRequestSignaling(); |
| } |
| |
| } // namespace cricket |