diff options
Diffstat (limited to 'Plugins/jingle/libjingle/talk/p2p/base/session.cc')
-rw-r--r-- | Plugins/jingle/libjingle/talk/p2p/base/session.cc | 1029 |
1 files changed, 1029 insertions, 0 deletions
diff --git a/Plugins/jingle/libjingle/talk/p2p/base/session.cc b/Plugins/jingle/libjingle/talk/p2p/base/session.cc new file mode 100644 index 0000000..1ea0dc3 --- /dev/null +++ b/Plugins/jingle/libjingle/talk/p2p/base/session.cc @@ -0,0 +1,1029 @@ +/* + * 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<buzz::XmlElement*>* 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<buzz::XmlElement*>::iterator iter = extra_xml->begin(); + for (std::vector<buzz::XmlElement*>::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<buzz::XmlElement> 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<buzz::XmlElement*>& elems) { + scoped_ptr<buzz::XmlElement> 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<buzz::XmlElement> 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<buzz::XmlElement> 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<buzz::XmlElement> 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 |