mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-19 16:47:50 +01:00

This change list passes the instance of RtcEventLog from Peerconnection down to P2PTransportChannel, and binds the structured ICE logging with ICE layer objects. Logs of ICE connectivity checks are injected for candidate pairs. TBR=terelius@webrtc.org Bug: None Change-Id: Ia979dbbac6d31dcf0f8988da1065bdfc3e461821 Reviewed-on: https://webrtc-review.googlesource.com/34660 Commit-Queue: Qingsi Wang <qingsi@google.com> Reviewed-by: Peter Thatcher <pthatcher@webrtc.org> Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org> Cr-Commit-Position: refs/heads/master@{#21884}
1124 lines
40 KiB
C++
1124 lines
40 KiB
C++
/*
|
|
* Copyright 2015 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 "pc/transportcontroller.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "p2p/base/port.h"
|
|
#include "rtc_base/bind.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/ptr_util.h"
|
|
#include "rtc_base/thread.h"
|
|
|
|
using webrtc::SdpType;
|
|
|
|
namespace {
|
|
|
|
enum {
|
|
MSG_ICECONNECTIONSTATE,
|
|
MSG_RECEIVING,
|
|
MSG_ICEGATHERINGSTATE,
|
|
MSG_CANDIDATESGATHERED,
|
|
};
|
|
|
|
struct CandidatesData : public rtc::MessageData {
|
|
CandidatesData(const std::string& transport_name,
|
|
const cricket::Candidates& candidates)
|
|
: transport_name(transport_name), candidates(candidates) {}
|
|
|
|
std::string transport_name;
|
|
cricket::Candidates candidates;
|
|
};
|
|
|
|
bool VerifyCandidate(const cricket::Candidate& cand, std::string* error) {
|
|
// No address zero.
|
|
if (cand.address().IsNil() || cand.address().IsAnyIP()) {
|
|
*error = "candidate has address of zero";
|
|
return false;
|
|
}
|
|
|
|
// Disallow all ports below 1024, except for 80 and 443 on public addresses.
|
|
int port = cand.address().port();
|
|
if (cand.protocol() == cricket::TCP_PROTOCOL_NAME &&
|
|
(cand.tcptype() == cricket::TCPTYPE_ACTIVE_STR || port == 0)) {
|
|
// Expected for active-only candidates per
|
|
// http://tools.ietf.org/html/rfc6544#section-4.5 so no error.
|
|
// Libjingle clients emit port 0, in "active" mode.
|
|
return true;
|
|
}
|
|
if (port < 1024) {
|
|
if ((port != 80) && (port != 443)) {
|
|
*error = "candidate has port below 1024, but not 80 or 443";
|
|
return false;
|
|
}
|
|
|
|
if (cand.address().IsPrivateIP()) {
|
|
*error = "candidate has port of 80 or 443 with private IP address";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VerifyCandidates(const cricket::Candidates& candidates,
|
|
std::string* error) {
|
|
for (const cricket::Candidate& candidate : candidates) {
|
|
if (!VerifyCandidate(candidate, error)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace cricket {
|
|
|
|
// This class groups the DTLS and ICE channels, and helps keep track of
|
|
// how many external objects (BaseChannels) reference each channel.
|
|
class TransportController::ChannelPair {
|
|
public:
|
|
// TODO(deadbeef): Change the types of |dtls| and |ice| to
|
|
// DtlsTransport and P2PTransportChannelWrapper, once TransportChannelImpl is
|
|
// removed.
|
|
ChannelPair(DtlsTransportInternal* dtls, IceTransportInternal* ice)
|
|
: ice_(ice), dtls_(dtls) {}
|
|
|
|
// Currently, all ICE-related calls still go through this DTLS channel. But
|
|
// that will change once we get rid of TransportChannelImpl, and the DTLS
|
|
// channel interface no longer includes ICE-specific methods.
|
|
const DtlsTransportInternal* dtls() const { return dtls_.get(); }
|
|
DtlsTransportInternal* dtls() { return dtls_.get(); }
|
|
const IceTransportInternal* ice() const { return ice_.get(); }
|
|
IceTransportInternal* ice() { return ice_.get(); }
|
|
|
|
private:
|
|
std::unique_ptr<IceTransportInternal> ice_;
|
|
std::unique_ptr<DtlsTransportInternal> dtls_;
|
|
|
|
RTC_DISALLOW_COPY_AND_ASSIGN(ChannelPair);
|
|
};
|
|
|
|
TransportController::TransportController(
|
|
rtc::Thread* signaling_thread,
|
|
rtc::Thread* network_thread,
|
|
PortAllocator* port_allocator,
|
|
bool redetermine_role_on_ice_restart,
|
|
const rtc::CryptoOptions& crypto_options,
|
|
webrtc::RtcEventLog* event_log)
|
|
: signaling_thread_(signaling_thread),
|
|
network_thread_(network_thread),
|
|
port_allocator_(port_allocator),
|
|
redetermine_role_on_ice_restart_(redetermine_role_on_ice_restart),
|
|
crypto_options_(crypto_options),
|
|
event_log_(event_log) {}
|
|
|
|
TransportController::~TransportController() {
|
|
// Channel destructors may try to send packets, so this needs to happen on
|
|
// the network thread.
|
|
network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::DestroyAllChannels_n, this));
|
|
}
|
|
|
|
bool TransportController::SetSslMaxProtocolVersion(
|
|
rtc::SSLProtocolVersion version) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::SetSslMaxProtocolVersion_n,
|
|
this, version));
|
|
}
|
|
|
|
void TransportController::SetIceConfig(const IceConfig& config) {
|
|
network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::SetIceConfig_n, this, config));
|
|
}
|
|
|
|
void TransportController::SetIceRole(IceRole ice_role) {
|
|
network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::SetIceRole_n, this, ice_role));
|
|
}
|
|
|
|
void TransportController::SetNeedsIceRestartFlag() {
|
|
for (auto& kv : transports_) {
|
|
kv.second->SetNeedsIceRestartFlag();
|
|
}
|
|
}
|
|
|
|
bool TransportController::NeedsIceRestart(
|
|
const std::string& transport_name) const {
|
|
const JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
return false;
|
|
}
|
|
return transport->NeedsIceRestart();
|
|
}
|
|
|
|
bool TransportController::GetSslRole(const std::string& transport_name,
|
|
rtc::SSLRole* role) const {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::GetSslRole_n, this,
|
|
transport_name, role));
|
|
}
|
|
|
|
bool TransportController::SetLocalCertificate(
|
|
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::SetLocalCertificate_n,
|
|
this, certificate));
|
|
}
|
|
|
|
bool TransportController::GetLocalCertificate(
|
|
const std::string& transport_name,
|
|
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) const {
|
|
if (network_thread_->IsCurrent()) {
|
|
return GetLocalCertificate_n(transport_name, certificate);
|
|
}
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::GetLocalCertificate_n,
|
|
this, transport_name, certificate));
|
|
}
|
|
|
|
std::unique_ptr<rtc::SSLCertificate>
|
|
TransportController::GetRemoteSSLCertificate(
|
|
const std::string& transport_name) const {
|
|
if (network_thread_->IsCurrent()) {
|
|
return GetRemoteSSLCertificate_n(transport_name);
|
|
}
|
|
return network_thread_->Invoke<std::unique_ptr<rtc::SSLCertificate>>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::GetRemoteSSLCertificate_n,
|
|
this, transport_name));
|
|
}
|
|
|
|
std::unique_ptr<rtc::SSLCertChain> TransportController::GetRemoteSSLCertChain(
|
|
const std::string& transport_name) const {
|
|
if (!network_thread_->IsCurrent()) {
|
|
return network_thread_->Invoke<std::unique_ptr<rtc::SSLCertChain>>(
|
|
RTC_FROM_HERE, [&] { return GetRemoteSSLCertChain(transport_name); });
|
|
}
|
|
|
|
const RefCountedChannel* ch =
|
|
GetChannel_n(transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP);
|
|
if (!ch) {
|
|
return nullptr;
|
|
}
|
|
return ch->dtls()->GetRemoteSSLCertChain();
|
|
}
|
|
|
|
bool TransportController::SetLocalTransportDescription(
|
|
const std::string& transport_name,
|
|
const TransportDescription& tdesc,
|
|
SdpType type,
|
|
std::string* err) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::SetLocalTransportDescription_n, this,
|
|
transport_name, tdesc, type, err));
|
|
}
|
|
|
|
bool TransportController::SetRemoteTransportDescription(
|
|
const std::string& transport_name,
|
|
const TransportDescription& tdesc,
|
|
SdpType type,
|
|
std::string* err) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::SetRemoteTransportDescription_n, this,
|
|
transport_name, tdesc, type, err));
|
|
}
|
|
|
|
void TransportController::MaybeStartGathering() {
|
|
network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::MaybeStartGathering_n, this));
|
|
}
|
|
|
|
bool TransportController::AddRemoteCandidates(const std::string& transport_name,
|
|
const Candidates& candidates,
|
|
std::string* err) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::AddRemoteCandidates_n,
|
|
this, transport_name, candidates, err));
|
|
}
|
|
|
|
bool TransportController::RemoveRemoteCandidates(const Candidates& candidates,
|
|
std::string* err) {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::RemoveRemoteCandidates_n,
|
|
this, candidates, err));
|
|
}
|
|
|
|
bool TransportController::ReadyForRemoteCandidates(
|
|
const std::string& transport_name) const {
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::ReadyForRemoteCandidates_n,
|
|
this, transport_name));
|
|
}
|
|
|
|
bool TransportController::GetStats(const std::string& transport_name,
|
|
TransportStats* stats) {
|
|
if (network_thread_->IsCurrent()) {
|
|
return GetStats_n(transport_name, stats);
|
|
}
|
|
return network_thread_->Invoke<bool>(
|
|
RTC_FROM_HERE,
|
|
rtc::Bind(&TransportController::GetStats_n, this, transport_name, stats));
|
|
}
|
|
|
|
void TransportController::SetMetricsObserver(
|
|
webrtc::MetricsObserverInterface* metrics_observer) {
|
|
return network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::SetMetricsObserver_n, this,
|
|
metrics_observer));
|
|
}
|
|
|
|
DtlsTransportInternal* TransportController::CreateDtlsTransport(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
return network_thread_->Invoke<DtlsTransportInternal*>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::CreateDtlsTransport_n,
|
|
this, transport_name, component));
|
|
}
|
|
|
|
DtlsTransportInternal* TransportController::CreateDtlsTransport_n(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
RefCountedChannel* existing_channel = GetChannel_n(transport_name, component);
|
|
if (existing_channel) {
|
|
// Channel already exists; increment reference count and return.
|
|
existing_channel->AddRef();
|
|
return existing_channel->dtls();
|
|
}
|
|
|
|
// Need to create a new channel.
|
|
JsepTransport* transport = GetOrCreateJsepTransport(transport_name);
|
|
|
|
// Create DTLS channel wrapping ICE channel, and configure it.
|
|
IceTransportInternal* ice =
|
|
CreateIceTransportChannel_n(transport_name, component);
|
|
DtlsTransportInternal* dtls =
|
|
CreateDtlsTransportChannel_n(transport_name, component, ice);
|
|
dtls->ice_transport()->SetMetricsObserver(metrics_observer_);
|
|
dtls->ice_transport()->SetIceRole(ice_role_);
|
|
dtls->ice_transport()->SetIceTiebreaker(ice_tiebreaker_);
|
|
dtls->ice_transport()->SetIceConfig(ice_config_);
|
|
if (certificate_) {
|
|
bool set_cert_success = dtls->SetLocalCertificate(certificate_);
|
|
RTC_DCHECK(set_cert_success);
|
|
}
|
|
|
|
// Connect to signals offered by the channels. Currently, the DTLS channel
|
|
// forwards signals from the ICE channel, so we only need to connect to the
|
|
// DTLS channel. In the future this won't be the case.
|
|
dtls->SignalWritableState.connect(
|
|
this, &TransportController::OnChannelWritableState_n);
|
|
dtls->SignalReceivingState.connect(
|
|
this, &TransportController::OnChannelReceivingState_n);
|
|
dtls->SignalDtlsHandshakeError.connect(
|
|
this, &TransportController::OnDtlsHandshakeError);
|
|
dtls->ice_transport()->SignalGatheringState.connect(
|
|
this, &TransportController::OnChannelGatheringState_n);
|
|
dtls->ice_transport()->SignalCandidateGathered.connect(
|
|
this, &TransportController::OnChannelCandidateGathered_n);
|
|
dtls->ice_transport()->SignalCandidatesRemoved.connect(
|
|
this, &TransportController::OnChannelCandidatesRemoved_n);
|
|
dtls->ice_transport()->SignalRoleConflict.connect(
|
|
this, &TransportController::OnChannelRoleConflict_n);
|
|
dtls->ice_transport()->SignalStateChanged.connect(
|
|
this, &TransportController::OnChannelStateChanged_n);
|
|
RefCountedChannel* new_pair = new RefCountedChannel(dtls, ice);
|
|
new_pair->AddRef();
|
|
channels_.insert(channels_.end(), new_pair);
|
|
bool channel_added = transport->AddChannel(dtls, component);
|
|
RTC_DCHECK(channel_added);
|
|
// Adding a channel could cause aggregate state to change.
|
|
UpdateAggregateStates_n();
|
|
return dtls;
|
|
}
|
|
|
|
void TransportController::DestroyDtlsTransport(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
network_thread_->Invoke<void>(
|
|
RTC_FROM_HERE, rtc::Bind(&TransportController::DestroyDtlsTransport_n,
|
|
this, transport_name, component));
|
|
}
|
|
|
|
void TransportController::DestroyDtlsTransport_n(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
auto it = GetChannelIterator_n(transport_name, component);
|
|
if (it == channels_.end()) {
|
|
RTC_LOG(LS_WARNING) << "Attempting to delete " << transport_name
|
|
<< " TransportChannel " << component
|
|
<< ", which doesn't exist.";
|
|
return;
|
|
}
|
|
// Release one reference to the RefCountedChannel, and do additional cleanup
|
|
// only if it was the last one. Matches the AddRef logic in
|
|
// CreateDtlsTransport_n.
|
|
if ((*it)->Release() == rtc::RefCountReleaseStatus::kOtherRefsRemained) {
|
|
return;
|
|
}
|
|
channels_.erase(it);
|
|
|
|
JsepTransport* t = GetJsepTransport(transport_name);
|
|
bool channel_removed = t->RemoveChannel(component);
|
|
RTC_DCHECK(channel_removed);
|
|
// Just as we create a Transport when its first channel is created,
|
|
// we delete it when its last channel is deleted.
|
|
if (!t->HasChannels()) {
|
|
transports_.erase(transport_name);
|
|
}
|
|
// Removing a channel could cause aggregate state to change.
|
|
UpdateAggregateStates_n();
|
|
}
|
|
|
|
webrtc::SrtpTransport* TransportController::CreateSdesTransport(
|
|
const std::string& transport_name,
|
|
bool rtcp_mux_enabled) {
|
|
if (!network_thread_->IsCurrent()) {
|
|
return network_thread_->Invoke<webrtc::SrtpTransport*>(RTC_FROM_HERE, [&] {
|
|
return CreateSdesTransport(transport_name, rtcp_mux_enabled);
|
|
});
|
|
}
|
|
|
|
auto existing_rtp_transport = FindRtpTransport(transport_name);
|
|
|
|
if (existing_rtp_transport) {
|
|
// For SRTP transport wrapper, the |srtp_transport| is expected to be
|
|
// non-null and |dtls_srtp_transport| is expected to be a nullptr.
|
|
if (!existing_rtp_transport->srtp_transport ||
|
|
existing_rtp_transport->dtls_srtp_transport) {
|
|
RTC_LOG(LS_ERROR)
|
|
<< "Failed to create an RTP transport for SDES using name: "
|
|
<< transport_name << " because the type doesn't match.";
|
|
return nullptr;
|
|
}
|
|
existing_rtp_transport->AddRef();
|
|
return existing_rtp_transport->srtp_transport;
|
|
}
|
|
|
|
auto new_srtp_transport =
|
|
rtc::MakeUnique<webrtc::SrtpTransport>(rtcp_mux_enabled);
|
|
|
|
// The SDES should use an IceTransport rather than a DtlsTransport. We call
|
|
// |CreateDtlsTransport_n| here because the DtlsTransport will downgrade to an
|
|
// wrapper over IceTransport if we don't set the certificates and it will just
|
|
// forward the packets and signals without using DTLS. The support of SDES
|
|
// will be removed once all the downstream application stop using it.
|
|
new_srtp_transport->SetRtpPacketTransport(CreateDtlsTransport_n(
|
|
transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP));
|
|
if (!rtcp_mux_enabled) {
|
|
new_srtp_transport->SetRtcpPacketTransport(CreateDtlsTransport_n(
|
|
transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTCP));
|
|
}
|
|
|
|
#if defined(ENABLE_EXTERNAL_AUTH)
|
|
new_srtp_transport->EnableExternalAuth();
|
|
#endif
|
|
|
|
auto new_rtp_transport_wrapper = new RefCountedRtpTransport();
|
|
new_rtp_transport_wrapper->srtp_transport = new_srtp_transport.get();
|
|
new_rtp_transport_wrapper->rtp_transport = std::move(new_srtp_transport);
|
|
new_rtp_transport_wrapper->AddRef();
|
|
rtp_transports_[transport_name] = new_rtp_transport_wrapper;
|
|
return rtp_transports_[transport_name]->srtp_transport;
|
|
}
|
|
|
|
webrtc::DtlsSrtpTransport* TransportController::CreateDtlsSrtpTransport(
|
|
const std::string& transport_name,
|
|
bool rtcp_mux_enabled) {
|
|
if (!network_thread_->IsCurrent()) {
|
|
return network_thread_->Invoke<webrtc::DtlsSrtpTransport*>(
|
|
RTC_FROM_HERE, [&] {
|
|
return CreateDtlsSrtpTransport(transport_name, rtcp_mux_enabled);
|
|
});
|
|
}
|
|
auto existing_rtp_transport = FindRtpTransport(transport_name);
|
|
|
|
if (existing_rtp_transport) {
|
|
// For DTLS-SRTP transport wrapper, the |dtls_srtp_transport| is expected to
|
|
// be non-null and |srtp_transport| is expected to be a nullptr.
|
|
if (existing_rtp_transport->srtp_transport ||
|
|
!existing_rtp_transport->dtls_srtp_transport) {
|
|
RTC_LOG(LS_ERROR)
|
|
<< "Failed to create an RTP transport for DTLS-SRTP using name: "
|
|
<< transport_name << " because the type doesn't match.";
|
|
return nullptr;
|
|
}
|
|
existing_rtp_transport->AddRef();
|
|
return existing_rtp_transport->dtls_srtp_transport;
|
|
}
|
|
|
|
auto new_srtp_transport =
|
|
rtc::MakeUnique<webrtc::SrtpTransport>(rtcp_mux_enabled);
|
|
|
|
#if defined(ENABLE_EXTERNAL_AUTH)
|
|
new_srtp_transport->EnableExternalAuth();
|
|
#endif
|
|
|
|
auto new_dtls_srtp_transport =
|
|
rtc::MakeUnique<webrtc::DtlsSrtpTransport>(std::move(new_srtp_transport));
|
|
|
|
auto rtp_dtls_transport = CreateDtlsTransport_n(
|
|
transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP);
|
|
auto rtcp_dtls_transport =
|
|
rtcp_mux_enabled
|
|
? nullptr
|
|
: CreateDtlsTransport_n(transport_name,
|
|
cricket::ICE_CANDIDATE_COMPONENT_RTCP);
|
|
|
|
new_dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport,
|
|
rtcp_dtls_transport);
|
|
|
|
auto new_rtp_transport_wrapper = new RefCountedRtpTransport();
|
|
new_rtp_transport_wrapper->dtls_srtp_transport =
|
|
new_dtls_srtp_transport.get();
|
|
new_rtp_transport_wrapper->rtp_transport = std::move(new_dtls_srtp_transport);
|
|
new_rtp_transport_wrapper->AddRef();
|
|
rtp_transports_[transport_name] = new_rtp_transport_wrapper;
|
|
return rtp_transports_[transport_name]->dtls_srtp_transport;
|
|
}
|
|
|
|
void TransportController::DestroyTransport(const std::string& transport_name) {
|
|
if (!network_thread_->IsCurrent()) {
|
|
network_thread_->Invoke<void>(RTC_FROM_HERE,
|
|
[&] { DestroyTransport(transport_name); });
|
|
return;
|
|
}
|
|
|
|
auto existing_rtp_transport = FindRtpTransport(transport_name);
|
|
if (!existing_rtp_transport) {
|
|
RTC_LOG(LS_WARNING) << "Attempting to delete " << transport_name
|
|
<< " transport , which doesn't exist.";
|
|
return;
|
|
}
|
|
if (existing_rtp_transport->Release() ==
|
|
rtc::RefCountReleaseStatus::kDroppedLastRef) {
|
|
rtp_transports_.erase(transport_name);
|
|
}
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> TransportController::transport_names_for_testing() {
|
|
std::vector<std::string> ret;
|
|
for (const auto& kv : transports_) {
|
|
ret.push_back(kv.first);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::vector<DtlsTransportInternal*>
|
|
TransportController::channels_for_testing() {
|
|
std::vector<DtlsTransportInternal*> ret;
|
|
for (RefCountedChannel* channel : channels_) {
|
|
ret.push_back(channel->dtls());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
DtlsTransportInternal* TransportController::get_channel_for_testing(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
RefCountedChannel* ch = GetChannel_n(transport_name, component);
|
|
return ch ? ch->dtls() : nullptr;
|
|
}
|
|
|
|
IceTransportInternal* TransportController::CreateIceTransportChannel_n(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
return new P2PTransportChannel(transport_name, component, port_allocator_,
|
|
event_log_);
|
|
}
|
|
|
|
DtlsTransportInternal* TransportController::CreateDtlsTransportChannel_n(
|
|
const std::string&,
|
|
int,
|
|
IceTransportInternal* ice) {
|
|
DtlsTransport* dtls = new DtlsTransport(ice, crypto_options_);
|
|
dtls->SetSslMaxProtocolVersion(ssl_max_version_);
|
|
return dtls;
|
|
}
|
|
|
|
void TransportController::OnMessage(rtc::Message* pmsg) {
|
|
RTC_DCHECK(signaling_thread_->IsCurrent());
|
|
|
|
switch (pmsg->message_id) {
|
|
case MSG_ICECONNECTIONSTATE: {
|
|
rtc::TypedMessageData<IceConnectionState>* data =
|
|
static_cast<rtc::TypedMessageData<IceConnectionState>*>(pmsg->pdata);
|
|
SignalConnectionState(data->data());
|
|
delete data;
|
|
break;
|
|
}
|
|
case MSG_RECEIVING: {
|
|
rtc::TypedMessageData<bool>* data =
|
|
static_cast<rtc::TypedMessageData<bool>*>(pmsg->pdata);
|
|
SignalReceiving(data->data());
|
|
delete data;
|
|
break;
|
|
}
|
|
case MSG_ICEGATHERINGSTATE: {
|
|
rtc::TypedMessageData<IceGatheringState>* data =
|
|
static_cast<rtc::TypedMessageData<IceGatheringState>*>(pmsg->pdata);
|
|
SignalGatheringState(data->data());
|
|
delete data;
|
|
break;
|
|
}
|
|
case MSG_CANDIDATESGATHERED: {
|
|
CandidatesData* data = static_cast<CandidatesData*>(pmsg->pdata);
|
|
SignalCandidatesGathered(data->transport_name, data->candidates);
|
|
delete data;
|
|
break;
|
|
}
|
|
default:
|
|
RTC_NOTREACHED();
|
|
}
|
|
}
|
|
|
|
const TransportController::RefCountedRtpTransport*
|
|
TransportController::FindRtpTransport(const std::string& transport_name) {
|
|
auto it = rtp_transports_.find(transport_name);
|
|
return it == rtp_transports_.end() ? nullptr : it->second;
|
|
}
|
|
|
|
std::vector<TransportController::RefCountedChannel*>::iterator
|
|
TransportController::GetChannelIterator_n(const std::string& transport_name,
|
|
int component) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
return std::find_if(channels_.begin(), channels_.end(),
|
|
[transport_name, component](RefCountedChannel* channel) {
|
|
return channel->dtls()->transport_name() ==
|
|
transport_name &&
|
|
channel->dtls()->component() == component;
|
|
});
|
|
}
|
|
|
|
std::vector<TransportController::RefCountedChannel*>::const_iterator
|
|
TransportController::GetChannelIterator_n(const std::string& transport_name,
|
|
int component) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
return std::find_if(
|
|
channels_.begin(), channels_.end(),
|
|
[transport_name, component](const RefCountedChannel* channel) {
|
|
return channel->dtls()->transport_name() == transport_name &&
|
|
channel->dtls()->component() == component;
|
|
});
|
|
}
|
|
|
|
const JsepTransport* TransportController::GetJsepTransport(
|
|
const std::string& transport_name) const {
|
|
auto it = transports_.find(transport_name);
|
|
return (it == transports_.end()) ? nullptr : it->second.get();
|
|
}
|
|
|
|
JsepTransport* TransportController::GetJsepTransport(
|
|
const std::string& transport_name) {
|
|
auto it = transports_.find(transport_name);
|
|
return (it == transports_.end()) ? nullptr : it->second.get();
|
|
}
|
|
|
|
const TransportController::RefCountedChannel* TransportController::GetChannel_n(
|
|
const std::string& transport_name,
|
|
int component) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
auto it = GetChannelIterator_n(transport_name, component);
|
|
return (it == channels_.end()) ? nullptr : *it;
|
|
}
|
|
|
|
TransportController::RefCountedChannel* TransportController::GetChannel_n(
|
|
const std::string& transport_name,
|
|
int component) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
auto it = GetChannelIterator_n(transport_name, component);
|
|
return (it == channels_.end()) ? nullptr : *it;
|
|
}
|
|
|
|
JsepTransport* TransportController::GetOrCreateJsepTransport(
|
|
const std::string& transport_name) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (transport) {
|
|
return transport;
|
|
}
|
|
|
|
transport = new JsepTransport(transport_name, certificate_);
|
|
transports_[transport_name] = std::unique_ptr<JsepTransport>(transport);
|
|
return transport;
|
|
}
|
|
|
|
void TransportController::DestroyAllChannels_n() {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
transports_.clear();
|
|
// TODO(nisse): If |channels_| were a vector of scoped_refptr, we
|
|
// wouldn't need this strange hack.
|
|
for (RefCountedChannel* channel : channels_) {
|
|
// Even though these objects are normally ref-counted, if
|
|
// TransportController is deleted while they still have references, just
|
|
// remove all references.
|
|
while (channel->Release() ==
|
|
rtc::RefCountReleaseStatus::kOtherRefsRemained) {
|
|
}
|
|
}
|
|
channels_.clear();
|
|
}
|
|
|
|
bool TransportController::SetSslMaxProtocolVersion_n(
|
|
rtc::SSLProtocolVersion version) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// Max SSL version can only be set before transports are created.
|
|
if (!transports_.empty()) {
|
|
return false;
|
|
}
|
|
|
|
ssl_max_version_ = version;
|
|
return true;
|
|
}
|
|
|
|
void TransportController::SetIceConfig_n(const IceConfig& config) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
ice_config_ = config;
|
|
for (auto& channel : channels_) {
|
|
channel->dtls()->ice_transport()->SetIceConfig(ice_config_);
|
|
}
|
|
}
|
|
|
|
void TransportController::SetIceRole_n(IceRole ice_role) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
ice_role_ = ice_role;
|
|
for (auto& channel : channels_) {
|
|
channel->dtls()->ice_transport()->SetIceRole(ice_role_);
|
|
}
|
|
}
|
|
|
|
bool TransportController::GetSslRole_n(const std::string& transport_name,
|
|
rtc::SSLRole* role) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
const JsepTransport* t = GetJsepTransport(transport_name);
|
|
if (!t) {
|
|
return false;
|
|
}
|
|
rtc::Optional<rtc::SSLRole> current_role = t->GetSslRole();
|
|
if (!current_role) {
|
|
return false;
|
|
}
|
|
*role = *current_role;
|
|
return true;
|
|
}
|
|
|
|
bool TransportController::SetLocalCertificate_n(
|
|
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// Can't change a certificate, or set a null certificate.
|
|
if (certificate_ || !certificate) {
|
|
return false;
|
|
}
|
|
certificate_ = certificate;
|
|
|
|
// Set certificate for JsepTransport, which verifies it matches the
|
|
// fingerprint in SDP, and DTLS transport.
|
|
// Fallback from DTLS to SDES is not supported.
|
|
for (auto& kv : transports_) {
|
|
kv.second->SetLocalCertificate(certificate_);
|
|
}
|
|
for (auto& channel : channels_) {
|
|
bool set_cert_success = channel->dtls()->SetLocalCertificate(certificate_);
|
|
RTC_DCHECK(set_cert_success);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TransportController::GetLocalCertificate_n(
|
|
const std::string& transport_name,
|
|
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
const JsepTransport* t = GetJsepTransport(transport_name);
|
|
if (!t) {
|
|
return false;
|
|
}
|
|
return t->GetLocalCertificate(certificate);
|
|
}
|
|
|
|
std::unique_ptr<rtc::SSLCertificate>
|
|
TransportController::GetRemoteSSLCertificate_n(
|
|
const std::string& transport_name) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// Get the certificate from the RTP channel's DTLS handshake. Should be
|
|
// identical to the RTCP channel's, since they were given the same remote
|
|
// fingerprint.
|
|
const RefCountedChannel* ch = GetChannel_n(transport_name, 1);
|
|
if (!ch) {
|
|
return nullptr;
|
|
}
|
|
return ch->dtls()->GetRemoteSSLCertificate();
|
|
}
|
|
|
|
bool TransportController::SetLocalTransportDescription_n(
|
|
const std::string& transport_name,
|
|
const TransportDescription& tdesc,
|
|
SdpType type,
|
|
std::string* err) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
// If we didn't find a transport, that's not an error;
|
|
// it could have been deleted as a result of bundling.
|
|
// TODO(deadbeef): Make callers smarter so they won't attempt to set a
|
|
// description on a deleted transport.
|
|
return true;
|
|
}
|
|
|
|
// The initial offer side may use ICE Lite, in which case, per RFC5245 Section
|
|
// 5.1.1, the answer side should take the controlling role if it is in the
|
|
// full ICE mode.
|
|
//
|
|
// When both sides use ICE Lite, the initial offer side must take the
|
|
// controlling role, and this is the default logic implemented in
|
|
// SetLocalDescription in PeerConnection.
|
|
if (transport->remote_description() &&
|
|
transport->remote_description()->ice_mode == ICEMODE_LITE &&
|
|
ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_FULL) {
|
|
SetIceRole_n(ICEROLE_CONTROLLING);
|
|
}
|
|
|
|
// Older versions of Chrome expect the ICE role to be re-determined when an
|
|
// ICE restart occurs, and also don't perform conflict resolution correctly,
|
|
// so for now we can't safely stop doing this, unless the application opts in
|
|
// by setting |redetermine_role_on_ice_restart_| to false.
|
|
// See: https://bugs.chromium.org/p/chromium/issues/detail?id=628676
|
|
// TODO(deadbeef): Remove this when these old versions of Chrome reach a low
|
|
// enough population.
|
|
if (redetermine_role_on_ice_restart_ && transport->local_description() &&
|
|
IceCredentialsChanged(transport->local_description()->ice_ufrag,
|
|
transport->local_description()->ice_pwd,
|
|
tdesc.ice_ufrag, tdesc.ice_pwd) &&
|
|
// Don't change the ICE role if the remote endpoint is ICE lite; we
|
|
// should always be controlling in that case.
|
|
(!transport->remote_description() ||
|
|
transport->remote_description()->ice_mode != ICEMODE_LITE)) {
|
|
IceRole new_ice_role =
|
|
(type == SdpType::kOffer) ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED;
|
|
SetIceRole(new_ice_role);
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Set local transport description on " << transport_name;
|
|
return transport->SetLocalTransportDescription(tdesc, type, err);
|
|
}
|
|
|
|
bool TransportController::SetRemoteTransportDescription_n(
|
|
const std::string& transport_name,
|
|
const TransportDescription& tdesc,
|
|
SdpType type,
|
|
std::string* err) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// If our role is ICEROLE_CONTROLLED and the remote endpoint supports only
|
|
// ice_lite, this local endpoint should take the CONTROLLING role.
|
|
// TODO(deadbeef): This is a session-level attribute, so it really shouldn't
|
|
// be in a TransportDescription in the first place...
|
|
if (ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_LITE) {
|
|
SetIceRole_n(ICEROLE_CONTROLLING);
|
|
}
|
|
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
// If we didn't find a transport, that's not an error;
|
|
// it could have been deleted as a result of bundling.
|
|
// TODO(deadbeef): Make callers smarter so they won't attempt to set a
|
|
// description on a deleted transport.
|
|
return true;
|
|
}
|
|
|
|
// If we use ICE Lite and the remote endpoint uses the full implementation of
|
|
// ICE, the local endpoint must take the controlled role, and the other side
|
|
// must be the controlling role.
|
|
if (transport->local_description() &&
|
|
transport->local_description()->ice_mode == ICEMODE_LITE &&
|
|
ice_role_ == ICEROLE_CONTROLLING && tdesc.ice_mode == ICEMODE_FULL) {
|
|
SetIceRole_n(ICEROLE_CONTROLLED);
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Set remote transport description on " << transport_name;
|
|
return transport->SetRemoteTransportDescription(tdesc, type, err);
|
|
}
|
|
|
|
void TransportController::MaybeStartGathering_n() {
|
|
for (auto& channel : channels_) {
|
|
channel->dtls()->ice_transport()->MaybeStartGathering();
|
|
}
|
|
}
|
|
|
|
bool TransportController::AddRemoteCandidates_n(
|
|
const std::string& transport_name,
|
|
const Candidates& candidates,
|
|
std::string* err) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// Verify each candidate before passing down to the transport layer.
|
|
if (!VerifyCandidates(candidates, err)) {
|
|
return false;
|
|
}
|
|
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
// If we didn't find a transport, that's not an error;
|
|
// it could have been deleted as a result of bundling.
|
|
return true;
|
|
}
|
|
|
|
for (const Candidate& candidate : candidates) {
|
|
RefCountedChannel* channel =
|
|
GetChannel_n(transport_name, candidate.component());
|
|
if (!channel) {
|
|
*err = "Candidate has an unknown component: " + candidate.ToString() +
|
|
" for content: " + transport_name;
|
|
return false;
|
|
}
|
|
channel->dtls()->ice_transport()->AddRemoteCandidate(candidate);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TransportController::RemoveRemoteCandidates_n(const Candidates& candidates,
|
|
std::string* err) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// Verify each candidate before passing down to the transport layer.
|
|
if (!VerifyCandidates(candidates, err)) {
|
|
return false;
|
|
}
|
|
|
|
std::map<std::string, Candidates> candidates_by_transport_name;
|
|
for (const Candidate& cand : candidates) {
|
|
if (!cand.transport_name().empty()) {
|
|
candidates_by_transport_name[cand.transport_name()].push_back(cand);
|
|
} else {
|
|
RTC_LOG(LS_ERROR) << "Not removing candidate because it does not have a "
|
|
"transport name set: "
|
|
<< cand.ToString();
|
|
}
|
|
}
|
|
|
|
bool result = true;
|
|
for (const auto& kv : candidates_by_transport_name) {
|
|
const std::string& transport_name = kv.first;
|
|
const Candidates& candidates = kv.second;
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
// If we didn't find a transport, that's not an error;
|
|
// it could have been deleted as a result of bundling.
|
|
continue;
|
|
}
|
|
for (const Candidate& candidate : candidates) {
|
|
RefCountedChannel* channel =
|
|
GetChannel_n(transport_name, candidate.component());
|
|
if (channel) {
|
|
channel->dtls()->ice_transport()->RemoveRemoteCandidate(candidate);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool TransportController::ReadyForRemoteCandidates_n(
|
|
const std::string& transport_name) const {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
const JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
return false;
|
|
}
|
|
return transport->ready_for_remote_candidates();
|
|
}
|
|
|
|
bool TransportController::GetStats_n(const std::string& transport_name,
|
|
TransportStats* stats) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
JsepTransport* transport = GetJsepTransport(transport_name);
|
|
if (!transport) {
|
|
return false;
|
|
}
|
|
return transport->GetStats(stats);
|
|
}
|
|
|
|
void TransportController::SetMetricsObserver_n(
|
|
webrtc::MetricsObserverInterface* metrics_observer) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
metrics_observer_ = metrics_observer;
|
|
for (auto& channel : channels_) {
|
|
channel->dtls()->ice_transport()->SetMetricsObserver(metrics_observer);
|
|
}
|
|
}
|
|
|
|
void TransportController::OnChannelWritableState_n(
|
|
rtc::PacketTransportInternal* transport) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
RTC_LOG(LS_INFO) << " Transport " << transport->transport_name()
|
|
<< " writability changed to " << transport->writable()
|
|
<< ".";
|
|
UpdateAggregateStates_n();
|
|
}
|
|
|
|
void TransportController::OnChannelReceivingState_n(
|
|
rtc::PacketTransportInternal* transport) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
UpdateAggregateStates_n();
|
|
}
|
|
|
|
void TransportController::OnChannelGatheringState_n(
|
|
IceTransportInternal* channel) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
UpdateAggregateStates_n();
|
|
}
|
|
|
|
void TransportController::OnChannelCandidateGathered_n(
|
|
IceTransportInternal* channel,
|
|
const Candidate& candidate) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
// We should never signal peer-reflexive candidates.
|
|
if (candidate.type() == PRFLX_PORT_TYPE) {
|
|
RTC_NOTREACHED();
|
|
return;
|
|
}
|
|
std::vector<Candidate> candidates;
|
|
candidates.push_back(candidate);
|
|
CandidatesData* data =
|
|
new CandidatesData(channel->transport_name(), candidates);
|
|
signaling_thread_->Post(RTC_FROM_HERE, this, MSG_CANDIDATESGATHERED, data);
|
|
}
|
|
|
|
void TransportController::OnChannelCandidatesRemoved_n(
|
|
IceTransportInternal* channel,
|
|
const Candidates& candidates) {
|
|
invoker_.AsyncInvoke<void>(
|
|
RTC_FROM_HERE, signaling_thread_,
|
|
rtc::Bind(&TransportController::OnChannelCandidatesRemoved, this,
|
|
candidates));
|
|
}
|
|
|
|
void TransportController::OnChannelCandidatesRemoved(
|
|
const Candidates& candidates) {
|
|
RTC_DCHECK(signaling_thread_->IsCurrent());
|
|
SignalCandidatesRemoved(candidates);
|
|
}
|
|
|
|
void TransportController::OnChannelRoleConflict_n(
|
|
IceTransportInternal* channel) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
// Note: since the role conflict is handled entirely on the network thread,
|
|
// we don't need to worry about role conflicts occurring on two ports at once.
|
|
// The first one encountered should immediately reverse the role.
|
|
IceRole reversed_role = (ice_role_ == ICEROLE_CONTROLLING)
|
|
? ICEROLE_CONTROLLED
|
|
: ICEROLE_CONTROLLING;
|
|
RTC_LOG(LS_INFO) << "Got role conflict; switching to "
|
|
<< (reversed_role == ICEROLE_CONTROLLING ? "controlling"
|
|
: "controlled")
|
|
<< " role.";
|
|
SetIceRole_n(reversed_role);
|
|
}
|
|
|
|
void TransportController::OnChannelStateChanged_n(
|
|
IceTransportInternal* channel) {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
RTC_LOG(LS_INFO) << channel->transport_name() << " TransportChannel "
|
|
<< channel->component()
|
|
<< " state changed. Check if state is complete.";
|
|
UpdateAggregateStates_n();
|
|
}
|
|
|
|
void TransportController::UpdateAggregateStates_n() {
|
|
RTC_DCHECK(network_thread_->IsCurrent());
|
|
|
|
IceConnectionState new_connection_state = kIceConnectionConnecting;
|
|
IceGatheringState new_gathering_state = kIceGatheringNew;
|
|
bool any_receiving = false;
|
|
bool any_failed = false;
|
|
bool all_connected = !channels_.empty();
|
|
bool all_completed = !channels_.empty();
|
|
bool any_gathering = false;
|
|
bool all_done_gathering = !channels_.empty();
|
|
for (const auto& channel : channels_) {
|
|
any_receiving = any_receiving || channel->dtls()->receiving();
|
|
any_failed = any_failed || channel->dtls()->ice_transport()->GetState() ==
|
|
IceTransportState::STATE_FAILED;
|
|
all_connected = all_connected && channel->dtls()->writable();
|
|
all_completed =
|
|
all_completed && channel->dtls()->writable() &&
|
|
channel->dtls()->ice_transport()->GetState() ==
|
|
IceTransportState::STATE_COMPLETED &&
|
|
channel->dtls()->ice_transport()->GetIceRole() == ICEROLE_CONTROLLING &&
|
|
channel->dtls()->ice_transport()->gathering_state() ==
|
|
kIceGatheringComplete;
|
|
any_gathering =
|
|
any_gathering ||
|
|
channel->dtls()->ice_transport()->gathering_state() != kIceGatheringNew;
|
|
all_done_gathering = all_done_gathering &&
|
|
channel->dtls()->ice_transport()->gathering_state() ==
|
|
kIceGatheringComplete;
|
|
}
|
|
if (any_failed) {
|
|
new_connection_state = kIceConnectionFailed;
|
|
} else if (all_completed) {
|
|
new_connection_state = kIceConnectionCompleted;
|
|
} else if (all_connected) {
|
|
new_connection_state = kIceConnectionConnected;
|
|
}
|
|
if (connection_state_ != new_connection_state) {
|
|
connection_state_ = new_connection_state;
|
|
signaling_thread_->Post(
|
|
RTC_FROM_HERE, this, MSG_ICECONNECTIONSTATE,
|
|
new rtc::TypedMessageData<IceConnectionState>(new_connection_state));
|
|
}
|
|
|
|
if (receiving_ != any_receiving) {
|
|
receiving_ = any_receiving;
|
|
signaling_thread_->Post(RTC_FROM_HERE, this, MSG_RECEIVING,
|
|
new rtc::TypedMessageData<bool>(any_receiving));
|
|
}
|
|
|
|
if (all_done_gathering) {
|
|
new_gathering_state = kIceGatheringComplete;
|
|
} else if (any_gathering) {
|
|
new_gathering_state = kIceGatheringGathering;
|
|
}
|
|
if (gathering_state_ != new_gathering_state) {
|
|
gathering_state_ = new_gathering_state;
|
|
signaling_thread_->Post(
|
|
RTC_FROM_HERE, this, MSG_ICEGATHERINGSTATE,
|
|
new rtc::TypedMessageData<IceGatheringState>(new_gathering_state));
|
|
}
|
|
}
|
|
|
|
void TransportController::OnDtlsHandshakeError(rtc::SSLHandshakeError error) {
|
|
SignalDtlsHandshakeError(error);
|
|
}
|
|
|
|
} // namespace cricket
|