/* * 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/base/common.h" #include "talk/base/logging.h" #include "talk/p2p/base/helpers.h" #include "talk/p2p/base/session.h" namespace cricket { const uint32 MSG_TIMEOUT = 1; const uint32 MSG_ERROR = 2; const uint32 MSG_STATE = 3; Session::Session(SessionManager *session_manager, const std::string &name, const SessionID& id) { session_manager_ = session_manager; ASSERT(session_manager_->signaling_thread()->IsCurrent()); name_ = name; id_ = id; error_ = ERROR_NONE; state_ = STATE_INIT; initiator_ = false; description_ = NULL; remote_description_ = NULL; socket_manager_ = new SocketManager(session_manager_); socket_manager_->SignalCandidatesReady.connect(this, &Session::OnCandidatesReady); socket_manager_->SignalNetworkError.connect(this, &Session::OnNetworkError); socket_manager_->SignalState.connect(this, &Session::OnSocketState); socket_manager_->SignalRequestSignaling.connect(this, &Session::OnRequestSignaling); } Session::~Session() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); delete description_; delete remote_description_; delete socket_manager_; session_manager_->signaling_thread()->Clear(this); } P2PSocket *Session::CreateSocket(const std::string &name) { return socket_manager_->CreateSocket(name); } void Session::DestroySocket(P2PSocket *socket) { socket_manager_->DestroySocket(socket); } void Session::OnCandidatesReady(const std::vector& candidates) { SendSessionMessage(SessionMessage::TYPE_CANDIDATES, NULL, &candidates, NULL); } void Session::OnNetworkError() { // Socket manager is experiencing a network error trying to allocate // network resources (usually port allocation) set_error(ERROR_NETWORK); } void Session::OnSocketState() { // If the call is not in progress, then we don't care about writability. // We have separate timers for making sure we transition back to the in- // progress state in time. if (state_ != STATE_INPROGRESS) return; // Put the timer into the write state. This is called when the state changes, // so we will restart the timer each time we lose writability. if (socket_manager_->writable()) { session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); } else { session_manager_->signaling_thread()->PostDelayed( session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); } } void Session::OnRequestSignaling() { SignalRequestSignaling(); } void Session::OnSignalingReady() { socket_manager_->OnSignalingReady(); } void Session::SendSessionMessage(SessionMessage::Type type, const SessionDescription* description, const std::vector* candidates, SessionMessage::Cookie* redirect_cookie) { SessionMessage m; m.set_type(type); m.set_to(remote_address_); m.set_name(name_); m.set_description(description); m.set_session_id(id_); if (candidates) m.set_candidates(*candidates); m.set_redirect_target(redirect_target_); m.set_redirect_cookie(redirect_cookie); SignalOutgoingMessage(this, m); } bool Session::Initiate(const std::string &to, const SessionDescription *description) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Only from STATE_INIT if (state_ != STATE_INIT) return false; // Setup for signaling. Initiate is asynchronous. It occurs once the address // candidates are ready. initiator_ = true; remote_address_ = to; description_ = description; SendSessionMessage(SessionMessage::TYPE_INITIATE, description, NULL, NULL); set_state(Session::STATE_SENTINITIATE); // Let the socket manager know we now want the candidates socket_manager_->StartProcessingCandidates(); // Start the session timeout session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); return true; } 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. Accept is asynchronous. It occurs once the address // candidates are ready. initiator_ = false; description_ = description; SendSessionMessage(SessionMessage::TYPE_ACCEPT, description, NULL, NULL); set_state(Session::STATE_SENTACCEPT); return true; } bool Session::Modify(const SessionDescription *description) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Only if session already STATE_INPROGRESS if (state_ != STATE_INPROGRESS) return false; // Modify is asynchronous. It occurs once the address candidates are ready. // Either side can send a modify. It is only valid in an already accepted // session. description_ = description; SendSessionMessage(SessionMessage::TYPE_MODIFY, description, NULL, NULL); set_state(Session::STATE_SENTMODIFY); // Start the session timeout session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); 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; initiator_ = false; redirect_target_ = target; SendSessionMessage(SessionMessage::TYPE_REDIRECT, NULL, NULL, NULL); // A redirect puts us in the same state as reject. It just sends a different // kind of reject message, if you like. set_state(STATE_SENTREDIRECT); 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; initiator_ = false; SendSessionMessage(SessionMessage::TYPE_REJECT, NULL, NULL, NULL); set_state(STATE_SENTREJECT); return true; } bool Session::Terminate() { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // Either side can terminate, at any time. if (state_ == STATE_SENTTERMINATE && state_ != STATE_RECEIVEDTERMINATE) return false; // But we don't need to terminate if we already rejected. The other client // already knows that we're done with this session. if (state_ != STATE_SENTREDIRECT) SendSessionMessage(SessionMessage::TYPE_TERMINATE, NULL, NULL, NULL); set_state(STATE_SENTTERMINATE); return true; } void Session::OnIncomingError(const SessionMessage &m) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); // If a candidate message errors out or gets dropped for some reason we // ignore the error. if (m.type() != SessionMessage::TYPE_CANDIDATES) { set_error(ERROR_RESPONSE); } } void Session::OnIncomingMessage(const SessionMessage &m) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); switch (m.type()) { case SessionMessage::TYPE_INITIATE: remote_description_ = m.description(); remote_address_ = m.from(); name_ = m.name(); initiator_ = false; set_state(STATE_RECEIVEDINITIATE); // Let the socket manager know we now want the initial candidates socket_manager_->StartProcessingCandidates(); break; case SessionMessage::TYPE_ACCEPT: remote_description_ = m.description(); set_state(STATE_RECEIVEDACCEPT); break; case SessionMessage::TYPE_MODIFY: remote_description_ = m.description(); set_state(STATE_RECEIVEDMODIFY); break; case SessionMessage::TYPE_CANDIDATES: socket_manager_->AddRemoteCandidates(m.candidates()); break; case SessionMessage::TYPE_REJECT: set_state(STATE_RECEIVEDREJECT); break; case SessionMessage::TYPE_REDIRECT: OnRedirectMessage(m); break; case SessionMessage::TYPE_TERMINATE: set_state(STATE_RECEIVEDTERMINATE); break; } } void Session::OnRedirectMessage(const SessionMessage &m) { ASSERT(state_ == STATE_SENTINITIATE); if (state_ != STATE_SENTINITIATE) return; ASSERT(m.redirect_target().size() != 0); remote_address_ = m.redirect_target(); SendSessionMessage(SessionMessage::TYPE_INITIATE, description_, NULL, m.redirect_cookie()->Copy()); // Restart the session timeout. session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); session_manager_->signaling_thread()->PostDelayed(session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); // Reset all of the sockets back into the initial state. socket_manager_->ResetSockets(); } Session::State Session::state() { return state_; } void Session::set_state(State state) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); if (state != state_) { state_ = state; SignalState(this, state); session_manager_->signaling_thread()->Post(this, MSG_STATE); } } Session::Error Session::error() { return error_; } void Session::set_error(Error error) { ASSERT(session_manager_->signaling_thread()->IsCurrent()); if (error != error_) { error_ = error; SignalError(this, error); session_manager_->signaling_thread()->Post(this, MSG_ERROR); } } const std::string &Session::name() { return name_; } const std::string &Session::remote_address() { return remote_address_; } bool Session::initiator() { return initiator_; } const SessionID& Session::id() { return id_; } const SessionDescription *Session::description() { return description_; } const SessionDescription *Session::remote_description() { return remote_description_; } SessionManager *Session::session_manager() { return session_manager_; } void Session::OnMessage(Message *pmsg) { switch(pmsg->message_id) { case MSG_TIMEOUT: // Session timeout has occured. Check to see if the session is still trying // to signal. If so, the session has timed out. // The Sockets have their own timeout for connectivity. set_error(ERROR_TIME); break; case MSG_ERROR: switch (error_) { case ERROR_RESPONSE: // This state could be reached if we get an error in response to an IQ // or if the network is so slow we time out on an individual IQ exchange. // In either case, Terminate (send more messages) and ignore the likely // cascade of more errors. // fall through case ERROR_NETWORK: case ERROR_TIME: // Time ran out - no response Terminate(); break; default: break; } break; case MSG_STATE: switch (state_) { case STATE_SENTACCEPT: case STATE_RECEIVEDACCEPT: set_state(STATE_INPROGRESS); session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); OnSocketState(); // Update the writability timeout state. 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