| /* |
| * 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() { |
| for (ChannelMap::iterator iter = channels_.begin(); |
| iter != channels_.end(); ++iter) { |
| iter->second->SignalDestroyed(iter->second); |
| delete iter->second; |
| } |
| } |
| |
| 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(int component) { |
| ASSERT(rtc::Thread::Current() == worker_thread_); |
| ASSERT(GetChannel(component) == NULL); |
| ASSERT(!transport_->get()->HasChannel(component)); |
| |
| // We always create a proxy in case we need to change out the transport later. |
| TransportChannelProxy* channel_proxy = |
| new TransportChannelProxy(content_name(), component); |
| channels_[component] = channel_proxy; |
| |
| // 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); |
| } |
| return channel_proxy; |
| } |
| |
| bool TransportProxy::HasChannel(int component) { |
| return transport_->get()->HasChannel(component); |
| } |
| |
| void TransportProxy::DestroyChannel(int component) { |
| ASSERT(rtc::Thread::Current() == worker_thread_); |
| TransportChannelProxy* channel_proxy = GetChannelProxy(component); |
| if (channel_proxy) { |
| // 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); |
| delete channel_proxy; |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| TransportChannelProxy* TransportProxy::GetChannelProxy(int component) const { |
| ChannelMap::const_iterator iter = channels_.find(component); |
| return (iter != channels_.end()) ? iter->second : NULL; |
| } |
| |
| void TransportProxy::CreateChannelImpl(int component) { |
| worker_thread_->Invoke<void>(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<void>(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<void>(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(); |
| |
| // Ignore candidates for if the proxy content_name doesn't match the content |
| // name of the actual transport. This stops video candidates from being sent |
| // down to the audio transport when BUNDLE is enabled. |
| if (content_name_ != transport_->get()->content_name()) { |
| return true; |
| } |
| |
| // 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::SetCertificate( |
| const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) { |
| transport_->get()->SetCertificate(certificate); |
| } |
| |
| 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), |
| ssl_max_version_(rtc::SSL_PROTOCOL_DTLS_10), |
| ice_tiebreaker_(rtc::CreateRandomId64()), |
| role_switch_(false), |
| ice_receiving_timeout_(-1) { |
| 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::SetCertificate( |
| const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) { |
| if (certificate_) |
| return false; |
| if (!certificate) |
| return false; |
| certificate_ = certificate; |
| for (TransportMap::iterator iter = transports_.begin(); |
| iter != transports_.end(); ++iter) { |
| iter->second->SetCertificate(certificate_); |
| } |
| return true; |
| } |
| |
| bool BaseSession::SetSslMaxProtocolVersion(rtc::SSLProtocolVersion version) { |
| if (state_ != STATE_INIT) { |
| return false; |
| } |
| |
| ssl_max_version_ = version; |
| 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; |
| } |
| |
| void BaseSession::SetIceConnectionReceivingTimeout(int timeout_ms) { |
| ice_receiving_timeout_ = timeout_ms; |
| for (const auto& kv : transport_proxies()) { |
| Transport* transport = kv.second->impl(); |
| if (transport) { |
| transport->SetChannelReceivingTimeout(timeout_ms); |
| } |
| } |
| } |
| |
| TransportChannel* BaseSession::CreateChannel(const std::string& content_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(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_); |
| transport->SetSslMaxProtocolVersion(ssl_max_version_); |
| // 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->SignalReceivingState.connect( |
| this, &BaseSession::OnTransportReceiving); |
| 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 (certificate_) |
| transproxy->SetCertificate(certificate_); |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| |
| Transport* BaseSession::CreateTransport(const std::string& content_name) { |
| ASSERT(transport_type_ == NS_GINGLE_P2P); |
| Transport* transport = new DtlsTransport<P2PTransport>( |
| signaling_thread(), worker_thread(), content_name, port_allocator(), |
| certificate_); |
| transport->SetChannelReceivingTimeout(ice_receiving_timeout_); |
| return transport; |
| } |
| |
| 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); |
| } |
| } |
| |
| 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) { |
| if (!BundleContentGroup(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. |
| if (!candidates_allocated) { |
| MaybeCandidateAllocationDone(); |
| } |
| } else { |
| LOG(LS_INFO) << "BUNDLE group missing from remote or local description."; |
| } |
| return true; |
| } |
| |
| bool BaseSession::BundleContentGroup(const ContentGroup* bundle_group) { |
| const std::string* content_name = bundle_group->FirstContentName(); |
| if (!content_name) { |
| LOG(LS_INFO) << "No content names specified in BUNDLE group."; |
| return true; |
| } |
| |
| TransportProxy* selected_proxy = GetTransportProxy(*content_name); |
| if (!selected_proxy) { |
| LOG(LS_WARNING) << "No transport found for content \"" |
| << *content_name << "\"."; |
| return false; |
| } |
| |
| 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 (bundle_group->HasContentName(iter->first) && |
| !iter->second->SetupMux(selected_proxy)) { |
| LOG(LS_WARNING) << "Failed to bundle " << iter->first << " to " |
| << *content_name; |
| return false; |
| } |
| LOG(LS_INFO) << "Bundling " << iter->first << " to " << *content_name; |
| } |
| |
| 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()) { |
| LOG(LS_INFO) << "Candidate allocation not done for " |
| << iter->second->content_name(); |
| 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::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 |