|  | /* | 
|  | *  Copyright 2012 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/peer_connection.h" | 
|  |  | 
|  | #include <limits.h> | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <cstdint> | 
|  | #include <functional> | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/algorithm/container.h" | 
|  | #include "absl/strings/match.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "api/adaptation/resource.h" | 
|  | #include "api/audio/audio_device.h" | 
|  | #include "api/candidate.h" | 
|  | #include "api/crypto/crypto_options.h" | 
|  | #include "api/data_channel_interface.h" | 
|  | #include "api/dtls_transport_interface.h" | 
|  | #include "api/environment/environment.h" | 
|  | #include "api/jsep.h" | 
|  | #include "api/jsep_ice_candidate.h" | 
|  | #include "api/make_ref_counted.h" | 
|  | #include "api/media_stream_interface.h" | 
|  | #include "api/media_types.h" | 
|  | #include "api/peer_connection_interface.h" | 
|  | #include "api/rtc_error.h" | 
|  | #include "api/rtc_event_log_output.h" | 
|  | #include "api/rtp_parameters.h" | 
|  | #include "api/rtp_receiver_interface.h" | 
|  | #include "api/rtp_sender_interface.h" | 
|  | #include "api/rtp_transceiver_direction.h" | 
|  | #include "api/rtp_transceiver_interface.h" | 
|  | #include "api/scoped_refptr.h" | 
|  | #include "api/sctp_transport_interface.h" | 
|  | #include "api/sequence_checker.h" | 
|  | #include "api/set_local_description_observer_interface.h" | 
|  | #include "api/set_remote_description_observer_interface.h" | 
|  | #include "api/stats/rtc_stats_collector_callback.h" | 
|  | #include "api/task_queue/pending_task_safety_flag.h" | 
|  | #include "api/transport/bandwidth_estimation_settings.h" | 
|  | #include "api/transport/bitrate_settings.h" | 
|  | #include "api/transport/data_channel_transport_interface.h" | 
|  | #include "api/transport/enums.h" | 
|  | #include "api/turn_customizer.h" | 
|  | #include "api/uma_metrics.h" | 
|  | #include "api/units/time_delta.h" | 
|  | #include "api/video/video_codec_constants.h" | 
|  | #include "call/audio_state.h" | 
|  | #include "call/packet_receiver.h" | 
|  | #include "call/payload_type.h" | 
|  | #include "media/base/codec.h" | 
|  | #include "media/base/media_config.h" | 
|  | #include "media/base/media_engine.h" | 
|  | #include "media/base/rid_description.h" | 
|  | #include "media/base/stream_params.h" | 
|  | #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" | 
|  | #include "modules/rtp_rtcp/source/rtp_packet_received.h" | 
|  | #include "p2p/base/connection_info.h" | 
|  | #include "p2p/base/ice_transport_internal.h" | 
|  | #include "p2p/base/p2p_constants.h" | 
|  | #include "p2p/base/p2p_transport_channel.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_internal.h" | 
|  | #include "pc/channel_interface.h" | 
|  | #include "pc/codec_vendor.h" | 
|  | #include "pc/connection_context.h" | 
|  | #include "pc/data_channel_utils.h" | 
|  | #include "pc/dtls_transport.h" | 
|  | #include "pc/ice_server_parsing.h" | 
|  | #include "pc/jsep_transport_controller.h" | 
|  | #include "pc/legacy_stats_collector.h" | 
|  | #include "pc/rtc_stats_collector.h" | 
|  | #include "pc/rtp_receiver.h" | 
|  | #include "pc/rtp_receiver_proxy.h" | 
|  | #include "pc/rtp_sender.h" | 
|  | #include "pc/rtp_sender_proxy.h" | 
|  | #include "pc/rtp_transceiver.h" | 
|  | #include "pc/rtp_transmission_manager.h" | 
|  | #include "pc/rtp_transport_internal.h" | 
|  | #include "pc/sctp_data_channel.h" | 
|  | #include "pc/sctp_transport.h" | 
|  | #include "pc/sdp_offer_answer.h" | 
|  | #include "pc/session_description.h" | 
|  | #include "pc/simulcast_description.h" | 
|  | #include "pc/transceiver_list.h" | 
|  | #include "pc/transport_stats.h" | 
|  | #include "pc/usage_pattern.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/copy_on_write_buffer.h" | 
|  | #include "rtc_base/crypto_random.h" | 
|  | #include "rtc_base/ip_address.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/net_helper.h" | 
|  | #include "rtc_base/net_helpers.h" | 
|  | #include "rtc_base/network.h" | 
|  | #include "rtc_base/network_constants.h" | 
|  | #include "rtc_base/rtc_certificate.h" | 
|  | #include "rtc_base/socket_address.h" | 
|  | #include "rtc_base/ssl_certificate.h" | 
|  | #include "rtc_base/ssl_stream_adapter.h" | 
|  | #include "rtc_base/string_encode.h" | 
|  | #include "rtc_base/thread.h" | 
|  | #include "rtc_base/trace_event.h" | 
|  | #include "rtc_base/unique_id_generator.h" | 
|  | #include "system_wrappers/include/metrics.h" | 
|  |  | 
|  | using cricket::MediaContentDescription; | 
|  | using cricket::RidDescription; | 
|  | using cricket::RidDirection; | 
|  | using cricket::SimulcastDescription; | 
|  | using cricket::SimulcastLayer; | 
|  | using cricket::SimulcastLayerList; | 
|  | using cricket::StreamParams; | 
|  | using cricket::TransportInfo; | 
|  | using ::webrtc::ContentInfo; | 
|  | using ::webrtc::ContentInfos; | 
|  | using ::webrtc::MediaProtocolType; | 
|  | using ::webrtc::SessionDescription; | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | namespace { | 
|  | static const int REPORT_USAGE_PATTERN_DELAY_MS = 60000; | 
|  |  | 
|  | class CodecLookupHelperForPeerConnection : public cricket::CodecLookupHelper { | 
|  | public: | 
|  | explicit CodecLookupHelperForPeerConnection(PeerConnection* self) | 
|  | : self_(self) {} | 
|  |  | 
|  | webrtc::PayloadTypeSuggester* PayloadTypeSuggester() override { | 
|  | return self_->transport_controller_s(); | 
|  | } | 
|  |  | 
|  | cricket::CodecVendor* CodecVendor(const std::string& mid) override { | 
|  | if (codec_vendors_.count(mid) == 0) { | 
|  | codec_vendors_.emplace( | 
|  | mid, cricket::CodecVendor{self_->context()->media_engine(), | 
|  | self_->context()->use_rtx(), | 
|  | self_->context()->env().field_trials()}); | 
|  | } | 
|  | return &codec_vendors_.at(mid); | 
|  | } | 
|  |  | 
|  | private: | 
|  | PeerConnection* self_; | 
|  | std::map<std::string, cricket::CodecVendor> codec_vendors_; | 
|  | }; | 
|  |  | 
|  | uint32_t ConvertIceTransportTypeToCandidateFilter( | 
|  | PeerConnectionInterface::IceTransportsType type) { | 
|  | switch (type) { | 
|  | case PeerConnectionInterface::kNone: | 
|  | return CF_NONE; | 
|  | case PeerConnectionInterface::kRelay: | 
|  | return CF_RELAY; | 
|  | case PeerConnectionInterface::kNoHost: | 
|  | return (CF_ALL & ~CF_HOST); | 
|  | case PeerConnectionInterface::kAll: | 
|  | return CF_ALL; | 
|  | default: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | } | 
|  | return CF_NONE; | 
|  | } | 
|  |  | 
|  | IceCandidatePairType GetIceCandidatePairCounter(const Candidate& local, | 
|  | const Candidate& remote) { | 
|  | if (local.is_local() && remote.is_local()) { | 
|  | bool local_hostname = | 
|  | !local.address().hostname().empty() && local.address().IsUnresolvedIP(); | 
|  | bool remote_hostname = !remote.address().hostname().empty() && | 
|  | remote.address().IsUnresolvedIP(); | 
|  | bool local_private = IPIsPrivate(local.address().ipaddr()); | 
|  | bool remote_private = IPIsPrivate(remote.address().ipaddr()); | 
|  | if (local_hostname) { | 
|  | if (remote_hostname) { | 
|  | return kIceCandidatePairHostNameHostName; | 
|  | } else if (remote_private) { | 
|  | return kIceCandidatePairHostNameHostPrivate; | 
|  | } else { | 
|  | return kIceCandidatePairHostNameHostPublic; | 
|  | } | 
|  | } else if (local_private) { | 
|  | if (remote_hostname) { | 
|  | return kIceCandidatePairHostPrivateHostName; | 
|  | } else if (remote_private) { | 
|  | return kIceCandidatePairHostPrivateHostPrivate; | 
|  | } else { | 
|  | return kIceCandidatePairHostPrivateHostPublic; | 
|  | } | 
|  | } else { | 
|  | if (remote_hostname) { | 
|  | return kIceCandidatePairHostPublicHostName; | 
|  | } else if (remote_private) { | 
|  | return kIceCandidatePairHostPublicHostPrivate; | 
|  | } else { | 
|  | return kIceCandidatePairHostPublicHostPublic; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (local.is_local()) { | 
|  | if (remote.is_stun()) | 
|  | return kIceCandidatePairHostSrflx; | 
|  | if (remote.is_relay()) | 
|  | return kIceCandidatePairHostRelay; | 
|  | if (remote.is_prflx()) | 
|  | return kIceCandidatePairHostPrflx; | 
|  | } else if (local.is_stun()) { | 
|  | if (remote.is_local()) | 
|  | return kIceCandidatePairSrflxHost; | 
|  | if (remote.is_stun()) | 
|  | return kIceCandidatePairSrflxSrflx; | 
|  | if (remote.is_relay()) | 
|  | return kIceCandidatePairSrflxRelay; | 
|  | if (remote.is_prflx()) | 
|  | return kIceCandidatePairSrflxPrflx; | 
|  | } else if (local.is_relay()) { | 
|  | if (remote.is_local()) | 
|  | return kIceCandidatePairRelayHost; | 
|  | if (remote.is_stun()) | 
|  | return kIceCandidatePairRelaySrflx; | 
|  | if (remote.is_relay()) | 
|  | return kIceCandidatePairRelayRelay; | 
|  | if (remote.is_prflx()) | 
|  | return kIceCandidatePairRelayPrflx; | 
|  | } else if (local.is_prflx()) { | 
|  | if (remote.is_local()) | 
|  | return kIceCandidatePairPrflxHost; | 
|  | if (remote.is_stun()) | 
|  | return kIceCandidatePairPrflxSrflx; | 
|  | if (remote.is_relay()) | 
|  | return kIceCandidatePairPrflxRelay; | 
|  | } | 
|  |  | 
|  | return kIceCandidatePairMax; | 
|  | } | 
|  |  | 
|  | // Check if the changes of IceTransportsType motives an ice restart. | 
|  | bool NeedIceRestart(bool surface_ice_candidates_on_ice_transport_type_changed, | 
|  | PeerConnectionInterface::IceTransportsType current, | 
|  | PeerConnectionInterface::IceTransportsType modified) { | 
|  | if (current == modified) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!surface_ice_candidates_on_ice_transport_type_changed) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | auto current_filter = ConvertIceTransportTypeToCandidateFilter(current); | 
|  | auto modified_filter = ConvertIceTransportTypeToCandidateFilter(modified); | 
|  |  | 
|  | // If surface_ice_candidates_on_ice_transport_type_changed is true and we | 
|  | // extend the filter, then no ice restart is needed. | 
|  | return (current_filter & modified_filter) != current_filter; | 
|  | } | 
|  |  | 
|  | // Checks for valid pool size range and if a previous value has already been | 
|  | // set, which is done via SetLocalDescription. | 
|  | RTCError ValidateIceCandidatePoolSize( | 
|  | int ice_candidate_pool_size, | 
|  | std::optional<int> previous_ice_candidate_pool_size) { | 
|  | // Note that this isn't possible through chromium, since it's an unsigned | 
|  | // short in WebIDL. | 
|  | if (ice_candidate_pool_size < 0 || | 
|  | ice_candidate_pool_size > static_cast<int>(UINT16_MAX)) { | 
|  | return RTCError(RTCErrorType::INVALID_RANGE); | 
|  | } | 
|  |  | 
|  | // According to JSEP, after setLocalDescription, changing the candidate pool | 
|  | // size is not allowed, and changing the set of ICE servers will not result | 
|  | // in new candidates being gathered. | 
|  | if (previous_ice_candidate_pool_size.has_value() && | 
|  | ice_candidate_pool_size != previous_ice_candidate_pool_size.value()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, | 
|  | "Can't change candidate pool size after calling " | 
|  | "SetLocalDescription."); | 
|  | } | 
|  |  | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | // The simplest (and most future-compatible) way to tell if a config was | 
|  | // modified in an invalid way is to copy each property we do support modifying, | 
|  | // then use operator==. There are far more properties we don't support modifying | 
|  | // than those we do, and more could be added. | 
|  | // This helper function accepts a proposed new `configuration` object, an | 
|  | // existing configuration and returns a valid, modified, configuration that's | 
|  | // based on the existing configuration, with modified properties copied from | 
|  | // `configuration`. | 
|  | // If the result of creating a modified configuration doesn't pass the above | 
|  | // `operator==` test or a call to `ValidateConfiguration()`, then the function | 
|  | // will return an error. Otherwise, the return value will be the new config. | 
|  | RTCErrorOr<PeerConnectionInterface::RTCConfiguration> ApplyConfiguration( | 
|  | const PeerConnectionInterface::RTCConfiguration& configuration, | 
|  | const PeerConnectionInterface::RTCConfiguration& existing_configuration) { | 
|  | PeerConnectionInterface::RTCConfiguration modified_config = | 
|  | existing_configuration; | 
|  | modified_config.servers = configuration.servers; | 
|  | modified_config.type = configuration.type; | 
|  | modified_config.ice_candidate_pool_size = | 
|  | configuration.ice_candidate_pool_size; | 
|  | modified_config.prune_turn_ports = configuration.prune_turn_ports; | 
|  | modified_config.turn_port_prune_policy = configuration.turn_port_prune_policy; | 
|  | modified_config.surface_ice_candidates_on_ice_transport_type_changed = | 
|  | configuration.surface_ice_candidates_on_ice_transport_type_changed; | 
|  | modified_config.ice_check_min_interval = configuration.ice_check_min_interval; | 
|  | modified_config.ice_check_interval_strong_connectivity = | 
|  | configuration.ice_check_interval_strong_connectivity; | 
|  | modified_config.ice_check_interval_weak_connectivity = | 
|  | configuration.ice_check_interval_weak_connectivity; | 
|  | modified_config.ice_unwritable_timeout = configuration.ice_unwritable_timeout; | 
|  | modified_config.ice_unwritable_min_checks = | 
|  | configuration.ice_unwritable_min_checks; | 
|  | modified_config.ice_inactive_timeout = configuration.ice_inactive_timeout; | 
|  | modified_config.stun_candidate_keepalive_interval = | 
|  | configuration.stun_candidate_keepalive_interval; | 
|  | modified_config.turn_customizer = configuration.turn_customizer; | 
|  | modified_config.network_preference = configuration.network_preference; | 
|  | modified_config.active_reset_srtp_params = | 
|  | configuration.active_reset_srtp_params; | 
|  | modified_config.turn_logging_id = configuration.turn_logging_id; | 
|  | modified_config.stable_writable_connection_ping_interval_ms = | 
|  | configuration.stable_writable_connection_ping_interval_ms; | 
|  | if (configuration != modified_config) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, | 
|  | "Modifying the configuration in an unsupported way."); | 
|  | } | 
|  |  | 
|  | RTCError err = IceConfig(modified_config).IsValid(); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | return modified_config; | 
|  | } | 
|  |  | 
|  | bool HasRtcpMuxEnabled(const ContentInfo* content) { | 
|  | return content->media_description()->rtcp_mux(); | 
|  | } | 
|  |  | 
|  | bool DtlsEnabled(const PeerConnectionInterface::RTCConfiguration& configuration, | 
|  | const PeerConnectionFactoryInterface::Options& options, | 
|  | const PeerConnectionDependencies& dependencies) { | 
|  | if (options.disable_encryption) | 
|  | return false; | 
|  |  | 
|  | // Enable DTLS by default if we have an identity store or a certificate. | 
|  | return (dependencies.cert_generator || !configuration.certificates.empty()); | 
|  | } | 
|  |  | 
|  | void NoteServerUsage(UsagePattern& usage_pattern, | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers) { | 
|  | if (!stun_servers.empty()) { | 
|  | usage_pattern.NoteUsageEvent(UsageEvent::STUN_SERVER_ADDED); | 
|  | } | 
|  | if (!turn_servers.empty()) { | 
|  | usage_pattern.NoteUsageEvent(UsageEvent::TURN_SERVER_ADDED); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | bool PeerConnectionInterface::RTCConfiguration::operator==( | 
|  | const PeerConnectionInterface::RTCConfiguration& o) const { | 
|  | // This static_assert prevents us from accidentally breaking operator==. | 
|  | // Note: Order matters! Fields must be ordered the same as RTCConfiguration. | 
|  | struct stuff_being_tested_for_equality { | 
|  | IceServers servers; | 
|  | IceTransportsType type; | 
|  | BundlePolicy bundle_policy; | 
|  | RtcpMuxPolicy rtcp_mux_policy; | 
|  | std::vector<rtc::scoped_refptr<RTCCertificate>> certificates; | 
|  | int ice_candidate_pool_size; | 
|  | bool disable_ipv6_on_wifi; | 
|  | int max_ipv6_networks; | 
|  | bool disable_link_local_networks; | 
|  | std::optional<int> screencast_min_bitrate; | 
|  | TcpCandidatePolicy tcp_candidate_policy; | 
|  | CandidateNetworkPolicy candidate_network_policy; | 
|  | int audio_jitter_buffer_max_packets; | 
|  | bool audio_jitter_buffer_fast_accelerate; | 
|  | int audio_jitter_buffer_min_delay_ms; | 
|  | int ice_connection_receiving_timeout; | 
|  | int ice_backup_candidate_pair_ping_interval; | 
|  | ContinualGatheringPolicy continual_gathering_policy; | 
|  | bool prioritize_most_likely_ice_candidate_pairs; | 
|  | struct cricket::MediaConfig media_config; | 
|  | bool prune_turn_ports; | 
|  | PortPrunePolicy turn_port_prune_policy; | 
|  | bool presume_writable_when_fully_relayed; | 
|  | bool enable_ice_renomination; | 
|  | bool redetermine_role_on_ice_restart; | 
|  | bool surface_ice_candidates_on_ice_transport_type_changed; | 
|  | std::optional<int> ice_check_interval_strong_connectivity; | 
|  | std::optional<int> ice_check_interval_weak_connectivity; | 
|  | std::optional<int> ice_check_min_interval; | 
|  | std::optional<int> ice_unwritable_timeout; | 
|  | std::optional<int> ice_unwritable_min_checks; | 
|  | std::optional<int> ice_inactive_timeout; | 
|  | std::optional<int> stun_candidate_keepalive_interval; | 
|  | TurnCustomizer* turn_customizer; | 
|  | SdpSemantics sdp_semantics; | 
|  | std::optional<rtc::AdapterType> network_preference; | 
|  | bool active_reset_srtp_params; | 
|  | std::optional<CryptoOptions> crypto_options; | 
|  | bool offer_extmap_allow_mixed; | 
|  | std::string turn_logging_id; | 
|  | bool enable_implicit_rollback; | 
|  | std::optional<int> report_usage_pattern_delay_ms; | 
|  | std::optional<int> stable_writable_connection_ping_interval_ms; | 
|  | VpnPreference vpn_preference; | 
|  | std::vector<rtc::NetworkMask> vpn_list; | 
|  | PortAllocatorConfig port_allocator_config; | 
|  | std::optional<TimeDelta> pacer_burst_interval; | 
|  | }; | 
|  | static_assert(sizeof(stuff_being_tested_for_equality) == sizeof(*this), | 
|  | "Did you add something to RTCConfiguration and forget to " | 
|  | "update operator==?"); | 
|  | return type == o.type && servers == o.servers && | 
|  | bundle_policy == o.bundle_policy && | 
|  | rtcp_mux_policy == o.rtcp_mux_policy && | 
|  | tcp_candidate_policy == o.tcp_candidate_policy && | 
|  | candidate_network_policy == o.candidate_network_policy && | 
|  | audio_jitter_buffer_max_packets == o.audio_jitter_buffer_max_packets && | 
|  | audio_jitter_buffer_fast_accelerate == | 
|  | o.audio_jitter_buffer_fast_accelerate && | 
|  | audio_jitter_buffer_min_delay_ms == | 
|  | o.audio_jitter_buffer_min_delay_ms && | 
|  | ice_connection_receiving_timeout == | 
|  | o.ice_connection_receiving_timeout && | 
|  | ice_backup_candidate_pair_ping_interval == | 
|  | o.ice_backup_candidate_pair_ping_interval && | 
|  | continual_gathering_policy == o.continual_gathering_policy && | 
|  | certificates == o.certificates && | 
|  | prioritize_most_likely_ice_candidate_pairs == | 
|  | o.prioritize_most_likely_ice_candidate_pairs && | 
|  | media_config == o.media_config && | 
|  | disable_ipv6_on_wifi == o.disable_ipv6_on_wifi && | 
|  | max_ipv6_networks == o.max_ipv6_networks && | 
|  | disable_link_local_networks == o.disable_link_local_networks && | 
|  | screencast_min_bitrate == o.screencast_min_bitrate && | 
|  | ice_candidate_pool_size == o.ice_candidate_pool_size && | 
|  | prune_turn_ports == o.prune_turn_ports && | 
|  | turn_port_prune_policy == o.turn_port_prune_policy && | 
|  | presume_writable_when_fully_relayed == | 
|  | o.presume_writable_when_fully_relayed && | 
|  | enable_ice_renomination == o.enable_ice_renomination && | 
|  | redetermine_role_on_ice_restart == o.redetermine_role_on_ice_restart && | 
|  | surface_ice_candidates_on_ice_transport_type_changed == | 
|  | o.surface_ice_candidates_on_ice_transport_type_changed && | 
|  | ice_check_interval_strong_connectivity == | 
|  | o.ice_check_interval_strong_connectivity && | 
|  | ice_check_interval_weak_connectivity == | 
|  | o.ice_check_interval_weak_connectivity && | 
|  | ice_check_min_interval == o.ice_check_min_interval && | 
|  | ice_unwritable_timeout == o.ice_unwritable_timeout && | 
|  | ice_unwritable_min_checks == o.ice_unwritable_min_checks && | 
|  | ice_inactive_timeout == o.ice_inactive_timeout && | 
|  | stun_candidate_keepalive_interval == | 
|  | o.stun_candidate_keepalive_interval && | 
|  | turn_customizer == o.turn_customizer && | 
|  | sdp_semantics == o.sdp_semantics && | 
|  | network_preference == o.network_preference && | 
|  | active_reset_srtp_params == o.active_reset_srtp_params && | 
|  | crypto_options == o.crypto_options && | 
|  | offer_extmap_allow_mixed == o.offer_extmap_allow_mixed && | 
|  | turn_logging_id == o.turn_logging_id && | 
|  | enable_implicit_rollback == o.enable_implicit_rollback && | 
|  | report_usage_pattern_delay_ms == o.report_usage_pattern_delay_ms && | 
|  | stable_writable_connection_ping_interval_ms == | 
|  | o.stable_writable_connection_ping_interval_ms && | 
|  | vpn_preference == o.vpn_preference && vpn_list == o.vpn_list && | 
|  | port_allocator_config.min_port == o.port_allocator_config.min_port && | 
|  | port_allocator_config.max_port == o.port_allocator_config.max_port && | 
|  | port_allocator_config.flags == o.port_allocator_config.flags && | 
|  | pacer_burst_interval == o.pacer_burst_interval; | 
|  | } | 
|  |  | 
|  | bool PeerConnectionInterface::RTCConfiguration::operator!=( | 
|  | const PeerConnectionInterface::RTCConfiguration& o) const { | 
|  | return !(*this == o); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<PeerConnection> PeerConnection::Create( | 
|  | const Environment& env, | 
|  | rtc::scoped_refptr<ConnectionContext> context, | 
|  | const PeerConnectionFactoryInterface::Options& options, | 
|  | std::unique_ptr<Call> call, | 
|  | const PeerConnectionInterface::RTCConfiguration& configuration, | 
|  | PeerConnectionDependencies& dependencies, | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers) { | 
|  | RTC_DCHECK(IceConfig(configuration).IsValid().ok()); | 
|  | RTC_DCHECK(dependencies.observer); | 
|  | RTC_DCHECK(dependencies.async_dns_resolver_factory); | 
|  | RTC_DCHECK(dependencies.allocator); | 
|  |  | 
|  | bool is_unified_plan = | 
|  | configuration.sdp_semantics == SdpSemantics::kUnifiedPlan; | 
|  | bool dtls_enabled = DtlsEnabled(configuration, options, dependencies); | 
|  |  | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::Create"); | 
|  | return rtc::make_ref_counted<PeerConnection>( | 
|  | configuration, env, context, options, is_unified_plan, std::move(call), | 
|  | dependencies, stun_servers, turn_servers, dtls_enabled); | 
|  | } | 
|  |  | 
|  | PeerConnection::PeerConnection( | 
|  | const PeerConnectionInterface::RTCConfiguration& configuration, | 
|  | const Environment& env, | 
|  | rtc::scoped_refptr<ConnectionContext> context, | 
|  | const PeerConnectionFactoryInterface::Options& options, | 
|  | bool is_unified_plan, | 
|  | std::unique_ptr<Call> call, | 
|  | PeerConnectionDependencies& dependencies, | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers, | 
|  | bool dtls_enabled) | 
|  | : env_(env), | 
|  | context_(context), | 
|  | options_(options), | 
|  | observer_(dependencies.observer), | 
|  | is_unified_plan_(is_unified_plan), | 
|  | dtls_enabled_(dtls_enabled), | 
|  | configuration_(configuration), | 
|  | async_dns_resolver_factory_( | 
|  | std::move(dependencies.async_dns_resolver_factory)), | 
|  | port_allocator_(std::move(dependencies.allocator)), | 
|  | ice_transport_factory_(std::move(dependencies.ice_transport_factory)), | 
|  | tls_cert_verifier_(std::move(dependencies.tls_cert_verifier)), | 
|  | call_(std::move(call)), | 
|  | network_thread_safety_( | 
|  | PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true, | 
|  | network_thread())), | 
|  | worker_thread_safety_(PendingTaskSafetyFlag::CreateAttachedToTaskQueue( | 
|  | /*alive=*/call_ != nullptr, | 
|  | worker_thread())), | 
|  | call_ptr_(call_.get()), | 
|  | legacy_stats_(std::make_unique<LegacyStatsCollector>(this)), | 
|  | stats_collector_(RTCStatsCollector::Create(this, env_)), | 
|  | // RFC 3264: The numeric value of the session id and version in the | 
|  | // o line MUST be representable with a "64 bit signed integer". | 
|  | // Due to this constraint session id `session_id_` is max limited to | 
|  | // LLONG_MAX. | 
|  | session_id_(rtc::ToString(CreateRandomId64() & LLONG_MAX)), | 
|  | data_channel_controller_(this), | 
|  | message_handler_(signaling_thread()), | 
|  | codec_lookup_helper_( | 
|  | std::make_unique<CodecLookupHelperForPeerConnection>(this)), | 
|  | weak_factory_(this) { | 
|  | // Field trials specific to the peerconnection should be owned by the `env`, | 
|  | RTC_DCHECK(dependencies.trials == nullptr); | 
|  |  | 
|  | transport_controller_copy_ = | 
|  | InitializeNetworkThread(stun_servers, turn_servers); | 
|  |  | 
|  | if (call_ptr_) { | 
|  | worker_thread()->BlockingCall([this, tc = transport_controller_copy_] { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | call_->SetPayloadTypeSuggester(tc); | 
|  | }); | 
|  | } | 
|  |  | 
|  | sdp_handler_ = SdpOfferAnswerHandler::Create( | 
|  | this, configuration_, std::move(dependencies.cert_generator), | 
|  | std::move(dependencies.video_bitrate_allocator_factory), context_.get(), | 
|  | codec_lookup_helper_.get()); | 
|  | rtp_manager_ = std::make_unique<RtpTransmissionManager>( | 
|  | env_, IsUnifiedPlan(), context_.get(), codec_lookup_helper_.get(), | 
|  | &usage_pattern_, observer_, legacy_stats_.get(), [this]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | }); | 
|  | // Add default audio/video transceivers for Plan B SDP. | 
|  | if (!IsUnifiedPlan()) { | 
|  | rtp_manager_->transceivers()->Add( | 
|  | RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( | 
|  | signaling_thread(), rtc::make_ref_counted<RtpTransceiver>( | 
|  | cricket::MEDIA_TYPE_AUDIO, context_.get(), | 
|  | codec_lookup_helper_.get()))); | 
|  | rtp_manager_->transceivers()->Add( | 
|  | RtpTransceiverProxyWithInternal<RtpTransceiver>::Create( | 
|  | signaling_thread(), rtc::make_ref_counted<RtpTransceiver>( | 
|  | cricket::MEDIA_TYPE_VIDEO, context_.get(), | 
|  | codec_lookup_helper_.get()))); | 
|  | } | 
|  |  | 
|  | const int delay_ms = configuration_.report_usage_pattern_delay_ms | 
|  | ? *configuration_.report_usage_pattern_delay_ms | 
|  | : REPORT_USAGE_PATTERN_DELAY_MS; | 
|  | message_handler_.RequestUsagePatternReport( | 
|  | [this]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | ReportUsagePattern(); | 
|  | }, | 
|  | delay_ms); | 
|  | } | 
|  |  | 
|  | PeerConnection::~PeerConnection() { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::~PeerConnection"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  |  | 
|  | if (sdp_handler_) { | 
|  | sdp_handler_->PrepareForShutdown(); | 
|  | } | 
|  |  | 
|  | // In case `Close()` wasn't called, always make sure the controller cancels | 
|  | // potentially pending operations. | 
|  | data_channel_controller_.PrepareForShutdown(); | 
|  |  | 
|  | // Need to stop transceivers before destroying the stats collector because | 
|  | // AudioRtpSender has a reference to the LegacyStatsCollector it will update | 
|  | // when stopping. | 
|  | if (rtp_manager()) { | 
|  | for (const auto& transceiver : rtp_manager()->transceivers()->List()) { | 
|  | transceiver->StopInternal(); | 
|  | } | 
|  | } | 
|  |  | 
|  | legacy_stats_.reset(nullptr); | 
|  | if (stats_collector_) { | 
|  | stats_collector_->WaitForPendingRequest(); | 
|  | stats_collector_ = nullptr; | 
|  | } | 
|  |  | 
|  | if (sdp_handler_) { | 
|  | // Don't destroy BaseChannels until after stats has been cleaned up so that | 
|  | // the last stats request can still read from the channels. | 
|  | sdp_handler_->DestroyMediaChannels(); | 
|  | RTC_LOG(LS_INFO) << "Session: " << session_id() << " is destroyed."; | 
|  | sdp_handler_->ResetSessionDescFactory(); | 
|  | } | 
|  |  | 
|  | // port_allocator_ and transport_controller_ live on the network thread and | 
|  | // should be destroyed there. | 
|  | transport_controller_copy_ = nullptr; | 
|  | network_thread()->BlockingCall([this] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | TeardownDataChannelTransport_n(RTCError::OK()); | 
|  | transport_controller_.reset(); | 
|  | port_allocator_.reset(); | 
|  | if (network_thread_safety_) | 
|  | network_thread_safety_->SetNotAlive(); | 
|  | }); | 
|  | sctp_mid_s_.reset(); | 
|  | SetSctpTransportName(""); | 
|  |  | 
|  | // call_ must be destroyed on the worker thread. | 
|  | worker_thread()->BlockingCall([this] { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | worker_thread_safety_->SetNotAlive(); | 
|  | call_.reset(); | 
|  | }); | 
|  |  | 
|  | data_channel_controller_.PrepareForShutdown(); | 
|  | } | 
|  |  | 
|  | JsepTransportController* PeerConnection::InitializeNetworkThread( | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  |  | 
|  | NoteServerUsage(usage_pattern_, stun_servers, turn_servers); | 
|  | return network_thread()->BlockingCall([&, config = &configuration_] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | RTC_DCHECK(network_thread_safety_->alive()); | 
|  | InitializePortAllocatorResult pa_result = | 
|  | InitializePortAllocator_n(stun_servers, turn_servers, *config); | 
|  | // Send information about IPv4/IPv6 status. | 
|  | PeerConnectionAddressFamilyCounter address_family = | 
|  | pa_result.enable_ipv6 ? kPeerConnection_IPv6 : kPeerConnection_IPv4; | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics", address_family, | 
|  | kPeerConnectionAddressFamilyCounter_Max); | 
|  | return InitializeTransportController_n(*config); | 
|  | }); | 
|  | } | 
|  |  | 
|  | JsepTransportController* PeerConnection::InitializeTransportController_n( | 
|  | const RTCConfiguration& configuration) { | 
|  | JsepTransportController::Config config; | 
|  | config.redetermine_role_on_ice_restart = | 
|  | configuration.redetermine_role_on_ice_restart; | 
|  | config.ssl_max_version = options_.ssl_max_version; | 
|  | config.disable_encryption = options_.disable_encryption; | 
|  | config.bundle_policy = configuration.bundle_policy; | 
|  | config.rtcp_mux_policy = configuration.rtcp_mux_policy; | 
|  | // TODO(bugs.webrtc.org/9891) - Remove options_.crypto_options then remove | 
|  | // this stub. | 
|  | config.crypto_options = configuration.crypto_options.has_value() | 
|  | ? *configuration.crypto_options | 
|  | : options_.crypto_options; | 
|  | config.transport_observer = this; | 
|  | config.rtcp_handler = InitializeRtcpCallback(); | 
|  | config.un_demuxable_packet_handler = InitializeUnDemuxablePacketHandler(); | 
|  | config.event_log = &env_.event_log(); | 
|  | #if defined(ENABLE_EXTERNAL_AUTH) | 
|  | config.enable_external_auth = true; | 
|  | #endif | 
|  | config.active_reset_srtp_params = configuration.active_reset_srtp_params; | 
|  |  | 
|  | // DTLS has to be enabled to use SCTP. | 
|  | if (dtls_enabled_) { | 
|  | config.sctp_factory = context_->sctp_transport_factory(); | 
|  | } | 
|  |  | 
|  | config.ice_transport_factory = ice_transport_factory_.get(); | 
|  | config.on_dtls_handshake_error_ = | 
|  | [weak_ptr = weak_factory_.GetWeakPtr()](rtc::SSLHandshakeError s) { | 
|  | if (weak_ptr) { | 
|  | weak_ptr->OnTransportControllerDtlsHandshakeError(s); | 
|  | } | 
|  | }; | 
|  |  | 
|  | transport_controller_.reset( | 
|  | new JsepTransportController(env_, network_thread(), port_allocator_.get(), | 
|  | async_dns_resolver_factory_.get(), | 
|  | payload_type_picker_, std::move(config))); | 
|  |  | 
|  | transport_controller_->SubscribeIceConnectionState( | 
|  | [this](cricket::IceConnectionState s) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, s]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerConnectionState(s); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeConnectionState( | 
|  | [this](PeerConnectionInterface::PeerConnectionState s) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, s]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | SetConnectionState(s); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeStandardizedIceConnectionState( | 
|  | [this](PeerConnectionInterface::IceConnectionState s) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, s]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | SetStandardizedIceConnectionState(s); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeIceGatheringState( | 
|  | [this](::webrtc::IceGatheringState s) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, s]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerGatheringState(s); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeIceCandidateGathered( | 
|  | [this](const std::string& transport, | 
|  | const std::vector<cricket::Candidate>& candidates) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), | 
|  | [this, t = transport, c = candidates]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerCandidatesGathered(t, c); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeIceCandidateError( | 
|  | [this](const cricket::IceCandidateErrorEvent& event) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, event = event]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerCandidateError(event); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeIceCandidatesRemoved( | 
|  | [this](const std::vector<cricket::Candidate>& c) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, c = c]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerCandidatesRemoved(c); | 
|  | })); | 
|  | }); | 
|  | transport_controller_->SubscribeIceCandidatePairChanged( | 
|  | [this](const cricket::CandidatePairChangeEvent& event) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | signaling_thread()->PostTask( | 
|  | SafeTask(signaling_thread_safety_.flag(), [this, event = event]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | OnTransportControllerCandidateChanged(event); | 
|  | })); | 
|  | }); | 
|  |  | 
|  | IceConfig ice_config(configuration); | 
|  | ice_config.dtls_handshake_in_stun = | 
|  | CanAttemptDtlsStunPiggybacking(configuration); | 
|  |  | 
|  | transport_controller_->SetIceConfig(ice_config); | 
|  | return transport_controller_.get(); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<StreamCollectionInterface> PeerConnection::local_streams() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_CHECK(!IsUnifiedPlan()) << "local_streams is not available with Unified " | 
|  | "Plan SdpSemantics. Please use GetSenders " | 
|  | "instead."; | 
|  | return sdp_handler_->local_streams(); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<StreamCollectionInterface> PeerConnection::remote_streams() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_CHECK(!IsUnifiedPlan()) << "remote_streams is not available with Unified " | 
|  | "Plan SdpSemantics. Please use GetReceivers " | 
|  | "instead."; | 
|  | return sdp_handler_->remote_streams(); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::AddStream(MediaStreamInterface* local_stream) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_CHECK(!IsUnifiedPlan()) << "AddStream is not available with Unified Plan " | 
|  | "SdpSemantics. Please use AddTrack instead."; | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::AddStream"); | 
|  | if (!ConfiguredForMedia()) { | 
|  | RTC_LOG(LS_ERROR) << "AddStream: Not configured for media"; | 
|  | return false; | 
|  | } | 
|  | return sdp_handler_->AddStream(local_stream); | 
|  | } | 
|  |  | 
|  | void PeerConnection::RemoveStream(MediaStreamInterface* local_stream) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(ConfiguredForMedia()); | 
|  | RTC_CHECK(!IsUnifiedPlan()) << "RemoveStream is not available with Unified " | 
|  | "Plan SdpSemantics. Please use RemoveTrack " | 
|  | "instead."; | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream"); | 
|  | sdp_handler_->RemoveStream(local_stream); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack( | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track, | 
|  | const std::vector<std::string>& stream_ids) { | 
|  | return AddTrack(std::move(track), stream_ids, nullptr); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack( | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track, | 
|  | const std::vector<std::string>& stream_ids, | 
|  | const std::vector<RtpEncodingParameters>& init_send_encodings) { | 
|  | return AddTrack(std::move(track), stream_ids, &init_send_encodings); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack( | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track, | 
|  | const std::vector<std::string>& stream_ids, | 
|  | const std::vector<RtpEncodingParameters>* init_send_encodings) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::AddTrack"); | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  | if (!track) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Track is null."); | 
|  | } | 
|  | if (!(track->kind() == MediaStreamTrackInterface::kAudioKind || | 
|  | track->kind() == MediaStreamTrackInterface::kVideoKind)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "Track has invalid kind: " + track->kind()); | 
|  | } | 
|  | if (IsClosed()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, | 
|  | "PeerConnection is closed."); | 
|  | } | 
|  | if (rtp_manager()->FindSenderForTrack(track.get())) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "Sender already exists for track " + track->id() + "."); | 
|  | } | 
|  | auto sender_or_error = | 
|  | rtp_manager()->AddTrack(track, stream_ids, init_send_encodings); | 
|  | if (sender_or_error.ok()) { | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | legacy_stats_->AddTrack(track.get()); | 
|  | } | 
|  | return sender_or_error; | 
|  | } | 
|  |  | 
|  | RTCError PeerConnection::RemoveTrackOrError( | 
|  | rtc::scoped_refptr<RtpSenderInterface> sender) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  | if (!sender) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "Sender is null."); | 
|  | } | 
|  | if (IsClosed()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, | 
|  | "PeerConnection is closed."); | 
|  | } | 
|  | if (IsUnifiedPlan()) { | 
|  | auto transceiver = FindTransceiverBySender(sender); | 
|  | if (!transceiver || !sender->track()) { | 
|  | return RTCError::OK(); | 
|  | } | 
|  | sender->SetTrack(nullptr); | 
|  | if (transceiver->direction() == RtpTransceiverDirection::kSendRecv) { | 
|  | transceiver->internal()->set_direction( | 
|  | RtpTransceiverDirection::kRecvOnly); | 
|  | } else if (transceiver->direction() == RtpTransceiverDirection::kSendOnly) { | 
|  | transceiver->internal()->set_direction( | 
|  | RtpTransceiverDirection::kInactive); | 
|  | } | 
|  | } else { | 
|  | bool removed; | 
|  | if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) { | 
|  | removed = rtp_manager()->GetAudioTransceiver()->internal()->RemoveSender( | 
|  | sender.get()); | 
|  | } else { | 
|  | RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type()); | 
|  | removed = rtp_manager()->GetVideoTransceiver()->internal()->RemoveSender( | 
|  | sender.get()); | 
|  | } | 
|  | if (!removed) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "Couldn't find sender " + sender->id() + " to remove."); | 
|  | } | 
|  | } | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> | 
|  | PeerConnection::FindTransceiverBySender( | 
|  | rtc::scoped_refptr<RtpSenderInterface> sender) { | 
|  | return rtp_manager()->transceivers()->FindBySender(sender); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::AddTransceiver( | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track) { | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  |  | 
|  | return AddTransceiver(track, RtpTransceiverInit()); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::AddTransceiver( | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track, | 
|  | const RtpTransceiverInit& init) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  | RTC_CHECK(IsUnifiedPlan()) | 
|  | << "AddTransceiver is only available with Unified Plan SdpSemantics"; | 
|  | if (!track) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, "track is null"); | 
|  | } | 
|  | cricket::MediaType media_type; | 
|  | if (track->kind() == MediaStreamTrackInterface::kAudioKind) { | 
|  | media_type = cricket::MEDIA_TYPE_AUDIO; | 
|  | } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { | 
|  | media_type = cricket::MEDIA_TYPE_VIDEO; | 
|  | } else { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "Track kind is not audio or video"); | 
|  | } | 
|  | return AddTransceiver(media_type, track, init); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::AddTransceiver(cricket::MediaType media_type) { | 
|  | return AddTransceiver(media_type, RtpTransceiverInit()); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::AddTransceiver(cricket::MediaType media_type, | 
|  | const RtpTransceiverInit& init) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  | RTC_CHECK(IsUnifiedPlan()) | 
|  | << "AddTransceiver is only available with Unified Plan SdpSemantics"; | 
|  | if (!(media_type == cricket::MEDIA_TYPE_AUDIO || | 
|  | media_type == cricket::MEDIA_TYPE_VIDEO)) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "media type is not audio or video"); | 
|  | } | 
|  | return AddTransceiver(media_type, nullptr, init); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::AddTransceiver( | 
|  | cricket::MediaType media_type, | 
|  | rtc::scoped_refptr<MediaStreamTrackInterface> track, | 
|  | const RtpTransceiverInit& init, | 
|  | bool update_negotiation_needed) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!ConfiguredForMedia()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION, | 
|  | "Not configured for media"); | 
|  | } | 
|  | RTC_DCHECK((media_type == cricket::MEDIA_TYPE_AUDIO || | 
|  | media_type == cricket::MEDIA_TYPE_VIDEO)); | 
|  | if (track) { | 
|  | RTC_DCHECK_EQ(media_type, | 
|  | (track->kind() == MediaStreamTrackInterface::kAudioKind | 
|  | ? cricket::MEDIA_TYPE_AUDIO | 
|  | : cricket::MEDIA_TYPE_VIDEO)); | 
|  | } | 
|  |  | 
|  | size_t num_rids = absl::c_count_if(init.send_encodings, | 
|  | [](const RtpEncodingParameters& encoding) { | 
|  | return !encoding.rid.empty(); | 
|  | }); | 
|  | if (num_rids > 0 && num_rids != init.send_encodings.size()) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "RIDs must be provided for either all or none of the send encodings."); | 
|  | } | 
|  |  | 
|  | if (num_rids > 0 && absl::c_any_of(init.send_encodings, | 
|  | [](const RtpEncodingParameters& encoding) { | 
|  | return !IsLegalRsidName(encoding.rid); | 
|  | })) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "Invalid RID value provided."); | 
|  | } | 
|  |  | 
|  | if (absl::c_any_of(init.send_encodings, | 
|  | [](const RtpEncodingParameters& encoding) { | 
|  | return encoding.ssrc.has_value(); | 
|  | })) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::UNSUPPORTED_PARAMETER, | 
|  | "Attempted to set an unimplemented parameter of RtpParameters."); | 
|  | } | 
|  |  | 
|  | RtpParameters parameters; | 
|  | parameters.encodings = init.send_encodings; | 
|  |  | 
|  | // Encodings are dropped from the tail if too many are provided. | 
|  | size_t max_simulcast_streams = | 
|  | media_type == cricket::MEDIA_TYPE_VIDEO ? kMaxSimulcastStreams : 1u; | 
|  | if (parameters.encodings.size() > max_simulcast_streams) { | 
|  | parameters.encodings.erase( | 
|  | parameters.encodings.begin() + max_simulcast_streams, | 
|  | parameters.encodings.end()); | 
|  | } | 
|  |  | 
|  | // Single RID should be removed. | 
|  | if (parameters.encodings.size() == 1 && | 
|  | !parameters.encodings[0].rid.empty()) { | 
|  | RTC_LOG(LS_INFO) << "Removing RID: " << parameters.encodings[0].rid << "."; | 
|  | parameters.encodings[0].rid.clear(); | 
|  | } | 
|  |  | 
|  | // If RIDs were not provided, they are generated for simulcast scenario. | 
|  | if (parameters.encodings.size() > 1 && num_rids == 0) { | 
|  | UniqueStringGenerator rid_generator; | 
|  | for (RtpEncodingParameters& encoding : parameters.encodings) { | 
|  | encoding.rid = rid_generator.GenerateString(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // If no encoding parameters were provided, a default entry is created. | 
|  | if (parameters.encodings.empty()) { | 
|  | parameters.encodings.push_back({}); | 
|  | } | 
|  |  | 
|  | if (UnimplementedRtpParameterHasValue(parameters)) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::UNSUPPORTED_PARAMETER, | 
|  | "Attempted to set an unimplemented parameter of RtpParameters."); | 
|  | } | 
|  |  | 
|  | std::vector<cricket::Codec> codecs; | 
|  | // Gather the current codec capabilities to allow checking scalabilityMode and | 
|  | // codec selection against supported values. | 
|  | cricket::CodecVendor codec_vendor(context_->media_engine(), false, | 
|  | context_->env().field_trials()); | 
|  | if (media_type == cricket::MEDIA_TYPE_VIDEO) { | 
|  | codecs = codec_vendor.video_send_codecs().codecs(); | 
|  | } else { | 
|  | codecs = codec_vendor.audio_send_codecs().codecs(); | 
|  | } | 
|  |  | 
|  | auto result = cricket::CheckRtpParametersValues( | 
|  | parameters, codecs, std::nullopt, env_.field_trials()); | 
|  | if (!result.ok()) { | 
|  | if (result.type() == RTCErrorType::INVALID_MODIFICATION) { | 
|  | result.set_type(RTCErrorType::UNSUPPORTED_OPERATION); | 
|  | } | 
|  | LOG_AND_RETURN_ERROR(result.type(), result.message()); | 
|  | } | 
|  |  | 
|  | RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type) | 
|  | << " transceiver in response to a call to AddTransceiver."; | 
|  | // Set the sender ID equal to the track ID if the track is specified unless | 
|  | // that sender ID is already in use. | 
|  | std::string sender_id = (track && !rtp_manager()->FindSenderById(track->id()) | 
|  | ? track->id() | 
|  | : CreateRandomUuid()); | 
|  | auto sender = rtp_manager()->CreateSender( | 
|  | media_type, sender_id, track, init.stream_ids, parameters.encodings); | 
|  | auto receiver = rtp_manager()->CreateReceiver(media_type, CreateRandomUuid()); | 
|  | auto transceiver = rtp_manager()->CreateAndAddTransceiver(sender, receiver); | 
|  | transceiver->internal()->set_direction(init.direction); | 
|  |  | 
|  | if (update_negotiation_needed) { | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | } | 
|  |  | 
|  | return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnNegotiationNeeded() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(!IsClosed()); | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<RtpSenderInterface> PeerConnection::CreateSender( | 
|  | const std::string& kind, | 
|  | const std::string& stream_id) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!ConfiguredForMedia()) { | 
|  | RTC_LOG(LS_ERROR) << "Not configured for media"; | 
|  | return nullptr; | 
|  | } | 
|  | RTC_CHECK(!IsUnifiedPlan()) << "CreateSender is not available with Unified " | 
|  | "Plan SdpSemantics. Please use AddTransceiver " | 
|  | "instead."; | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::CreateSender"); | 
|  | if (IsClosed()) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Internally we need to have one stream with Plan B semantics, so we | 
|  | // generate a random stream ID if not specified. | 
|  | std::vector<std::string> stream_ids; | 
|  | if (stream_id.empty()) { | 
|  | stream_ids.push_back(CreateRandomUuid()); | 
|  | RTC_LOG(LS_INFO) | 
|  | << "No stream_id specified for sender. Generated stream ID: " | 
|  | << stream_ids[0]; | 
|  | } else { | 
|  | stream_ids.push_back(stream_id); | 
|  | } | 
|  |  | 
|  | // TODO(steveanton): Move construction of the RtpSenders to RtpTransceiver. | 
|  | rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender; | 
|  | if (kind == MediaStreamTrackInterface::kAudioKind) { | 
|  | auto audio_sender = | 
|  | AudioRtpSender::Create(env_, worker_thread(), CreateRandomUuid(), | 
|  | legacy_stats_.get(), rtp_manager()); | 
|  | audio_sender->SetMediaChannel(rtp_manager()->voice_media_send_channel()); | 
|  | new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( | 
|  | signaling_thread(), audio_sender); | 
|  | rtp_manager()->GetAudioTransceiver()->internal()->AddSender(new_sender); | 
|  | } else if (kind == MediaStreamTrackInterface::kVideoKind) { | 
|  | auto video_sender = VideoRtpSender::Create( | 
|  | env_, worker_thread(), CreateRandomUuid(), rtp_manager()); | 
|  | video_sender->SetMediaChannel(rtp_manager()->video_media_send_channel()); | 
|  | new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create( | 
|  | signaling_thread(), video_sender); | 
|  | rtp_manager()->GetVideoTransceiver()->internal()->AddSender(new_sender); | 
|  | } else { | 
|  | RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind; | 
|  | return nullptr; | 
|  | } | 
|  | new_sender->internal()->set_stream_ids(stream_ids); | 
|  |  | 
|  | return new_sender; | 
|  | } | 
|  |  | 
|  | std::vector<rtc::scoped_refptr<RtpSenderInterface>> PeerConnection::GetSenders() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | std::vector<rtc::scoped_refptr<RtpSenderInterface>> ret; | 
|  | if (ConfiguredForMedia()) { | 
|  | for (const auto& sender : rtp_manager()->GetSendersInternal()) { | 
|  | ret.push_back(sender); | 
|  | } | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | std::vector<rtc::scoped_refptr<RtpReceiverInterface>> | 
|  | PeerConnection::GetReceivers() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | std::vector<rtc::scoped_refptr<RtpReceiverInterface>> ret; | 
|  | if (ConfiguredForMedia()) { | 
|  | for (const auto& receiver : rtp_manager()->GetReceiversInternal()) { | 
|  | ret.push_back(receiver); | 
|  | } | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> | 
|  | PeerConnection::GetTransceivers() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_CHECK(IsUnifiedPlan()) | 
|  | << "GetTransceivers is only supported with Unified Plan SdpSemantics."; | 
|  | std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers; | 
|  | if (ConfiguredForMedia()) { | 
|  | for (const auto& transceiver : rtp_manager()->transceivers()->List()) { | 
|  | all_transceivers.push_back(transceiver); | 
|  | } | 
|  | } | 
|  | return all_transceivers; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::GetStats(StatsObserver* observer, | 
|  | MediaStreamTrackInterface* track, | 
|  | StatsOutputLevel level) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::GetStats (legacy)"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!observer) { | 
|  | RTC_LOG(LS_ERROR) << "Legacy GetStats - observer is NULL."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | RTC_LOG_THREAD_BLOCK_COUNT(); | 
|  |  | 
|  | legacy_stats_->UpdateStats(level); | 
|  |  | 
|  | RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(4); | 
|  |  | 
|  | // The LegacyStatsCollector is used to tell if a track is valid because it may | 
|  | // remember tracks that the PeerConnection previously removed. | 
|  | if (track && !legacy_stats_->IsValidTrack(track->id())) { | 
|  | RTC_LOG(LS_WARNING) << "Legacy GetStats is called with an invalid track: " | 
|  | << track->id(); | 
|  | return false; | 
|  | } | 
|  | message_handler_.PostGetStats(observer, legacy_stats_.get(), track); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void PeerConnection::GetStats(RTCStatsCollectorCallback* callback) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(stats_collector_); | 
|  | RTC_DCHECK(callback); | 
|  | RTC_LOG_THREAD_BLOCK_COUNT(); | 
|  | stats_collector_->GetStatsReport( | 
|  | rtc::scoped_refptr<RTCStatsCollectorCallback>(callback)); | 
|  | RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2); | 
|  | } | 
|  |  | 
|  | void PeerConnection::GetStats( | 
|  | rtc::scoped_refptr<RtpSenderInterface> selector, | 
|  | rtc::scoped_refptr<RTCStatsCollectorCallback> callback) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(callback); | 
|  | RTC_DCHECK(stats_collector_); | 
|  | RTC_LOG_THREAD_BLOCK_COUNT(); | 
|  | rtc::scoped_refptr<RtpSenderInternal> internal_sender; | 
|  | if (selector) { | 
|  | for (const auto& proxy_transceiver : | 
|  | rtp_manager()->transceivers()->List()) { | 
|  | for (const auto& proxy_sender : | 
|  | proxy_transceiver->internal()->senders()) { | 
|  | if (proxy_sender == selector) { | 
|  | internal_sender = proxy_sender->internal(); | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (internal_sender) | 
|  | break; | 
|  | } | 
|  | } | 
|  | // If there is no `internal_sender` then `selector` is either null or does not | 
|  | // belong to the PeerConnection (in Plan B, senders can be removed from the | 
|  | // PeerConnection). This means that "all the stats objects representing the | 
|  | // selector" is an empty set. Invoking GetStatsReport() with a null selector | 
|  | // produces an empty stats report. | 
|  | stats_collector_->GetStatsReport(internal_sender, callback); | 
|  | RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2); | 
|  | } | 
|  |  | 
|  | void PeerConnection::GetStats( | 
|  | rtc::scoped_refptr<RtpReceiverInterface> selector, | 
|  | rtc::scoped_refptr<RTCStatsCollectorCallback> callback) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::GetStats"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(callback); | 
|  | RTC_DCHECK(stats_collector_); | 
|  | RTC_LOG_THREAD_BLOCK_COUNT(); | 
|  | rtc::scoped_refptr<RtpReceiverInternal> internal_receiver; | 
|  | if (selector) { | 
|  | for (const auto& proxy_transceiver : | 
|  | rtp_manager()->transceivers()->List()) { | 
|  | for (const auto& proxy_receiver : | 
|  | proxy_transceiver->internal()->receivers()) { | 
|  | if (proxy_receiver == selector) { | 
|  | internal_receiver = proxy_receiver->internal(); | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (internal_receiver) | 
|  | break; | 
|  | } | 
|  | } | 
|  | // If there is no `internal_receiver` then `selector` is either null or does | 
|  | // not belong to the PeerConnection (in Plan B, receivers can be removed from | 
|  | // the PeerConnection). This means that "all the stats objects representing | 
|  | // the selector" is an empty set. Invoking GetStatsReport() with a null | 
|  | // selector produces an empty stats report. | 
|  | stats_collector_->GetStatsReport(internal_receiver, callback); | 
|  | RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2); | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::SignalingState PeerConnection::signaling_state() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->signaling_state(); | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::IceConnectionState | 
|  | PeerConnection::ice_connection_state() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return ice_connection_state_; | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::IceConnectionState | 
|  | PeerConnection::standardized_ice_connection_state() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return standardized_ice_connection_state_; | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::PeerConnectionState | 
|  | PeerConnection::peer_connection_state() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return connection_state_; | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::IceGatheringState | 
|  | PeerConnection::ice_gathering_state() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return ice_gathering_state_; | 
|  | } | 
|  |  | 
|  | std::optional<bool> PeerConnection::can_trickle_ice_candidates() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | const SessionDescriptionInterface* description = current_remote_description(); | 
|  | if (!description) { | 
|  | description = pending_remote_description(); | 
|  | } | 
|  | if (!description) { | 
|  | return std::nullopt; | 
|  | } | 
|  | // TODO(bugs.webrtc.org/7443): Change to retrieve from session-level option. | 
|  | if (description->description()->transport_infos().size() < 1) { | 
|  | return std::nullopt; | 
|  | } | 
|  | return description->description()->transport_infos()[0].description.HasOption( | 
|  | "trickle"); | 
|  | } | 
|  |  | 
|  | RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>> | 
|  | PeerConnection::CreateDataChannelOrError(const std::string& label, | 
|  | const DataChannelInit* config) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::CreateDataChannel"); | 
|  |  | 
|  | if (IsClosed()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, | 
|  | "CreateDataChannelOrError: PeerConnection is closed."); | 
|  | } | 
|  |  | 
|  | bool first_datachannel = !data_channel_controller_.HasUsedDataChannels(); | 
|  |  | 
|  | InternalDataChannelInit internal_config; | 
|  | if (config) { | 
|  | internal_config = InternalDataChannelInit(*config); | 
|  | } | 
|  |  | 
|  | internal_config.fallback_ssl_role = sdp_handler_->GuessSslRole(); | 
|  | RTCErrorOr<rtc::scoped_refptr<DataChannelInterface>> ret = | 
|  | data_channel_controller_.InternalCreateDataChannelWithProxy( | 
|  | label, internal_config); | 
|  | if (!ret.ok()) { | 
|  | return ret.MoveError(); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<DataChannelInterface> channel = ret.MoveValue(); | 
|  |  | 
|  | // Check the onRenegotiationNeeded event (with plan-b backward compat) | 
|  | if (configuration_.sdp_semantics == SdpSemantics::kUnifiedPlan || | 
|  | (configuration_.sdp_semantics == SdpSemantics::kPlanB_DEPRECATED && | 
|  | first_datachannel)) { | 
|  | sdp_handler_->UpdateNegotiationNeeded(); | 
|  | } | 
|  | NoteUsageEvent(UsageEvent::DATA_ADDED); | 
|  | return channel; | 
|  | } | 
|  |  | 
|  | void PeerConnection::RestartIce() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->RestartIce(); | 
|  | } | 
|  |  | 
|  | void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, | 
|  | const RTCOfferAnswerOptions& options) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->CreateOffer(observer, options); | 
|  | } | 
|  |  | 
|  | void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, | 
|  | const RTCOfferAnswerOptions& options) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->CreateAnswer(observer, options); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetLocalDescription( | 
|  | SetSessionDescriptionObserver* observer, | 
|  | SessionDescriptionInterface* desc_ptr) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetLocalDescription(observer, desc_ptr); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetLocalDescription( | 
|  | std::unique_ptr<SessionDescriptionInterface> desc, | 
|  | rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetLocalDescription(std::move(desc), observer); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetLocalDescription( | 
|  | SetSessionDescriptionObserver* observer) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetLocalDescription(observer); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetLocalDescription( | 
|  | rtc::scoped_refptr<SetLocalDescriptionObserverInterface> observer) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetLocalDescription(observer); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetRemoteDescription( | 
|  | SetSessionDescriptionObserver* observer, | 
|  | SessionDescriptionInterface* desc_ptr) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetRemoteDescription(observer, desc_ptr); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetRemoteDescription( | 
|  | std::unique_ptr<SessionDescriptionInterface> desc, | 
|  | rtc::scoped_refptr<SetRemoteDescriptionObserverInterface> observer) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->SetRemoteDescription(std::move(desc), observer); | 
|  | } | 
|  |  | 
|  | PeerConnectionInterface::RTCConfiguration PeerConnection::GetConfiguration() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return configuration_; | 
|  | } | 
|  |  | 
|  | RTCError PeerConnection::SetConfiguration( | 
|  | const RTCConfiguration& configuration) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::SetConfiguration"); | 
|  | if (IsClosed()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE, | 
|  | "SetConfiguration: PeerConnection is closed."); | 
|  | } | 
|  |  | 
|  | const bool has_local_description = local_description() != nullptr; | 
|  |  | 
|  | RTCError validate_error = ValidateIceCandidatePoolSize( | 
|  | configuration.ice_candidate_pool_size, | 
|  | has_local_description | 
|  | ? std::optional<int>(configuration_.ice_candidate_pool_size) | 
|  | : std::nullopt); | 
|  | if (!validate_error.ok()) { | 
|  | return validate_error; | 
|  | } | 
|  |  | 
|  | if (has_local_description && | 
|  | configuration.crypto_options != configuration_.crypto_options) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION, | 
|  | "Can't change crypto_options after calling " | 
|  | "SetLocalDescription."); | 
|  | } | 
|  |  | 
|  | // Create a new, configuration object whose Ice config will have been | 
|  | // validated.. | 
|  | RTCErrorOr<RTCConfiguration> validated_config = | 
|  | ApplyConfiguration(configuration, configuration_); | 
|  | if (!validated_config.ok()) { | 
|  | return validated_config.error(); | 
|  | } | 
|  |  | 
|  | // Parse ICE servers before hopping to network thread. | 
|  | cricket::ServerAddresses stun_servers; | 
|  | std::vector<RelayServerConfig> turn_servers; | 
|  | validate_error = ParseAndValidateIceServersFromConfiguration( | 
|  | configuration, stun_servers, turn_servers); | 
|  | if (!validate_error.ok()) { | 
|  | return validate_error; | 
|  | } | 
|  | NoteServerUsage(usage_pattern_, stun_servers, turn_servers); | 
|  |  | 
|  | const RTCConfiguration& modified_config = validated_config.value(); | 
|  | const bool needs_ice_restart = | 
|  | modified_config.servers != configuration_.servers || | 
|  | NeedIceRestart( | 
|  | configuration_.surface_ice_candidates_on_ice_transport_type_changed, | 
|  | configuration_.type, modified_config.type) || | 
|  | modified_config.GetTurnPortPrunePolicy() != | 
|  | configuration_.GetTurnPortPrunePolicy(); | 
|  | IceConfig ice_config(modified_config); | 
|  | ice_config.dtls_handshake_in_stun = | 
|  | CanAttemptDtlsStunPiggybacking(modified_config); | 
|  |  | 
|  | // Apply part of the configuration on the network thread.  In theory this | 
|  | // shouldn't fail. | 
|  | if (!network_thread()->BlockingCall( | 
|  | [this, needs_ice_restart, &ice_config, &stun_servers, &turn_servers, | 
|  | &modified_config, has_local_description] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | // As described in JSEP, calling setConfiguration with new ICE | 
|  | // servers or candidate policy must set a "needs-ice-restart" bit so | 
|  | // that the next offer triggers an ICE restart which will pick up | 
|  | // the changes. | 
|  | if (needs_ice_restart) | 
|  | transport_controller_->SetNeedsIceRestartFlag(); | 
|  |  | 
|  | transport_controller_->SetIceConfig(ice_config); | 
|  | transport_controller_->SetActiveResetSrtpParams( | 
|  | modified_config.active_reset_srtp_params); | 
|  | return ReconfigurePortAllocator_n( | 
|  | stun_servers, turn_servers, modified_config.type, | 
|  | modified_config.ice_candidate_pool_size, | 
|  | modified_config.GetTurnPortPrunePolicy(), | 
|  | modified_config.turn_customizer, | 
|  | modified_config.stun_candidate_keepalive_interval, | 
|  | has_local_description); | 
|  | })) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR, | 
|  | "Failed to apply configuration to PortAllocator."); | 
|  | } | 
|  |  | 
|  | configuration_ = modified_config; | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::AddIceCandidate( | 
|  | const IceCandidateInterface* ice_candidate) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | ClearStatsCache(); | 
|  | return sdp_handler_->AddIceCandidate(ice_candidate); | 
|  | } | 
|  |  | 
|  | void PeerConnection::AddIceCandidate( | 
|  | std::unique_ptr<IceCandidateInterface> candidate, | 
|  | std::function<void(RTCError)> callback) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sdp_handler_->AddIceCandidate(std::move(candidate), | 
|  | [this, callback](RTCError result) { | 
|  | ClearStatsCache(); | 
|  | callback(result); | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::RemoveIceCandidates( | 
|  | const std::vector<Candidate>& candidates) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::RemoveIceCandidates"); | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->RemoveIceCandidates(candidates); | 
|  | } | 
|  |  | 
|  | RTCError PeerConnection::SetBitrate(const BitrateSettings& bitrate) { | 
|  | if (!worker_thread()->IsCurrent()) { | 
|  | return worker_thread()->BlockingCall([&]() { return SetBitrate(bitrate); }); | 
|  | } | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  |  | 
|  | const bool has_min = bitrate.min_bitrate_bps.has_value(); | 
|  | const bool has_start = bitrate.start_bitrate_bps.has_value(); | 
|  | const bool has_max = bitrate.max_bitrate_bps.has_value(); | 
|  | if (has_min && *bitrate.min_bitrate_bps < 0) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "min_bitrate_bps <= 0"); | 
|  | } | 
|  | if (has_start) { | 
|  | if (has_min && *bitrate.start_bitrate_bps < *bitrate.min_bitrate_bps) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "start_bitrate_bps < min_bitrate_bps"); | 
|  | } else if (*bitrate.start_bitrate_bps < 0) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "curent_bitrate_bps < 0"); | 
|  | } | 
|  | } | 
|  | if (has_max) { | 
|  | if (has_start && *bitrate.max_bitrate_bps < *bitrate.start_bitrate_bps) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "max_bitrate_bps < start_bitrate_bps"); | 
|  | } else if (has_min && *bitrate.max_bitrate_bps < *bitrate.min_bitrate_bps) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "max_bitrate_bps < min_bitrate_bps"); | 
|  | } else if (*bitrate.max_bitrate_bps < 0) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER, | 
|  | "max_bitrate_bps < 0"); | 
|  | } | 
|  | } | 
|  |  | 
|  | RTC_DCHECK(call_.get()); | 
|  | call_->SetClientBitratePreferences(bitrate); | 
|  |  | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReconfigureBandwidthEstimation( | 
|  | const BandwidthEstimationSettings& settings) { | 
|  | worker_thread()->PostTask(SafeTask(worker_thread_safety_, [this, settings]() { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | call_->GetTransportControllerSend()->ReconfigureBandwidthEstimation( | 
|  | settings); | 
|  | })); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetAudioPlayout(bool playout) { | 
|  | if (!worker_thread()->IsCurrent()) { | 
|  | worker_thread()->BlockingCall( | 
|  | [this, playout] { SetAudioPlayout(playout); }); | 
|  | return; | 
|  | } | 
|  | auto audio_state = context_->media_engine()->voice().GetAudioState(); | 
|  | audio_state->SetPlayout(playout); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetAudioRecording(bool recording) { | 
|  | if (!worker_thread()->IsCurrent()) { | 
|  | worker_thread()->BlockingCall( | 
|  | [this, recording] { SetAudioRecording(recording); }); | 
|  | return; | 
|  | } | 
|  | auto audio_state = context_->media_engine()->voice().GetAudioState(); | 
|  | audio_state->SetRecording(recording); | 
|  | } | 
|  |  | 
|  | void PeerConnection::AddAdaptationResource( | 
|  | rtc::scoped_refptr<Resource> resource) { | 
|  | if (!worker_thread()->IsCurrent()) { | 
|  | return worker_thread()->BlockingCall( | 
|  | [this, resource]() { return AddAdaptationResource(resource); }); | 
|  | } | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | if (!call_) { | 
|  | // The PeerConnection has been closed. | 
|  | return; | 
|  | } | 
|  | call_->AddAdaptationResource(resource); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::ConfiguredForMedia() const { | 
|  | return context_->media_engine(); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::StartRtcEventLog(std::unique_ptr<RtcEventLogOutput> output, | 
|  | int64_t output_period_ms) { | 
|  | return worker_thread()->BlockingCall( | 
|  | [this, output = std::move(output), output_period_ms]() mutable { | 
|  | return StartRtcEventLog_w(std::move(output), output_period_ms); | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::StartRtcEventLog( | 
|  | std::unique_ptr<RtcEventLogOutput> output) { | 
|  | int64_t output_period_ms = 5000; | 
|  | if (trials().IsDisabled("WebRTC-RtcEventLogNewFormat")) { | 
|  | output_period_ms = RtcEventLog::kImmediateOutput; | 
|  | } | 
|  | return StartRtcEventLog(std::move(output), output_period_ms); | 
|  | } | 
|  |  | 
|  | void PeerConnection::StopRtcEventLog() { | 
|  | worker_thread()->BlockingCall([this] { StopRtcEventLog_w(); }); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<DtlsTransportInterface> | 
|  | PeerConnection::LookupDtlsTransportByMid(const std::string& mid) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return transport_controller_->LookupDtlsTransportByMid(mid); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<DtlsTransport> | 
|  | PeerConnection::LookupDtlsTransportByMidInternal(const std::string& mid) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | // TODO(bugs.webrtc.org/9987): Avoid the thread jump. | 
|  | // This might be done by caching the value on the signaling thread. | 
|  | return network_thread()->BlockingCall([this, mid]() { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return transport_controller_->LookupDtlsTransportByMid(mid); | 
|  | }); | 
|  | } | 
|  |  | 
|  | rtc::scoped_refptr<SctpTransportInterface> PeerConnection::GetSctpTransport() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | if (!sctp_mid_n_) | 
|  | return nullptr; | 
|  |  | 
|  | return transport_controller_->GetSctpTransport(*sctp_mid_n_); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::local_description() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->local_description(); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::remote_description() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->remote_description(); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::current_local_description() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->current_local_description(); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::current_remote_description() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->current_remote_description(); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::pending_local_description() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->pending_local_description(); | 
|  | } | 
|  |  | 
|  | const SessionDescriptionInterface* PeerConnection::pending_remote_description() | 
|  | const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->pending_remote_description(); | 
|  | } | 
|  |  | 
|  | void PeerConnection::Close() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::Close"); | 
|  |  | 
|  | RTC_LOG_THREAD_BLOCK_COUNT(); | 
|  |  | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  | // Update stats here so that we have the most recent stats for tracks and | 
|  | // streams before the channels are closed. | 
|  | legacy_stats_->UpdateStats(kStatsOutputLevelStandard); | 
|  |  | 
|  | ice_connection_state_ = PeerConnectionInterface::kIceConnectionClosed; | 
|  | Observer()->OnIceConnectionChange(ice_connection_state_); | 
|  | standardized_ice_connection_state_ = | 
|  | PeerConnectionInterface::IceConnectionState::kIceConnectionClosed; | 
|  | connection_state_ = PeerConnectionInterface::PeerConnectionState::kClosed; | 
|  | Observer()->OnConnectionChange(connection_state_); | 
|  |  | 
|  | sdp_handler_->Close(); | 
|  |  | 
|  | NoteUsageEvent(UsageEvent::CLOSE_CALLED); | 
|  |  | 
|  | if (ConfiguredForMedia()) { | 
|  | for (const auto& transceiver : rtp_manager()->transceivers()->List()) { | 
|  | transceiver->internal()->SetPeerConnectionClosed(); | 
|  | if (!transceiver->stopped()) | 
|  | transceiver->StopInternal(); | 
|  | } | 
|  | } | 
|  | // Ensure that all asynchronous stats requests are completed before destroying | 
|  | // the transport controller below. | 
|  | if (stats_collector_) { | 
|  | stats_collector_->WaitForPendingRequest(); | 
|  | } | 
|  |  | 
|  | // Don't destroy BaseChannels until after stats has been cleaned up so that | 
|  | // the last stats request can still read from the channels. | 
|  | // TODO(tommi): The voice/video channels will be partially uninitialized on | 
|  | // the network thread (see `RtpTransceiver::ClearChannel`), partially on the | 
|  | // worker thread (see `PushNewMediaChannelAndDeleteChannel`) and then | 
|  | // eventually freed on the signaling thread. | 
|  | // It would be good to combine those steps with the teardown steps here. | 
|  | sdp_handler_->DestroyMediaChannels(); | 
|  |  | 
|  | // The event log is used in the transport controller, which must be outlived | 
|  | // by the former. CreateOffer by the peer connection is implemented | 
|  | // asynchronously and if the peer connection is closed without resetting the | 
|  | // WebRTC session description factory, the session description factory would | 
|  | // call the transport controller. | 
|  | sdp_handler_->ResetSessionDescFactory(); | 
|  | if (ConfiguredForMedia()) { | 
|  | rtp_manager_->Close(); | 
|  | } | 
|  |  | 
|  | network_thread()->BlockingCall([this] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | TeardownDataChannelTransport_n({}); | 
|  | transport_controller_.reset(); | 
|  | port_allocator_->DiscardCandidatePool(); | 
|  | if (network_thread_safety_) { | 
|  | network_thread_safety_->SetNotAlive(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | sctp_mid_s_.reset(); | 
|  | SetSctpTransportName(""); | 
|  |  | 
|  | worker_thread()->BlockingCall([this] { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | worker_thread_safety_->SetNotAlive(); | 
|  | call_.reset(); | 
|  | StopRtcEventLog_w(); | 
|  | }); | 
|  | ReportUsagePattern(); | 
|  | ReportCloseUsageMetrics(); | 
|  |  | 
|  | // Signal shutdown to the sdp handler. This invalidates weak pointers for | 
|  | // internal pending callbacks. | 
|  | sdp_handler_->PrepareForShutdown(); | 
|  | data_channel_controller_.PrepareForShutdown(); | 
|  |  | 
|  | // The .h file says that observer can be discarded after close() returns. | 
|  | // Make sure this is true. | 
|  | observer_ = nullptr; | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetIceConnectionState(IceConnectionState new_state) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (ice_connection_state_ == new_state) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // After transitioning to "closed", ignore any additional states from | 
|  | // TransportController (such as "disconnected"). | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | RTC_LOG(LS_INFO) << "Changing IceConnectionState " << ice_connection_state_ | 
|  | << " => " << new_state; | 
|  | RTC_DCHECK(ice_connection_state_ != | 
|  | PeerConnectionInterface::kIceConnectionClosed); | 
|  |  | 
|  | ice_connection_state_ = new_state; | 
|  | Observer()->OnIceConnectionChange(ice_connection_state_); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetStandardizedIceConnectionState( | 
|  | PeerConnectionInterface::IceConnectionState new_state) { | 
|  | if (standardized_ice_connection_state_ == new_state) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | RTC_LOG(LS_INFO) << "Changing standardized IceConnectionState " | 
|  | << standardized_ice_connection_state_ << " => " << new_state; | 
|  |  | 
|  | standardized_ice_connection_state_ = new_state; | 
|  | Observer()->OnStandardizedIceConnectionChange(new_state); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetConnectionState( | 
|  | PeerConnectionInterface::PeerConnectionState new_state) { | 
|  | if (connection_state_ == new_state) | 
|  | return; | 
|  | if (IsClosed()) | 
|  | return; | 
|  | connection_state_ = new_state; | 
|  | Observer()->OnConnectionChange(new_state); | 
|  |  | 
|  | // The first connection state change to connected happens once per | 
|  | // connection which makes it a good point to report metrics. | 
|  | if (new_state == PeerConnectionState::kConnected && !was_ever_connected_) { | 
|  | was_ever_connected_ = true; | 
|  | ReportFirstConnectUsageMetrics(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportFirstConnectUsageMetrics() { | 
|  | // Record bundle-policy from configuration. Done here from | 
|  | // connectionStateChange to limit to actually established connections. | 
|  | BundlePolicyUsage policy = kBundlePolicyUsageMax; | 
|  | switch (configuration_.bundle_policy) { | 
|  | case kBundlePolicyBalanced: | 
|  | policy = kBundlePolicyUsageBalanced; | 
|  | break; | 
|  | case kBundlePolicyMaxBundle: | 
|  | policy = kBundlePolicyUsageMaxBundle; | 
|  | break; | 
|  | case kBundlePolicyMaxCompat: | 
|  | policy = kBundlePolicyUsageMaxCompat; | 
|  | break; | 
|  | } | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.BundlePolicy", policy, | 
|  | kBundlePolicyUsageMax); | 
|  |  | 
|  | // Record whether there was a local or remote provisional answer. | 
|  | ProvisionalAnswerUsage pranswer = kProvisionalAnswerNotUsed; | 
|  | if (local_description()->GetType() == SdpType::kPrAnswer) { | 
|  | pranswer = kProvisionalAnswerLocal; | 
|  | } else if (remote_description()->GetType() == SdpType::kPrAnswer) { | 
|  | pranswer = kProvisionalAnswerRemote; | 
|  | } | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.ProvisionalAnswer", pranswer, | 
|  | kProvisionalAnswerMax); | 
|  |  | 
|  | auto transport_infos = remote_description()->description()->transport_infos(); | 
|  | if (!transport_infos.empty()) { | 
|  | // Record the number of valid / invalid ice-ufrag. We do allow certain | 
|  | // non-spec ice-char for backward-compat reasons. At this point we know | 
|  | // that the ufrag/pwd consists of a valid ice-char or one of the four | 
|  | // not allowed characters since we have passed the IsIceChar check done | 
|  | // by the p2p transport description on setRemoteDescription calls. | 
|  | auto ice_parameters = transport_infos[0].description.GetIceParameters(); | 
|  | auto is_invalid_char = [](char c) { | 
|  | return c == '-' || c == '=' || c == '#' || c == '_'; | 
|  | }; | 
|  | bool isUsingInvalidIceCharInUfrag = | 
|  | absl::c_any_of(ice_parameters.ufrag, is_invalid_char); | 
|  | bool isUsingInvalidIceCharInPwd = | 
|  | absl::c_any_of(ice_parameters.pwd, is_invalid_char); | 
|  | RTC_HISTOGRAM_BOOLEAN( | 
|  | "WebRTC.PeerConnection.ValidIceChars", | 
|  | !(isUsingInvalidIceCharInUfrag || isUsingInvalidIceCharInPwd)); | 
|  |  | 
|  | // Record whether the hash algorithm of the first transport's | 
|  | // DTLS fingerprint is still using SHA-1. | 
|  | if (transport_infos[0].description.identity_fingerprint) { | 
|  | RTC_HISTOGRAM_BOOLEAN( | 
|  | "WebRTC.PeerConnection.DtlsFingerprintLegacySha1", | 
|  | absl::EqualsIgnoreCase( | 
|  | transport_infos[0].description.identity_fingerprint->algorithm, | 
|  | "sha-1")); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Record RtcpMuxPolicy setting. | 
|  | RtcpMuxPolicyUsage rtcp_mux_policy = kRtcpMuxPolicyUsageMax; | 
|  | switch (configuration_.rtcp_mux_policy) { | 
|  | case kRtcpMuxPolicyNegotiate: | 
|  | rtcp_mux_policy = kRtcpMuxPolicyUsageNegotiate; | 
|  | break; | 
|  | case kRtcpMuxPolicyRequire: | 
|  | rtcp_mux_policy = kRtcpMuxPolicyUsageRequire; | 
|  | break; | 
|  | } | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.RtcpMuxPolicy", | 
|  | rtcp_mux_policy, kRtcpMuxPolicyUsageMax); | 
|  | switch (local_description()->GetType()) { | 
|  | case SdpType::kOffer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.Offer.ConnectionEstablished", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kAnswer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.Answer.ConnectionEstablished", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kPrAnswer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.PrAnswer.ConnectionEstablished", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kRollback: | 
|  | // Rollback does not have SDP so can not be munged. | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportCloseUsageMetrics() { | 
|  | if (!was_ever_connected_) { | 
|  | return; | 
|  | } | 
|  | RTC_DCHECK(local_description()); | 
|  | RTC_DCHECK(sdp_handler_); | 
|  | switch (local_description()->GetType()) { | 
|  | case SdpType::kOffer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.Offer.ConnectionClosed", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kAnswer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.Answer.ConnectionClosed", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kPrAnswer: | 
|  | RTC_HISTOGRAM_ENUMERATION( | 
|  | "WebRTC.PeerConnection.SdpMunging.PrAnswer.ConnectionClosed", | 
|  | sdp_handler_->sdp_munging_type(), SdpMungingType::kMaxValue); | 
|  | break; | 
|  | case SdpType::kRollback: | 
|  | // Rollback does not have SDP so can not be munged. | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnIceGatheringChange( | 
|  | PeerConnectionInterface::IceGatheringState new_state) { | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  | ice_gathering_state_ = new_state; | 
|  | Observer()->OnIceGatheringChange(ice_gathering_state_); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnIceCandidate( | 
|  | std::unique_ptr<IceCandidateInterface> candidate) { | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  | ReportIceCandidateCollected(candidate->candidate()); | 
|  | ClearStatsCache(); | 
|  | Observer()->OnIceCandidate(candidate.get()); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnIceCandidateError(const std::string& address, | 
|  | int port, | 
|  | const std::string& url, | 
|  | int error_code, | 
|  | const std::string& error_text) { | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  | Observer()->OnIceCandidateError(address, port, url, error_code, error_text); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnIceCandidatesRemoved( | 
|  | const std::vector<Candidate>& candidates) { | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  | Observer()->OnIceCandidatesRemoved(candidates); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnSelectedCandidatePairChanged( | 
|  | const cricket::CandidatePairChangeEvent& event) { | 
|  | if (IsClosed()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (event.selected_candidate_pair.local_candidate().is_local() && | 
|  | event.selected_candidate_pair.remote_candidate().is_local()) { | 
|  | NoteUsageEvent(UsageEvent::DIRECT_CONNECTION_SELECTED); | 
|  | } | 
|  |  | 
|  | Observer()->OnIceSelectedCandidatePairChanged(event); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::CreateDataChannelTransport(absl::string_view mid) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(!sctp_mid().has_value() || mid == sctp_mid().value()); | 
|  | RTC_LOG(LS_INFO) << "Creating data channel, mid=" << mid; | 
|  |  | 
|  | std::optional<std::string> transport_name = | 
|  | network_thread()->BlockingCall([&] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return SetupDataChannelTransport_n(mid); | 
|  | }); | 
|  | if (!transport_name) | 
|  | return false; | 
|  |  | 
|  | sctp_mid_s_ = std::string(mid); | 
|  | SetSctpTransportName(transport_name.value()); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void PeerConnection::DestroyDataChannelTransport(RTCError error) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | network_thread()->BlockingCall([&] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | TeardownDataChannelTransport_n(error); | 
|  | }); | 
|  | sctp_mid_s_.reset(); | 
|  | SetSctpTransportName(""); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnSctpDataChannelStateChanged( | 
|  | int channel_id, | 
|  | DataChannelInterface::DataState state) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (stats_collector_) | 
|  | stats_collector_->OnSctpDataChannelStateChanged(channel_id, state); | 
|  | } | 
|  |  | 
|  | PeerConnection::InitializePortAllocatorResult | 
|  | PeerConnection::InitializePortAllocator_n( | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers, | 
|  | const RTCConfiguration& configuration) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  |  | 
|  | port_allocator_->Initialize(); | 
|  | // To handle both internal and externally created port allocator, we will | 
|  | // enable BUNDLE here. | 
|  | int port_allocator_flags = port_allocator_->flags(); | 
|  | port_allocator_flags |= PORTALLOCATOR_ENABLE_SHARED_SOCKET | | 
|  | PORTALLOCATOR_ENABLE_IPV6 | | 
|  | PORTALLOCATOR_ENABLE_IPV6_ON_WIFI; | 
|  | if (trials().IsDisabled("WebRTC-IPv6Default")) { | 
|  | port_allocator_flags &= ~(PORTALLOCATOR_ENABLE_IPV6); | 
|  | } | 
|  | if (configuration.disable_ipv6_on_wifi) { | 
|  | port_allocator_flags &= ~(PORTALLOCATOR_ENABLE_IPV6_ON_WIFI); | 
|  | RTC_LOG(LS_INFO) << "IPv6 candidates on Wi-Fi are disabled."; | 
|  | } | 
|  |  | 
|  | if (configuration.tcp_candidate_policy == kTcpCandidatePolicyDisabled) { | 
|  | port_allocator_flags |= PORTALLOCATOR_DISABLE_TCP; | 
|  | RTC_LOG(LS_INFO) << "TCP candidates are disabled."; | 
|  | } | 
|  |  | 
|  | if (configuration.candidate_network_policy == | 
|  | kCandidateNetworkPolicyLowCost) { | 
|  | port_allocator_flags |= PORTALLOCATOR_DISABLE_COSTLY_NETWORKS; | 
|  | RTC_LOG(LS_INFO) << "Do not gather candidates on high-cost networks"; | 
|  | } | 
|  |  | 
|  | if (configuration.disable_link_local_networks) { | 
|  | port_allocator_flags |= PORTALLOCATOR_DISABLE_LINK_LOCAL_NETWORKS; | 
|  | RTC_LOG(LS_INFO) << "Disable candidates on link-local network interfaces."; | 
|  | } | 
|  |  | 
|  | port_allocator_->set_flags(port_allocator_flags); | 
|  | // No step delay is used while allocating ports. | 
|  | port_allocator_->set_step_delay(kMinimumStepDelay); | 
|  | port_allocator_->SetCandidateFilter( | 
|  | ConvertIceTransportTypeToCandidateFilter(configuration.type)); | 
|  | port_allocator_->set_max_ipv6_networks(configuration.max_ipv6_networks); | 
|  |  | 
|  | auto turn_servers_copy = turn_servers; | 
|  | for (auto& turn_server : turn_servers_copy) { | 
|  | turn_server.tls_cert_verifier = tls_cert_verifier_.get(); | 
|  | } | 
|  | // Call this last since it may create pooled allocator sessions using the | 
|  | // properties set above. | 
|  | port_allocator_->SetConfiguration( | 
|  | stun_servers, std::move(turn_servers_copy), | 
|  | configuration.ice_candidate_pool_size, | 
|  | configuration.GetTurnPortPrunePolicy(), configuration.turn_customizer, | 
|  | configuration.stun_candidate_keepalive_interval); | 
|  |  | 
|  | InitializePortAllocatorResult res; | 
|  | res.enable_ipv6 = port_allocator_flags & PORTALLOCATOR_ENABLE_IPV6; | 
|  | return res; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::ReconfigurePortAllocator_n( | 
|  | const cricket::ServerAddresses& stun_servers, | 
|  | const std::vector<RelayServerConfig>& turn_servers, | 
|  | IceTransportsType type, | 
|  | int candidate_pool_size, | 
|  | PortPrunePolicy turn_port_prune_policy, | 
|  | TurnCustomizer* turn_customizer, | 
|  | std::optional<int> stun_candidate_keepalive_interval, | 
|  | bool have_local_description) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | port_allocator_->SetCandidateFilter( | 
|  | ConvertIceTransportTypeToCandidateFilter(type)); | 
|  | // Add the custom tls turn servers if they exist. | 
|  | auto turn_servers_copy = turn_servers; | 
|  | for (auto& turn_server : turn_servers_copy) { | 
|  | turn_server.tls_cert_verifier = tls_cert_verifier_.get(); | 
|  | } | 
|  | // Call this last since it may create pooled allocator sessions using the | 
|  | // candidate filter set above. | 
|  | return port_allocator_->SetConfiguration( | 
|  | stun_servers, std::move(turn_servers_copy), candidate_pool_size, | 
|  | turn_port_prune_policy, turn_customizer, | 
|  | stun_candidate_keepalive_interval); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::StartRtcEventLog_w( | 
|  | std::unique_ptr<RtcEventLogOutput> output, | 
|  | int64_t output_period_ms) { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | if (!worker_thread_safety_->alive()) { | 
|  | return false; | 
|  | } | 
|  | return env_.event_log().StartLogging(std::move(output), output_period_ms); | 
|  | } | 
|  |  | 
|  | void PeerConnection::StopRtcEventLog_w() { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | env_.event_log().StopLogging(); | 
|  | } | 
|  |  | 
|  | std::optional<SSLRole> PeerConnection::GetSctpSslRole_n() { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return sctp_mid_n_ ? transport_controller_->GetDtlsRole(*sctp_mid_n_) | 
|  | : std::nullopt; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::GetSslRole(const std::string& content_name, | 
|  | SSLRole* role) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (!local_description() || !remote_description()) { | 
|  | RTC_LOG(LS_INFO) | 
|  | << "Local and Remote descriptions must be applied to get the " | 
|  | "SSL Role of the session."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto dtls_role = network_thread()->BlockingCall([this, content_name]() { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return transport_controller_->GetDtlsRole(content_name); | 
|  | }); | 
|  | if (dtls_role) { | 
|  | *role = *dtls_role; | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::GetTransportDescription( | 
|  | const SessionDescription* description, | 
|  | const std::string& content_name, | 
|  | cricket::TransportDescription* tdesc) { | 
|  | if (!description || !tdesc) { | 
|  | return false; | 
|  | } | 
|  | const TransportInfo* transport_info = | 
|  | description->GetTransportInfoByName(content_name); | 
|  | if (!transport_info) { | 
|  | return false; | 
|  | } | 
|  | *tdesc = transport_info->description; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<DataChannelStats> PeerConnection::GetDataChannelStats() const { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return data_channel_controller_.GetDataChannelStats(); | 
|  | } | 
|  |  | 
|  | std::optional<std::string> PeerConnection::sctp_transport_name() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (sctp_mid_s_ && transport_controller_copy_) | 
|  | return sctp_transport_name_s_; | 
|  | return std::optional<std::string>(); | 
|  | } | 
|  |  | 
|  | void PeerConnection::SetSctpTransportName(std::string sctp_transport_name) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | sctp_transport_name_s_ = std::move(sctp_transport_name); | 
|  | ClearStatsCache(); | 
|  | } | 
|  |  | 
|  | std::optional<std::string> PeerConnection::sctp_mid() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sctp_mid_s_; | 
|  | } | 
|  |  | 
|  | cricket::CandidateStatsList PeerConnection::GetPooledCandidateStats() const { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | if (!network_thread_safety_->alive()) | 
|  | return {}; | 
|  | cricket::CandidateStatsList candidate_stats_list; | 
|  | port_allocator_->GetCandidateStatsFromPooledSessions(&candidate_stats_list); | 
|  | return candidate_stats_list; | 
|  | } | 
|  |  | 
|  | std::map<std::string, cricket::TransportStats> | 
|  | PeerConnection::GetTransportStatsByNames( | 
|  | const std::set<std::string>& transport_names) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::GetTransportStatsByNames"); | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | if (!network_thread_safety_->alive()) | 
|  | return {}; | 
|  |  | 
|  | rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; | 
|  | std::map<std::string, cricket::TransportStats> transport_stats_by_name; | 
|  | for (const std::string& transport_name : transport_names) { | 
|  | cricket::TransportStats transport_stats; | 
|  | bool success = | 
|  | transport_controller_->GetStats(transport_name, &transport_stats); | 
|  | if (success) { | 
|  | transport_stats_by_name[transport_name] = std::move(transport_stats); | 
|  | } else { | 
|  | RTC_LOG(LS_ERROR) << "Failed to get transport stats for transport_name=" | 
|  | << transport_name; | 
|  | } | 
|  | } | 
|  | return transport_stats_by_name; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::GetLocalCertificate( | 
|  | const std::string& transport_name, | 
|  | rtc::scoped_refptr<RTCCertificate>* certificate) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | if (!network_thread_safety_->alive() || !certificate) { | 
|  | return false; | 
|  | } | 
|  | *certificate = transport_controller_->GetLocalCertificate(transport_name); | 
|  | return *certificate != nullptr; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<rtc::SSLCertChain> PeerConnection::GetRemoteSSLCertChain( | 
|  | const std::string& transport_name) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return transport_controller_->GetRemoteSSLCertChain(transport_name); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::IceRestartPending(const std::string& content_name) const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->IceRestartPending(content_name); | 
|  | } | 
|  |  | 
|  | bool PeerConnection::NeedsIceRestart(const std::string& content_name) const { | 
|  | return network_thread()->BlockingCall([this, &content_name] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return transport_controller_->NeedsIceRestart(content_name); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerConnectionState( | 
|  | cricket::IceConnectionState state) { | 
|  | switch (state) { | 
|  | case cricket::kIceConnectionConnecting: | 
|  | // If the current state is Connected or Completed, then there were | 
|  | // writable channels but now there are not, so the next state must | 
|  | // be Disconnected. | 
|  | // kIceConnectionConnecting is currently used as the default, | 
|  | // un-connected state by the TransportController, so its only use is | 
|  | // detecting disconnections. | 
|  | if (ice_connection_state_ == | 
|  | PeerConnectionInterface::kIceConnectionConnected || | 
|  | ice_connection_state_ == | 
|  | PeerConnectionInterface::kIceConnectionCompleted) { | 
|  | SetIceConnectionState( | 
|  | PeerConnectionInterface::kIceConnectionDisconnected); | 
|  | } | 
|  | break; | 
|  | case cricket::kIceConnectionFailed: | 
|  | SetIceConnectionState(PeerConnectionInterface::kIceConnectionFailed); | 
|  | break; | 
|  | case cricket::kIceConnectionConnected: | 
|  | RTC_LOG(LS_INFO) << "Changing to ICE connected state because " | 
|  | "all transports are writable."; | 
|  | { | 
|  | std::vector<RtpTransceiverProxyRefPtr> transceivers; | 
|  | if (ConfiguredForMedia()) { | 
|  | transceivers = rtp_manager()->transceivers()->List(); | 
|  | } | 
|  |  | 
|  | network_thread()->PostTask( | 
|  | SafeTask(network_thread_safety_, | 
|  | [this, transceivers = std::move(transceivers)] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | ReportTransportStats(std::move(transceivers)); | 
|  | })); | 
|  | } | 
|  |  | 
|  | SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected); | 
|  | NoteUsageEvent(UsageEvent::ICE_STATE_CONNECTED); | 
|  | break; | 
|  | case cricket::kIceConnectionCompleted: | 
|  | RTC_LOG(LS_INFO) << "Changing to ICE completed state because " | 
|  | "all transports are complete."; | 
|  | if (ice_connection_state_ != | 
|  | PeerConnectionInterface::kIceConnectionConnected) { | 
|  | // If jumping directly from "checking" to "connected", | 
|  | // signal "connected" first. | 
|  | SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected); | 
|  | } | 
|  | SetIceConnectionState(PeerConnectionInterface::kIceConnectionCompleted); | 
|  |  | 
|  | NoteUsageEvent(UsageEvent::ICE_STATE_CONNECTED); | 
|  | break; | 
|  | default: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerCandidatesGathered( | 
|  | const std::string& transport_name, | 
|  | const cricket::Candidates& candidates) { | 
|  | // TODO(bugs.webrtc.org/12427): Expect this to come in on the network thread | 
|  | // (not signaling as it currently does), handle appropriately. | 
|  | int sdp_mline_index; | 
|  | if (!GetLocalCandidateMediaIndex(transport_name, &sdp_mline_index)) { | 
|  | RTC_LOG(LS_ERROR) | 
|  | << "OnTransportControllerCandidatesGathered: content name " | 
|  | << transport_name << " not found"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (cricket::Candidates::const_iterator citer = candidates.begin(); | 
|  | citer != candidates.end(); ++citer) { | 
|  | // Use transport_name as the candidate media id. | 
|  | std::unique_ptr<JsepIceCandidate> candidate( | 
|  | new JsepIceCandidate(transport_name, sdp_mline_index, *citer)); | 
|  | sdp_handler_->AddLocalIceCandidate(candidate.get()); | 
|  | OnIceCandidate(std::move(candidate)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerCandidateError( | 
|  | const cricket::IceCandidateErrorEvent& event) { | 
|  | OnIceCandidateError(event.address, event.port, event.url, event.error_code, | 
|  | event.error_text); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerCandidatesRemoved( | 
|  | const std::vector<Candidate>& candidates) { | 
|  | // Sanity check. | 
|  | for (const cricket::Candidate& candidate : candidates) { | 
|  | if (candidate.transport_name().empty()) { | 
|  | RTC_LOG(LS_ERROR) << "OnTransportControllerCandidatesRemoved: " | 
|  | "empty content name in candidate " | 
|  | << candidate.ToString(); | 
|  | return; | 
|  | } | 
|  | } | 
|  | sdp_handler_->RemoveLocalIceCandidates(candidates); | 
|  | OnIceCandidatesRemoved(candidates); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerCandidateChanged( | 
|  | const cricket::CandidatePairChangeEvent& event) { | 
|  | OnSelectedCandidatePairChanged(event); | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerDtlsHandshakeError( | 
|  | SSLHandshakeError error) { | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.DtlsHandshakeError", | 
|  | static_cast<int>(error), | 
|  | static_cast<int>(SSLHandshakeError::MAX_VALUE)); | 
|  | } | 
|  |  | 
|  | // Returns the media index for a local ice candidate given the content name. | 
|  | bool PeerConnection::GetLocalCandidateMediaIndex( | 
|  | const std::string& content_name, | 
|  | int* sdp_mline_index) { | 
|  | if (!local_description() || !sdp_mline_index) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool content_found = false; | 
|  | const ContentInfos& contents = local_description()->description()->contents(); | 
|  | for (size_t index = 0; index < contents.size(); ++index) { | 
|  | if (contents[index].mid() == content_name) { | 
|  | *sdp_mline_index = static_cast<int>(index); | 
|  | content_found = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | return content_found; | 
|  | } | 
|  |  | 
|  | Call::Stats PeerConnection::GetCallStats() { | 
|  | if (!worker_thread()->IsCurrent()) { | 
|  | return worker_thread()->BlockingCall([this] { return GetCallStats(); }); | 
|  | } | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; | 
|  | if (call_) { | 
|  | return call_->GetStats(); | 
|  | } else { | 
|  | return Call::Stats(); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::optional<AudioDeviceModule::Stats> PeerConnection::GetAudioDeviceStats() { | 
|  | if (context_->media_engine()) { | 
|  | return context_->media_engine()->voice().GetAudioDeviceStats(); | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | std::optional<std::string> PeerConnection::SetupDataChannelTransport_n( | 
|  | absl::string_view mid) { | 
|  | sctp_mid_n_ = std::string(mid); | 
|  | DataChannelTransportInterface* transport = | 
|  | transport_controller_->GetDataChannelTransport(*sctp_mid_n_); | 
|  | if (!transport) { | 
|  | RTC_LOG(LS_ERROR) | 
|  | << "Data channel transport is not available for data channels, mid=" | 
|  | << mid; | 
|  | sctp_mid_n_ = std::nullopt; | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | std::optional<std::string> transport_name; | 
|  | cricket::DtlsTransportInternal* dtls_transport = | 
|  | transport_controller_->GetDtlsTransport(*sctp_mid_n_); | 
|  | if (dtls_transport) { | 
|  | transport_name = dtls_transport->transport_name(); | 
|  | } else { | 
|  | // Make sure we still set a valid string. | 
|  | transport_name = std::string(""); | 
|  | } | 
|  |  | 
|  | data_channel_controller_.SetupDataChannelTransport_n(transport); | 
|  |  | 
|  | return transport_name; | 
|  | } | 
|  |  | 
|  | void PeerConnection::TeardownDataChannelTransport_n(RTCError error) { | 
|  | if (sctp_mid_n_) { | 
|  | // `sctp_mid_` may still be active through an SCTP transport.  If not, unset | 
|  | // it. | 
|  | RTC_LOG(LS_INFO) << "Tearing down data channel transport for mid=" | 
|  | << *sctp_mid_n_; | 
|  | sctp_mid_n_.reset(); | 
|  | } | 
|  |  | 
|  | data_channel_controller_.TeardownDataChannelTransport_n(error); | 
|  | } | 
|  |  | 
|  | // Returns false if bundle is enabled and rtcp_mux is disabled. | 
|  | bool PeerConnection::ValidateBundleSettings( | 
|  | const SessionDescription* desc, | 
|  | const std::map<std::string, const ContentGroup*>& bundle_groups_by_mid) { | 
|  | if (bundle_groups_by_mid.empty()) | 
|  | return true; | 
|  |  | 
|  | const cricket::ContentInfos& contents = desc->contents(); | 
|  | for (cricket::ContentInfos::const_iterator citer = contents.begin(); | 
|  | citer != contents.end(); ++citer) { | 
|  | const ContentInfo* content = (&*citer); | 
|  | RTC_DCHECK(content != NULL); | 
|  | auto it = bundle_groups_by_mid.find(content->mid()); | 
|  | if (it != bundle_groups_by_mid.end() && | 
|  | !(content->rejected || content->bundle_only) && | 
|  | content->type == MediaProtocolType::kRtp) { | 
|  | if (!HasRtcpMuxEnabled(content)) | 
|  | return false; | 
|  | } | 
|  | } | 
|  | // RTCP-MUX is enabled in all the contents. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportSdpBundleUsage( | 
|  | const SessionDescriptionInterface& remote_description) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  |  | 
|  | bool using_bundle = | 
|  | remote_description.description()->HasGroup(cricket::GROUP_TYPE_BUNDLE); | 
|  | int num_audio_mlines = 0; | 
|  | int num_video_mlines = 0; | 
|  | int num_data_mlines = 0; | 
|  | for (const ContentInfo& content : | 
|  | remote_description.description()->contents()) { | 
|  | cricket::MediaType media_type = content.media_description()->type(); | 
|  | if (media_type == cricket::MEDIA_TYPE_AUDIO) { | 
|  | num_audio_mlines += 1; | 
|  | } else if (media_type == cricket::MEDIA_TYPE_VIDEO) { | 
|  | num_video_mlines += 1; | 
|  | } else if (media_type == cricket::MEDIA_TYPE_DATA) { | 
|  | num_data_mlines += 1; | 
|  | } | 
|  | } | 
|  | bool simple = num_audio_mlines <= 1 && num_video_mlines <= 1; | 
|  | BundleUsage usage = kBundleUsageMax; | 
|  | if (num_audio_mlines == 0 && num_video_mlines == 0) { | 
|  | if (num_data_mlines > 0) { | 
|  | usage = using_bundle ? kBundleUsageBundleDatachannelOnly | 
|  | : kBundleUsageNoBundleDatachannelOnly; | 
|  | } else { | 
|  | usage = kBundleUsageEmpty; | 
|  | } | 
|  | } else if (configuration_.sdp_semantics == SdpSemantics::kPlanB_DEPRECATED) { | 
|  | // In plan-b, simple/complex usage will not show up in the number of | 
|  | // m-lines or BUNDLE. | 
|  | usage = using_bundle ? kBundleUsageBundlePlanB : kBundleUsageNoBundlePlanB; | 
|  | } else { | 
|  | if (simple) { | 
|  | usage = | 
|  | using_bundle ? kBundleUsageBundleSimple : kBundleUsageNoBundleSimple; | 
|  | } else { | 
|  | usage = using_bundle ? kBundleUsageBundleComplex | 
|  | : kBundleUsageNoBundleComplex; | 
|  | } | 
|  | } | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.BundleUsage", usage, | 
|  | kBundleUsageMax); | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportIceCandidateCollected(const Candidate& candidate) { | 
|  | NoteUsageEvent(UsageEvent::CANDIDATE_COLLECTED); | 
|  | if (candidate.address().IsPrivateIP()) { | 
|  | NoteUsageEvent(UsageEvent::PRIVATE_CANDIDATE_COLLECTED); | 
|  | } | 
|  | if (candidate.address().IsUnresolvedIP()) { | 
|  | NoteUsageEvent(UsageEvent::MDNS_CANDIDATE_COLLECTED); | 
|  | } | 
|  | if (candidate.address().family() == AF_INET6) { | 
|  | NoteUsageEvent(UsageEvent::IPV6_CANDIDATE_COLLECTED); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PeerConnection::NoteUsageEvent(UsageEvent event) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | usage_pattern_.NoteUsageEvent(event); | 
|  | } | 
|  |  | 
|  | // Asynchronously adds remote candidates on the network thread. | 
|  | void PeerConnection::AddRemoteCandidate(absl::string_view mid, | 
|  | const Candidate& candidate) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  |  | 
|  | if (candidate.network_type() != rtc::ADAPTER_TYPE_UNKNOWN) { | 
|  | RTC_DLOG(LS_WARNING) << "Using candidate with adapter type set - this " | 
|  | "should only happen in test"; | 
|  | } | 
|  |  | 
|  | // Clear fields that do not make sense as remote candidates. | 
|  | Candidate new_candidate(candidate); | 
|  | new_candidate.set_network_type(rtc::ADAPTER_TYPE_UNKNOWN); | 
|  | new_candidate.set_relay_protocol(""); | 
|  | new_candidate.set_underlying_type_for_vpn(rtc::ADAPTER_TYPE_UNKNOWN); | 
|  |  | 
|  | network_thread()->PostTask(SafeTask( | 
|  | network_thread_safety_, | 
|  | [this, mid = std::string(mid), candidate = new_candidate] { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | std::vector<Candidate> candidates = {candidate}; | 
|  | RTCError error = | 
|  | transport_controller_->AddRemoteCandidates(mid, candidates); | 
|  | if (error.ok()) { | 
|  | signaling_thread()->PostTask(SafeTask( | 
|  | signaling_thread_safety_.flag(), | 
|  | [this, candidate = std::move(candidate)] { | 
|  | ReportRemoteIceCandidateAdded(candidate); | 
|  | // Candidates successfully submitted for checking. | 
|  | if (ice_connection_state() == | 
|  | PeerConnectionInterface::kIceConnectionNew || | 
|  | ice_connection_state() == | 
|  | PeerConnectionInterface::kIceConnectionDisconnected) { | 
|  | // If state is New, then the session has just gotten its first | 
|  | // remote ICE candidates, so go to Checking. If state is | 
|  | // Disconnected, the session is re-using old candidates or | 
|  | // receiving additional ones, so go to Checking. If state is | 
|  | // Connected, stay Connected. | 
|  | // TODO(bemasc): If state is Connected, and the new candidates | 
|  | // are for a newly added transport, then the state actually | 
|  | // _should_ move to checking.  Add a way to distinguish that | 
|  | // case. | 
|  | SetIceConnectionState( | 
|  | PeerConnectionInterface::kIceConnectionChecking); | 
|  | } | 
|  | // TODO(bemasc): If state is Completed, go back to Connected. | 
|  | })); | 
|  | } else { | 
|  | RTC_LOG(LS_WARNING) << error.message(); | 
|  | } | 
|  | })); | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportUsagePattern() const { | 
|  | usage_pattern_.ReportUsagePattern(observer_); | 
|  | } | 
|  |  | 
|  | void PeerConnection::ReportRemoteIceCandidateAdded(const Candidate& candidate) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  |  | 
|  | NoteUsageEvent(UsageEvent::REMOTE_CANDIDATE_ADDED); | 
|  |  | 
|  | if (candidate.address().IsPrivateIP()) { | 
|  | NoteUsageEvent(UsageEvent::REMOTE_PRIVATE_CANDIDATE_ADDED); | 
|  | } | 
|  | if (candidate.address().IsUnresolvedIP()) { | 
|  | NoteUsageEvent(UsageEvent::REMOTE_MDNS_CANDIDATE_ADDED); | 
|  | } | 
|  | if (candidate.address().family() == AF_INET6) { | 
|  | NoteUsageEvent(UsageEvent::REMOTE_IPV6_CANDIDATE_ADDED); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool PeerConnection::SrtpRequired() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return dtls_enabled_; | 
|  | } | 
|  |  | 
|  | void PeerConnection::OnTransportControllerGatheringState( | 
|  | ::webrtc::IceGatheringState state) { | 
|  | RTC_DCHECK(signaling_thread()->IsCurrent()); | 
|  | if (state == ::webrtc::kIceGatheringGathering) { | 
|  | OnIceGatheringChange(PeerConnectionInterface::kIceGatheringGathering); | 
|  | } else if (state == ::webrtc::kIceGatheringComplete) { | 
|  | OnIceGatheringChange(PeerConnectionInterface::kIceGatheringComplete); | 
|  | } else if (state == ::webrtc::kIceGatheringNew) { | 
|  | OnIceGatheringChange(PeerConnectionInterface::kIceGatheringNew); | 
|  | } else { | 
|  | RTC_LOG(LS_ERROR) << "Unknown state received: " << state; | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Runs on network_thread(). | 
|  | void PeerConnection::ReportTransportStats( | 
|  | std::vector<RtpTransceiverProxyRefPtr> transceivers) { | 
|  | TRACE_EVENT0("webrtc", "PeerConnection::ReportTransportStats"); | 
|  | rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; | 
|  | std::map<std::string, std::set<cricket::MediaType>> | 
|  | media_types_by_transport_name; | 
|  | for (const auto& transceiver : transceivers) { | 
|  | if (transceiver->internal()->channel()) { | 
|  | std::string transport_name( | 
|  | transceiver->internal()->channel()->transport_name()); | 
|  | media_types_by_transport_name[transport_name].insert( | 
|  | transceiver->media_type()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (sctp_mid_n_) { | 
|  | cricket::DtlsTransportInternal* dtls_transport = | 
|  | transport_controller_->GetDtlsTransport(*sctp_mid_n_); | 
|  | if (dtls_transport) { | 
|  | media_types_by_transport_name[dtls_transport->transport_name()].insert( | 
|  | cricket::MEDIA_TYPE_DATA); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (const auto& entry : media_types_by_transport_name) { | 
|  | const std::string& transport_name = entry.first; | 
|  | const std::set<cricket::MediaType> media_types = entry.second; | 
|  | cricket::TransportStats stats; | 
|  | if (transport_controller_->GetStats(transport_name, &stats)) { | 
|  | ReportBestConnectionState(stats); | 
|  | ReportNegotiatedCiphers(dtls_enabled_, stats, media_types); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Walk through the ConnectionInfos to gather best connection usage | 
|  | // for IPv4 and IPv6. | 
|  | // static (no member state required) | 
|  | void PeerConnection::ReportBestConnectionState( | 
|  | const cricket::TransportStats& stats) { | 
|  | for (const cricket::TransportChannelStats& channel_stats : | 
|  | stats.channel_stats) { | 
|  | for (const cricket::ConnectionInfo& connection_info : | 
|  | channel_stats.ice_transport_stats.connection_infos) { | 
|  | if (!connection_info.best_connection) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const Candidate& local = connection_info.local_candidate; | 
|  | const Candidate& remote = connection_info.remote_candidate; | 
|  |  | 
|  | // Increment the counter for IceCandidatePairType. | 
|  | if (local.protocol() == cricket::TCP_PROTOCOL_NAME || | 
|  | (local.is_relay() && | 
|  | local.relay_protocol() == cricket::TCP_PROTOCOL_NAME)) { | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_TCP", | 
|  | GetIceCandidatePairCounter(local, remote), | 
|  | kIceCandidatePairMax); | 
|  | } else if (local.protocol() == cricket::UDP_PROTOCOL_NAME) { | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_UDP", | 
|  | GetIceCandidatePairCounter(local, remote), | 
|  | kIceCandidatePairMax); | 
|  | } else { | 
|  | RTC_CHECK_NOTREACHED(); | 
|  | } | 
|  |  | 
|  | // Increment the counter for IP type. | 
|  | if (local.address().family() == AF_INET) { | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics", | 
|  | kBestConnections_IPv4, | 
|  | kPeerConnectionAddressFamilyCounter_Max); | 
|  | } else if (local.address().family() == AF_INET6) { | 
|  | RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IPMetrics", | 
|  | kBestConnections_IPv6, | 
|  | kPeerConnectionAddressFamilyCounter_Max); | 
|  | } else { | 
|  | RTC_CHECK(!local.address().hostname().empty() && | 
|  | local.address().IsUnresolvedIP()); | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void PeerConnection::ReportNegotiatedCiphers( | 
|  | bool dtls_enabled, | 
|  | const cricket::TransportStats& stats, | 
|  | const std::set<cricket::MediaType>& media_types) { | 
|  | if (!dtls_enabled || stats.channel_stats.empty()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | int srtp_crypto_suite = stats.channel_stats[0].srtp_crypto_suite; | 
|  | int ssl_cipher_suite = stats.channel_stats[0].ssl_cipher_suite; | 
|  | if (srtp_crypto_suite == kSrtpInvalidCryptoSuite && | 
|  | ssl_cipher_suite == kTlsNullWithNullNull) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ssl_cipher_suite != kTlsNullWithNullNull) { | 
|  | for (cricket::MediaType media_type : media_types) { | 
|  | switch (media_type) { | 
|  | case cricket::MEDIA_TYPE_AUDIO: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslCipherSuite.Audio", ssl_cipher_suite, | 
|  | kSslCipherSuiteMaxValue); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_VIDEO: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslCipherSuite.Video", ssl_cipher_suite, | 
|  | kSslCipherSuiteMaxValue); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_DATA: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslCipherSuite.Data", ssl_cipher_suite, | 
|  | kSslCipherSuiteMaxValue); | 
|  | break; | 
|  | default: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | uint16_t ssl_peer_signature_algorithm = | 
|  | stats.channel_stats[0].ssl_peer_signature_algorithm; | 
|  | if (ssl_peer_signature_algorithm != kSslSignatureAlgorithmUnknown) { | 
|  | for (cricket::MediaType media_type : media_types) { | 
|  | switch (media_type) { | 
|  | case cricket::MEDIA_TYPE_AUDIO: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Audio", | 
|  | ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_VIDEO: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Video", | 
|  | ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue); | 
|  | break; | 
|  | case cricket::MEDIA_TYPE_DATA: | 
|  | RTC_HISTOGRAM_ENUMERATION_SPARSE( | 
|  | "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Data", | 
|  | ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue); | 
|  | break; | 
|  | default: | 
|  | RTC_DCHECK_NOTREACHED(); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool PeerConnection::OnTransportChanged( | 
|  | const std::string& mid, | 
|  | RtpTransportInternal* rtp_transport, | 
|  | rtc::scoped_refptr<DtlsTransport> dtls_transport, | 
|  | DataChannelTransportInterface* data_channel_transport) { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | bool ret = true; | 
|  | if (ConfiguredForMedia()) { | 
|  | for (const auto& transceiver : | 
|  | rtp_manager()->transceivers()->UnsafeList()) { | 
|  | cricket::ChannelInterface* channel = transceiver->internal()->channel(); | 
|  | if (channel && channel->mid() == mid) { | 
|  | ret = channel->SetRtpTransport(rtp_transport); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mid == sctp_mid_n_) { | 
|  | data_channel_controller_.OnTransportChanged(data_channel_transport); | 
|  | if (dtls_transport) { | 
|  | signaling_thread()->PostTask(SafeTask( | 
|  | signaling_thread_safety_.flag(), | 
|  | [this, | 
|  | name = std::string(dtls_transport->internal()->transport_name())] { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | SetSctpTransportName(std::move(name)); | 
|  | })); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | PeerConnectionObserver* PeerConnection::Observer() const { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(observer_); | 
|  | return observer_; | 
|  | } | 
|  |  | 
|  | RTCError PeerConnection::StartSctpTransport(const SctpOptions& options) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | RTC_DCHECK(sctp_mid_s_); | 
|  |  | 
|  | network_thread()->PostTask( | 
|  | SafeTask(network_thread_safety_, [this, mid = *sctp_mid_s_, options] { | 
|  | rtc::scoped_refptr<SctpTransport> sctp_transport = | 
|  | transport_controller_n()->GetSctpTransport(mid); | 
|  | if (sctp_transport) | 
|  | sctp_transport->Start(options); | 
|  | })); | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | CryptoOptions PeerConnection::GetCryptoOptions() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | // TODO(bugs.webrtc.org/9891) - Remove PeerConnectionFactory::CryptoOptions | 
|  | // after it has been removed. | 
|  | return configuration_.crypto_options.has_value() | 
|  | ? *configuration_.crypto_options | 
|  | : options_.crypto_options; | 
|  | } | 
|  |  | 
|  | void PeerConnection::ClearStatsCache() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | if (legacy_stats_) { | 
|  | legacy_stats_->InvalidateCache(); | 
|  | } | 
|  | if (stats_collector_) { | 
|  | stats_collector_->ClearCachedStatsReport(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool PeerConnection::ShouldFireNegotiationNeededEvent(uint32_t event_id) { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | return sdp_handler_->ShouldFireNegotiationNeededEvent(event_id); | 
|  | } | 
|  |  | 
|  | void PeerConnection::RequestUsagePatternReportForTesting() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | message_handler_.RequestUsagePatternReport( | 
|  | [this]() { | 
|  | RTC_DCHECK_RUN_ON(signaling_thread()); | 
|  | ReportUsagePattern(); | 
|  | }, | 
|  | /* delay_ms= */ 0); | 
|  | } | 
|  |  | 
|  | int PeerConnection::FeedbackAccordingToRfc8888CountForTesting() const { | 
|  | return worker_thread()->BlockingCall([this]() { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | return call_->FeedbackAccordingToRfc8888Count(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | int PeerConnection::FeedbackAccordingToTransportCcCountForTesting() const { | 
|  | return worker_thread()->BlockingCall([this]() { | 
|  | RTC_DCHECK_RUN_ON(worker_thread()); | 
|  | return call_->FeedbackAccordingToTransportCcCount(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | std::function<void(const rtc::CopyOnWriteBuffer& packet, | 
|  | int64_t packet_time_us)> | 
|  | PeerConnection::InitializeRtcpCallback() { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return [this](const rtc::CopyOnWriteBuffer& packet, | 
|  | int64_t /*packet_time_us*/) { | 
|  | worker_thread()->PostTask(SafeTask(worker_thread_safety_, [this, packet]() { | 
|  | call_ptr_->Receiver()->DeliverRtcpPacket(packet); | 
|  | })); | 
|  | }; | 
|  | } | 
|  |  | 
|  | std::function<void(const RtpPacketReceived& parsed_packet)> | 
|  | PeerConnection::InitializeUnDemuxablePacketHandler() { | 
|  | RTC_DCHECK_RUN_ON(network_thread()); | 
|  | return [this](const RtpPacketReceived& parsed_packet) { | 
|  | worker_thread()->PostTask( | 
|  | SafeTask(worker_thread_safety_, [this, parsed_packet]() { | 
|  | // Deliver the packet anyway to Call to allow Call to do BWE. | 
|  | // Even if there is no media receiver, the packet has still | 
|  | // been received on the network and has been correcly parsed. | 
|  | call_ptr_->Receiver()->DeliverRtpPacket( | 
|  | MediaType::ANY, parsed_packet, | 
|  | /*undemuxable_packet_handler=*/ | 
|  | [](const RtpPacketReceived& packet) { return false; }); | 
|  | })); | 
|  | }; | 
|  | } | 
|  |  | 
|  | bool PeerConnection::CanAttemptDtlsStunPiggybacking( | 
|  | const RTCConfiguration& configuration) { | 
|  | // Enable DTLS-in-STUN only if no certificates were passed those | 
|  | // may be RSA certificates and this feature only works with small | 
|  | // ECDSA certificates. Determining the type of the key is | 
|  | // not trivially possible at this point. | 
|  | return dtls_enabled_ && configuration.certificates.empty() && | 
|  | env_.field_trials().IsEnabled("WebRTC-IceHandshakeDtls"); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |