/* * 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/session.h" #include "talk/base/common.h" #include "talk/base/logging.h" #include "talk/base/helpers.h" #include "talk/xmpp/constants.h" #include "talk/xmpp/jid.h" #include "talk/p2p/base/sessionclient.h" #include "talk/p2p/base/transport.h" #include "talk/p2p/base/transportchannelproxy.h" #include "talk/p2p/base/p2ptransport.h" #include "talk/p2p/base/constants.h" namespace { const uint32 MSG_TIMEOUT = 1; const uint32 MSG_ERROR = 2; const uint32 MSG_STATE = 3; // This will be initialized at run time to hold the list of default transports. std::string* gDefaultTransports = NULL; size_t gNumDefaultTransports = 0; } // namespace namespace cricket { Session::Session(SessionManager *session_manager, const std::string& name, const SessionID& id, const std::string& session_type, SessionClient* client) { ASSERT(session_manager->signaling_thread()->IsCurrent()); ASSERT(client != NULL); session_manager_ = session_manager; name_ = name; id_ = id; session_type_ = session_type; client_ = client; error_ = ERROR_NONE; state_ = STATE_INIT; initiator_ = false; description_ = NULL; remote_description_ = NULL; transport_ = NULL; compatibility_mode_ = false; } Session::~Session() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT(state_ != STATE_DEINIT); state_ = STATE_DEINIT; SignalState(this, state_); delete description_; delete remote_description_; for (ChannelMap::iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { iter->second->SignalDestroyed(iter->second); delete iter->second; } for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { delete *iter; } delete transport_; } bool Session::Initiate(const std::string &to, std::vector* extra_xml, const SessionDescription *description) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Only from STATE_INIT if (state_ != STATE_INIT) return false; // Setup for signaling. remote_name_ = to; initiator_ = true; description_ = description; // Make sure we have transports to negotiate. CreateTransports(); // Send the initiate message, including the application and transport offers. XmlElements elems; elems.push_back(client_->TranslateSessionDescription(description)); for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { buzz::XmlElement* elem = (*iter)->CreateTransportOffer(); elems.push_back(elem); } if (extra_xml != NULL) { std::vector::iterator iter = extra_xml->begin(); for (std::vector::iterator iter = extra_xml->begin(); iter != extra_xml->end(); ++iter) { elems.push_back(new buzz::XmlElement(**iter)); } } SendSessionMessage("initiate", elems); SetState(Session::STATE_SENTINITIATE); // We speculatively start attempting connection of the P2P transports. ConnectDefaultTransportChannels(true); return true; } void Session::ConnectDefaultTransportChannels(bool create) { Transport* transport = GetTransport(kNsP2pTransport); if (transport) { for (ChannelMap::iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { ASSERT(create != transport->HasChannel(iter->first)); if (create) { transport->CreateChannel(iter->first, session_type()); } } transport->ConnectChannels(); } } void Session::CreateDefaultTransportChannel(const std::string& name) { // This method is only relevant when we have created the default transport // but not received a transport-accept. ASSERT(transport_ == NULL); ASSERT(state_ == STATE_SENTINITIATE); Transport* p2p_transport = GetTransport(kNsP2pTransport); if (p2p_transport) { ASSERT(!p2p_transport->HasChannel(name)); p2p_transport->CreateChannel(name, session_type()); } } bool Session::Accept(const SessionDescription *description) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Only if just received initiate if (state_ != STATE_RECEIVEDINITIATE) return false; // Setup for signaling. initiator_ = false; description_ = description; // If we haven't selected a transport, wait for ChooseTransport to complete if (transport_ == NULL) return true; // Send the accept message. XmlElements elems; elems.push_back(client_->TranslateSessionDescription(description_)); SendSessionMessage("accept", elems); SetState(Session::STATE_SENTACCEPT); return true; } bool Session::Reject() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Reject is sent in response to an initiate or modify, to reject the // request if (state_ != STATE_RECEIVEDINITIATE && state_ != STATE_RECEIVEDMODIFY) return false; // Setup for signaling. initiator_ = false; // Send the reject message. SendSessionMessage("reject", XmlElements()); SetState(STATE_SENTREJECT); return true; } bool Session::Redirect(const std::string & target) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Redirect is sent in response to an initiate or modify, to redirect the // request if (state_ != STATE_RECEIVEDINITIATE) return false; // Setup for signaling. initiator_ = false; // Send a redirect message to the given target. We include an element that // names the redirector (us), which may be useful to the other side. buzz::XmlElement* target_elem = new buzz::XmlElement(QN_REDIRECT_TARGET); target_elem->AddAttr(buzz::QN_NAME, target); buzz::XmlElement* cookie = new buzz::XmlElement(QN_REDIRECT_COOKIE); buzz::XmlElement* regarding = new buzz::XmlElement(QN_REDIRECT_REGARDING); regarding->AddAttr(buzz::QN_NAME, name_); cookie->AddElement(regarding); XmlElements elems; elems.push_back(target_elem); elems.push_back(cookie); SendSessionMessage("redirect", elems); // A redirect puts us in the same state as reject. It just sends a different // kind of reject message, if you like. SetState(STATE_SENTREDIRECT); return true; } bool Session::Terminate() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Either side can terminate, at any time. switch (state_) { case STATE_SENTTERMINATE: case STATE_RECEIVEDTERMINATE: return false; case STATE_SENTREDIRECT: // We must not send terminate if we redirect. break; case STATE_SENTREJECT: case STATE_RECEIVEDREJECT: // We don't need to send terminate if we sent or received a reject... // it's implicit. break; default: SendSessionMessage("terminate", XmlElements()); break; } SetState(STATE_SENTTERMINATE); return true; } void Session::SendInfoMessage(const XmlElements& elems) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); SendSessionMessage("info", elems); } void Session::SetPotentialTransports(const std::string names[], size_t length) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); for (size_t i = 0; i < length; ++i) { Transport* transport = NULL; if (names[i] == kNsP2pTransport) { transport = new P2PTransport(session_manager_); } else { ASSERT(false); } if (transport) { ASSERT(transport->name() == names[i]); potential_transports_.push_back(transport); transport->SignalConnecting.connect( this, &Session::OnTransportConnecting); transport->SignalWritableState.connect( this, &Session::OnTransportWritable); transport->SignalRequestSignaling.connect( this, &Session::OnTransportRequestSignaling); transport->SignalTransportMessage.connect( this, &Session::OnTransportSendMessage); transport->SignalTransportError.connect( this, &Session::OnTransportSendError); transport->SignalChannelGone.connect( this, &Session::OnTransportChannelGone); } } } Transport* Session::GetTransport(const std::string& name) { if (transport_ != NULL) { if (name == transport_->name()) return transport_; } else { for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { if (name == (*iter)->name()) return *iter; } } return NULL; } TransportChannel* Session::CreateChannel(const std::string& name) { //ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT(channels_.find(name) == channels_.end()); TransportChannelProxy* channel = new TransportChannelProxy(name, session_type_); channels_[name] = channel; if (transport_) { ASSERT(!transport_->HasChannel(name)); channel->SetImplementation(transport_->CreateChannel(name, session_type_)); } else if (state_ == STATE_SENTINITIATE) { // In this case, we have already speculatively created the default // transport. We should create this channel as well so that it may begin // early connection. CreateDefaultTransportChannel(name); } return channel; } TransportChannel* Session::GetChannel(const std::string& name) { ChannelMap::iterator iter = channels_.find(name); return (iter != channels_.end()) ? iter->second : NULL; } void Session::DestroyChannel(TransportChannel* channel) { ChannelMap::iterator iter = channels_.find(channel->name()); ASSERT(iter != channels_.end()); ASSERT(channel == iter->second); channels_.erase(iter); channel->SignalDestroyed(channel); delete channel; } TransportChannelImpl* Session::GetImplementation(TransportChannel* channel) { ChannelMap::iterator iter = channels_.find(channel->name()); return (iter != channels_.end()) ? iter->second->impl() : NULL; } void Session::CreateTransports() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT((state_ == STATE_INIT) || (state_ == STATE_RECEIVEDINITIATE)); if (potential_transports_.empty()) { if (gDefaultTransports == NULL) { gNumDefaultTransports = 1; gDefaultTransports = new std::string[1]; gDefaultTransports[0] = kNsP2pTransport; } SetPotentialTransports(gDefaultTransports, gNumDefaultTransports); } } bool Session::ChooseTransport(const buzz::XmlElement* stanza) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT(state_ == STATE_RECEIVEDINITIATE); ASSERT(transport_ == NULL); // Make sure we have decided on our own transports. CreateTransports(); // Retrieve the session message. const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); ASSERT(session != NULL); // Try the offered transports until we find one that we support. bool found_offer = false; const buzz::XmlElement* elem = session->FirstElement(); while (elem) { if (elem->Name().LocalPart() == "transport") { found_offer = true; Transport* transport = GetTransport(elem->Name().Namespace()); if (transport && transport->OnTransportOffer(elem)) { SetTransport(transport); break; } } elem = elem->NextElement(); } // If the offer did not include any transports, then we are talking to an // old client. In that case, we turn on compatibility mode, and we assume // an offer containing just P2P, which is all that old clients support. if (!found_offer) { compatibility_mode_ = true; Transport* transport = GetTransport(kNsP2pTransport); ASSERT(transport != NULL); scoped_ptr transport_offer( new buzz::XmlElement(kQnP2pTransport, true)); bool valid = transport->OnTransportOffer(transport_offer.get()); ASSERT(valid); if (valid) SetTransport(transport); } if (!transport_) { SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ACCEPTABLE, "modify", "no supported transport in offer", NULL); return false; } // Get the description of the transport we picked. buzz::XmlElement* answer = transport_->CreateTransportAnswer(); ASSERT(answer->Name() == buzz::QName(transport_->name(), "transport")); // Send a transport-accept message telling the other side our decision, // unless this is an old client that is not expecting one. if (!compatibility_mode_) { XmlElements elems; elems.push_back(answer); SendSessionMessage("transport-accept", elems); } // If the user wants to accept, allow that now if (description_) { Accept(description_); } return true; } void Session::SetTransport(Transport* transport) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT(transport_ == NULL); transport_ = transport; // Drop the transports that were not selected. bool found = false; for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { if (*iter == transport_) { found = true; } else { delete *iter; } } potential_transports_.clear(); // We require the selected transport to be one of the potential transports ASSERT(found); // Create implementations for all of the channels if they don't exist. for (ChannelMap::iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { TransportChannelProxy* channel = iter->second; TransportChannelImpl* impl = transport_->GetChannel(channel->name()); if (impl == NULL) impl = transport_->CreateChannel(channel->name(), session_type()); ASSERT(impl != NULL); channel->SetImplementation(impl); } // Have this transport start connecting if it is not already. // (We speculatively connect the most common transport right away.) transport_->ConnectChannels(); } void Session::SetState(State state) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); if (state != state_) { state_ = state; SignalState(this, state_); session_manager_->signaling_thread()->Post(this, MSG_STATE); } } void Session::SetError(Error error) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); if (error != error_) { error_ = error; SignalError(this, error); if (error_ != ERROR_NONE) session_manager_->signaling_thread()->Post(this, MSG_ERROR); } } void Session::OnSignalingReady() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Forward this to every transport. Those that did not request it should // ignore this call. if (transport_ != NULL) { transport_->OnSignalingReady(); } else { for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { (*iter)->OnSignalingReady(); } } } void Session::OnTransportConnecting(Transport* transport) { // This is an indication that we should begin watching the writability // state of the transport. OnTransportWritable(transport); } void Session::OnTransportWritable(Transport* transport) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT((NULL == transport_) || (transport == transport_)); // If the transport is not writable, start a timer to make sure that it // becomes writable within a reasonable amount of time. If it does not, we // terminate since we can't actually send data. If the transport is writable, // cancel the timer. Note that writability transitions may occur repeatedly // during the lifetime of the session. session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); if (transport->HasChannels() && !transport->writable()) { session_manager_->signaling_thread()->PostDelayed( session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); } } void Session::OnTransportRequestSignaling(Transport* transport) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); SignalRequestSignaling(this); } void Session::OnTransportSendMessage(Transport* transport, const XmlElements& elems) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); for (size_t i = 0; i < elems.size(); ++i) ASSERT(elems[i]->Name() == buzz::QName(transport->name(), "transport")); if (compatibility_mode_) { // In backward compatibility mode, we send a candidates message. XmlElements candidates; for (size_t i = 0; i < elems.size(); ++i) { for (const buzz::XmlElement* elem = elems[i]->FirstElement(); elem != NULL; elem = elem->NextElement()) { ASSERT(elem->Name() == kQnP2pCandidate); // Convert this candidate to an old style candidate (namespace change) buzz::XmlElement* legacy_candidate = new buzz::XmlElement(*elem); legacy_candidate->SetName(kQnLegacyCandidate); candidates.push_back(legacy_candidate); } delete elems[i]; } SendSessionMessage("candidates", candidates); } else { // If we haven't finished negotiation, then we may later discover that we // need compatibility mode, in which case, we will need to re-send these. if ((transport_ == NULL) && (transport->name() == kNsP2pTransport)) { for (size_t i = 0; i < elems.size(); ++i) candidates_.push_back(new buzz::XmlElement(*elems[i])); } SendSessionMessage("transport-info", elems); } } void Session::OnTransportSendError(Transport* transport, const buzz::XmlElement* stanza, const buzz::QName& name, const std::string& type, const std::string& text, const buzz::XmlElement* extra_info) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); SignalErrorMessage(this, stanza, name, type, text, extra_info); } void Session::OnTransportChannelGone(Transport* transport, const std::string& name) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); SignalChannelGone(this, name); } void Session::SendSessionMessage( const std::string& type, const std::vector& elems) { scoped_ptr iq(new buzz::XmlElement(buzz::QN_IQ)); iq->SetAttr(buzz::QN_TO, remote_name_); iq->SetAttr(buzz::QN_TYPE, buzz::STR_SET); buzz::XmlElement *session = new buzz::XmlElement(QN_SESSION, true); session->AddAttr(buzz::QN_TYPE, type); session->AddAttr(buzz::QN_ID, id_.id_str()); session->AddAttr(QN_INITIATOR, id_.initiator()); for (size_t i = 0; i < elems.size(); ++i) session->AddElement(elems[i]); iq->AddElement(session); SignalOutgoingMessage(this, iq.get()); } void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) { scoped_ptr ack(new buzz::XmlElement(buzz::QN_IQ)); ack->SetAttr(buzz::QN_TO, remote_name_); ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID)); ack->SetAttr(buzz::QN_TYPE, "result"); SignalOutgoingMessage(this, ack.get()); } void Session::OnIncomingMessage(const buzz::XmlElement* stanza) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); ASSERT(stanza->Name() == buzz::QN_IQ); buzz::Jid remote(remote_name_); buzz::Jid from(stanza->Attr(buzz::QN_FROM)); ASSERT(state_ == STATE_INIT || from == remote); const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); ASSERT(session != NULL); if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) { ASSERT(false); return; } ASSERT(session->HasAttr(buzz::QN_TYPE)); std::string type = session->Attr(buzz::QN_TYPE); bool valid = false; if (type == "initiate") { valid = OnInitiateMessage(stanza, session); } else if (type == "accept") { valid = OnAcceptMessage(stanza, session); } else if (type == "reject") { valid = OnRejectMessage(stanza, session); } else if (type == "redirect") { valid = OnRedirectMessage(stanza, session); } else if (type == "info") { valid = OnInfoMessage(stanza, session); } else if (type == "transport-accept") { valid = OnTransportAcceptMessage(stanza, session); } else if (type == "transport-info") { valid = OnTransportInfoMessage(stanza, session); } else if (type == "terminate") { valid = OnTerminateMessage(stanza, session); } else if (type == "candidates") { // This is provided for backward compatibility. // TODO: Remove this once old candidates are gone. valid = OnCandidatesMessage(stanza, session); } else { SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", "unknown session message type", NULL); } // If the message was not valid, we should have sent back an error above. // If it was valid, then we send an acknowledgement here. if (valid) SendAcknowledgementMessage(stanza); } void Session::OnFailedSend(const buzz::XmlElement* orig_stanza, const buzz::XmlElement* error_stanza) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); const buzz::XmlElement* orig_session = orig_stanza->FirstNamed(QN_SESSION); ASSERT(orig_session != NULL); std::string error_type = "cancel"; const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR); ASSERT(error != NULL); if (error) { ASSERT(error->HasAttr(buzz::QN_TYPE)); error_type = error->Attr(buzz::QN_TYPE); LOG(LERROR) << "Session error:\n" << error->Str() << "\n" << "in response to:\n" << orig_session->Str(); } bool fatal_error = false; ASSERT(orig_session->HasAttr(buzz::QN_TYPE)); if ((orig_session->Attr(buzz::QN_TYPE) == "transport-info") || (orig_session->Attr(buzz::QN_TYPE) == "candidates")) { // Transport messages frequently generate errors because they are sent right // when we detect a network failure. For that reason, we ignore such // errors, because if we do not establish writability again, we will // terminate anyway. The exceptions are transport-specific error tags, // which we pass on to the respective transport. for (const buzz::XmlElement* elem = error->FirstElement(); NULL != elem; elem = elem->NextElement()) { if (Transport* transport = GetTransport(elem->Name().Namespace())) { if (!transport->OnTransportError(orig_session, elem)) { fatal_error = true; break; } } } } else if ((error_type != "continue") && (error_type != "wait")) { // We do not set an error if the other side said it is okay to continue // (possibly after waiting). These errors can be ignored. fatal_error = true; } if (fatal_error) { SetError(ERROR_RESPONSE); } } bool Session::OnInitiateMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { if (!CheckState(stanza, STATE_INIT)) return false; if (!FindRemoteSessionDescription(stanza, session)) return false; initiator_ = false; remote_name_ = stanza->Attr(buzz::QN_FROM); SetState(STATE_RECEIVEDINITIATE); return true; } bool Session::OnAcceptMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { if (!CheckState(stanza, STATE_SENTINITIATE)) return false; if (!FindRemoteSessionDescription(stanza, session)) return false; SetState(STATE_RECEIVEDACCEPT); return true; } bool Session::OnRejectMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { if (!CheckState(stanza, STATE_SENTINITIATE)) return false; SetState(STATE_RECEIVEDREJECT); return true; } bool Session::OnRedirectMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { if (!CheckState(stanza, STATE_SENTINITIATE)) return false; const buzz::XmlElement *redirect_target; if (!FindRequiredElement(stanza, session, QN_REDIRECT_TARGET, &redirect_target)) return false; if (!FindRequiredAttribute(stanza, redirect_target, buzz::QN_NAME, &remote_name_)) return false; const buzz::XmlElement* redirect_cookie = session->FirstNamed(QN_REDIRECT_COOKIE); XmlElements elems; elems.push_back(client_->TranslateSessionDescription(description_)); if (redirect_cookie) elems.push_back(new buzz::XmlElement(*redirect_cookie)); SendSessionMessage("initiate", elems); // Clear the connection timeout (if any). We will start the connection // timer from scratch when SignalConnecting fires. session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); // Reset all of the sockets back into the initial state. for (TransportList::iterator iter = potential_transports_.begin(); iter != potential_transports_.end(); ++iter) { (*iter)->ResetChannels(); } ConnectDefaultTransportChannels(false); return true; } bool Session::OnInfoMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { XmlElements elems; for (const buzz::XmlElement* elem = session->FirstElement(); elem != NULL; elem = elem->NextElement()) { elems.push_back(new buzz::XmlElement(*elem)); } SignalInfoMessage(this, elems); return true; } bool Session::OnTransportAcceptMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { if (!CheckState(stanza, STATE_SENTINITIATE)) return false; Transport* transport = NULL; const buzz::XmlElement* transport_elem = NULL; for(const buzz::XmlElement* elem = session->FirstElement(); elem != NULL; elem = elem->NextElement()) { if (elem->Name().LocalPart() == "transport") { Transport* transport = GetTransport(elem->Name().Namespace()); if (transport) { if (transport_elem) { // trying to accept two transport? SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", "transport-accept has two answers", NULL); return false; } transport_elem = elem; if (!transport->OnTransportAnswer(transport_elem)) { SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", "transport-accept is not acceptable", NULL); return false; } SetTransport(transport); } } } if (!transport_elem) { SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ALLOWED, "modify", "no supported transport in answer", NULL); return false; } // If we discovered that we need compatibility mode and we have sent some // candidates already (using transport-info), then we need to re-send them // using the candidates message. if (compatibility_mode_ && (candidates_.size() > 0)) { ASSERT(transport_ != NULL); ASSERT(transport_->name() == kNsP2pTransport); OnTransportSendMessage(transport_, candidates_); } else { for (size_t i = 0; i < candidates_.size(); ++i) delete candidates_[i]; } candidates_.clear(); return true; } bool Session::OnTransportInfoMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { for(const buzz::XmlElement* elem = session->FirstElement(); elem != NULL; elem = elem->NextElement()) { if (elem->Name().LocalPart() == "transport") { Transport* transport = GetTransport(elem->Name().Namespace()); if (transport) { if (!transport->OnTransportMessage(elem, stanza)) return false; } } } return true; } bool Session::OnTerminateMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { for (const buzz::XmlElement *elem = session->FirstElement(); elem != NULL; elem = elem->NextElement()) { // elem->Name().LocalPart() is the reason for termination SignalReceivedTerminateReason(this, elem->Name().LocalPart()); // elem->FirstElement() might contain a debug string for termination const buzz::XmlElement *debugElement = elem->FirstElement(); if (debugElement != NULL) { LOG(LS_VERBOSE) << "Received error on call: " << debugElement->Name().LocalPart(); } } SetState(STATE_RECEIVEDTERMINATE); return true; } bool Session::OnCandidatesMessage(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { // If we don't have a transport, then this is the first candidates message. // We first create a fake transport-accept message in order to finish the // negotiation and create a transport. if (!transport_) { compatibility_mode_ = true; scoped_ptr transport_accept( new buzz::XmlElement(QN_SESSION)); transport_accept->SetAttr(buzz::QN_TYPE, "transport-accept"); buzz::XmlElement* transport_offer = new buzz::XmlElement(kQnP2pTransport, true); transport_accept->AddElement(transport_offer); // It is okay to pass the original stanza here. That is only used if we // send an error message. Normal processing looks only at transport_accept. bool valid = OnTransportAcceptMessage(stanza, transport_accept.get()); ASSERT(valid); } ASSERT(transport_ != NULL); ASSERT(transport_->name() == kNsP2pTransport); // Wrap the candidates in a transport element as they would appear in a // transport-info message and send this to the transport. scoped_ptr transport_info( new buzz::XmlElement(kQnP2pTransport, true)); for (const buzz::XmlElement* elem = session->FirstNamed(kQnLegacyCandidate); elem != NULL; elem = elem->NextNamed(kQnLegacyCandidate)) { buzz::XmlElement* new_candidate = new buzz::XmlElement(*elem); new_candidate->SetName(kQnP2pCandidate); transport_info->AddElement(new_candidate); } return transport_->OnTransportMessage(transport_info.get(), stanza); } bool Session::CheckState(const buzz::XmlElement* stanza, State state) { ASSERT(state_ == state); if (state_ != state) { SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ALLOWED, "modify", "message not allowed in current state", NULL); return false; } return true; } bool Session::FindRequiredElement(const buzz::XmlElement* stanza, const buzz::XmlElement* parent, const buzz::QName& name, const buzz::XmlElement** elem) { *elem = parent->FirstNamed(name); if (*elem == NULL) { std::string text; text += "element '" + parent->Name().Merged() + "' missing required child '" + name.Merged() + "'"; SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", text, NULL); return false; } return true; } bool Session::FindRemoteSessionDescription(const buzz::XmlElement* stanza, const buzz::XmlElement* session) { buzz::QName qn_session(session_type_, "description"); const buzz::XmlElement* desc; if (!FindRequiredElement(stanza, session, qn_session, &desc)) return false; remote_description_ = client_->CreateSessionDescription(desc); return true; } bool Session::FindRequiredAttribute(const buzz::XmlElement* stanza, const buzz::XmlElement* elem, const buzz::QName& name, std::string* value) { if (!elem->HasAttr(name)) { std::string text; text += "element '" + elem->Name().Merged() + "' missing required attribute '" + name.Merged() + "'"; SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", text, NULL); return false; } else { *value = elem->Attr(name); return true; } } void Session::OnMessage(talk_base::Message *pmsg) { switch(pmsg->message_id) { case MSG_TIMEOUT: // Session timeout has occured. SetError(ERROR_TIME); break; case MSG_ERROR: // Any of the defined errors is most likely fatal. Terminate(); break; case MSG_STATE: switch (state_) { case STATE_SENTACCEPT: case STATE_RECEIVEDACCEPT: SetState(STATE_INPROGRESS); ASSERT(transport_ != NULL); break; case STATE_SENTREJECT: case STATE_SENTREDIRECT: case STATE_RECEIVEDREJECT: Terminate(); break; case STATE_SENTTERMINATE: case STATE_RECEIVEDTERMINATE: session_manager_->DestroySession(this); break; default: // Explicitly ignoring some states here. break; } break; } } } // namespace cricket