| /* | |
| * 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 <cstddef> | |
| #include <map> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/algorithm/container.h" | |
| #include "absl/functional/any_invocable.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/local_network_access_permission.h" | |
| #include "api/make_ref_counted.h" | |
| #include "api/peer_connection_interface.h" | |
| #include "api/rtc_error.h" | |
| #include "api/rtp_header_extension_id.h" | |
| #include "api/rtp_parameters.h" | |
| #include "api/scoped_refptr.h" | |
| #include "api/sequence_checker.h" | |
| #include "api/task_queue/pending_task_safety_flag.h" | |
| #include "api/task_queue/task_queue_base.h" | |
| #include "api/transport/data_channel_transport_interface.h" | |
| #include "api/transport/ecn_marking.h" | |
| #include "api/transport/enums.h" | |
| #include "api/units/timestamp.h" | |
| #include "media/sctp/sctp_transport_internal.h" | |
| #include "modules/rtp_rtcp/source/rtp_packet_received.h" | |
| #include "p2p/base/ice_transport_internal.h" | |
| #include "p2p/base/p2p_constants.h" | |
| #include "p2p/base/packet_transport_internal.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 "p2p/dtls/dtls_transport.h" | |
| #include "p2p/dtls/dtls_transport_internal.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/transport_stats.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/containers/flat_map.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" | |
| namespace webrtc { | |
| JsepTransportController::JsepTransportController( | |
| const Environment& env, | |
| TaskQueueBase* signaling_thread, | |
| Thread* network_thread, | |
| PortAllocator* port_allocator, | |
| AsyncDnsResolverFactoryInterface* async_dns_resolver_factory, | |
| LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory, | |
| Config config) | |
| : env_(env), | |
| signaling_thread_(signaling_thread), | |
| network_thread_(network_thread), | |
| port_allocator_(port_allocator), | |
| async_dns_resolver_factory_(async_dns_resolver_factory), | |
| lna_permission_factory_(lna_permission_factory), | |
| transports_( | |
| [this](absl::string_view mid, JsepTransport* transport) { | |
| return OnTransportChanged(mid, transport); | |
| }, | |
| [this]() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| UpdateAggregateStates_n(); | |
| }), | |
| config_(std::move(config)), | |
| bundles_(config_.bundle_policy), | |
| role_update_safety_flag_s_( | |
| PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true, | |
| signaling_thread_)), | |
| role_update_safety_flag_n_(role_update_safety_flag_s_) { | |
| RTC_DCHECK(signaling_thread_); | |
| RTC_DCHECK(network_thread_); | |
| RTC_DCHECK(port_allocator_); | |
| // 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_.signal_ice_candidates_gathered); | |
| RTC_DCHECK(config_.signal_ice_connection_state); | |
| RTC_DCHECK(config_.signal_connection_state); | |
| RTC_DCHECK(config_.signal_standardized_ice_connection_state); | |
| RTC_DCHECK(config_.signal_ice_gathering_state); | |
| RTC_DCHECK(config_.signal_ice_candidate_error); | |
| RTC_DCHECK(config_.signal_ice_candidates_removed); | |
| RTC_DCHECK(config_.signal_ice_candidate_pair_changed); | |
| } | |
| 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(); | |
| } | |
| absl::AnyInvocable<void() &&> JsepTransportController::MakeCloseTask() { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| role_update_safety_flag_s_->SetNotAlive(); | |
| return [this]() mutable { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| DestroyAllJsepTransports_n(); | |
| }; | |
| } | |
| RTCError JsepTransportController::SetLocalDescription( | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const SessionDescription* remote_desc) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| RTC_DCHECK(local_desc); | |
| TRACE_EVENT0("webrtc", "JsepTransportController::SetLocalDescription"); | |
| flat_map<std::string, TransportState> new_states; | |
| RTCError error = network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTCError err = SetLocalDescription_n(type, local_desc, remote_desc); | |
| new_states = GetTransportStates_n(); | |
| return err; | |
| }); | |
| transport_states_ = std::move(new_states); | |
| return error; | |
| } | |
| // RTC_RUN_ON(network_thread_) | |
| RTCError JsepTransportController::SetLocalDescription_n( | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const SessionDescription* remote_desc) { | |
| // ice_role_ is initialized to ICEROLE_CONTROLLING. Check if we still have the | |
| // initialized value and might need to set the role. For a non-offer, we'll | |
| // set the ice role to `controlled`. In the case where `type` is kOffer and | |
| // the ice_role_ is already 'controlling' we'll still call `SetRole_n()`. That | |
| // call will either will be a noop or initialization for the transports. | |
| if (ice_role_ == ICEROLE_CONTROLLING) { | |
| const IceRole role = | |
| (type == SdpType::kOffer) ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED; | |
| // Note that ApplyDescription_n() will always call DetermineIceRole() where | |
| // SetIceRole_n() will be called again such as in the case where the answer | |
| // side takes the controlling role. See "Section 5.1.1" below. | |
| SetIceRole_n(role); | |
| } | |
| return ApplyDescription_n(/*local=*/true, type, local_desc, remote_desc); | |
| } | |
| RTCError JsepTransportController::SetRemoteDescription( | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const SessionDescription* remote_desc) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| RTC_DCHECK(remote_desc); | |
| TRACE_EVENT0("webrtc", "JsepTransportController::SetRemoteDescription"); | |
| flat_map<std::string, TransportState> new_states; | |
| RTCError error = network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTCError err = SetRemoteDescription_n(type, local_desc, remote_desc); | |
| new_states = GetTransportStates_n(); | |
| return err; | |
| }); | |
| transport_states_ = std::move(new_states); | |
| return error; | |
| } | |
| RTCError JsepTransportController::SetRemoteDescription_n( | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const SessionDescription* remote_desc) { | |
| 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( | |
| absl::string_view mid) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| auto jsep_transport = GetJsepTransportForMid(mid); | |
| if (!jsep_transport) { | |
| return nullptr; | |
| } | |
| return jsep_transport->data_channel_transport(); | |
| } | |
| DtlsTransportInternal* JsepTransportController::GetDtlsTransport( | |
| absl::string_view mid) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| auto jsep_transport = GetJsepTransportForMid(mid); | |
| if (!jsep_transport) { | |
| return nullptr; | |
| } | |
| return jsep_transport->rtp_dtls_transport(); | |
| } | |
| scoped_refptr<DtlsTransport> | |
| JsepTransportController::LookupDtlsTransportByMid_n(absl::string_view mid) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| auto jsep_transport = GetJsepTransportForMid(mid); | |
| if (!jsep_transport) { | |
| return nullptr; | |
| } | |
| return jsep_transport->RtpDtlsTransport(); | |
| } | |
| scoped_refptr<DtlsTransport> JsepTransportController::LookupDtlsTransportByMid( | |
| absl::string_view mid) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| return network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return LookupDtlsTransportByMid_n(mid); | |
| }); | |
| } | |
| scoped_refptr<SctpTransport> JsepTransportController::GetSctpTransport( | |
| absl::string_view 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 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_); | |
| transports_.ForEachTransport( | |
| [](JsepTransport& t) { t.SetNeedsIceRestartFlag(); }); | |
| } | |
| bool JsepTransportController::NeedsIceRestart( | |
| absl::string_view transport_name) const { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| auto it = transport_states_.find(std::string(transport_name)); | |
| if (it != transport_states_.end()) { | |
| return it->second.needs_ice_restart; | |
| } | |
| return false; | |
| } | |
| std::optional<SSLRole> JsepTransportController::GetDtlsRole( | |
| absl::string_view mid) const { | |
| if (signaling_thread_->IsCurrent()) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| auto it = transport_states_.find(std::string(mid)); | |
| if (it != transport_states_.end()) { | |
| return it->second.dtls_role; | |
| } | |
| return std::nullopt; | |
| } | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| const JsepTransport* t = GetJsepTransportForMid(mid); | |
| if (!t) { | |
| return std::optional<SSLRole>(); | |
| } | |
| return t->GetDtlsRole(); | |
| } | |
| void JsepTransportController::SetTransportStates( | |
| flat_map<std::string, TransportState> states) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| transport_states_ = std::move(states); | |
| } | |
| bool JsepTransportController::SetLocalCertificate( | |
| const scoped_refptr<RTCCertificate>& certificate) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| return network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return SetLocalCertificate_n(certificate); | |
| }); | |
| } | |
| bool JsepTransportController::SetLocalCertificate_n( | |
| const scoped_refptr<RTCCertificate>& 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. | |
| transports_.ForEachTransport( | |
| [&](JsepTransport& t) { t.SetLocalCertificate(certificate_); }); | |
| for (auto& dtls : GetDtlsTransports()) { | |
| bool set_cert_success = dtls->SetLocalCertificate(certificate_); | |
| RTC_DCHECK(set_cert_success); | |
| } | |
| return true; | |
| } | |
| scoped_refptr<RTCCertificate> JsepTransportController::GetLocalCertificate( | |
| absl::string_view transport_name) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| const JsepTransport* t = GetJsepTransportByName(transport_name); | |
| if (!t) { | |
| return nullptr; | |
| } | |
| return t->GetLocalCertificate(); | |
| } | |
| std::unique_ptr<SSLCertChain> JsepTransportController::GetRemoteSSLCertChain( | |
| absl::string_view 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(); | |
| } | |
| std::vector<IceParameters> JsepTransportController::MaybeStartGathering() { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| return network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| MaybeStartGathering_n(); | |
| return port_allocator_->GetPooledIceCredentials(); | |
| }); | |
| } | |
| void JsepTransportController::MaybeStartGathering_n() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| for (auto& dtls : GetDtlsTransports()) { | |
| dtls->ice_transport()->MaybeStartGathering(); | |
| } | |
| } | |
| RTCError JsepTransportController::AddRemoteCandidates( | |
| absl::string_view transport_name, | |
| const 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); | |
| } | |
| bool JsepTransportController::RemoveRemoteCandidate(const IceCandidate* c) { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| return network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| return RemoveRemoteCandidate_n(c); | |
| }); | |
| } | |
| bool JsepTransportController::RemoveRemoteCandidate_n(const IceCandidate* c) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| std::string mid = c->sdp_mid(); | |
| if (!VerifyCandidate(c->candidate()).ok() || mid.empty()) { | |
| RTC_LOG(LS_ERROR) << "Candidate invalid or missing sdp_mid: " | |
| << c->candidate().ToSensitiveString(); | |
| return false; | |
| } | |
| JsepTransport* jsep_transport = GetJsepTransportForMid(mid); | |
| if (!jsep_transport) { | |
| RTC_LOG(LS_WARNING) << "No Transport for mid=" << mid; | |
| return false; | |
| } | |
| DtlsTransportInternal* dtls = | |
| c->candidate().component() == ICE_CANDIDATE_COMPONENT_RTP | |
| ? jsep_transport->rtp_dtls_transport() | |
| : jsep_transport->rtcp_dtls_transport(); | |
| if (dtls) { | |
| dtls->ice_transport()->RemoveRemoteCandidate(c->candidate()); | |
| } | |
| return true; | |
| } | |
| bool JsepTransportController::GetStats(absl::string_view transport_name, | |
| TransportStats* stats) const { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| const JsepTransport* transport = GetJsepTransportByName(transport_name); | |
| if (!transport) { | |
| return false; | |
| } | |
| return transport->GetStats(stats); | |
| } | |
| RTCError JsepTransportController::RollbackTransports() { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| scoped_refptr<PendingTaskSafetyFlag> new_flag = | |
| PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true, signaling_thread_); | |
| flat_map<std::string, TransportState> new_states; | |
| RTCError error = network_thread_->BlockingCall([&] { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| RTCError err = RollbackTransports_n(); | |
| new_states = GetTransportStates_n(); | |
| role_update_safety_flag_n_ = new_flag; | |
| return err; | |
| }); | |
| transport_states_ = std::move(new_states); | |
| role_update_safety_flag_s_->SetNotAlive(); | |
| role_update_safety_flag_s_ = std::move(new_flag); | |
| return error; | |
| } | |
| RTCError JsepTransportController::RollbackTransports_n() { | |
| bundles_.Rollback(); | |
| if (!transports_.RollbackTransports()) { | |
| return RTC_LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR) | |
| << "Failed to roll back transport state."); | |
| } | |
| return RTCError::OK(); | |
| } | |
| scoped_refptr<IceTransportInterface> | |
| JsepTransportController::CreateIceTransport(absl::string_view transport_name, | |
| bool rtcp) { | |
| int component = | |
| rtcp ? ICE_CANDIDATE_COMPONENT_RTCP : ICE_CANDIDATE_COMPONENT_RTP; | |
| IceTransportInit init(env_); | |
| init.set_port_allocator(port_allocator_); | |
| init.set_async_dns_resolver_factory(async_dns_resolver_factory_); | |
| init.set_lna_permission_factory(lna_permission_factory_); | |
| scoped_refptr<IceTransportInterface> transport = | |
| config_.ice_transport_factory->CreateIceTransport( | |
| std::string(transport_name), component, std::move(init)); | |
| // Ideally all of the below should be done as part of construction. That way | |
| // construction could happen on one thread and then safely be used on another | |
| // without there being an inherent race in the interface. | |
| IceTransportInternal* internal = transport->internal(); | |
| internal->AddGatheringStateCallback(this, | |
| [this](IceTransportInternal* transport) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportGatheringState_n(transport); | |
| }); | |
| internal->SubscribeCandidateGathered( | |
| this, | |
| [this](IceTransportInternal* transport, const Candidate& candidate) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportCandidateGathered_n(transport, candidate); | |
| }); | |
| internal->SetCandidateErrorCallback( | |
| [this](IceTransportInternal* transport, | |
| const IceCandidateErrorEvent& error) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportCandidateError_n(transport, error); | |
| }); | |
| internal->SetCandidatesRemovedCallback( | |
| [this](IceTransportInternal* transport, const Candidates& candidates) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportCandidatesRemoved_n(transport, candidates); | |
| }); | |
| internal->SubscribeRoleConflict(this, | |
| [this](IceTransportInternal* transport) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportRoleConflict_n(transport); | |
| }); | |
| internal->SubscribeIceTransportStateChanged( | |
| this, [this](IceTransportInternal* transport) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportStateChanged_n(transport); | |
| }); | |
| internal->SetCandidatePairChangeCallback( | |
| [this](const CandidatePairChangeEvent& event) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportCandidatePairChanged_n(event); | |
| }); | |
| internal->SetIceRole(ice_role_); | |
| internal->SetIceConfig(ice_config_); | |
| return transport; | |
| } | |
| std::unique_ptr<DtlsTransportInternal> | |
| JsepTransportController::CreateDtlsTransport(const ContentInfo& content_info, | |
| bool rtcp) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| scoped_refptr<IceTransportInterface> ice = | |
| CreateIceTransport(content_info.mid(), rtcp); | |
| std::unique_ptr<DtlsTransportInternal> dtls = | |
| config_.dtls_transport_factory | |
| ? config_.dtls_transport_factory->CreateDtlsTransport( | |
| std::move(ice), config_.crypto_options, config_.ssl_max_version) | |
| : std::make_unique<DtlsTransportInternalImpl>( | |
| env_, std::move(ice), config_.crypto_options, | |
| config_.ssl_max_version); | |
| // Ideally all of the below should be done as part of construction. That way | |
| // construction could happen on one thread and then safely be used on another | |
| // without there being an inherent race in the interface. | |
| 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->SubscribeWritableState(this, | |
| [this](PacketTransportInternal* transport) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportWritableState_n(transport); | |
| }); | |
| dtls->SubscribeReceivingState(this, | |
| [this](PacketTransportInternal* transport) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnTransportReceivingState_n(transport); | |
| }); | |
| dtls->SubscribeDtlsHandshakeError( | |
| this, [this](SSLHandshakeError error) { OnDtlsHandshakeError(error); }); | |
| dtls->SubscribeDtlsRoleChange( | |
| [this](DtlsTransportInternal* transport, SSLRole role) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnDtlsRoleChange_n(transport, role); | |
| }); | |
| return dtls; | |
| } | |
| std::unique_ptr<RtpTransport> | |
| JsepTransportController::CreateUnencryptedRtpTransport( | |
| absl::string_view transport_name, | |
| std::unique_ptr<PacketTransportInternal> rtp_packet_transport, | |
| std::unique_ptr<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->SetRtpPacketTransportOwned( | |
| std::move(rtp_packet_transport)); | |
| if (rtcp_packet_transport) { | |
| unencrypted_rtp_transport->SetRtcpPacketTransportOwned( | |
| std::move(rtcp_packet_transport)); | |
| } | |
| return unencrypted_rtp_transport; | |
| } | |
| std::unique_ptr<DtlsSrtpTransport> | |
| JsepTransportController::CreateDtlsSrtpTransport( | |
| absl::string_view transport_name, | |
| std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport, | |
| std::unique_ptr<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()); | |
| dtls_srtp_transport->SetDtlsTransportsOwned(std::move(rtp_dtls_transport), | |
| std::move(rtcp_dtls_transport)); | |
| // 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::unique_ptr<RtpTransport> JsepTransportController::CreateRtpTransport( | |
| absl::string_view transport_name, | |
| std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport, | |
| std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport) { | |
| std::unique_ptr<RtpTransport> rtp_transport; | |
| if (config_.disable_encryption) { | |
| RTC_LOG(LS_INFO) | |
| << "Creating UnencryptedRtpTransport, because encryption is disabled."; | |
| rtp_transport = CreateUnencryptedRtpTransport( | |
| transport_name, std::move(rtp_dtls_transport), | |
| std::move(rtcp_dtls_transport)); | |
| } else if (config_.rtp_transport_factory) { | |
| RTC_LOG(LS_INFO) << "Creating RtpTransport from injected factory."; | |
| rtp_transport = config_.rtp_transport_factory->CreateRtpTransport( | |
| transport_name, std::move(rtp_dtls_transport), | |
| std::move(rtcp_dtls_transport)); | |
| } else { | |
| RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport."; | |
| rtp_transport = | |
| CreateDtlsSrtpTransport(transport_name, std::move(rtp_dtls_transport), | |
| std::move(rtcp_dtls_transport)); | |
| } | |
| rtp_transport->SubscribeRtcpPacketReceived( | |
| this, [this](CopyOnWriteBuffer packet, | |
| std::optional<Timestamp> arrival_time, EcnMarking ecn) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnRtcpPacketReceived_n(std::move(packet), arrival_time, ecn); | |
| }); | |
| rtp_transport->SetUnDemuxableRtpPacketReceivedHandler( | |
| [this](RtpPacketReceived& packet) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| OnUnDemuxableRtpPacketReceived_n(packet); | |
| }); | |
| return rtp_transport; | |
| } | |
| std::vector<DtlsTransportInternal*> | |
| JsepTransportController::GetDtlsTransports() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| std::vector<DtlsTransportInternal*> dtls_transports; | |
| transports_.ForEachTransport([&](JsepTransport& t) { | |
| if (t.rtp_dtls_transport()) { | |
| dtls_transports.push_back(t.rtp_dtls_transport()); | |
| } | |
| if (t.rtcp_dtls_transport()) { | |
| dtls_transports.push_back(t.rtcp_dtls_transport()); | |
| } | |
| }); | |
| return dtls_transports; | |
| } | |
| std::vector<DtlsTransportInternal*> | |
| JsepTransportController::GetActiveDtlsTransports() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| std::vector<DtlsTransportInternal*> dtls_transports; | |
| transports_.ForEachActiveTransport([&](JsepTransport& t) { | |
| if (t.rtp_dtls_transport()) { | |
| dtls_transports.push_back(t.rtp_dtls_transport()); | |
| } | |
| if (t.rtcp_dtls_transport()) { | |
| dtls_transports.push_back(t.rtcp_dtls_transport()); | |
| } | |
| }); | |
| return dtls_transports; | |
| } | |
| RTCError JsepTransportController::ApplyDescription_n( | |
| bool local, | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const 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 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 ContentGroup*, std::vector<RtpHeaderExtensionId>> | |
| merged_encrypted_extension_ids_by_bundle; | |
| if (!bundles_.bundle_groups().empty()) { | |
| merged_encrypted_extension_ids_by_bundle = | |
| MergeEncryptedHeaderExtensionIdsForBundles(description); | |
| } | |
| for (const 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.mid())) { | |
| 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 ContentInfo& content_info = description->contents()[i]; | |
| const 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 ContentGroup* established_bundle_group = | |
| bundles_.LookupGroupByMid(content_info.mid()); | |
| // 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.mid() != *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.mid() + "'."); | |
| } | |
| } | |
| JsepTransport* transport = GetJsepTransportForMid(content_info.mid()); | |
| if (!transport) { | |
| return RTC_LOG_ERROR( | |
| RTCError(RTCErrorType::INVALID_PARAMETER) | |
| << "Could not find transport for m= section with mid='" | |
| << content_info.mid() << "'"); | |
| } | |
| if (established_bundle_group && | |
| content_info.mid() != *established_bundle_group->FirstContentName()) { | |
| continue; // Skip code below. | |
| } | |
| // The code below this is only executed for contents that are the | |
| // leader of their bundle group, or is unbundled. | |
| error = ValidateContent(content_info); | |
| if (!error.ok()) { | |
| return error; | |
| } | |
| std::vector<RtpHeaderExtensionId> extension_ids; | |
| if (established_bundle_group) { | |
| // If bundled: Check that this is BUNDLE-tagged (first in the group). | |
| RTC_DCHECK(content_info.mid() == | |
| *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); | |
| } | |
| SetIceRole_n(DetermineIceRole(transport, transport_info, type, local)); | |
| JsepTransportDescription jsep_description = CreateJsepTransportDescription( | |
| content_info, transport_info, extension_ids); | |
| jsep_description.transport_desc.cryptex = | |
| content_info.media_description()->cryptex(); | |
| if (local) { | |
| error = | |
| transport->SetLocalJsepTransportDescription(jsep_description, type); | |
| } else { | |
| error = | |
| transport->SetRemoteJsepTransportDescription(jsep_description, type); | |
| } | |
| if (!error.ok()) { | |
| return RTC_LOG_ERROR( | |
| RTCError(RTCErrorType::INVALID_PARAMETER) | |
| << "Failed to apply the description for m= section with mid='" | |
| << content_info.mid() << "': " << error.message()); | |
| } | |
| } | |
| if (type == SdpType::kAnswer) { | |
| transports_.CommitTransports(); | |
| bundles_.Commit(); | |
| } | |
| return RTCError::OK(); | |
| } | |
| flat_map<std::string, JsepTransportController::TransportState> | |
| JsepTransportController::GetTransportStates_n() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| flat_map<std::string, TransportState> states; | |
| transports_.ForEachTransport([&](JsepTransport& t) { | |
| std::optional<SSLRole> role = t.GetDtlsRole(); | |
| bool needs_ice_restart = t.needs_ice_restart(); | |
| TransportState state; | |
| state.dtls_role = role; | |
| state.needs_ice_restart = needs_ice_restart; | |
| bool bundled = false; | |
| for (const std::unique_ptr<ContentGroup>& bundle_group : | |
| bundles_.bundle_groups()) { | |
| const std::string* first_mid = bundle_group->FirstContentName(); | |
| if (first_mid && *first_mid == t.name()) { | |
| bundled = true; | |
| for (const std::string& mid : bundle_group->content_names()) { | |
| states[mid] = state; | |
| } | |
| break; | |
| } | |
| } | |
| if (!bundled) { | |
| states[std::string(t.name())] = state; | |
| } | |
| }); | |
| return states; | |
| } | |
| RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups( | |
| bool local, | |
| SdpType type, | |
| const SessionDescription* local_desc, | |
| const SessionDescription* remote_desc) { | |
| const SessionDescription* description = local ? local_desc : remote_desc; | |
| RTC_DCHECK(description); | |
| std::vector<const ContentGroup*> new_bundle_groups = | |
| description->GetGroupsByName(GROUP_TYPE_BUNDLE); | |
| // Verify `new_bundle_groups`. | |
| std::map<std::string, const ContentGroup*> new_bundle_groups_by_mid; | |
| for (const 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 ContentGroup*, const ContentGroup*> | |
| new_bundle_groups_by_existing_bundle_groups; | |
| std::map<const ContentGroup*, const ContentGroup*> | |
| existing_bundle_groups_by_new_bundle_groups; | |
| for (const ContentGroup* new_bundle_group : new_bundle_groups) { | |
| for (const std::string& mid : new_bundle_group->content_names()) { | |
| 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 ContentGroup*> offered_bundle_groups = | |
| local ? remote_desc->GetGroupsByName(GROUP_TYPE_BUNDLE) | |
| : local_desc->GetGroupsByName(GROUP_TYPE_BUNDLE); | |
| std::map<std::string, const ContentGroup*> offered_bundle_groups_by_mid; | |
| for (const 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 ContentGroup*, const ContentGroup*> | |
| new_bundle_groups_by_offered_bundle_groups; | |
| for (const 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 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(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) { | |
| return RTCError(RTCErrorType::INVALID_PARAMETER, | |
| "A BUNDLE group contains a MID='" + content_name + | |
| "' matching no m= section."); | |
| } | |
| 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 ContentInfo& content_info) { | |
| if (config_.rtcp_mux_policy == | |
| PeerConnectionInterface::kRtcpMuxPolicyRequire && | |
| content_info.type == MediaProtocolType::kRtp && | |
| !content_info.bundle_only && | |
| !content_info.media_description()->rtcp_mux()) { | |
| return RTCError(RTCErrorType::INVALID_PARAMETER, | |
| "The m= section with mid='" + content_info.mid() + | |
| "' is invalid. RTCP-MUX is not " | |
| "enabled when it is required."); | |
| } | |
| return RTCError::OK(); | |
| } | |
| void JsepTransportController::HandleRejectedContent( | |
| const ContentInfo& content_info) { | |
| // If the content is rejected, let the | |
| // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first, | |
| // then destroy the JsepTransport. | |
| ContentGroup* bundle_group = bundles_.LookupGroupByMid(content_info.mid()); | |
| if (bundle_group && !bundle_group->content_names().empty() && | |
| content_info.mid() == *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.mid()); | |
| if (bundle_group) { | |
| // Remove the rejected content from the `bundle_group`. | |
| bundles_.DeleteMid(bundle_group, content_info.mid()); | |
| } | |
| } | |
| } | |
| bool JsepTransportController::HandleBundledContent( | |
| const ContentInfo& content_info, | |
| const 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 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.mid(), jsep_transport); | |
| } | |
| JsepTransportDescription | |
| JsepTransportController::CreateJsepTransportDescription( | |
| const ContentInfo& content_info, | |
| const TransportInfo& transport_info, | |
| const std::vector<RtpHeaderExtensionId>& encrypted_extension_ids) { | |
| TRACE_EVENT0("webrtc", | |
| "JsepTransportController::CreateJsepTransportDescription"); | |
| const MediaContentDescription* content_desc = | |
| content_info.media_description(); | |
| RTC_DCHECK(content_desc); | |
| bool rtcp_mux_enabled = content_info.type == MediaProtocolType::kSctp | |
| ? true | |
| : content_desc->rtcp_mux(); | |
| return JsepTransportDescription(rtcp_mux_enabled, encrypted_extension_ids, | |
| transport_info.description); | |
| } | |
| std::vector<RtpHeaderExtensionId> | |
| JsepTransportController::GetEncryptedHeaderExtensionIds( | |
| const ContentInfo& content_info) { | |
| const MediaContentDescription* content_desc = | |
| content_info.media_description(); | |
| if (!config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions) { | |
| return std::vector<RtpHeaderExtensionId>(); | |
| } | |
| std::vector<RtpHeaderExtensionId> 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 ContentGroup*, std::vector<RtpHeaderExtensionId>> | |
| JsepTransportController::MergeEncryptedHeaderExtensionIdsForBundles( | |
| const SessionDescription* description) { | |
| RTC_DCHECK(description); | |
| RTC_DCHECK(!bundles_.bundle_groups().empty()); | |
| std::map<const ContentGroup*, std::vector<RtpHeaderExtensionId>> | |
| merged_encrypted_extension_ids_by_bundle; | |
| // Union the encrypted header IDs in the group when bundle is enabled. | |
| for (const ContentInfo& content_info : description->contents()) { | |
| auto group = bundles_.LookupGroupByMid(content_info.mid()); | |
| if (!group) | |
| continue; | |
| // Get or create list of IDs for the BUNDLE group. | |
| std::vector<RtpHeaderExtensionId>& merged_ids = | |
| merged_encrypted_extension_ids_by_bundle[group]; | |
| // Add IDs not already in the list. | |
| std::vector<RtpHeaderExtensionId> extension_ids = | |
| GetEncryptedHeaderExtensionIds(content_info); | |
| for (RtpHeaderExtensionId id : extension_ids) { | |
| if (!absl::c_linear_search(merged_ids, id)) { | |
| merged_ids.push_back(id); | |
| } | |
| } | |
| } | |
| return merged_encrypted_extension_ids_by_bundle; | |
| } | |
| const JsepTransport* JsepTransportController::GetJsepTransportForMid( | |
| absl::string_view mid) const { | |
| return transports_.GetTransportForMid(mid); | |
| } | |
| JsepTransport* JsepTransportController::GetJsepTransportForMid( | |
| absl::string_view mid) { | |
| return transports_.GetTransportForMid(mid); | |
| } | |
| const JsepTransport* JsepTransportController::GetJsepTransportByName( | |
| absl::string_view transport_name) const { | |
| return transports_.GetTransportByName(transport_name); | |
| } | |
| JsepTransport* JsepTransportController::GetJsepTransportByName( | |
| absl::string_view transport_name) { | |
| return transports_.GetTransportByName(transport_name); | |
| } | |
| RTCError JsepTransportController::MaybeCreateJsepTransport( | |
| bool local, | |
| const ContentInfo& content_info, | |
| const SessionDescription& description) { | |
| JsepTransport* transport = GetJsepTransportByName(content_info.mid()); | |
| if (transport) { | |
| return RTCError::OK(); | |
| } | |
| std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport = | |
| CreateDtlsTransport(content_info, /*rtcp=*/false); | |
| DtlsTransportInternal* rtp_dtls_transport_ptr = rtp_dtls_transport.get(); | |
| std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport; | |
| if (config_.rtcp_mux_policy != | |
| PeerConnectionInterface::kRtcpMuxPolicyRequire && | |
| content_info.type == MediaProtocolType::kRtp) { | |
| rtcp_dtls_transport = CreateDtlsTransport(content_info, /*rtcp=*/true); | |
| } | |
| std::unique_ptr<RtpTransport> rtp_transport = | |
| CreateRtpTransport(content_info.mid(), std::move(rtp_dtls_transport), | |
| std::move(rtcp_dtls_transport)); | |
| std::unique_ptr<SctpTransportInternal> sctp_transport; | |
| if (config_.sctp_factory) { | |
| sctp_transport = | |
| config_.sctp_factory->CreateSctpTransport(env_, rtp_dtls_transport_ptr); | |
| } | |
| scoped_refptr<DtlsTransport> dtls_transport = | |
| make_ref_counted<DtlsTransport>(rtp_dtls_transport_ptr); | |
| std::unique_ptr<JsepTransport> jsep_transport = | |
| std::make_unique<JsepTransport>( | |
| certificate_, std::move(rtp_transport), std::move(dtls_transport), | |
| std::move(sctp_transport), | |
| [&]() { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| UpdateAggregateStates_n(); | |
| }, | |
| config_.crypto_options.srtp.cryptex_policy); | |
| // Object hierarchy for objects injected into JsepTransport: | |
| // | |
| // rtp_transport | |
| // -> dtls_srtp_transport / unencrypted_rtp_transport | |
| // -> rtp_dtls_transport | |
| // -> ice->internal() <- ownership | |
| // -> rtcp_dtls_transport | |
| // -> ice->internal() <- ownership (rtcp_ice) | |
| // -> sctp_transport | |
| // -> rtp_dtls_transport <- raw pointer. | |
| // dtls_transport (reference counted) | |
| // -> rtp_dtls_transport <- raw pointer. | |
| // | |
| // TODO(tommi): At this point `jsep_transport` has been constructed | |
| // and initialized with callbacks subscribed to. Write and call | |
| // a new method, e.g. jsep_transport->InitializationDone(). | |
| // At that point, all the transport objects should detach any sequence | |
| // checkers and be ready for use on a different TQ. | |
| // | |
| // Since there are still raw pointers handed to some of the transports, | |
| // and since `dtls_transport` is reference counted, teardown then needs | |
| // to be explicit as it currently is in ~JsepTransport() where Clear() is | |
| // called. | |
| transports_.RegisterTransport(std::move(jsep_transport)); | |
| UpdateAggregateStates_n(); | |
| return RTCError::OK(); | |
| } | |
| void JsepTransportController::DestroyAllJsepTransports_n() { | |
| transports_.DestroyAllTransports(); | |
| } | |
| void JsepTransportController::SetIceRole_n(IceRole ice_role) { | |
| ice_role_ = ice_role; | |
| auto dtls_transports = GetDtlsTransports(); | |
| for (auto& dtls : dtls_transports) { | |
| dtls->ice_transport()->SetIceRole(ice_role_); | |
| } | |
| } | |
| IceRole JsepTransportController::DetermineIceRole( | |
| JsepTransport* jsep_transport, | |
| const TransportInfo& transport_info, | |
| SdpType type, | |
| bool local) { | |
| 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 == | |
| ICEMODE_LITE && | |
| ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_FULL) { | |
| ice_role = ICEROLE_CONTROLLING; | |
| } | |
| } else { | |
| // If our role is ICEROLE_CONTROLLED and the remote endpoint | |
| // supports only ice_lite, this local endpoint should take the CONTROLLING | |
| // role. | |
| // TODO(deadbeef): This is a session-level attribute, so it really shouldn't | |
| // be in a TransportDescription in the first place... | |
| if (ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_LITE) { | |
| ice_role = 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 == | |
| ICEMODE_LITE && | |
| ice_role_ == ICEROLE_CONTROLLING && tdesc.ice_mode == ICEMODE_FULL) { | |
| ice_role = ICEROLE_CONTROLLED; | |
| } | |
| } | |
| return ice_role; | |
| } | |
| void JsepTransportController::OnTransportWritableState_n( | |
| PacketTransportInternal* transport) { | |
| RTC_LOG(LS_INFO) << " Transport " << transport->transport_name() | |
| << " writability changed to " << transport->writable() | |
| << "."; | |
| UpdateAggregateStates_n(); | |
| } | |
| void JsepTransportController::OnTransportReceivingState_n( | |
| PacketTransportInternal* transport) { | |
| UpdateAggregateStates_n(); | |
| } | |
| void JsepTransportController::OnTransportGatheringState_n( | |
| IceTransportInternal* transport) { | |
| UpdateAggregateStates_n(); | |
| } | |
| void JsepTransportController::OnTransportCandidateGathered_n( | |
| IceTransportInternal* transport, | |
| const Candidate& candidate) { | |
| // We should never signal peer-reflexive candidates. | |
| if (candidate.is_prflx()) { | |
| RTC_DCHECK_NOTREACHED(); | |
| return; | |
| } | |
| config_.signal_ice_candidates_gathered(transport->transport_name(), | |
| std::vector<Candidate>{candidate}); | |
| } | |
| void JsepTransportController::OnTransportCandidateError_n( | |
| IceTransportInternal* transport, | |
| const IceCandidateErrorEvent& event) { | |
| config_.signal_ice_candidate_error(event); | |
| } | |
| void JsepTransportController::OnTransportCandidatesRemoved_n( | |
| IceTransportInternal* transport, | |
| const Candidates& candidates) { | |
| config_.signal_ice_candidates_removed(transport, candidates); | |
| } | |
| void JsepTransportController::OnTransportCandidatePairChanged_n( | |
| const CandidatePairChangeEvent& event) { | |
| RTC_DCHECK(!event.transport_name.empty()); | |
| config_.signal_ice_candidate_pair_changed(event); | |
| } | |
| void JsepTransportController::OnTransportRoleConflict_n( | |
| 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. | |
| IceRole reversed_role = (ice_role_ == ICEROLE_CONTROLLING) | |
| ? ICEROLE_CONTROLLED | |
| : ICEROLE_CONTROLLING; | |
| RTC_LOG(LS_INFO) << "Got role conflict; switching to " | |
| << (reversed_role == ICEROLE_CONTROLLING ? "controlling" | |
| : "controlled") | |
| << " role."; | |
| SetIceRole_n(reversed_role); | |
| } | |
| void JsepTransportController::OnTransportStateChanged_n( | |
| 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(); | |
| IceConnectionState new_connection_state = kIceConnectionConnecting; | |
| PeerConnectionInterface::IceConnectionState new_ice_connection_state = | |
| PeerConnectionInterface::IceConnectionState::kIceConnectionNew; | |
| PeerConnectionInterface::PeerConnectionState new_combined_state = | |
| PeerConnectionInterface::PeerConnectionState::kNew; | |
| IceGatheringState new_gathering_state = 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() == | |
| IceTransportStateInternal::STATE_FAILED; | |
| all_connected = all_connected && dtls->writable(); | |
| all_completed = | |
| all_completed && dtls->writable() && | |
| dtls->ice_transport()->GetState() == | |
| IceTransportStateInternal::STATE_COMPLETED && | |
| dtls->ice_transport()->GetIceRole() == ICEROLE_CONTROLLING && | |
| dtls->ice_transport()->gathering_state() == kIceGatheringComplete; | |
| any_gathering = any_gathering || dtls->ice_transport()->gathering_state() != | |
| kIceGatheringNew; | |
| all_done_gathering = | |
| all_done_gathering && | |
| dtls->ice_transport()->gathering_state() == kIceGatheringComplete; | |
| dtls_state_counts[dtls->dtls_state()]++; | |
| ice_state_counts[dtls->ice_transport()->GetIceTransportState()]++; | |
| } | |
| if (any_failed) { | |
| new_connection_state = kIceConnectionFailed; | |
| } else if (all_completed) { | |
| new_connection_state = kIceConnectionCompleted; | |
| } else if (all_connected) { | |
| new_connection_state = kIceConnectionConnected; | |
| } | |
| if (ice_connection_state_ != new_connection_state) { | |
| ice_connection_state_ = new_connection_state; | |
| config_.signal_ice_connection_state(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. | |
| config_.signal_standardized_ice_connection_state( | |
| PeerConnectionInterface::kIceConnectionConnected); | |
| } | |
| standardized_ice_connection_state_ = new_ice_connection_state; | |
| config_.signal_standardized_ice_connection_state(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; | |
| config_.signal_connection_state(new_combined_state); | |
| } | |
| // Compute the gathering state. | |
| if (dtls_transports.empty()) { | |
| new_gathering_state = kIceGatheringNew; | |
| } else if (all_done_gathering) { | |
| new_gathering_state = kIceGatheringComplete; | |
| } else if (any_gathering) { | |
| new_gathering_state = kIceGatheringGathering; | |
| } | |
| if (ice_gathering_state_ != new_gathering_state) { | |
| ice_gathering_state_ = new_gathering_state; | |
| config_.signal_ice_gathering_state(new_gathering_state); | |
| } | |
| } | |
| void JsepTransportController::OnRtcpPacketReceived_n( | |
| CopyOnWriteBuffer packet, | |
| std::optional<Timestamp> arrival_time, | |
| EcnMarking ecn) { | |
| RTC_DCHECK(config_.rtcp_handler); | |
| config_.rtcp_handler(std::move(packet), | |
| arrival_time.has_value() ? arrival_time->us() : -1); | |
| } | |
| void JsepTransportController::OnUnDemuxableRtpPacketReceived_n( | |
| const RtpPacketReceived& packet) { | |
| RTC_DCHECK(config_.un_demuxable_packet_handler); | |
| config_.un_demuxable_packet_handler(packet); | |
| } | |
| void JsepTransportController::OnDtlsRoleChange_n( | |
| DtlsTransportInternal* transport, | |
| SSLRole role) { | |
| RTC_DCHECK_RUN_ON(network_thread_); | |
| flat_map<std::string, TransportState> new_states = GetTransportStates_n(); | |
| signaling_thread_->PostTask( | |
| SafeTask(role_update_safety_flag_n_, | |
| [this, new_states = std::move(new_states)]() mutable { | |
| RTC_DCHECK_RUN_ON(signaling_thread_); | |
| transport_states_ = std::move(new_states); | |
| })); | |
| } | |
| void JsepTransportController::OnDtlsHandshakeError(SSLHandshakeError error) { | |
| config_.on_dtls_handshake_error(error); | |
| } | |
| bool JsepTransportController::OnTransportChanged( | |
| absl::string_view mid, | |
| 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 |