/* * Copyright 2004 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/p2p/base/session.h" #include "webrtc/p2p/base/dtlstransport.h" #include "webrtc/p2p/base/p2ptransport.h" #include "webrtc/p2p/base/transport.h" #include "webrtc/p2p/base/transportchannelproxy.h" #include "webrtc/p2p/base/transportinfo.h" #include "webrtc/base/bind.h" #include "webrtc/base/common.h" #include "webrtc/base/helpers.h" #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/stringencode.h" #include "webrtc/base/sslstreamadapter.h" #include "webrtc/p2p/base/constants.h" namespace cricket { using rtc::Bind; TransportProxy::~TransportProxy() { ASSERT(channels_.empty()); } bool TransportProxy::HasChannels() const { ASSERT(rtc::Thread::Current() == worker_thread_); return !channels_.empty(); } const std::string& TransportProxy::type() const { return transport_->get()->type(); } TransportChannel* TransportProxy::GetChannel(int component) { ASSERT(rtc::Thread::Current() == worker_thread_); return GetChannelProxy(component); } TransportChannel* TransportProxy::CreateChannel(const std::string& name, int component) { ASSERT(rtc::Thread::Current() == worker_thread_); TransportChannelProxyRef* channel_proxy; if (channels_.find(component) == channels_.end()) { channel_proxy = new TransportChannelProxyRef(content_name(), name, component); channels_[component] = channel_proxy; // TransportProxy maintains its own reference // here. RefCountedObject automatically deletes the pointer when // the refcount hits 0. This prevents RefCountedObject from // deleting the object when all *external* references are // gone. Things need to be done in DestroyChannel prior to the // proxy being deleted. channel_proxy->AddRef(); // If we're already negotiated, create an impl and hook it up to the proxy // channel. If we're connecting, create an impl but don't hook it up yet. if (negotiated_) { CreateChannelImpl_w(component); SetChannelImplFromTransport_w(channel_proxy, component); } else if (connecting_) { CreateChannelImpl_w(component); } } else { channel_proxy = channels_[component]; } channel_proxy->AddRef(); return channel_proxy; } bool TransportProxy::HasChannel(int component) { return transport_->get()->HasChannel(component); } void TransportProxy::DestroyChannel(int component) { ASSERT(rtc::Thread::Current() == worker_thread_); ChannelMap::const_iterator iter = channels_.find(component); if (iter == channels_.end()) { return; } TransportChannelProxyRef* channel_proxy = iter->second; int ref_count = channel_proxy->Release(); if (ref_count > 1) { return; } // TransportProxy owns the last reference on the TransportChannelProxy. // It should *never* be the case that ref_count is less than one // here but this makes me sleep better at night. ASSERT(ref_count == 1); // If the state of TransportProxy is not NEGOTIATED then // TransportChannelProxy and its impl are not connected. Both must // be connected before deletion. // // However, if we haven't entered the connecting state then there // is no implementation to hook up. if (connecting_ && !negotiated_) { SetChannelImplFromTransport_w(channel_proxy, component); } channels_.erase(component); channel_proxy->SignalDestroyed(channel_proxy); // Implicitly deletes the object since ref_count is now 0. channel_proxy->Release(); } void TransportProxy::ConnectChannels() { if (!connecting_) { if (!negotiated_) { for (auto& iter : channels_) { CreateChannelImpl(iter.first); } } connecting_ = true; } // TODO(juberti): Right now Transport::ConnectChannels doesn't work if we // don't have any channels yet, so we need to allow this method to be called // multiple times. Once we fix Transport, we can move this call inside the // if (!connecting_) block. transport_->get()->ConnectChannels(); } void TransportProxy::CompleteNegotiation() { if (!negotiated_) { // Negotiating assumes connecting_ has happened and // implementations exist. If not we need to create the // implementations. for (auto& iter : channels_) { if (!connecting_) { CreateChannelImpl(iter.first); } SetChannelImplFromTransport(iter.second, iter.first); } negotiated_ = true; } } void TransportProxy::AddSentCandidates(const Candidates& candidates) { for (Candidates::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) { sent_candidates_.push_back(*cand); } } void TransportProxy::AddUnsentCandidates(const Candidates& candidates) { for (Candidates::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) { unsent_candidates_.push_back(*cand); } } bool TransportProxy::GetChannelNameFromComponent( int component, std::string* channel_name) const { const TransportChannelProxy* channel = GetChannelProxy(component); if (channel == NULL) { return false; } *channel_name = channel->name(); return true; } bool TransportProxy::GetComponentFromChannelName( const std::string& channel_name, int* component) const { const TransportChannelProxy* channel = GetChannelProxyByName(channel_name); if (channel == NULL) { return false; } *component = channel->component(); return true; } TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const { ChannelMap::const_iterator iter = channels_.find(component); return (iter != channels_.end()) ? iter->second : NULL; } TransportChannelProxy* TransportProxy::GetChannelProxyByName( const std::string& name) const { for (ChannelMap::const_iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { if (iter->second->name() == name) { return iter->second; } } return NULL; } void TransportProxy::CreateChannelImpl(int component) { worker_thread_->Invoke(Bind( &TransportProxy::CreateChannelImpl_w, this, component)); } void TransportProxy::CreateChannelImpl_w(int component) { ASSERT(rtc::Thread::Current() == worker_thread_); transport_->get()->CreateChannel(component); } void TransportProxy::SetChannelImplFromTransport(TransportChannelProxy* proxy, int component) { worker_thread_->Invoke(Bind( &TransportProxy::SetChannelImplFromTransport_w, this, proxy, component)); } void TransportProxy::SetChannelImplFromTransport_w(TransportChannelProxy* proxy, int component) { ASSERT(rtc::Thread::Current() == worker_thread_); TransportChannelImpl* impl = transport_->get()->GetChannel(component); ASSERT(impl != NULL); ReplaceChannelImpl_w(proxy, impl); } void TransportProxy::ReplaceChannelImpl(TransportChannelProxy* proxy, TransportChannelImpl* impl) { worker_thread_->Invoke(Bind( &TransportProxy::ReplaceChannelImpl_w, this, proxy, impl)); } void TransportProxy::ReplaceChannelImpl_w(TransportChannelProxy* proxy, TransportChannelImpl* impl) { ASSERT(rtc::Thread::Current() == worker_thread_); ASSERT(proxy != NULL); proxy->SetImplementation(impl); } // This function muxes |this| onto |target| by repointing |this| at // |target|'s transport and setting our TransportChannelProxies // to point to |target|'s underlying implementations. bool TransportProxy::SetupMux(TransportProxy* target) { // Bail out if there's nothing to do. if (transport_ == target->transport_) { return true; } // Run through all channels and remove any non-rtp transport channels before // setting target transport channels. for (ChannelMap::const_iterator iter = channels_.begin(); iter != channels_.end(); ++iter) { if (!target->transport_->get()->HasChannel(iter->first)) { // Remove if channel doesn't exist in |transport_|. ReplaceChannelImpl(iter->second, NULL); } else { // Replace the impl for all the TransportProxyChannels with the channels // from |target|'s transport. Fail if there's not an exact match. ReplaceChannelImpl( iter->second, target->transport_->get()->CreateChannel(iter->first)); } } // Now replace our transport. Must happen afterwards because // it deletes all impls as a side effect. transport_ = target->transport_; transport_->get()->SignalCandidatesReady.connect( this, &TransportProxy::OnTransportCandidatesReady); set_candidates_allocated(target->candidates_allocated()); return true; } void TransportProxy::SetIceRole(IceRole role) { transport_->get()->SetIceRole(role); } bool TransportProxy::SetLocalTransportDescription( const TransportDescription& description, ContentAction action, std::string* error_desc) { // If this is an answer, finalize the negotiation. if (action == CA_ANSWER) { CompleteNegotiation(); } bool result = transport_->get()->SetLocalTransportDescription(description, action, error_desc); if (result) local_description_set_ = true; return result; } bool TransportProxy::SetRemoteTransportDescription( const TransportDescription& description, ContentAction action, std::string* error_desc) { // If this is an answer, finalize the negotiation. if (action == CA_ANSWER) { CompleteNegotiation(); } bool result = transport_->get()->SetRemoteTransportDescription(description, action, error_desc); if (result) remote_description_set_ = true; return result; } void TransportProxy::OnSignalingReady() { // If we're starting a new allocation sequence, reset our state. set_candidates_allocated(false); transport_->get()->OnSignalingReady(); } bool TransportProxy::OnRemoteCandidates(const Candidates& candidates, std::string* error) { // Ensure the transport is negotiated before handling candidates. // TODO(juberti): Remove this once everybody calls SetLocalTD. CompleteNegotiation(); // Verify each candidate before passing down to transport layer. for (Candidates::const_iterator cand = candidates.begin(); cand != candidates.end(); ++cand) { if (!transport_->get()->VerifyCandidate(*cand, error)) return false; if (!HasChannel(cand->component())) { *error = "Candidate has unknown component: " + cand->ToString() + " for content: " + content_name_; return false; } } transport_->get()->OnRemoteCandidates(candidates); return true; } void TransportProxy::SetIdentity( rtc::SSLIdentity* identity) { transport_->get()->SetIdentity(identity); } std::string BaseSession::StateToString(State state) { switch (state) { case STATE_INIT: return "STATE_INIT"; case STATE_SENTINITIATE: return "STATE_SENTINITIATE"; case STATE_RECEIVEDINITIATE: return "STATE_RECEIVEDINITIATE"; case STATE_SENTPRACCEPT: return "STATE_SENTPRACCEPT"; case STATE_SENTACCEPT: return "STATE_SENTACCEPT"; case STATE_RECEIVEDPRACCEPT: return "STATE_RECEIVEDPRACCEPT"; case STATE_RECEIVEDACCEPT: return "STATE_RECEIVEDACCEPT"; case STATE_SENTMODIFY: return "STATE_SENTMODIFY"; case STATE_RECEIVEDMODIFY: return "STATE_RECEIVEDMODIFY"; case STATE_SENTREJECT: return "STATE_SENTREJECT"; case STATE_RECEIVEDREJECT: return "STATE_RECEIVEDREJECT"; case STATE_SENTREDIRECT: return "STATE_SENTREDIRECT"; case STATE_SENTTERMINATE: return "STATE_SENTTERMINATE"; case STATE_RECEIVEDTERMINATE: return "STATE_RECEIVEDTERMINATE"; case STATE_INPROGRESS: return "STATE_INPROGRESS"; case STATE_DEINIT: return "STATE_DEINIT"; default: break; } return "STATE_" + rtc::ToString(state); } BaseSession::BaseSession(rtc::Thread* signaling_thread, rtc::Thread* worker_thread, PortAllocator* port_allocator, const std::string& sid, const std::string& content_type, bool initiator) : state_(STATE_INIT), error_(ERROR_NONE), signaling_thread_(signaling_thread), worker_thread_(worker_thread), port_allocator_(port_allocator), sid_(sid), content_type_(content_type), transport_type_(NS_GINGLE_P2P), initiator_(initiator), identity_(NULL), ice_tiebreaker_(rtc::CreateRandomId64()), role_switch_(false) { ASSERT(signaling_thread->IsCurrent()); } BaseSession::~BaseSession() { ASSERT(signaling_thread()->IsCurrent()); ASSERT(state_ != STATE_DEINIT); LogState(state_, STATE_DEINIT); state_ = STATE_DEINIT; SignalState(this, state_); for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { delete iter->second; } } const SessionDescription* BaseSession::local_description() const { // TODO(tommi): Assert on thread correctness. return local_description_.get(); } const SessionDescription* BaseSession::remote_description() const { // TODO(tommi): Assert on thread correctness. return remote_description_.get(); } SessionDescription* BaseSession::remote_description() { // TODO(tommi): Assert on thread correctness. return remote_description_.get(); } void BaseSession::set_local_description(const SessionDescription* sdesc) { // TODO(tommi): Assert on thread correctness. if (sdesc != local_description_.get()) local_description_.reset(sdesc); } void BaseSession::set_remote_description(SessionDescription* sdesc) { // TODO(tommi): Assert on thread correctness. if (sdesc != remote_description_) remote_description_.reset(sdesc); } const SessionDescription* BaseSession::initiator_description() const { // TODO(tommi): Assert on thread correctness. return initiator_ ? local_description_.get() : remote_description_.get(); } bool BaseSession::SetIdentity(rtc::SSLIdentity* identity) { if (identity_) return false; identity_ = identity; for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { iter->second->SetIdentity(identity_); } return true; } bool BaseSession::PushdownTransportDescription(ContentSource source, ContentAction action, std::string* error_desc) { if (source == CS_LOCAL) { return PushdownLocalTransportDescription(local_description(), action, error_desc); } return PushdownRemoteTransportDescription(remote_description(), action, error_desc); } bool BaseSession::PushdownLocalTransportDescription( const SessionDescription* sdesc, ContentAction action, std::string* error_desc) { // Update the Transports with the right information, and trigger them to // start connecting. for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { // If no transport info was in this session description, ret == false // and we just skip this one. TransportDescription tdesc; bool ret = GetTransportDescription( sdesc, iter->second->content_name(), &tdesc); if (ret) { if (!iter->second->SetLocalTransportDescription(tdesc, action, error_desc)) { return false; } iter->second->ConnectChannels(); } } return true; } bool BaseSession::PushdownRemoteTransportDescription( const SessionDescription* sdesc, ContentAction action, std::string* error_desc) { // Update the Transports with the right information. for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { TransportDescription tdesc; // If no transport info was in this session description, ret == false // and we just skip this one. bool ret = GetTransportDescription( sdesc, iter->second->content_name(), &tdesc); if (ret) { if (!iter->second->SetRemoteTransportDescription(tdesc, action, error_desc)) { return false; } } } return true; } TransportChannel* BaseSession::CreateChannel(const std::string& content_name, const std::string& channel_name, int component) { // We create the proxy "on demand" here because we need to support // creating channels at any time, even before we send or receive // initiate messages, which is before we create the transports. TransportProxy* transproxy = GetOrCreateTransportProxy(content_name); return transproxy->CreateChannel(channel_name, component); } TransportChannel* BaseSession::GetChannel(const std::string& content_name, int component) { TransportProxy* transproxy = GetTransportProxy(content_name); if (transproxy == NULL) return NULL; return transproxy->GetChannel(component); } void BaseSession::DestroyChannel(const std::string& content_name, int component) { TransportProxy* transproxy = GetTransportProxy(content_name); ASSERT(transproxy != NULL); transproxy->DestroyChannel(component); } TransportProxy* BaseSession::GetOrCreateTransportProxy( const std::string& content_name) { TransportProxy* transproxy = GetTransportProxy(content_name); if (transproxy) return transproxy; Transport* transport = CreateTransport(content_name); transport->SetIceRole(initiator_ ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED); transport->SetIceTiebreaker(ice_tiebreaker_); // TODO: Connect all the Transport signals to TransportProxy // then to the BaseSession. transport->SignalConnecting.connect( this, &BaseSession::OnTransportConnecting); transport->SignalWritableState.connect( this, &BaseSession::OnTransportWritable); transport->SignalRequestSignaling.connect( this, &BaseSession::OnTransportRequestSignaling); transport->SignalRouteChange.connect( this, &BaseSession::OnTransportRouteChange); transport->SignalCandidatesAllocationDone.connect( this, &BaseSession::OnTransportCandidatesAllocationDone); transport->SignalRoleConflict.connect( this, &BaseSession::OnRoleConflict); transport->SignalCompleted.connect( this, &BaseSession::OnTransportCompleted); transport->SignalFailed.connect( this, &BaseSession::OnTransportFailed); transproxy = new TransportProxy(worker_thread_, sid_, content_name, new TransportWrapper(transport)); transproxy->SignalCandidatesReady.connect( this, &BaseSession::OnTransportProxyCandidatesReady); if (identity_) transproxy->SetIdentity(identity_); transports_[content_name] = transproxy; return transproxy; } Transport* BaseSession::GetTransport(const std::string& content_name) { TransportProxy* transproxy = GetTransportProxy(content_name); if (transproxy == NULL) return NULL; return transproxy->impl(); } TransportProxy* BaseSession::GetTransportProxy( const std::string& content_name) { TransportMap::iterator iter = transports_.find(content_name); return (iter != transports_.end()) ? iter->second : NULL; } TransportProxy* BaseSession::GetTransportProxy(const Transport* transport) { for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { TransportProxy* transproxy = iter->second; if (transproxy->impl() == transport) { return transproxy; } } return NULL; } TransportProxy* BaseSession::GetFirstTransportProxy() { if (transports_.empty()) return NULL; return transports_.begin()->second; } void BaseSession::DestroyTransportProxyWhenUnused( const std::string& content_name) { TransportProxy *tp = GetTransportProxy(content_name); if(tp && !tp->HasChannels()) { DestroyTransportProxy(content_name); } } void BaseSession::DestroyTransportProxy(const std::string& content_name) { TransportMap::iterator iter = transports_.find(content_name); if (iter != transports_.end()) { delete iter->second; transports_.erase(content_name); } } cricket::Transport* BaseSession::CreateTransport( const std::string& content_name) { ASSERT(transport_type_ == NS_GINGLE_P2P); return new cricket::DtlsTransport( signaling_thread(), worker_thread(), content_name, port_allocator(), identity_); } bool BaseSession::GetStats(SessionStats* stats) { for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { std::string proxy_id = iter->second->content_name(); // We are ignoring not-yet-instantiated transports. if (iter->second->impl()) { std::string transport_id = iter->second->impl()->content_name(); stats->proxy_to_transport[proxy_id] = transport_id; if (stats->transport_stats.find(transport_id) == stats->transport_stats.end()) { TransportStats subinfos; if (!iter->second->impl()->GetStats(&subinfos)) { return false; } stats->transport_stats[transport_id] = subinfos; } } } return true; } void BaseSession::SetState(State state) { ASSERT(signaling_thread_->IsCurrent()); if (state != state_) { LogState(state_, state); state_ = state; SignalState(this, state_); signaling_thread_->Post(this, MSG_STATE); } SignalNewDescription(); } void BaseSession::SetError(Error error, const std::string& error_desc) { ASSERT(signaling_thread_->IsCurrent()); if (error != error_) { error_ = error; error_desc_ = error_desc; SignalError(this, error); } } void BaseSession::OnSignalingReady() { ASSERT(signaling_thread()->IsCurrent()); for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { iter->second->OnSignalingReady(); } } // TODO(juberti): Since PushdownLocalTD now triggers the connection process to // start, remove this method once everyone calls PushdownLocalTD. void BaseSession::SpeculativelyConnectAllTransportChannels() { // Put all transports into the connecting state. for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { iter->second->ConnectChannels(); } } bool BaseSession::OnRemoteCandidates(const std::string& content_name, const Candidates& candidates, std::string* error) { // Give candidates to the appropriate transport, and tell that transport // to start connecting, if it's not already doing so. TransportProxy* transproxy = GetTransportProxy(content_name); if (!transproxy) { *error = "Unknown content name " + content_name; return false; } if (!transproxy->OnRemoteCandidates(candidates, error)) { return false; } // TODO(juberti): Remove this call once we can be sure that we always have // a local transport description (which will trigger the connection). transproxy->ConnectChannels(); return true; } bool BaseSession::MaybeEnableMuxingSupport() { // We need both a local and remote description to decide if we should mux. if ((state_ == STATE_SENTINITIATE || state_ == STATE_RECEIVEDINITIATE) && ((local_description_ == NULL) || (remote_description_ == NULL))) { return false; } // In order to perform the multiplexing, we need all proxies to be in the // negotiated state, i.e. to have implementations underneath. // Ensure that this is the case, regardless of whether we are going to mux. for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { ASSERT(iter->second->negotiated()); if (!iter->second->negotiated()) return false; } // If both sides agree to BUNDLE, mux all the specified contents onto the // transport belonging to the first content name in the BUNDLE group. // If the contents are already muxed, this will be a no-op. // TODO(juberti): Should this check that local and remote have configured // BUNDLE the same way? bool candidates_allocated = IsCandidateAllocationDone(); const ContentGroup* local_bundle_group = local_description()->GetGroupByName(GROUP_TYPE_BUNDLE); const ContentGroup* remote_bundle_group = remote_description()->GetGroupByName(GROUP_TYPE_BUNDLE); if (local_bundle_group && remote_bundle_group && local_bundle_group->FirstContentName()) { const std::string* content_name = local_bundle_group->FirstContentName(); const ContentInfo* content = local_description_->GetContentByName(*content_name); if (!content) { LOG(LS_WARNING) << "Content \"" << *content_name << "\" referenced in BUNDLE group is not present"; return false; } if (!SetSelectedProxy(content->name, local_bundle_group)) { LOG(LS_WARNING) << "Failed to set up BUNDLE"; return false; } // If we weren't done gathering before, we might be done now, as a result // of enabling mux. LOG(LS_INFO) << "Enabling BUNDLE, bundling onto transport: " << *content_name; if (!candidates_allocated) { MaybeCandidateAllocationDone(); } } else { LOG(LS_INFO) << "No BUNDLE information, not bundling."; } return true; } bool BaseSession::SetSelectedProxy(const std::string& content_name, const ContentGroup* muxed_group) { TransportProxy* selected_proxy = GetTransportProxy(content_name); if (!selected_proxy) { return false; } ASSERT(selected_proxy->negotiated()); for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { // If content is part of the mux group, then repoint its proxy at the // transport object that we have chosen to mux onto. If the proxy // is already pointing at the right object, it will be a no-op. if (muxed_group->HasContentName(iter->first) && !iter->second->SetupMux(selected_proxy)) { return false; } } return true; } void BaseSession::OnTransportCandidatesAllocationDone(Transport* transport) { // TODO(juberti): This is a clunky way of processing the done signal. Instead, // TransportProxy should receive the done signal directly, set its allocated // flag internally, and then reissue the done signal to Session. // Overall we should make TransportProxy receive *all* the signals from // Transport, since this removes the need to manually iterate over all // the transports, as is needed to make sure signals are handled properly // when BUNDLEing. // TODO(juberti): Per b/7998978, devs and QA are hitting this assert in ways // that make it prohibitively difficult to run dbg builds. Disabled for now. //ASSERT(!IsCandidateAllocationDone()); for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { if (iter->second->impl() == transport) { iter->second->set_candidates_allocated(true); } } MaybeCandidateAllocationDone(); } bool BaseSession::IsCandidateAllocationDone() const { for (TransportMap::const_iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { if (!iter->second->candidates_allocated()) return false; } return true; } void BaseSession::MaybeCandidateAllocationDone() { if (IsCandidateAllocationDone()) { LOG(LS_INFO) << "Candidate gathering is complete."; OnCandidatesAllocationDone(); } } void BaseSession::OnRoleConflict() { if (role_switch_) { LOG(LS_WARNING) << "Repeat of role conflict signal from Transport."; return; } role_switch_ = true; for (TransportMap::iterator iter = transports_.begin(); iter != transports_.end(); ++iter) { // Role will be reverse of initial role setting. IceRole role = initiator_ ? ICEROLE_CONTROLLED : ICEROLE_CONTROLLING; iter->second->SetIceRole(role); } } void BaseSession::LogState(State old_state, State new_state) { LOG(LS_INFO) << "Session:" << id() << " Old state:" << StateToString(old_state) << " New state:" << StateToString(new_state) << " Type:" << content_type() << " Transport:" << transport_type(); } // static bool BaseSession::GetTransportDescription(const SessionDescription* description, const std::string& content_name, TransportDescription* tdesc) { if (!description || !tdesc) { return false; } const TransportInfo* transport_info = description->GetTransportInfoByName(content_name); if (!transport_info) { return false; } *tdesc = transport_info->description; return true; } void BaseSession::SignalNewDescription() { ContentAction action; ContentSource source; if (!GetContentAction(&action, &source)) { return; } if (source == CS_LOCAL) { SignalNewLocalDescription(this, action); } else { SignalNewRemoteDescription(this, action); } } bool BaseSession::GetContentAction(ContentAction* action, ContentSource* source) { switch (state_) { // new local description case STATE_SENTINITIATE: *action = CA_OFFER; *source = CS_LOCAL; break; case STATE_SENTPRACCEPT: *action = CA_PRANSWER; *source = CS_LOCAL; break; case STATE_SENTACCEPT: *action = CA_ANSWER; *source = CS_LOCAL; break; // new remote description case STATE_RECEIVEDINITIATE: *action = CA_OFFER; *source = CS_REMOTE; break; case STATE_RECEIVEDPRACCEPT: *action = CA_PRANSWER; *source = CS_REMOTE; break; case STATE_RECEIVEDACCEPT: *action = CA_ANSWER; *source = CS_REMOTE; break; default: return false; } return true; } void BaseSession::OnMessage(rtc::Message *pmsg) { switch (pmsg->message_id) { case MSG_TIMEOUT: // Session timeout has occured. SetError(ERROR_TIME, "Session timeout has occured."); break; case MSG_STATE: switch (state_) { case STATE_SENTACCEPT: case STATE_RECEIVEDACCEPT: SetState(STATE_INPROGRESS); break; default: // Explicitly ignoring some states here. break; } break; } } } // namespace cricket