blob: d7dbf3fc544cdae0db5ac06d05aa3325c1243179 [file] [log] [blame] [edit]
/*
* Copyright 2017 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/jsep_transport_controller.h"
#include <stddef.h>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
#include "api/async_dns_resolver.h"
#include "api/candidate.h"
#include "api/dtls_transport_interface.h"
#include "api/environment/environment.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/transport/data_channel_transport_interface.h"
#include "api/transport/enums.h"
#include "call/payload_type.h"
#include "call/payload_type_picker.h"
#include "media/base/codec.h"
#include "media/sctp/sctp_transport_internal.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "p2p/base/dtls_transport.h"
#include "p2p/base/dtls_transport_internal.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport.h"
#include "pc/rtp_transport.h"
#include "pc/rtp_transport_internal.h"
#include "pc/sctp_transport.h"
#include "pc/session_description.h"
#include "pc/srtp_transport.h"
#include "pc/transport_stats.h"
#include "rtc_base/checks.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/trace_event.h"
using webrtc::SdpType;
namespace webrtc {
JsepTransportController::JsepTransportController(
const Environment& env,
rtc::Thread* network_thread,
cricket::PortAllocator* port_allocator,
AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
PayloadTypePicker& payload_type_picker,
Config config)
: env_(env),
network_thread_(network_thread),
port_allocator_(port_allocator),
async_dns_resolver_factory_(async_dns_resolver_factory),
transports_(
[this](const std::string& mid, cricket::JsepTransport* transport) {
return OnTransportChanged(mid, transport);
},
[this]() {
RTC_DCHECK_RUN_ON(network_thread_);
UpdateAggregateStates_n();
}),
config_(std::move(config)),
active_reset_srtp_params_(config.active_reset_srtp_params),
bundles_(config.bundle_policy),
payload_type_picker_(payload_type_picker) {
// The `transport_observer` is assumed to be non-null.
RTC_DCHECK(config_.transport_observer);
RTC_DCHECK(config_.rtcp_handler);
RTC_DCHECK(config_.ice_transport_factory);
RTC_DCHECK(config_.on_dtls_handshake_error_);
}
JsepTransportController::~JsepTransportController() {
// Channel destructors may try to send packets, so this needs to happen on
// the network thread.
RTC_DCHECK_RUN_ON(network_thread_);
DestroyAllJsepTransports_n();
}
RTCError JsepTransportController::SetLocalDescription(
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc) {
RTC_DCHECK(local_desc);
TRACE_EVENT0("webrtc", "JsepTransportController::SetLocalDescription");
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall([this, type, local_desc, remote_desc] {
return SetLocalDescription(type, local_desc, remote_desc);
});
}
RTC_DCHECK_RUN_ON(network_thread_);
if (!initial_offerer_.has_value()) {
initial_offerer_.emplace(type == SdpType::kOffer);
if (*initial_offerer_) {
SetIceRole_n(cricket::ICEROLE_CONTROLLING);
} else {
SetIceRole_n(cricket::ICEROLE_CONTROLLED);
}
}
return ApplyDescription_n(/*local=*/true, type, local_desc, remote_desc);
}
RTCError JsepTransportController::SetRemoteDescription(
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc) {
RTC_DCHECK(remote_desc);
TRACE_EVENT0("webrtc", "JsepTransportController::SetRemoteDescription");
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall([this, type, local_desc, remote_desc] {
return SetRemoteDescription(type, local_desc, remote_desc);
});
}
RTC_DCHECK_RUN_ON(network_thread_);
return ApplyDescription_n(/*local=*/false, type, local_desc, remote_desc);
}
RtpTransportInternal* JsepTransportController::GetRtpTransport(
absl::string_view mid) const {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->rtp_transport();
}
DataChannelTransportInterface* JsepTransportController::GetDataChannelTransport(
const std::string& mid) const {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->data_channel_transport();
}
cricket::DtlsTransportInternal* JsepTransportController::GetDtlsTransport(
const std::string& mid) {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->rtp_dtls_transport();
}
const cricket::DtlsTransportInternal*
JsepTransportController::GetRtcpDtlsTransport(const std::string& mid) const {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->rtcp_dtls_transport();
}
rtc::scoped_refptr<DtlsTransport>
JsepTransportController::LookupDtlsTransportByMid(const std::string& mid) {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->RtpDtlsTransport();
}
rtc::scoped_refptr<SctpTransport> JsepTransportController::GetSctpTransport(
const std::string& mid) const {
RTC_DCHECK_RUN_ON(network_thread_);
auto jsep_transport = GetJsepTransportForMid(mid);
if (!jsep_transport) {
return nullptr;
}
return jsep_transport->SctpTransport();
}
void JsepTransportController::SetIceConfig(const cricket::IceConfig& config) {
RTC_DCHECK_RUN_ON(network_thread_);
ice_config_ = config;
for (auto& dtls : GetDtlsTransports()) {
dtls->ice_transport()->SetIceConfig(ice_config_);
}
}
void JsepTransportController::SetNeedsIceRestartFlag() {
RTC_DCHECK_RUN_ON(network_thread_);
for (auto& transport : transports_.Transports()) {
transport->SetNeedsIceRestartFlag();
}
}
bool JsepTransportController::NeedsIceRestart(
const std::string& transport_name) const {
RTC_DCHECK_RUN_ON(network_thread_);
const cricket::JsepTransport* transport =
GetJsepTransportByName(transport_name);
if (!transport) {
return false;
}
return transport->needs_ice_restart();
}
std::optional<rtc::SSLRole> JsepTransportController::GetDtlsRole(
const std::string& mid) const {
// TODO(tommi): Remove this hop. Currently it's called from the signaling
// thread during negotiations, potentially multiple times.
// WebRtcSessionDescriptionFactory::InternalCreateAnswer is one example.
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall([&] { return GetDtlsRole(mid); });
}
RTC_DCHECK_RUN_ON(network_thread_);
const cricket::JsepTransport* t = GetJsepTransportForMid(mid);
if (!t) {
return std::optional<rtc::SSLRole>();
}
return t->GetDtlsRole();
}
RTCErrorOr<webrtc::PayloadType> JsepTransportController::SuggestPayloadType(
const std::string& mid,
cricket::Codec codec) {
// Because SDP processing runs on the signal thread and Call processing
// runs on the worker thread, we allow cross thread invocation until we
// can clean up the thread work.
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall([&] {
RTC_DCHECK_RUN_ON(network_thread_);
return SuggestPayloadType(mid, codec);
});
}
RTC_DCHECK_RUN_ON(network_thread_);
const cricket::JsepTransport* transport = GetJsepTransportForMid(mid);
if (transport) {
auto local_result =
transport->local_payload_types().LookupPayloadType(codec);
if (local_result.ok()) {
return local_result;
}
auto remote_result =
transport->remote_payload_types().LookupPayloadType(codec);
if (remote_result.ok()) {
return remote_result;
}
return payload_type_picker_.SuggestMapping(
codec, &transport->local_payload_types());
}
// If there is no transport, there are no exclusions.
return payload_type_picker_.SuggestMapping(codec, nullptr);
}
RTCError JsepTransportController::AddLocalMapping(const std::string& mid,
PayloadType payload_type,
const cricket::Codec& codec) {
// Because SDP processing runs on the signal thread and Call processing
// runs on the worker thread, we allow cross thread invocation until we
// can clean up the thread work.
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall([&] {
RTC_DCHECK_RUN_ON(network_thread_);
return AddLocalMapping(mid, payload_type, codec);
});
}
RTC_DCHECK_RUN_ON(network_thread_);
cricket::JsepTransport* transport = GetJsepTransportForMid(mid);
if (!transport) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"AddLocalMapping: no transport for mid");
}
return transport->local_payload_types().AddMapping(payload_type, codec);
}
bool JsepTransportController::SetLocalCertificate(
const rtc::scoped_refptr<rtc::RTCCertificate>& certificate) {
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall(
[&] { return SetLocalCertificate(certificate); });
}
RTC_DCHECK_RUN_ON(network_thread_);
// 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& transport : transports_.Transports()) {
transport->SetLocalCertificate(certificate_);
}
for (auto& dtls : GetDtlsTransports()) {
bool set_cert_success = dtls->SetLocalCertificate(certificate_);
RTC_DCHECK(set_cert_success);
}
return true;
}
rtc::scoped_refptr<rtc::RTCCertificate>
JsepTransportController::GetLocalCertificate(
const std::string& transport_name) const {
RTC_DCHECK_RUN_ON(network_thread_);
const cricket::JsepTransport* t = GetJsepTransportByName(transport_name);
if (!t) {
return nullptr;
}
return t->GetLocalCertificate();
}
std::unique_ptr<rtc::SSLCertChain>
JsepTransportController::GetRemoteSSLCertChain(
const std::string& transport_name) const {
RTC_DCHECK_RUN_ON(network_thread_);
// Get the certificate from the RTP transport's DTLS handshake. Should be
// identical to the RTCP transport's, since they were given the same remote
// fingerprint.
auto jsep_transport = GetJsepTransportByName(transport_name);
if (!jsep_transport) {
return nullptr;
}
auto dtls = jsep_transport->rtp_dtls_transport();
if (!dtls) {
return nullptr;
}
return dtls->GetRemoteSSLCertChain();
}
void JsepTransportController::MaybeStartGathering() {
if (!network_thread_->IsCurrent()) {
network_thread_->BlockingCall([&] { MaybeStartGathering(); });
return;
}
for (auto& dtls : GetDtlsTransports()) {
dtls->ice_transport()->MaybeStartGathering();
}
}
RTCError JsepTransportController::AddRemoteCandidates(
const std::string& transport_name,
const cricket::Candidates& candidates) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DCHECK(VerifyCandidates(candidates).ok());
auto jsep_transport = GetJsepTransportByName(transport_name);
if (!jsep_transport) {
RTC_LOG(LS_WARNING) << "Not adding candidate because the JsepTransport "
"doesn't exist. Ignore it.";
return RTCError::OK();
}
return jsep_transport->AddRemoteCandidates(candidates);
}
RTCError JsepTransportController::RemoveRemoteCandidates(
const cricket::Candidates& candidates) {
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall(
[&] { return RemoveRemoteCandidates(candidates); });
}
RTC_DCHECK_RUN_ON(network_thread_);
// Verify each candidate before passing down to the transport layer.
RTCError error = VerifyCandidates(candidates);
if (!error.ok()) {
return error;
}
std::map<std::string, cricket::Candidates> candidates_by_transport_name;
for (const cricket::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.ToSensitiveString();
}
}
for (const auto& kv : candidates_by_transport_name) {
const std::string& transport_name = kv.first;
const cricket::Candidates& candidates = kv.second;
cricket::JsepTransport* jsep_transport =
GetJsepTransportByName(transport_name);
if (!jsep_transport) {
RTC_LOG(LS_WARNING)
<< "Not removing candidate because the JsepTransport doesn't exist.";
continue;
}
for (const cricket::Candidate& candidate : candidates) {
cricket::DtlsTransportInternal* dtls =
candidate.component() == cricket::ICE_CANDIDATE_COMPONENT_RTP
? jsep_transport->rtp_dtls_transport()
: jsep_transport->rtcp_dtls_transport();
if (dtls) {
dtls->ice_transport()->RemoveRemoteCandidate(candidate);
}
}
}
return RTCError::OK();
}
bool JsepTransportController::GetStats(const std::string& transport_name,
cricket::TransportStats* stats) const {
RTC_DCHECK_RUN_ON(network_thread_);
const cricket::JsepTransport* transport =
GetJsepTransportByName(transport_name);
if (!transport) {
return false;
}
return transport->GetStats(stats);
}
void JsepTransportController::SetActiveResetSrtpParams(
bool active_reset_srtp_params) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_LOG(LS_INFO)
<< "Updating the active_reset_srtp_params for JsepTransportController: "
<< active_reset_srtp_params;
active_reset_srtp_params_ = active_reset_srtp_params;
for (auto& transport : transports_.Transports()) {
transport->SetActiveResetSrtpParams(active_reset_srtp_params);
}
}
RTCError JsepTransportController::RollbackTransports() {
if (!network_thread_->IsCurrent()) {
return network_thread_->BlockingCall(
[this] { return RollbackTransports(); });
}
RTC_DCHECK_RUN_ON(network_thread_);
bundles_.Rollback();
if (!transports_.RollbackTransports()) {
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
"Failed to roll back transport state.");
}
return RTCError::OK();
}
rtc::scoped_refptr<IceTransportInterface>
JsepTransportController::CreateIceTransport(const std::string& transport_name,
bool rtcp) {
int component = rtcp ? cricket::ICE_CANDIDATE_COMPONENT_RTCP
: cricket::ICE_CANDIDATE_COMPONENT_RTP;
IceTransportInit init;
init.set_port_allocator(port_allocator_);
init.set_async_dns_resolver_factory(async_dns_resolver_factory_);
init.set_event_log(config_.event_log);
init.set_field_trials(&env_.field_trials());
auto transport = config_.ice_transport_factory->CreateIceTransport(
transport_name, component, std::move(init));
RTC_DCHECK(transport);
transport->internal()->SetIceRole(ice_role_);
transport->internal()->SetIceConfig(ice_config_);
return transport;
}
std::unique_ptr<cricket::DtlsTransportInternal>
JsepTransportController::CreateDtlsTransport(
const cricket::ContentInfo& content_info,
cricket::IceTransportInternal* ice) {
RTC_DCHECK_RUN_ON(network_thread_);
std::unique_ptr<cricket::DtlsTransportInternal> dtls;
if (config_.dtls_transport_factory) {
dtls = config_.dtls_transport_factory->CreateDtlsTransport(
ice, config_.crypto_options, config_.ssl_max_version);
} else {
dtls = std::make_unique<cricket::DtlsTransport>(ice, config_.crypto_options,
config_.event_log,
config_.ssl_max_version);
}
RTC_DCHECK(dtls);
RTC_DCHECK_EQ(ice, dtls->ice_transport());
if (certificate_) {
bool set_cert_success = dtls->SetLocalCertificate(certificate_);
RTC_DCHECK(set_cert_success);
}
// Connect to signals offered by the DTLS and ICE transport.
dtls->SignalWritableState.connect(
this, &JsepTransportController::OnTransportWritableState_n);
dtls->SignalReceivingState.connect(
this, &JsepTransportController::OnTransportReceivingState_n);
dtls->ice_transport()->AddGatheringStateCallback(
this, [this](cricket::IceTransportInternal* transport) {
RTC_DCHECK_RUN_ON(network_thread_);
OnTransportGatheringState_n(transport);
});
dtls->ice_transport()->SignalCandidateGathered.connect(
this, &JsepTransportController::OnTransportCandidateGathered_n);
dtls->ice_transport()->SetCandidateErrorCallback(
[this](cricket::IceTransportInternal* transport,
const cricket::IceCandidateErrorEvent& error) {
RTC_DCHECK_RUN_ON(network_thread_);
OnTransportCandidateError_n(transport, error);
});
dtls->ice_transport()->SetCandidatesRemovedCallback(
[this](cricket::IceTransportInternal* transport,
const cricket::Candidates& candidates) {
RTC_DCHECK_RUN_ON(network_thread_);
OnTransportCandidatesRemoved_n(transport, candidates);
});
dtls->ice_transport()->SignalRoleConflict.connect(
this, &JsepTransportController::OnTransportRoleConflict_n);
dtls->ice_transport()->SignalStateChanged.connect(
this, &JsepTransportController::OnTransportStateChanged_n);
dtls->ice_transport()->SignalIceTransportStateChanged.connect(
this, &JsepTransportController::OnTransportStateChanged_n);
dtls->ice_transport()->SetCandidatePairChangeCallback(
[this](const cricket::CandidatePairChangeEvent& event) {
RTC_DCHECK_RUN_ON(network_thread_);
OnTransportCandidatePairChanged_n(event);
});
dtls->SubscribeDtlsHandshakeError(
[this](rtc::SSLHandshakeError error) { OnDtlsHandshakeError(error); });
return dtls;
}
std::unique_ptr<RtpTransport>
JsepTransportController::CreateUnencryptedRtpTransport(
const std::string& transport_name,
rtc::PacketTransportInternal* rtp_packet_transport,
rtc::PacketTransportInternal* rtcp_packet_transport) {
RTC_DCHECK_RUN_ON(network_thread_);
auto unencrypted_rtp_transport = std::make_unique<RtpTransport>(
rtcp_packet_transport == nullptr, env_.field_trials());
unencrypted_rtp_transport->SetRtpPacketTransport(rtp_packet_transport);
if (rtcp_packet_transport) {
unencrypted_rtp_transport->SetRtcpPacketTransport(rtcp_packet_transport);
}
return unencrypted_rtp_transport;
}
std::unique_ptr<SrtpTransport> JsepTransportController::CreateSdesTransport(
const std::string& transport_name,
cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport) {
RTC_DCHECK_RUN_ON(network_thread_);
auto srtp_transport = std::make_unique<SrtpTransport>(
rtcp_dtls_transport == nullptr, env_.field_trials());
RTC_DCHECK(rtp_dtls_transport);
srtp_transport->SetRtpPacketTransport(rtp_dtls_transport);
if (rtcp_dtls_transport) {
srtp_transport->SetRtcpPacketTransport(rtcp_dtls_transport);
}
if (config_.enable_external_auth) {
srtp_transport->EnableExternalAuth();
}
return srtp_transport;
}
std::unique_ptr<DtlsSrtpTransport>
JsepTransportController::CreateDtlsSrtpTransport(
const std::string& transport_name,
cricket::DtlsTransportInternal* rtp_dtls_transport,
cricket::DtlsTransportInternal* rtcp_dtls_transport) {
RTC_DCHECK_RUN_ON(network_thread_);
auto dtls_srtp_transport = std::make_unique<DtlsSrtpTransport>(
rtcp_dtls_transport == nullptr, env_.field_trials());
if (config_.enable_external_auth) {
dtls_srtp_transport->EnableExternalAuth();
}
dtls_srtp_transport->SetDtlsTransports(rtp_dtls_transport,
rtcp_dtls_transport);
dtls_srtp_transport->SetActiveResetSrtpParams(active_reset_srtp_params_);
// Capturing this in the callback because JsepTransportController will always
// outlive the DtlsSrtpTransport.
dtls_srtp_transport->SetOnDtlsStateChange([this]() {
RTC_DCHECK_RUN_ON(this->network_thread_);
this->UpdateAggregateStates_n();
});
return dtls_srtp_transport;
}
std::vector<cricket::DtlsTransportInternal*>
JsepTransportController::GetDtlsTransports() {
RTC_DCHECK_RUN_ON(network_thread_);
std::vector<cricket::DtlsTransportInternal*> dtls_transports;
for (auto jsep_transport : transports_.Transports()) {
RTC_DCHECK(jsep_transport);
if (jsep_transport->rtp_dtls_transport()) {
dtls_transports.push_back(jsep_transport->rtp_dtls_transport());
}
if (jsep_transport->rtcp_dtls_transport()) {
dtls_transports.push_back(jsep_transport->rtcp_dtls_transport());
}
}
return dtls_transports;
}
std::vector<cricket::DtlsTransportInternal*>
JsepTransportController::GetActiveDtlsTransports() {
RTC_DCHECK_RUN_ON(network_thread_);
std::vector<cricket::DtlsTransportInternal*> dtls_transports;
for (auto jsep_transport : transports_.ActiveTransports()) {
RTC_DCHECK(jsep_transport);
if (jsep_transport->rtp_dtls_transport()) {
dtls_transports.push_back(jsep_transport->rtp_dtls_transport());
}
if (jsep_transport->rtcp_dtls_transport()) {
dtls_transports.push_back(jsep_transport->rtcp_dtls_transport());
}
}
return dtls_transports;
}
RTCError JsepTransportController::ApplyDescription_n(
bool local,
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc) {
TRACE_EVENT0("webrtc", "JsepTransportController::ApplyDescription_n");
// Stash away the description object that we'll be applying (since this
// function is used for both local and remote).
const cricket::SessionDescription* description =
local ? local_desc : remote_desc;
RTC_DCHECK(description);
RTCError error;
error =
ValidateAndMaybeUpdateBundleGroups(local, type, local_desc, remote_desc);
if (!error.ok()) {
return error;
}
std::map<const cricket::ContentGroup*, std::vector<int>>
merged_encrypted_extension_ids_by_bundle;
if (!bundles_.bundle_groups().empty()) {
merged_encrypted_extension_ids_by_bundle =
MergeEncryptedHeaderExtensionIdsForBundles(description);
}
for (const cricket::ContentInfo& content_info : description->contents()) {
// Don't create transports for rejected m-lines and bundled m-lines.
if (content_info.rejected ||
!bundles_.IsFirstMidInGroup(content_info.name)) {
continue;
}
error = MaybeCreateJsepTransport(local, content_info, *description);
if (!error.ok()) {
return error;
}
}
RTC_DCHECK(description->contents().size() ==
description->transport_infos().size());
for (size_t i = 0; i < description->contents().size(); ++i) {
const cricket::ContentInfo& content_info = description->contents()[i];
const cricket::TransportInfo& transport_info =
description->transport_infos()[i];
if (content_info.rejected) {
// This may cause groups to be removed from |bundles_.bundle_groups()|.
HandleRejectedContent(content_info);
continue;
}
const cricket::ContentGroup* established_bundle_group =
bundles_.LookupGroupByMid(content_info.name);
// For bundle members that are not BUNDLE-tagged (not first in the group),
// configure their transport to be the same as the BUNDLE-tagged transport.
if (established_bundle_group &&
content_info.name != *established_bundle_group->FirstContentName()) {
if (!HandleBundledContent(content_info, *established_bundle_group)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"Failed to process the bundled m= section with "
"mid='" +
content_info.name + "'.");
}
continue;
}
error = ValidateContent(content_info);
if (!error.ok()) {
return error;
}
std::vector<int> extension_ids;
// Is BUNDLE-tagged (first in the group)?
if (established_bundle_group &&
content_info.name == *established_bundle_group->FirstContentName()) {
auto it = merged_encrypted_extension_ids_by_bundle.find(
established_bundle_group);
RTC_DCHECK(it != merged_encrypted_extension_ids_by_bundle.end());
extension_ids = it->second;
} else {
extension_ids = GetEncryptedHeaderExtensionIds(content_info);
}
int rtp_abs_sendtime_extn_id =
GetRtpAbsSendTimeHeaderExtensionId(content_info);
cricket::JsepTransport* transport =
GetJsepTransportForMid(content_info.name);
if (!transport) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"Could not find transport for m= section with mid='" +
content_info.name + "'");
}
SetIceRole_n(DetermineIceRole(transport, transport_info, type, local));
cricket::JsepTransportDescription jsep_description =
CreateJsepTransportDescription(content_info, transport_info,
extension_ids, rtp_abs_sendtime_extn_id);
if (local) {
error =
transport->SetLocalJsepTransportDescription(jsep_description, type);
} else {
error =
transport->SetRemoteJsepTransportDescription(jsep_description, type);
}
if (!error.ok()) {
LOG_AND_RETURN_ERROR(
RTCErrorType::INVALID_PARAMETER,
"Failed to apply the description for m= section with mid='" +
content_info.name + "': " + error.message());
}
error = transport->RecordPayloadTypes(local, type, content_info);
if (!error.ok()) {
RTC_LOG(LS_ERROR) << "RecordPayloadTypes failed: "
<< ToString(error.type()) << " - " << error.message();
return error;
}
}
if (type == SdpType::kAnswer) {
transports_.CommitTransports();
bundles_.Commit();
}
return RTCError::OK();
}
RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups(
bool local,
SdpType type,
const cricket::SessionDescription* local_desc,
const cricket::SessionDescription* remote_desc) {
const cricket::SessionDescription* description =
local ? local_desc : remote_desc;
RTC_DCHECK(description);
std::vector<const cricket::ContentGroup*> new_bundle_groups =
description->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
// Verify `new_bundle_groups`.
std::map<std::string, const cricket::ContentGroup*> new_bundle_groups_by_mid;
for (const cricket::ContentGroup* new_bundle_group : new_bundle_groups) {
for (const std::string& content_name : new_bundle_group->content_names()) {
// The BUNDLE group must not contain a MID that is a member of a different
// BUNDLE group, or that contains the same MID multiple times.
if (new_bundle_groups_by_mid.find(content_name) !=
new_bundle_groups_by_mid.end()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"A BUNDLE group contains a MID='" + content_name +
"' that is already in a BUNDLE group.");
}
new_bundle_groups_by_mid.insert(
std::make_pair(content_name, new_bundle_group));
// The BUNDLE group must not contain a MID that no m= section has.
if (!description->GetContentByName(content_name)) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"A BUNDLE group contains a MID='" + content_name +
"' matching no m= section.");
}
}
}
if (type == SdpType::kOffer) {
// For an offer, we need to verify that there is not a conflicting mapping
// between existing and new bundle groups. For example, if the existing
// groups are [[1,2],[3,4]] and new are [[1,3],[2,4]] or [[1,2,3,4]], or
// vice versa. Switching things around like this requires a separate offer
// that removes the relevant sections from their group, as per RFC 8843,
// section 7.5.2.
std::map<const cricket::ContentGroup*, const cricket::ContentGroup*>
new_bundle_groups_by_existing_bundle_groups;
std::map<const cricket::ContentGroup*, const cricket::ContentGroup*>
existing_bundle_groups_by_new_bundle_groups;
for (const cricket::ContentGroup* new_bundle_group : new_bundle_groups) {
for (const std::string& mid : new_bundle_group->content_names()) {
cricket::ContentGroup* existing_bundle_group =
bundles_.LookupGroupByMid(mid);
if (!existing_bundle_group) {
continue;
}
auto it = new_bundle_groups_by_existing_bundle_groups.find(
existing_bundle_group);
if (it != new_bundle_groups_by_existing_bundle_groups.end() &&
it->second != new_bundle_group) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"MID " + mid + " in the offer has changed group.");
}
new_bundle_groups_by_existing_bundle_groups.insert(
std::make_pair(existing_bundle_group, new_bundle_group));
it = existing_bundle_groups_by_new_bundle_groups.find(new_bundle_group);
if (it != existing_bundle_groups_by_new_bundle_groups.end() &&
it->second != existing_bundle_group) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"MID " + mid + " in the offer has changed group.");
}
existing_bundle_groups_by_new_bundle_groups.insert(
std::make_pair(new_bundle_group, existing_bundle_group));
}
}
} else if (type == SdpType::kAnswer) {
if ((local && remote_desc) || (!local && local_desc)) {
std::vector<const cricket::ContentGroup*> offered_bundle_groups =
local ? remote_desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE)
: local_desc->GetGroupsByName(cricket::GROUP_TYPE_BUNDLE);
std::map<std::string, const cricket::ContentGroup*>
offered_bundle_groups_by_mid;
for (const cricket::ContentGroup* offered_bundle_group :
offered_bundle_groups) {
for (const std::string& content_name :
offered_bundle_group->content_names()) {
offered_bundle_groups_by_mid[content_name] = offered_bundle_group;
}
}
std::map<const cricket::ContentGroup*, const cricket::ContentGroup*>
new_bundle_groups_by_offered_bundle_groups;
for (const cricket::ContentGroup* new_bundle_group : new_bundle_groups) {
if (!new_bundle_group->FirstContentName()) {
// Empty groups could be a subset of any group.
continue;
}
// The group in the answer (new_bundle_group) must have a corresponding
// group in the offer (original_group), because the answer groups may
// only be subsets of the offer groups.
auto it = offered_bundle_groups_by_mid.find(
*new_bundle_group->FirstContentName());
if (it == offered_bundle_groups_by_mid.end()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"A BUNDLE group was added in the answer that did not "
"exist in the offer.");
}
const cricket::ContentGroup* offered_bundle_group = it->second;
if (new_bundle_groups_by_offered_bundle_groups.find(
offered_bundle_group) !=
new_bundle_groups_by_offered_bundle_groups.end()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"A MID in the answer has changed group.");
}
new_bundle_groups_by_offered_bundle_groups.insert(
std::make_pair(offered_bundle_group, new_bundle_group));
for (const std::string& content_name :
new_bundle_group->content_names()) {
it = offered_bundle_groups_by_mid.find(content_name);
// The BUNDLE group in answer should be a subset of offered group.
if (it == offered_bundle_groups_by_mid.end() ||
it->second != offered_bundle_group) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"A BUNDLE group in answer contains a MID='" +
content_name +
"' that was not in the offered group.");
}
}
}
for (const auto& bundle_group : bundles_.bundle_groups()) {
for (const std::string& content_name : bundle_group->content_names()) {
// An answer that removes m= sections from pre-negotiated BUNDLE group
// without rejecting it, is invalid.
auto it = new_bundle_groups_by_mid.find(content_name);
if (it == new_bundle_groups_by_mid.end()) {
auto* content_info = description->GetContentByName(content_name);
if (!content_info || !content_info->rejected) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"Answer cannot remove m= section with mid='" +
content_name +
"' from already-established BUNDLE group.");
}
}
}
}
}
}
if (config_.bundle_policy ==
PeerConnectionInterface::kBundlePolicyMaxBundle &&
!description->HasGroup(cricket::GROUP_TYPE_BUNDLE) &&
description->contents().size() > 1) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"max-bundle is used but no bundle group found.");
}
bundles_.Update(description, type);
for (const auto& bundle_group : bundles_.bundle_groups()) {
if (!bundle_group->FirstContentName())
continue;
// The first MID in a BUNDLE group is BUNDLE-tagged.
auto bundled_content =
description->GetContentByName(*bundle_group->FirstContentName());
if (!bundled_content) {
return RTCError(
RTCErrorType::INVALID_PARAMETER,
"An m= section associated with the BUNDLE-tag doesn't exist.");
}
// If the `bundled_content` is rejected, other contents in the bundle group
// must also be rejected.
if (bundled_content->rejected) {
for (const auto& content_name : bundle_group->content_names()) {
auto other_content = description->GetContentByName(content_name);
if (!other_content->rejected) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"The m= section with mid='" + content_name +
"' should be rejected.");
}
}
}
}
return RTCError::OK();
}
RTCError JsepTransportController::ValidateContent(
const cricket::ContentInfo& content_info) {
if (config_.rtcp_mux_policy ==
PeerConnectionInterface::kRtcpMuxPolicyRequire &&
content_info.type == cricket::MediaProtocolType::kRtp &&
!content_info.bundle_only &&
!content_info.media_description()->rtcp_mux()) {
return RTCError(RTCErrorType::INVALID_PARAMETER,
"The m= section with mid='" + content_info.name +
"' is invalid. RTCP-MUX is not "
"enabled when it is required.");
}
return RTCError::OK();
}
void JsepTransportController::HandleRejectedContent(
const cricket::ContentInfo& content_info) {
// If the content is rejected, let the
// BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
// then destroy the cricket::JsepTransport.
cricket::ContentGroup* bundle_group =
bundles_.LookupGroupByMid(content_info.name);
if (bundle_group && !bundle_group->content_names().empty() &&
content_info.name == *bundle_group->FirstContentName()) {
// Rejecting a BUNDLE group's first mid means we are rejecting the entire
// group.
for (const auto& content_name : bundle_group->content_names()) {
transports_.RemoveTransportForMid(content_name);
}
// Delete the BUNDLE group.
bundles_.DeleteGroup(bundle_group);
} else {
transports_.RemoveTransportForMid(content_info.name);
if (bundle_group) {
// Remove the rejected content from the `bundle_group`.
bundles_.DeleteMid(bundle_group, content_info.name);
}
}
}
bool JsepTransportController::HandleBundledContent(
const cricket::ContentInfo& content_info,
const cricket::ContentGroup& bundle_group) {
TRACE_EVENT0("webrtc", "JsepTransportController::HandleBundledContent");
RTC_DCHECK(bundle_group.FirstContentName());
auto jsep_transport =
GetJsepTransportByName(*bundle_group.FirstContentName());
RTC_DCHECK(jsep_transport);
// If the content is bundled, let the
// BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
// then destroy the cricket::JsepTransport.
// TODO(bugs.webrtc.org/9719) For media transport this is far from ideal,
// because it means that we first create media transport and start
// connecting it, and then we destroy it. We will need to address it before
// video path is enabled.
return transports_.SetTransportForMid(content_info.name, jsep_transport);
}
cricket::JsepTransportDescription
JsepTransportController::CreateJsepTransportDescription(
const cricket::ContentInfo& content_info,
const cricket::TransportInfo& transport_info,
const std::vector<int>& encrypted_extension_ids,
int rtp_abs_sendtime_extn_id) {
TRACE_EVENT0("webrtc",
"JsepTransportController::CreateJsepTransportDescription");
const cricket::MediaContentDescription* content_desc =
content_info.media_description();
RTC_DCHECK(content_desc);
bool rtcp_mux_enabled = content_info.type == cricket::MediaProtocolType::kSctp
? true
: content_desc->rtcp_mux();
return cricket::JsepTransportDescription(
rtcp_mux_enabled, encrypted_extension_ids, rtp_abs_sendtime_extn_id,
transport_info.description);
}
std::vector<int> JsepTransportController::GetEncryptedHeaderExtensionIds(
const cricket::ContentInfo& content_info) {
const cricket::MediaContentDescription* content_desc =
content_info.media_description();
if (!config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions) {
return std::vector<int>();
}
std::vector<int> encrypted_header_extension_ids;
for (const auto& extension : content_desc->rtp_header_extensions()) {
if (!extension.encrypt) {
continue;
}
if (!absl::c_linear_search(encrypted_header_extension_ids, extension.id)) {
encrypted_header_extension_ids.push_back(extension.id);
}
}
return encrypted_header_extension_ids;
}
std::map<const cricket::ContentGroup*, std::vector<int>>
JsepTransportController::MergeEncryptedHeaderExtensionIdsForBundles(
const cricket::SessionDescription* description) {
RTC_DCHECK(description);
RTC_DCHECK(!bundles_.bundle_groups().empty());
std::map<const cricket::ContentGroup*, std::vector<int>>
merged_encrypted_extension_ids_by_bundle;
// Union the encrypted header IDs in the group when bundle is enabled.
for (const cricket::ContentInfo& content_info : description->contents()) {
auto group = bundles_.LookupGroupByMid(content_info.name);
if (!group)
continue;
// Get or create list of IDs for the BUNDLE group.
std::vector<int>& merged_ids =
merged_encrypted_extension_ids_by_bundle[group];
// Add IDs not already in the list.
std::vector<int> extension_ids =
GetEncryptedHeaderExtensionIds(content_info);
for (int id : extension_ids) {
if (!absl::c_linear_search(merged_ids, id)) {
merged_ids.push_back(id);
}
}
}
return merged_encrypted_extension_ids_by_bundle;
}
int JsepTransportController::GetRtpAbsSendTimeHeaderExtensionId(
const cricket::ContentInfo& content_info) {
if (!config_.enable_external_auth) {
return -1;
}
const cricket::MediaContentDescription* content_desc =
content_info.media_description();
const RtpExtension* send_time_extension =
RtpExtension::FindHeaderExtensionByUri(
content_desc->rtp_header_extensions(), RtpExtension::kAbsSendTimeUri,
config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions
? RtpExtension::kPreferEncryptedExtension
: RtpExtension::kDiscardEncryptedExtension);
return send_time_extension ? send_time_extension->id : -1;
}
const cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
const std::string& mid) const {
return transports_.GetTransportForMid(mid);
}
cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
const std::string& mid) {
return transports_.GetTransportForMid(mid);
}
const cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
absl::string_view mid) const {
return transports_.GetTransportForMid(mid);
}
cricket::JsepTransport* JsepTransportController::GetJsepTransportForMid(
absl::string_view mid) {
return transports_.GetTransportForMid(mid);
}
const cricket::JsepTransport* JsepTransportController::GetJsepTransportByName(
const std::string& transport_name) const {
return transports_.GetTransportByName(transport_name);
}
cricket::JsepTransport* JsepTransportController::GetJsepTransportByName(
const std::string& transport_name) {
return transports_.GetTransportByName(transport_name);
}
RTCError JsepTransportController::MaybeCreateJsepTransport(
bool local,
const cricket::ContentInfo& content_info,
const cricket::SessionDescription& description) {
cricket::JsepTransport* transport = GetJsepTransportByName(content_info.name);
if (transport) {
return RTCError::OK();
}
rtc::scoped_refptr<IceTransportInterface> ice =
CreateIceTransport(content_info.name, /*rtcp=*/false);
std::unique_ptr<cricket::DtlsTransportInternal> rtp_dtls_transport =
CreateDtlsTransport(content_info, ice->internal());
std::unique_ptr<cricket::DtlsTransportInternal> rtcp_dtls_transport;
std::unique_ptr<RtpTransport> unencrypted_rtp_transport;
std::unique_ptr<SrtpTransport> sdes_transport;
std::unique_ptr<DtlsSrtpTransport> dtls_srtp_transport;
rtc::scoped_refptr<IceTransportInterface> rtcp_ice;
if (config_.rtcp_mux_policy !=
PeerConnectionInterface::kRtcpMuxPolicyRequire &&
content_info.type == cricket::MediaProtocolType::kRtp) {
rtcp_ice = CreateIceTransport(content_info.name, /*rtcp=*/true);
rtcp_dtls_transport =
CreateDtlsTransport(content_info, rtcp_ice->internal());
}
if (config_.disable_encryption) {
RTC_LOG(LS_INFO)
<< "Creating UnencryptedRtpTransport, becayse encryption is disabled.";
unencrypted_rtp_transport = CreateUnencryptedRtpTransport(
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
} else {
RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport.";
dtls_srtp_transport = CreateDtlsSrtpTransport(
content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
}
std::unique_ptr<cricket::SctpTransportInternal> sctp_transport;
if (config_.sctp_factory) {
sctp_transport = config_.sctp_factory->CreateSctpTransport(
env_, rtp_dtls_transport.get());
}
std::unique_ptr<cricket::JsepTransport> jsep_transport =
std::make_unique<cricket::JsepTransport>(
content_info.name, certificate_, std::move(ice), std::move(rtcp_ice),
std::move(unencrypted_rtp_transport), std::move(sdes_transport),
std::move(dtls_srtp_transport), std::move(rtp_dtls_transport),
std::move(rtcp_dtls_transport), std::move(sctp_transport),
[&]() {
RTC_DCHECK_RUN_ON(network_thread_);
UpdateAggregateStates_n();
},
payload_type_picker_);
jsep_transport->rtp_transport()->SubscribeRtcpPacketReceived(
this, [this](rtc::CopyOnWriteBuffer* buffer, int64_t packet_time_ms) {
RTC_DCHECK_RUN_ON(network_thread_);
OnRtcpPacketReceived_n(buffer, packet_time_ms);
});
jsep_transport->rtp_transport()->SetUnDemuxableRtpPacketReceivedHandler(
[this](RtpPacketReceived& packet) {
RTC_DCHECK_RUN_ON(network_thread_);
OnUnDemuxableRtpPacketReceived_n(packet);
});
transports_.RegisterTransport(content_info.name, std::move(jsep_transport));
UpdateAggregateStates_n();
return RTCError::OK();
}
void JsepTransportController::DestroyAllJsepTransports_n() {
transports_.DestroyAllTransports();
}
void JsepTransportController::SetIceRole_n(cricket::IceRole ice_role) {
ice_role_ = ice_role;
auto dtls_transports = GetDtlsTransports();
for (auto& dtls : dtls_transports) {
dtls->ice_transport()->SetIceRole(ice_role_);
}
}
cricket::IceRole JsepTransportController::DetermineIceRole(
cricket::JsepTransport* jsep_transport,
const cricket::TransportInfo& transport_info,
SdpType type,
bool local) {
cricket::IceRole ice_role = ice_role_;
auto tdesc = transport_info.description;
if (local) {
// 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 JsepTransportController.
if (jsep_transport->remote_description() &&
jsep_transport->remote_description()->transport_desc.ice_mode ==
cricket::ICEMODE_LITE &&
ice_role_ == cricket::ICEROLE_CONTROLLED &&
tdesc.ice_mode == cricket::ICEMODE_FULL) {
ice_role = cricket::ICEROLE_CONTROLLING;
}
} else {
// If our role is cricket::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_ == cricket::ICEROLE_CONTROLLED &&
tdesc.ice_mode == cricket::ICEMODE_LITE) {
ice_role = cricket::ICEROLE_CONTROLLING;
}
// 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 (jsep_transport->local_description() &&
jsep_transport->local_description()->transport_desc.ice_mode ==
cricket::ICEMODE_LITE &&
ice_role_ == cricket::ICEROLE_CONTROLLING &&
tdesc.ice_mode == cricket::ICEMODE_FULL) {
ice_role = cricket::ICEROLE_CONTROLLED;
}
}
return ice_role;
}
void JsepTransportController::OnTransportWritableState_n(
rtc::PacketTransportInternal* transport) {
RTC_LOG(LS_INFO) << " Transport " << transport->transport_name()
<< " writability changed to " << transport->writable()
<< ".";
UpdateAggregateStates_n();
}
void JsepTransportController::OnTransportReceivingState_n(
rtc::PacketTransportInternal* transport) {
UpdateAggregateStates_n();
}
void JsepTransportController::OnTransportGatheringState_n(
cricket::IceTransportInternal* transport) {
UpdateAggregateStates_n();
}
void JsepTransportController::OnTransportCandidateGathered_n(
cricket::IceTransportInternal* transport,
const cricket::Candidate& candidate) {
// We should never signal peer-reflexive candidates.
if (candidate.is_prflx()) {
RTC_DCHECK_NOTREACHED();
return;
}
signal_ice_candidates_gathered_.Send(
transport->transport_name(), std::vector<cricket::Candidate>{candidate});
}
void JsepTransportController::OnTransportCandidateError_n(
cricket::IceTransportInternal* transport,
const cricket::IceCandidateErrorEvent& event) {
signal_ice_candidate_error_.Send(event);
}
void JsepTransportController::OnTransportCandidatesRemoved_n(
cricket::IceTransportInternal* transport,
const cricket::Candidates& candidates) {
signal_ice_candidates_removed_.Send(candidates);
}
void JsepTransportController::OnTransportCandidatePairChanged_n(
const cricket::CandidatePairChangeEvent& event) {
signal_ice_candidate_pair_changed_.Send(event);
}
void JsepTransportController::OnTransportRoleConflict_n(
cricket::IceTransportInternal* transport) {
// 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.
cricket::IceRole reversed_role = (ice_role_ == cricket::ICEROLE_CONTROLLING)
? cricket::ICEROLE_CONTROLLED
: cricket::ICEROLE_CONTROLLING;
RTC_LOG(LS_INFO) << "Got role conflict; switching to "
<< (reversed_role == cricket::ICEROLE_CONTROLLING
? "controlling"
: "controlled")
<< " role.";
SetIceRole_n(reversed_role);
}
void JsepTransportController::OnTransportStateChanged_n(
cricket::IceTransportInternal* transport) {
RTC_LOG(LS_INFO) << transport->transport_name() << " Transport "
<< transport->component()
<< " state changed. Check if state is complete.";
UpdateAggregateStates_n();
}
void JsepTransportController::UpdateAggregateStates_n() {
TRACE_EVENT0("webrtc", "JsepTransportController::UpdateAggregateStates_n");
auto dtls_transports = GetActiveDtlsTransports();
cricket::IceConnectionState new_connection_state =
cricket::kIceConnectionConnecting;
PeerConnectionInterface::IceConnectionState new_ice_connection_state =
PeerConnectionInterface::IceConnectionState::kIceConnectionNew;
PeerConnectionInterface::PeerConnectionState new_combined_state =
PeerConnectionInterface::PeerConnectionState::kNew;
cricket::IceGatheringState new_gathering_state = cricket::kIceGatheringNew;
bool any_failed = false;
bool all_connected = !dtls_transports.empty();
bool all_completed = !dtls_transports.empty();
bool any_gathering = false;
bool all_done_gathering = !dtls_transports.empty();
std::map<IceTransportState, int> ice_state_counts;
std::map<DtlsTransportState, int> dtls_state_counts;
for (const auto& dtls : dtls_transports) {
any_failed = any_failed || dtls->ice_transport()->GetState() ==
cricket::IceTransportState::STATE_FAILED;
all_connected = all_connected && dtls->writable();
all_completed =
all_completed && dtls->writable() &&
dtls->ice_transport()->GetState() ==
cricket::IceTransportState::STATE_COMPLETED &&
dtls->ice_transport()->GetIceRole() == cricket::ICEROLE_CONTROLLING &&
dtls->ice_transport()->gathering_state() ==
cricket::kIceGatheringComplete;
any_gathering = any_gathering || dtls->ice_transport()->gathering_state() !=
cricket::kIceGatheringNew;
all_done_gathering =
all_done_gathering && dtls->ice_transport()->gathering_state() ==
cricket::kIceGatheringComplete;
dtls_state_counts[dtls->dtls_state()]++;
ice_state_counts[dtls->ice_transport()->GetIceTransportState()]++;
}
if (any_failed) {
new_connection_state = cricket::kIceConnectionFailed;
} else if (all_completed) {
new_connection_state = cricket::kIceConnectionCompleted;
} else if (all_connected) {
new_connection_state = cricket::kIceConnectionConnected;
}
if (ice_connection_state_ != new_connection_state) {
ice_connection_state_ = new_connection_state;
signal_ice_connection_state_.Send(new_connection_state);
}
// Compute the current RTCIceConnectionState as described in
// https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate.
// The PeerConnection is responsible for handling the "closed" state.
int total_ice_checking = ice_state_counts[IceTransportState::kChecking];
int total_ice_connected = ice_state_counts[IceTransportState::kConnected];
int total_ice_completed = ice_state_counts[IceTransportState::kCompleted];
int total_ice_failed = ice_state_counts[IceTransportState::kFailed];
int total_ice_disconnected =
ice_state_counts[IceTransportState::kDisconnected];
int total_ice_closed = ice_state_counts[IceTransportState::kClosed];
int total_ice_new = ice_state_counts[IceTransportState::kNew];
int total_ice = dtls_transports.size();
if (total_ice_failed > 0) {
// Any RTCIceTransports are in the "failed" state.
new_ice_connection_state = PeerConnectionInterface::kIceConnectionFailed;
} else if (total_ice_disconnected > 0) {
// None of the previous states apply and any RTCIceTransports are in the
// "disconnected" state.
new_ice_connection_state =
PeerConnectionInterface::kIceConnectionDisconnected;
} else if (total_ice_new + total_ice_closed == total_ice) {
// None of the previous states apply and all RTCIceTransports are in the
// "new" or "closed" state, or there are no transports.
new_ice_connection_state = PeerConnectionInterface::kIceConnectionNew;
} else if (total_ice_new + total_ice_checking > 0) {
// None of the previous states apply and any RTCIceTransports are in the
// "new" or "checking" state.
new_ice_connection_state = PeerConnectionInterface::kIceConnectionChecking;
} else if (total_ice_completed + total_ice_closed == total_ice ||
all_completed) {
// None of the previous states apply and all RTCIceTransports are in the
// "completed" or "closed" state.
//
// TODO(https://bugs.webrtc.org/10356): The all_completed condition is added
// to mimic the behavior of the old ICE connection state, and should be
// removed once we get end-of-candidates signaling in place.
new_ice_connection_state = PeerConnectionInterface::kIceConnectionCompleted;
} else if (total_ice_connected + total_ice_completed + total_ice_closed ==
total_ice) {
// None of the previous states apply and all RTCIceTransports are in the
// "connected", "completed" or "closed" state.
new_ice_connection_state = PeerConnectionInterface::kIceConnectionConnected;
} else {
RTC_DCHECK_NOTREACHED();
}
if (standardized_ice_connection_state_ != new_ice_connection_state) {
if (standardized_ice_connection_state_ ==
PeerConnectionInterface::kIceConnectionChecking &&
new_ice_connection_state ==
PeerConnectionInterface::kIceConnectionCompleted) {
// Ensure that we never skip over the "connected" state.
signal_standardized_ice_connection_state_.Send(
PeerConnectionInterface::kIceConnectionConnected);
}
standardized_ice_connection_state_ = new_ice_connection_state;
signal_standardized_ice_connection_state_.Send(new_ice_connection_state);
}
// Compute the current RTCPeerConnectionState as described in
// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnectionstate.
// The PeerConnection is responsible for handling the "closed" state.
// Note that "connecting" is only a valid state for DTLS transports while
// "checking", "completed" and "disconnected" are only valid for ICE
// transports.
int total_connected =
total_ice_connected + dtls_state_counts[DtlsTransportState::kConnected];
int total_dtls_connecting =
dtls_state_counts[DtlsTransportState::kConnecting];
int total_failed =
total_ice_failed + dtls_state_counts[DtlsTransportState::kFailed];
int total_closed =
total_ice_closed + dtls_state_counts[DtlsTransportState::kClosed];
int total_new = total_ice_new + dtls_state_counts[DtlsTransportState::kNew];
int total_transports = total_ice * 2;
if (total_failed > 0) {
// Any of the RTCIceTransports or RTCDtlsTransports are in a "failed" state.
new_combined_state = PeerConnectionInterface::PeerConnectionState::kFailed;
} else if (total_ice_disconnected > 0) {
// None of the previous states apply and any RTCIceTransports or
// RTCDtlsTransports are in the "disconnected" state.
new_combined_state =
PeerConnectionInterface::PeerConnectionState::kDisconnected;
} else if (total_new + total_closed == total_transports) {
// None of the previous states apply and all RTCIceTransports and
// RTCDtlsTransports are in the "new" or "closed" state, or there are no
// transports.
new_combined_state = PeerConnectionInterface::PeerConnectionState::kNew;
} else if (total_new + total_dtls_connecting + total_ice_checking > 0) {
// None of the previous states apply and all RTCIceTransports or
// RTCDtlsTransports are in the "new", "connecting" or "checking" state.
new_combined_state =
PeerConnectionInterface::PeerConnectionState::kConnecting;
} else if (total_connected + total_ice_completed + total_closed ==
total_transports) {
// None of the previous states apply and all RTCIceTransports and
// RTCDtlsTransports are in the "connected", "completed" or "closed" state.
new_combined_state =
PeerConnectionInterface::PeerConnectionState::kConnected;
} else {
RTC_DCHECK_NOTREACHED();
}
if (combined_connection_state_ != new_combined_state) {
combined_connection_state_ = new_combined_state;
signal_connection_state_.Send(new_combined_state);
}
// Compute the gathering state.
if (dtls_transports.empty()) {
new_gathering_state = cricket::kIceGatheringNew;
} else if (all_done_gathering) {
new_gathering_state = cricket::kIceGatheringComplete;
} else if (any_gathering) {
new_gathering_state = cricket::kIceGatheringGathering;
}
if (ice_gathering_state_ != new_gathering_state) {
ice_gathering_state_ = new_gathering_state;
signal_ice_gathering_state_.Send(new_gathering_state);
}
}
void JsepTransportController::OnRtcpPacketReceived_n(
rtc::CopyOnWriteBuffer* packet,
int64_t packet_time_us) {
RTC_DCHECK(config_.rtcp_handler);
config_.rtcp_handler(*packet, packet_time_us);
}
void JsepTransportController::OnUnDemuxableRtpPacketReceived_n(
const RtpPacketReceived& packet) {
RTC_DCHECK(config_.un_demuxable_packet_handler);
config_.un_demuxable_packet_handler(packet);
}
void JsepTransportController::OnDtlsHandshakeError(
rtc::SSLHandshakeError error) {
config_.on_dtls_handshake_error_(error);
}
bool JsepTransportController::OnTransportChanged(
const std::string& mid,
cricket::JsepTransport* jsep_transport) {
if (config_.transport_observer) {
if (jsep_transport) {
return config_.transport_observer->OnTransportChanged(
mid, jsep_transport->rtp_transport(),
jsep_transport->RtpDtlsTransport(),
jsep_transport->data_channel_transport());
} else {
return config_.transport_observer->OnTransportChanged(mid, nullptr,
nullptr, nullptr);
}
}
return false;
}
} // namespace webrtc