summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/pc/jsep_transport_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/pc/jsep_transport_controller.cc')
-rw-r--r--third_party/libwebrtc/pc/jsep_transport_controller.cc1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/third_party/libwebrtc/pc/jsep_transport_controller.cc b/third_party/libwebrtc/pc/jsep_transport_controller.cc
new file mode 100644
index 0000000000..7c669a5ae3
--- /dev/null
+++ b/third_party/libwebrtc/pc/jsep_transport_controller.cc
@@ -0,0 +1,1451 @@
+/*
+ * 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 <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "absl/algorithm/container.h"
+#include "api/dtls_transport_interface.h"
+#include "api/rtp_parameters.h"
+#include "api/sequence_checker.h"
+#include "api/transport/enums.h"
+#include "media/sctp/sctp_transport_internal.h"
+#include "p2p/base/dtls_transport.h"
+#include "p2p/base/ice_transport_internal.h"
+#include "p2p/base/p2p_constants.h"
+#include "p2p/base/port.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/trace_event.h"
+
+using webrtc::SdpType;
+
+namespace webrtc {
+
+JsepTransportController::JsepTransportController(
+ rtc::Thread* network_thread,
+ cricket::PortAllocator* port_allocator,
+ AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
+ Config config)
+ : 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) {
+ // 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_);
+ RTC_DCHECK(config_.field_trials);
+ if (port_allocator_) {
+ port_allocator_->SetIceTiebreaker(ice_tiebreaker_);
+ }
+}
+
+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* description) {
+ TRACE_EVENT0("webrtc", "JsepTransportController::SetLocalDescription");
+ if (!network_thread_->IsCurrent()) {
+ return network_thread_->BlockingCall(
+ [=] { return SetLocalDescription(type, description); });
+ }
+
+ 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, description);
+}
+
+RTCError JsepTransportController::SetRemoteDescription(
+ SdpType type,
+ const cricket::SessionDescription* description) {
+ TRACE_EVENT0("webrtc", "JsepTransportController::SetRemoteDescription");
+ if (!network_thread_->IsCurrent()) {
+ return network_thread_->BlockingCall(
+ [=] { return SetRemoteDescription(type, description); });
+ }
+
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ApplyDescription_n(/*local=*/false, type, description);
+}
+
+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<webrtc::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();
+}
+
+absl::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 absl::optional<rtc::SSLRole>();
+ }
+ return t->GetDtlsRole();
+}
+
+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) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+
+ 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([=] { 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<webrtc::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(config_.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()->SetIceTiebreaker(ice_tiebreaker_);
+ 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()->SignalGatheringState.connect(
+ this, &JsepTransportController::OnTransportGatheringState_n);
+ dtls->ice_transport()->SignalCandidateGathered.connect(
+ this, &JsepTransportController::OnTransportCandidateGathered_n);
+ dtls->ice_transport()->SignalCandidateError.connect(
+ this, &JsepTransportController::OnTransportCandidateError_n);
+ dtls->ice_transport()->SignalCandidatesRemoved.connect(
+ this, &JsepTransportController::OnTransportCandidatesRemoved_n);
+ 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()->SignalCandidatePairChanged.connect(
+ this, &JsepTransportController::OnTransportCandidatePairChanged_n);
+
+ dtls->SubscribeDtlsHandshakeError(
+ [this](rtc::SSLHandshakeError error) { OnDtlsHandshakeError(error); });
+ return dtls;
+}
+
+std::unique_ptr<webrtc::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);
+ 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<webrtc::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<webrtc::SrtpTransport>(
+ rtcp_dtls_transport == nullptr, *config_.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<webrtc::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<webrtc::DtlsSrtpTransport>(
+ rtcp_dtls_transport == nullptr, *config_.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* description) {
+ TRACE_EVENT0("webrtc", "JsepTransportController::ApplyDescription_n");
+ RTC_DCHECK(description);
+
+ if (local) {
+ local_desc_ = description;
+ } else {
+ remote_desc_ = description;
+ }
+
+ RTCError error;
+ error = ValidateAndMaybeUpdateBundleGroups(local, type, description);
+ 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());
+ }
+ }
+ if (type == SdpType::kAnswer) {
+ transports_.CommitTransports();
+ bundles_.Commit();
+ }
+ return RTCError::OK();
+}
+
+RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups(
+ bool local,
+ SdpType type,
+ const cricket::SessionDescription* description) {
+ 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) {
+ 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, content_desc->cryptos(), 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 webrtc::RtpExtension* send_time_extension =
+ webrtc::RtpExtension::FindHeaderExtensionByUri(
+ content_desc->rtp_header_extensions(),
+ webrtc::RtpExtension::kAbsSendTimeUri,
+ config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions
+ ? webrtc::RtpExtension::kPreferEncryptedExtension
+ : webrtc::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();
+ }
+ const cricket::MediaContentDescription* content_desc =
+ content_info.media_description();
+ if (certificate_ && !content_desc->cryptos().empty()) {
+ return RTCError(RTCErrorType::INVALID_PARAMETER,
+ "SDES and DTLS-SRTP cannot be enabled at the same time.");
+ }
+
+ rtc::scoped_refptr<webrtc::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<webrtc::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 if (!content_desc->cryptos().empty()) {
+ sdes_transport = CreateSdesTransport(
+ content_info.name, rtp_dtls_transport.get(), rtcp_dtls_transport.get());
+ RTC_LOG(LS_INFO) << "Creating SdesTransport.";
+ } 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(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();
+ });
+
+ 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](webrtc::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.type() == cricket::PRFLX_PORT_TYPE) {
+ 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 webrtc::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