/*
 *  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 <climits>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/functional/any_invocable.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.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_event_observer_interface.h"
#include "api/data_channel_interface.h"
#include "api/dtls_transport_interface.h"
#include "api/environment/environment.h"
#include "api/jsep.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/rtc_event_log.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/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_engine.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/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/media_session.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/scoped_operations_batcher.h"
#include "pc/sctp_data_channel.h"
#include "pc/sctp_transport.h"
#include "pc/sdp_offer_answer.h"
#include "pc/sdp_state_provider.h"
#include "pc/session_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/containers/flat_map.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_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/system/plan_b_only.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"

namespace webrtc {

namespace {
const int REPORT_USAGE_PATTERN_DELAY_MS = 60000;

class CodecLookupHelperForPeerConnection : public CodecLookupHelper {
 public:
  explicit CodecLookupHelperForPeerConnection(PeerConnection* self)
      : self_(self),
        codec_vendor_(self_->context()->media_engine(),
                      self_->context()->use_rtx(),
                      self_->trials()) {}

  webrtc::PayloadTypeSuggester* PayloadTypeSuggester() override {
    return self_->pt_suggester();
  }

  CodecVendor* GetCodecVendor() override { return &codec_vendor_; }

 private:
  PeerConnection* self_;
  CodecVendor codec_vendor_;
};

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 GetIceCandidatePairType(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()) {
    return RTC_LOG_ERROR(RTCError(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.type = configuration.type;
  modified_config.crypto_options = configuration.crypto_options;
  modified_config.always_negotiate_data_channels =
      configuration.always_negotiate_data_channels;

  // ICE configuration.
  modified_config.servers = configuration.servers;
  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.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) {
    return RTC_LOG_ERROR(
        RTCError(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 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);
  }
}

template <typename Observer,
          typename std::enable_if_t<
              std::is_same_v<Observer, SetSessionDescriptionObserver*>,
              bool> = true>
absl::AnyInvocable<void() &&> ReportFailure(Observer& o, RTCError error) {
  return [o = scoped_refptr<SetSessionDescriptionObserver>(o),
          error = std::move(error)]() { o->OnFailure(error); };
}

template <
    typename Observer,
    typename std::enable_if_t<
        std::is_same_v<Observer,
                       scoped_refptr<SetLocalDescriptionObserverInterface>>,
        bool> = true>
absl::AnyInvocable<void() &&> ReportFailure(Observer& o, RTCError error) {
  return [o = std::move(o), error = std::move(error)]() {
    o->OnSetLocalDescriptionComplete(error);
  };
}

template <
    typename Observer,
    typename std::enable_if_t<
        std::is_same_v<Observer,
                       scoped_refptr<SetRemoteDescriptionObserverInterface>>,
        bool> = true>
absl::AnyInvocable<void() &&> ReportFailure(Observer& o, RTCError error) {
  return [o = std::move(o), error = std::move(error)]() {
    o->OnSetRemoteDescriptionComplete(error);
  };
}

// Checks if the observer and description pointers are valid.
// If there's an error, the function will return false and optionally
// notify the observer asynchronously of the error.
// If both are valid, the function will reset the thread ownership of
// the description object and return true.
template <typename Observer, typename Description>
bool CheckValidSetDescription(Observer& observer,
                              Description& desc,
                              Thread* signaling) {
  if (!observer) {
    RTC_LOG(LS_ERROR) << "Observer is NULL.";
    return false;
  } else if (!desc) {
    signaling->PostTask(
        ReportFailure(observer, RTCError(RTCErrorType::INVALID_PARAMETER,
                                         "SessionDescription is NULL.")));
    return false;
  }
  // Make sure the description object now considers the current thread its home
  // by detaching from any potential previous thread.
  desc->RelinquishThreadOwnership();
  return true;
}

}  // namespace

scoped_refptr<PeerConnection> PeerConnection::Create(
    const Environment& env,
    scoped_refptr<ConnectionContext> context,
    const PeerConnectionFactoryInterface::Options& options,
    std::unique_ptr<Call> call,
    const PeerConnectionInterface::RTCConfiguration& configuration,
    PeerConnectionDependencies& dependencies,
    const 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 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,
    scoped_refptr<ConnectionContext> context,
    const PeerConnectionFactoryInterface::Options& options,
    bool is_unified_plan,
    std::unique_ptr<Call> call,
    PeerConnectionDependencies& dependencies,
    const 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)),
      lna_permission_factory_(std::move(dependencies.lna_permission_factory)),
      ice_transport_factory_(std::move(dependencies.ice_transport_factory)),
      dtls_transport_factory_(std::move(dependencies.dtls_transport_factory)),
      rtp_transport_factory_(std::move(dependencies.rtp_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, env_.clock())),
      stats_collector_(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_(absl::StrCat(CreateRandomId64() & LLONG_MAX)),
      data_channel_controller_(this),
      message_handler_(signaling_thread()),
      codec_lookup_helper_(
          std::make_unique<CodecLookupHelperForPeerConnection>(this)) {
  // Field trials specific to the peerconnection should be owned by the `env`,
  RTC_DCHECK(dependencies.trials == nullptr);

  // Enable SNAP from field trial unless enabled explicitly.
  if (!configuration_.enable_sctp_snap) {
    configuration_.enable_sctp_snap =
        env.field_trials().IsEnabled("WebRTC-Sctp-Snap");
  }

  std::vector<IceParameters> pooled_credentials;
  std::tie(transport_controller_copy_, pooled_credentials) =
      InitializeNetworkThread(stun_servers, turn_servers);

  if (call_ptr_) {
    worker_thread()->BlockingCall([this] {
      RTC_DCHECK_RUN_ON(worker_thread());
      if (context_->is_configured_for_media()) {
        media_engine_ref_ =
            std::make_unique<ConnectionContext::MediaEngineReference>(context_);
      }
    });
  }

  sdp_handler_ = SdpOfferAnswerHandler::Create(
      env_, this, std::move(dependencies.cert_generator),
      std::move(dependencies.video_bitrate_allocator_factory), context_.get(),
      codec_lookup_helper_.get());
  sdp_handler_->UpdateCachedIceCredentials(std::move(pooled_credentials));
  rtp_manager_ = std::make_unique<RtpTransmissionManager>(
      env_, call_ptr_, 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() && ConfiguredForMedia()) {
    rtp_manager_->transceivers()->Add(
        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
            signaling_thread(),
            make_ref_counted<RtpTransceiver>(
                env_, MediaType::AUDIO, context_.get(),
                codec_lookup_helper_.get(), legacy_stats_.get())));
    rtp_manager_->transceivers()->Add(
        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
            signaling_thread(),
            make_ref_counted<RtpTransceiver>(
                env_, MediaType::VIDEO, context_.get(),
                codec_lookup_helper_.get(), legacy_stats_.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());

  sdp_handler_->PrepareForShutdown();

  // In case `Close()` wasn't called, always make sure the controller cancels
  // potentially pending operations.
  data_channel_controller_.PrepareForShutdown();

  ScopedOperationsBatcher network_tasks(network_thread());
  ScopedOperationsBatcher worker_tasks(worker_thread());

  // Stop transceivers before destroying the stats collector because
  // AudioRtpSender has a reference to the LegacyStatsCollector that it will
  // update when stopping. The BaseChannels will eventually be deleted below
  // when all the network and worker tasks are executed.
  sdp_handler_->GetMediaChannelTeardownTasks(network_tasks, worker_tasks);

  legacy_stats_.reset(nullptr);
  stats_collector_.CancelPendingRequestAndGetShutdownTasks(network_tasks,
                                                           worker_tasks);
  network_tasks.AddWithFinalizer(MakeCloseOnNetworkThreadTask());

  // call_ must be destroyed on the worker thread.
  worker_tasks.Add([this]() {
    RTC_DCHECK_RUN_ON(worker_thread());
    worker_thread_safety_->SetNotAlive();
    call_.reset();
    media_engine_ref_.reset();
  });

  network_tasks.Run();
  worker_tasks.Run();

  if (sdp_handler_) {
    sdp_handler_->ResetSessionDescFactory();
  }

  data_channel_controller_.PrepareForShutdown();
}

std::pair<JsepTransportController*, std::vector<IceParameters>>
PeerConnection::InitializeNetworkThread(
    const ServerAddresses& stun_servers,
    const std::vector<RelayServerConfig>& turn_servers) {
  RTC_DCHECK_RUN_ON(signaling_thread());

  NoteServerUsage(usage_pattern_, stun_servers, turn_servers);

  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;
  config.crypto_options = configuration_.crypto_options;

  // Maybe enable or disable PQC from FieldTrials
  config.crypto_options.ephemeral_key_exchange_cipher_groups.Update(&trials());
  config.transport_observer = this;
  config.rtcp_handler = InitializeRtcpCallback();
  config.un_demuxable_packet_handler = InitializeUnDemuxablePacketHandler();

  // 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.dtls_transport_factory = dtls_transport_factory_.get();
  config.rtp_transport_factory = rtp_transport_factory_.get();
  config.on_dtls_handshake_error = [](SSLHandshakeError s) {
    RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.DtlsHandshakeError",
                              static_cast<int>(s),
                              static_cast<int>(SSLHandshakeError::MAX_VALUE));
  };
  config.signal_ice_candidates_gathered =
      [this](absl::string_view transport,
             const std::vector<Candidate>& candidates) {
        RTC_DCHECK_RUN_ON(network_thread());
        signaling_thread()->PostTask(
            SafeTask(signaling_thread_safety_.flag(),
                     [this, t = std::string(transport), c = candidates]() {
                       RTC_DCHECK_RUN_ON(signaling_thread());
                       OnTransportControllerCandidatesGathered(t, c);
                     }));
      };
  config.signal_ice_connection_state = [this](webrtc::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);
        }));
  };
  config.signal_connection_state =
      [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);
            }));
      };
  config.signal_standardized_ice_connection_state =
      [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);
            }));
      };
  config.signal_ice_gathering_state = [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);
        }));
  };
  config.signal_ice_candidate_error =
      [this](const 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);
            }));
      };
  config.signal_ice_candidates_removed =
      [this](IceTransportInternal* transport, const std::vector<Candidate>& c) {
        RTC_DCHECK_RUN_ON(network_thread());
        std::string mid = transport->transport_name();
        signaling_thread()->PostTask(SafeTask(
            signaling_thread_safety_.flag(), [this, mid = mid, c = c]() {
              RTC_DCHECK_RUN_ON(signaling_thread());
              OnTransportControllerCandidatesRemoved(mid, c);
            }));
      };
  config.signal_ice_candidate_pair_changed =
      [this](const 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);
            }));
      };

  auto transport_controller = std::make_unique<JsepTransportController>(
      env_, signaling_thread(), network_thread(), port_allocator_.get(),
      async_dns_resolver_factory_.get(), lna_permission_factory_.get(),
      std::move(config));

  return network_thread()->BlockingCall([&, config = &configuration_,
                                         controller = std::move(
                                             transport_controller)]() mutable {
    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);
    JsepTransportController* controller_ptr =
        InitializeTransportController_n(std::move(controller), *config);
    std::vector<IceParameters> credentials =
        port_allocator_->GetPooledIceCredentials();
    return std::make_pair(controller_ptr, credentials);
  });
}

ScopedOperationsBatcher::BatchTaskWithFinalizer
PeerConnection::MakeCloseOnNetworkThreadTask() {
  RTC_DCHECK_RUN_ON(signaling_thread());

  if (!transport_controller_copy_) {
    return nullptr;
  }

  auto jsep_close_task = transport_controller_copy_->MakeCloseTask();

  return [this, jsep_close_task = std::move(jsep_close_task)]() mutable
             -> RTCErrorOr<ScopedOperationsBatcher::FinalizerTask> {
    RTC_DCHECK_RUN_ON(network_thread());
    if (network_thread_safety_->alive()) {
      // port_allocator_ and transport_controller_ live on the network thread
      // and must be destroyed there.
      TeardownDataChannelTransport_n(RTCError::OK());
      if (call_ptr_) {
        call_ptr_->DisconnectFromNetworkThread();
      }
      port_allocator_->DiscardCandidatePool();

      std::move(jsep_close_task)();

      transport_controller_.reset();
      port_allocator_.reset();
      network_thread_safety_->SetNotAlive();
    }

    return ScopedOperationsBatcher::FinalizerTask([this]() {
      RTC_DCHECK_RUN_ON(signaling_thread());
      if (transport_controller_copy_) {
        transport_controller_copy_ = nullptr;
        sctp_mid_s_.reset();
        SetSctpTransportName("");
      } else {
        RTC_DCHECK(!sctp_mid_s_);
        RTC_DCHECK(sctp_transport_name_s_.empty());
      }
    });
  };
}

JsepTransportController* PeerConnection::InitializeTransportController_n(
    std::unique_ptr<JsepTransportController> controller,
    const RTCConfiguration& configuration) {
  transport_controller_ = std::move(controller);

  IceConfig ice_config(configuration);
  ice_config.dtls_handshake_in_stun = CanAttemptDtlsStunPiggybacking();

  transport_controller_->SetIceConfig(ice_config);
  return transport_controller_.get();
}

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();
}

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<scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(
    scoped_refptr<MediaStreamTrackInterface> track,
    const std::vector<std::string>& stream_ids) {
  return AddTrack(std::move(track), stream_ids, nullptr);
}

RTCErrorOr<scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(
    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<scoped_refptr<RtpSenderInterface>> PeerConnection::AddTrack(
    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()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }
  if (!track) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Track is null.");
  }
  if (!(track->kind() == MediaStreamTrackInterface::kAudioKind ||
        track->kind() == MediaStreamTrackInterface::kVideoKind)) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Track has invalid kind: " << track->kind());
  }
  if (IsClosed()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_STATE)
                         << "PeerConnection is closed.");
  }
  if (rtp_manager()->FindSenderForTrack(track.get())) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Sender already exists for track " << track->id()
                         << ".");
  }
  RTCErrorOr<scoped_refptr<RtpSenderInterface>> sender_or_error;
  if (IsUnifiedPlan()) {
    sender_or_error = rtp_manager()->AddTrackUnifiedPlan(
        configuration_.media_config, sdp_handler_->audio_options(),
        sdp_handler_->video_options(), GetCryptoOptions(),
        sdp_handler_->video_bitrate_allocator_factory(), track, stream_ids,
        init_send_encodings);
  } else {
    RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
    sender_or_error =
        rtp_manager()->AddTrackPlanB(track, stream_ids, init_send_encodings);
    RTC_ALLOW_PLAN_B_DEPRECATION_END();
  }
  if (sender_or_error.ok()) {
    sdp_handler_->UpdateNegotiationNeeded();
    legacy_stats_->AddTrack(track.get());
  }
  return sender_or_error;
}

RTCError PeerConnection::RemoveTrackOrError(
    scoped_refptr<RtpSenderInterface> sender) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!ConfiguredForMedia()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }
  if (!sender) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Sender is null.");
  }
  if (IsClosed()) {
    return RTC_LOG_ERROR(RTCError(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;
    RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
    if (sender->media_type() == webrtc::MediaType::AUDIO) {
      removed =
          rtp_manager()->GetAudioTransceiver()->internal()->RemoveSenderPlanB(
              sender.get());
    } else {
      RTC_DCHECK_EQ(webrtc::MediaType::VIDEO, sender->media_type());
      removed =
          rtp_manager()->GetVideoTransceiver()->internal()->RemoveSenderPlanB(
              sender.get());
    }
    RTC_ALLOW_PLAN_B_DEPRECATION_END();
    if (!removed) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "Couldn't find sender " << sender->id()
                           << " to remove.");
    }
  }
  sdp_handler_->UpdateNegotiationNeeded();
  return RTCError::OK();
}

scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
PeerConnection::FindTransceiverBySender(
    scoped_refptr<RtpSenderInterface> sender) {
  return rtp_manager()->transceivers()->FindBySender(sender);
}

RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(scoped_refptr<MediaStreamTrackInterface> track) {
  if (!ConfiguredForMedia()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }

  return AddTransceiver(track, RtpTransceiverInit());
}

RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(scoped_refptr<MediaStreamTrackInterface> track,
                               const RtpTransceiverInit& init) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!ConfiguredForMedia()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }
  RTC_CHECK(IsUnifiedPlan())
      << "AddTransceiver is only available with Unified Plan SdpSemantics";
  if (!track) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "track is null");
  }
  MediaType media_type;
  if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
    media_type = MediaType::AUDIO;
  } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
    media_type = MediaType::VIDEO;
  } else {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Track kind is not audio or video");
  }
  return AddTransceiver(media_type, track, init);
}

RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(MediaType media_type) {
  return AddTransceiver(media_type, RtpTransceiverInit());
}

RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(MediaType media_type,
                               const RtpTransceiverInit& init) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!ConfiguredForMedia()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }
  RTC_CHECK(IsUnifiedPlan())
      << "AddTransceiver is only available with Unified Plan SdpSemantics";
  if (!(media_type == MediaType::AUDIO || media_type == MediaType::VIDEO)) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "media type is not audio or video");
  }
  return AddTransceiver(media_type, nullptr, init);
}

RTCErrorOr<scoped_refptr<RtpTransceiverInterface>>
PeerConnection::AddTransceiver(MediaType media_type,
                               scoped_refptr<MediaStreamTrackInterface> track,
                               const RtpTransceiverInit& init,
                               bool update_negotiation_needed) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!ConfiguredForMedia()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_OPERATION)
                         << "Not configured for media");
  }
  RTC_DCHECK(
      (media_type == MediaType::AUDIO || media_type == MediaType::VIDEO));
  if (track) {
    RTC_DCHECK_EQ(media_type,
                  (track->kind() == MediaStreamTrackInterface::kAudioKind
                       ? MediaType::AUDIO
                       : MediaType::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()) {
    return RTC_LOG_ERROR(
        RTCError(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);
                                     })) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Invalid RID value provided.");
  }

  if (absl::c_any_of(init.send_encodings,
                     [](const RtpEncodingParameters& encoding) {
                       return encoding.ssrc.has_value();
                     })) {
    return RTC_LOG_ERROR(
        RTCError(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 == MediaType::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)) {
    return RTC_LOG_ERROR(
        RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
        << "Attempted to set an unimplemented parameter of RtpParameters.");
  }

  std::vector<Codec> codecs;
  // Gather the current codec capabilities to allow checking scalabilityMode and
  // codec selection against supported values.
  CodecVendor codec_vendor(context_->media_engine(), false, trials());
  if (media_type == MediaType::VIDEO) {
    codecs = codec_vendor.video_send_codecs().codecs();
  } else {
    codecs = codec_vendor.audio_send_codecs().codecs();
  }

  auto result = CheckRtpParametersValues(parameters, codecs, std::nullopt,
                                         env_.field_trials());
  if (!result.ok()) {
    if (result.type() == RTCErrorType::INVALID_MODIFICATION) {
      result.set_type(RTCErrorType::UNSUPPORTED_OPERATION);
    }
    return RTC_LOG_ERROR(RTCError(result.type()) << result.message());
  }

  RTC_LOG(LS_INFO) << "Adding " << 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 transceiver = rtp_manager()->CreateAndAddTransceiver(
      configuration_.media_config, sdp_handler_->audio_options(),
      sdp_handler_->video_options(), configuration_.crypto_options,
      sdp_handler_->video_bitrate_allocator_factory(), media_type, track,
      init.stream_ids, parameters.encodings,
      /*header_extensions_to_negotiate=*/{},
      /*simulcast_rejected=*/false,
      /*initial_simulcast_layers=*/{}, sender_id);
  transceiver->internal()->set_direction(init.direction);

  if (update_negotiation_needed) {
    sdp_handler_->UpdateNegotiationNeeded();
  }

  return scoped_refptr<RtpTransceiverInterface>(transceiver);
}


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);
  }

  scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender;
  CodecVendor codec_vendor(context_->media_engine(), false, trials());

  if (kind == MediaStreamTrackInterface::kAudioKind) {
    auto audio_sender = AudioRtpSender::Create(
        env_, signaling_thread(), worker_thread(), CreateRandomUuid(),
        legacy_stats_.get(), nullptr,
        /*enable_sframe_at_owner=*/nullptr,
        rtp_manager()->voice_media_send_channel(), stream_ids,
        /*init_send_encodings=*/std::vector<RtpEncodingParameters>(1),
        codec_vendor.audio_send_codecs().codecs());
    new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
        signaling_thread(), audio_sender);
    rtp_manager()->GetAudioTransceiver()->internal()->AddSenderPlanB(
        new_sender);
  } else if (kind == MediaStreamTrackInterface::kVideoKind) {
    auto video_sender = VideoRtpSender::Create(
        env_, signaling_thread(), worker_thread(), CreateRandomUuid(), nullptr,
        /*enable_sframe_at_owner=*/nullptr,
        rtp_manager()->video_media_send_channel(),
        /*init_send_encodings=*/{}, /*simulcast_rejected=*/false,
        /*initial_simulcast_layers=*/{}, stream_ids,
        codec_vendor.video_send_codecs().codecs());
    new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
        signaling_thread(), video_sender);
    rtp_manager()->GetVideoTransceiver()->internal()->AddSenderPlanB(
        new_sender);
  } else {
    RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind;
  }

  return new_sender;
}

std::vector<scoped_refptr<RtpSenderInterface>> PeerConnection::GetSenders()
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::vector<scoped_refptr<RtpSenderInterface>> ret;
  if (ConfiguredForMedia()) {
    for (const auto& sender : rtp_manager()->GetSendersInternal()) {
      ret.push_back(sender);
    }
  }
  return ret;
}

std::vector<scoped_refptr<RtpReceiverInterface>> PeerConnection::GetReceivers()
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::vector<scoped_refptr<RtpReceiverInterface>> ret;
  if (ConfiguredForMedia()) {
    for (const auto& receiver : rtp_manager()->GetReceiversInternal()) {
      ret.push_back(receiver);
    }
  }
  return ret;
}

std::vector<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<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(callback);
  RTC_LOG_THREAD_BLOCK_COUNT();
  stats_collector_.GetStatsReport(
      scoped_refptr<RTCStatsCollectorCallback>(callback));
  RTC_DCHECK_BLOCK_COUNT_NO_MORE_THAN(2);
}

void PeerConnection::GetStats(
    scoped_refptr<RtpSenderInterface> selector,
    scoped_refptr<RTCStatsCollectorCallback> callback) {
  TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(callback);
  RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
  scoped_refptr<RtpSenderInternal> internal_sender;
  if (selector) {
    for (const auto& proxy_transceiver :
         rtp_manager()->transceivers()->List()) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
      for (const auto& proxy_sender :
           proxy_transceiver->internal()->senders()) {
        if (proxy_sender == selector) {
          internal_sender = proxy_sender->internal();
          break;
        }
      }
      RTC_ALLOW_PLAN_B_DEPRECATION_END()
      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);
}

void PeerConnection::GetStats(
    scoped_refptr<RtpReceiverInterface> selector,
    scoped_refptr<RTCStatsCollectorCallback> callback) {
  TRACE_EVENT0("webrtc", "PeerConnection::GetStats");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(callback);
  RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
  scoped_refptr<RtpReceiverInternal> internal_receiver;
  if (selector) {
    for (const auto& proxy_transceiver :
         rtp_manager()->transceivers()->List()) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
      for (const auto& proxy_receiver :
           proxy_transceiver->internal()->receivers()) {
        if (proxy_receiver == selector) {
          internal_receiver = proxy_receiver->internal();
          break;
        }
      }
      RTC_ALLOW_PLAN_B_DEPRECATION_END()
      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);
}

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().empty()) {
    return std::nullopt;
  }
  return description->description()->transport_infos()[0].description.HasOption(
      "trickle");
}

RTCErrorOr<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()) {
    return RTC_LOG_ERROR(
        RTCError(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<scoped_refptr<DataChannelInterface>> ret =
      data_channel_controller_.InternalCreateDataChannelWithProxy(
          label, internal_config);
  if (!ret.ok()) {
    return ret.MoveError();
  }

  ClearStatsCache();
  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) {
  auto desc_ptr = std::unique_ptr<SessionDescriptionInterface>(desc);
  if (!CheckValidSetDescription(observer, desc_ptr, signaling_thread()))
    return;

  RunOnSignalingThread([this, desc = std::move(desc_ptr),
                        observer = scoped_refptr<SetSessionDescriptionObserver>(
                            observer)]() mutable {
    RTC_DCHECK_RUN_ON(signaling_thread());
    sdp_handler_->SetLocalDescription(std::move(observer), std::move(desc));
  });
}

// This method bypasses the proxy, so can be called from any thread.
void PeerConnection::SetLocalDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
  if (!CheckValidSetDescription(observer, desc, signaling_thread()))
    return;

  RunOnSignalingThread(
      [this, desc = std::move(desc), observer = std::move(observer)]() mutable {
        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(
      scoped_refptr<SetSessionDescriptionObserver>(observer));
}

void PeerConnection::SetLocalDescription(
    scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  sdp_handler_->SetLocalDescription(observer);
}

void PeerConnection::SetRemoteDescription(
    SetSessionDescriptionObserver* observer,
    SessionDescriptionInterface* desc) {
  auto desc_ptr = std::unique_ptr<SessionDescriptionInterface>(desc);
  if (!CheckValidSetDescription(observer, desc_ptr, signaling_thread()))
    return;

  RunOnSignalingThread([this, desc = std::move(desc_ptr),
                        observer = scoped_refptr<SetSessionDescriptionObserver>(
                            observer)]() mutable {
    RTC_DCHECK_RUN_ON(signaling_thread());
    sdp_handler_->SetRemoteDescription(std::move(observer), std::move(desc));
  });
}

void PeerConnection::SetRemoteDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    scoped_refptr<SetRemoteDescriptionObserverInterface> observer) {
  if (!CheckValidSetDescription(observer, desc, signaling_thread()))
    return;

  RunOnSignalingThread(
      [this, desc = std::move(desc), observer = std::move(observer)]() mutable {
        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()) {
    return RTC_LOG_ERROR(RTCError(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) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_MODIFICATION)
                         << "Can't change crypto_options after calling "
                            "SetLocalDescription.");
  }

  // Create a new, configuration object whose peerconnection 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.
  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();

  // Apply part of the configuration on the network thread.  In theory this
  // shouldn't fail.
  std::vector<IceParameters> pooled_credentials;
  // TODO: webrtc:42222117 - For carrying `new_states` from the transport
  // controller on the network thread to the transport controller on the
  // signaling thread, we should instead use a
  // ScopedOperationsBatcher::BatchTaskWithFinalizer.
  flat_map<std::string, JsepTransportController::TransportState> new_states;
  if (!network_thread()->BlockingCall([&] {
        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);
        bool result = 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);
        pooled_credentials = port_allocator_->GetPooledIceCredentials();
        new_states = transport_controller_n()->GetTransportStates_n();
        return result;
      })) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                         << "Failed to apply configuration to PortAllocator.");
  }

  transport_controller_s()->SetTransportStates(std::move(new_states));
  sdp_handler_->UpdateCachedIceCredentials(std::move(pooled_credentials));
  configuration_ = modified_config;
  return RTCError::OK();
}

bool PeerConnection::AddIceCandidate(const IceCandidate* ice_candidate) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  ClearStatsCache();
  return sdp_handler_->AddIceCandidate(ice_candidate);
}

void PeerConnection::AddIceCandidate(std::unique_ptr<IceCandidate> 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::RemoveIceCandidate(const IceCandidate* candidate) {
  TRACE_EVENT0("webrtc", "PeerConnection::RemoveIceCandidate");
  RTC_DCHECK_RUN_ON(signaling_thread());
  return sdp_handler_->RemoveIceCandidate(candidate);
}

RTCError PeerConnection::SetBitrate(const BitrateSettings& bitrate) {
  if (!worker_thread()->IsCurrent()) {
    return worker_thread()->BlockingCall([&]() { return SetBitrate(bitrate); });
  }
  RTC_DCHECK_RUN_ON(worker_thread());

  if (!call_) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_STATE)
                         << "PeerConnection is closed.");
  }

  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) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "min_bitrate_bps <= 0");
  }
  if (has_start) {
    if (has_min && *bitrate.start_bitrate_bps < *bitrate.min_bitrate_bps) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "start_bitrate_bps < min_bitrate_bps");
    } else if (*bitrate.start_bitrate_bps < 0) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "start_bitrate_bps < 0");
    }
  }
  if (has_max) {
    if (has_start && *bitrate.max_bitrate_bps < *bitrate.start_bitrate_bps) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "max_bitrate_bps < start_bitrate_bps");
    } else if (has_min && *bitrate.max_bitrate_bps < *bitrate.min_bitrate_bps) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "max_bitrate_bps < min_bitrate_bps");
    } else if (*bitrate.max_bitrate_bps < 0) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "max_bitrate_bps < 0");
    }
  }

  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) {
  RTC_DCHECK(ConfiguredForMedia());
  if (!worker_thread()->IsCurrent()) {
    worker_thread()->BlockingCall(
        [this, playout] { SetAudioPlayout(playout); });
    return;
  }
  RTC_DCHECK_RUN_ON(worker_thread());
  auto audio_state = media_engine()->voice().GetAudioState();
  audio_state->SetPlayout(playout);
}

void PeerConnection::SetAudioRecording(bool recording) {
  RTC_DCHECK(ConfiguredForMedia());
  if (!worker_thread()->IsCurrent()) {
    worker_thread()->BlockingCall(
        [this, recording] { SetAudioRecording(recording); });
    return;
  }
  RTC_DCHECK_RUN_ON(worker_thread());
  auto audio_state = media_engine()->voice().GetAudioState();
  audio_state->SetRecording(recording);
}

void PeerConnection::AddAdaptationResource(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_->is_configured_for_media();
}

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(); });
}

void PeerConnection::SetDataChannelEventObserver(
    std::unique_ptr<DataChannelEventObserverInterface> observer) {
  network_thread()->PostTask(SafeTask(
      network_thread_safety_, [this, obs = std::move(observer)]() mutable {
        RTC_DCHECK_RUN_ON(network_thread());
        data_channel_controller_.SetEventObserver(std::move(obs));
      }));
}

scoped_refptr<DtlsTransportInterface> PeerConnection::LookupDtlsTransportByMid(
    const std::string& mid) {
  RTC_DCHECK_RUN_ON(network_thread());
  return transport_controller_->LookupDtlsTransportByMid_n(mid);
}

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 {
  return HandleSessionDescriptionAccessor<&SdpStateProvider::local_description>(
      local_description_clone_);
}

const SessionDescriptionInterface* PeerConnection::remote_description() const {
  return HandleSessionDescriptionAccessor<
      &SdpStateProvider::remote_description>(remote_description_clone_);
}

const SessionDescriptionInterface* PeerConnection::current_local_description()
    const {
  return HandleSessionDescriptionAccessor<
      &SdpStateProvider::current_local_description>(
      current_local_description_clone_);
}

const SessionDescriptionInterface* PeerConnection::current_remote_description()
    const {
  return HandleSessionDescriptionAccessor<
      &SdpStateProvider::current_remote_description>(
      current_remote_description_clone_);
}

const SessionDescriptionInterface* PeerConnection::pending_local_description()
    const {
  return HandleSessionDescriptionAccessor<
      &SdpStateProvider::pending_local_description>(
      pending_local_description_clone_);
}

const SessionDescriptionInterface* PeerConnection::pending_remote_description()
    const {
  return HandleSessionDescriptionAccessor<
      &SdpStateProvider::pending_remote_description>(
      pending_remote_description_clone_);
}

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;
  RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    observer->OnIceConnectionChange(ice_connection_state_);
  });
  standardized_ice_connection_state_ =
      PeerConnectionInterface::IceConnectionState::kIceConnectionClosed;
  connection_state_ = PeerConnectionInterface::PeerConnectionState::kClosed;
  RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    observer->OnConnectionChange(connection_state_);
  });

  sdp_handler_->Close();

  NoteUsageEvent(UsageEvent::CLOSE_CALLED);

  ScopedOperationsBatcher worker_tasks(worker_thread());

  if (ConfiguredForMedia()) {
    for (RtpTransceiver* transceiver :
         rtp_manager()->transceivers()->ListInternal()) {
      if (!transceiver->stopped()) {
        worker_tasks.Add(transceiver->GetStopTransceiverProcedure());
      }
    }
  }

  // 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.
  {
    ScopedOperationsBatcher network_tasks(network_thread());
    sdp_handler_->GetMediaChannelTeardownTasks(network_tasks, worker_tasks);
    network_tasks.AddWithFinalizer(MakeCloseOnNetworkThreadTask());
  }

  // 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();
  }

  worker_tasks.Add([this]() {
    RTC_DCHECK_RUN_ON(worker_thread());
    worker_thread_safety_->SetNotAlive();
    call_.reset();
    StopRtcEventLog_w();
  });

  worker_tasks.Run();
  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;
  RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    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;
  RunWithObserver([&](auto observer) {
    observer->OnStandardizedIceConnectionChange(new_state);
  });
}

void PeerConnection::SetConnectionState(
    PeerConnectionInterface::PeerConnectionState new_state) {
  if (connection_state_ == new_state)
    return;
  if (IsClosed())
    return;
  connection_state_ = new_state;
  RunWithObserver(
      [&](auto observer) { 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;
  }

  // Below this point are features where we check what was negotiated in
  // SDP. `desc` will contain the answer, i.e. what was negotiated.
  const SessionDescription* desc = nullptr;
  if (local_description()->GetType() == SdpType::kAnswer ||
      local_description()->GetType() == SdpType::kPrAnswer) {
    desc = local_description()->description();
  } else if (remote_description()->GetType() == SdpType::kAnswer ||
             remote_description()->GetType() == SdpType::kPrAnswer) {
    desc = remote_description()->description();
  }
  if (!desc) {
    RTC_LOG(LS_INFO) << "Connection established without an answer, local="
                     << local_description()->GetType()
                     << ", remote=" << remote_description()->GetType();
    return;
  }

  bool negotiated_sctp_snap = false;
  const ContentInfo* sctp_content = GetFirstDataContent(desc);
  if (sctp_content && !sctp_content->rejected) {
    const SctpDataContentDescription* sctp_desc =
        sctp_content->media_description()->as_sctp();
    if (sctp_desc) {
      negotiated_sctp_snap |= sctp_desc->sctp_init().has_value();
    }
  }
  RTC_HISTOGRAM_BOOLEAN("WebRTC.PeerConnection.NegotiatedSctpSnap",
                        negotiated_sctp_snap);

  // Record congestion control mechanism in use, if any.
  // The information is taken from the last seen answer SDP.
  std::optional<RtcpFeedbackType> feedback_type;
  for (const auto& content : desc->contents()) {
    std::optional<RtcpFeedbackType> this_feedback_type =
        content.media_description()->preferred_rtcp_cc_ack_type();
    if (this_feedback_type) {
      feedback_type = this_feedback_type;
      break;
    }
  }
  if (!feedback_type) {
    feedback_type = RtcpFeedbackType::NONE;
  }
  // Note that NONE will be reported for datachannel-only calls.
  // Only TRANSPORT_CC and CCFB are currently reported.
  RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.NegotiatedFeedbackType",
                            static_cast<int>(*feedback_type),
                            static_cast<int>(RtcpFeedbackType::MAX));

  CryptexPolicyUsage cryptex = kCryptexPolicyUsageMax;
  switch (configuration_.crypto_options.srtp.cryptex_policy) {
    case CryptoOptions::Srtp::CryptexPolicy::kDisabled:
      cryptex = kCryptexPolicyUsageDisabled;
      break;
    case CryptoOptions::Srtp::CryptexPolicy::kNegotiate:
      cryptex = kCryptexPolicyUsageNegotiate;
      // Special-case for when cryptex was not required but negotiated.
      if (desc->cryptex()) {
        cryptex = kCryptexPolicyUsageNegotiated;
      }
      break;
    case CryptoOptions::Srtp::CryptexPolicy::kRequire:
      cryptex = kCryptexPolicyUsageRequire;
      break;
  }
  RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CryptexUsage", cryptex,
                            kCryptexPolicyUsageMax);
}

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;
  RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    observer->OnIceGatheringChange(ice_gathering_state_);
  });
}

void PeerConnection::OnIceCandidate(std::unique_ptr<IceCandidate> candidate) {
  if (IsClosed()) {
    return;
  }
  ReportIceCandidateCollected(candidate->candidate());
  ClearStatsCache();
  RunWithObserver(
      [&](auto observer) { 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;
  }
  RunWithObserver([&](auto observer) {
    observer->OnIceCandidateError(address, port, url, error_code, error_text);
  });
}

void PeerConnection::OnIceCandidatesRemoved(
    absl::string_view mid,
    const std::vector<Candidate>& candidates) {
  if (IsClosed()) {
    return;
  }

  for (const Candidate& candidate : candidates) {
    IceCandidate c(mid, -1, candidate);
    RunWithObserver([&](auto o) { o->OnIceCandidateRemoved(&c); });
  }
}

void PeerConnection::OnSelectedCandidatePairChanged(
    const 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);
  }

  RunWithObserver([&](auto observer) {
    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());

  if (!sctp_mid_s_.has_value()) {
    RTC_LOG(LS_INFO) << "Data channel not configured.";
    RTC_DCHECK(sctp_transport_name_s_.empty());
    return;
  }

  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());
  stats_collector_.OnSctpDataChannelStateChanged(channel_id, state);
}

PeerConnection::InitializePortAllocatorResult
PeerConnection::InitializePortAllocator_n(
    const 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 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();
}

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 = transport_controller_s()->GetDtlsRole(content_name);
  if (dtls_role) {
    *role = *dtls_role;
    return true;
  }
  return false;
}


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();
}

// RTC_RUN_ON(worker_thread())
MediaEngineInterface* PeerConnection::media_engine() const {
  RTC_DCHECK(media_engine_ref_);
  return media_engine_ref_->media_engine();
}

std::optional<std::string> PeerConnection::sctp_mid() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return sctp_mid_s_;
}

CandidateStatsList PeerConnection::GetPooledCandidateStats() const {
  RTC_DCHECK_RUN_ON(network_thread());
  if (!network_thread_safety_->alive())
    return {};
  CandidateStatsList candidate_stats_list;
  port_allocator_->GetCandidateStatsFromPooledSessions(&candidate_stats_list);
  return candidate_stats_list;
}

std::map<std::string, 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 {};

  Thread::ScopedDisallowBlockingCalls no_blocking_calls;
  std::map<std::string, TransportStats> transport_stats_by_name;
  for (const std::string& transport_name : transport_names) {
    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,
    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<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 {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return transport_controller_s()->NeedsIceRestart(content_name);
}

void PeerConnection::OnTransportControllerConnectionState(
    webrtc::IceConnectionState state) {
  switch (state) {
    case webrtc::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 webrtc::kIceConnectionFailed:
      SetIceConnectionState(PeerConnectionInterface::kIceConnectionFailed);
      break;
    case webrtc::kIceConnectionConnected:
      RTC_LOG(LS_INFO) << "Changing to ICE connected state because "
                          "all transports are writable.";
      {
        std::vector<std::pair<std::string, MediaType>> transceiver_info;
        if (ConfiguredForMedia()) {
          for (const auto& t : rtp_manager()->transceivers()->List()) {
            if (t->internal()->HasChannel()) {
              std::optional<std::string> mid = t->mid();
              if (mid) {
                transceiver_info.emplace_back(*mid, t->media_type());
              }
            }
          }
        }

        network_thread()->PostTask(
            SafeTask(network_thread_safety_,
                     [this, transceiver_info = std::move(transceiver_info)] {
                       RTC_DCHECK_RUN_ON(network_thread());
                       ReportTransportStats(std::move(transceiver_info));
                     }));
      }

      SetIceConnectionState(PeerConnectionInterface::kIceConnectionConnected);
      NoteUsageEvent(UsageEvent::ICE_STATE_CONNECTED);
      break;
    case webrtc::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(
    absl::string_view transport_name,
    const 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 (Candidates::const_iterator citer = candidates.begin();
       citer != candidates.end(); ++citer) {
    // Use transport_name as the candidate media id.
    std::unique_ptr<IceCandidate> candidate(
        new IceCandidate(transport_name, sdp_mline_index, *citer));
    sdp_handler_->AddLocalIceCandidate(candidate.get());
    OnIceCandidate(std::move(candidate));
  }
}

void PeerConnection::OnTransportControllerCandidateError(
    const IceCandidateErrorEvent& event) {
  OnIceCandidateError(event.address, event.port, event.url, event.error_code,
                      event.error_text);
}

void PeerConnection::OnTransportControllerCandidatesRemoved(
    absl::string_view mid,
    const std::vector<Candidate>& candidates) {
  RTC_DCHECK(!mid.empty());
  sdp_handler_->RemoveLocalIceCandidates(mid, candidates);
  OnIceCandidatesRemoved(mid, candidates);
}

void PeerConnection::OnTransportControllerCandidateChanged(
    const CandidatePairChangeEvent& event) {
  OnSelectedCandidatePairChanged(event);
}

// Returns the media index for a local ice candidate given the content name.
bool PeerConnection::GetLocalCandidateMediaIndex(absl::string_view 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());
  Thread::ScopedDisallowBlockingCalls no_blocking_calls;
  if (call_) {
    return call_->GetStats();
  } else {
    return Call::Stats();
  }
}

std::optional<AudioDeviceModule::Stats> PeerConnection::GetAudioDeviceStats() {
  RTC_DCHECK_RUN_ON(worker_thread());
  if (context_->is_configured_for_media()) {
    return 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) {
#ifndef WEBRTC_HAVE_SCTP
    RTC_LOG(LS_ERROR) << "Data channel transport is not available"
                      << " as WebRTC has been compiled without SCTP support "
                         "(WEBRTC_HAVE_SCTP), mid="
                      << mid;
#else
    RTC_LOG(LS_ERROR)
        << "Data channel transport is not available for data channels, mid="
        << mid;
#endif
    sctp_mid_n_ = std::nullopt;
    return std::nullopt;
  }

  std::optional<std::string> transport_name;
  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 flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  if (bundle_groups_by_mid.empty())
    return true;

  const ContentInfos& contents = desc->contents();
  for (ContentInfos::const_iterator citer = contents.begin();
       citer != contents.end(); ++citer) {
    const ContentInfo* content = (&*citer);
    RTC_DCHECK(content != nullptr);
    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(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()) {
    MediaType media_type = content.media_description()->type();
    if (media_type == MediaType::AUDIO) {
      num_audio_mlines += 1;
    } else if (media_type == MediaType::VIDEO) {
      num_video_mlines += 1;
    } else if (media_type == MediaType::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() != 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(ADAPTER_TYPE_UNKNOWN);
  new_candidate.set_relay_protocol("");
  new_candidate.set_underlying_type_for_vpn(ADAPTER_TYPE_UNKNOWN);
  new_candidate.set_network_slice(NetworkSlice::NO_SLICE);

  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 {
  RunWithMaybeNullObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    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<std::pair<std::string, MediaType>> transceiver_info) {
  RTC_DCHECK_RUN_ON(network_thread());
  TRACE_EVENT0("webrtc", "PeerConnection::ReportTransportStats");
  Thread::ScopedDisallowBlockingCalls no_blocking_calls;
  flat_map<absl::string_view, std::set<MediaType>>
      media_types_by_transport_name;
  for (const auto& item : transceiver_info) {
    const auto& mid = item.first;
    const auto& media_type = item.second;
    RtpTransportInternal* transport =
        transport_controller_->GetRtpTransport(mid);
    if (transport) {
      media_types_by_transport_name[transport->transport_name()].insert(
          media_type);
    }
  }

  if (sctp_mid_n_) {
    DtlsTransportInternal* dtls_transport =
        transport_controller_->GetDtlsTransport(*sctp_mid_n_);
    if (dtls_transport) {
      media_types_by_transport_name[dtls_transport->transport_name()].insert(
          MediaType::DATA);
    }
  }

  for (const auto& entry : media_types_by_transport_name) {
    TransportStats stats;
    if (transport_controller_->GetStats(entry.first, &stats)) {
      ReportBestConnectionState(stats);
      ReportNegotiatedCiphers(dtls_enabled_, stats, entry.second);
    }
  }
}

// Walk through the ConnectionInfos to gather best connection usage
// for IPv4 and IPv6.
// static (no member state required)
void PeerConnection::ReportBestConnectionState(const TransportStats& stats) {
  for (const TransportChannelStats& channel_stats : stats.channel_stats) {
    for (const 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() == TCP_PROTOCOL_NAME ||
          (local.is_relay() && local.relay_protocol() == TCP_PROTOCOL_NAME)) {
        RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_TCP",
                                  GetIceCandidatePairType(local, remote),
                                  kIceCandidatePairMax);
      } else if (local.protocol() == UDP_PROTOCOL_NAME) {
        RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.CandidatePairType_UDP",
                                  GetIceCandidatePairType(local, remote),
                                  kIceCandidatePairMax);
      } else {
        RTC_LOG(LS_WARNING) << "ReportBestConnectionState: No histogram for "
                            << local.protocol();
      }

      // 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 TransportStats& stats,
    const std::set<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 (MediaType media_type : media_types) {
      switch (media_type) {
        case MediaType::AUDIO:
          RTC_HISTOGRAM_ENUMERATION_SPARSE(
              "WebRTC.PeerConnection.SslCipherSuite.Audio", ssl_cipher_suite,
              kSslCipherSuiteMaxValue);
          break;
        case MediaType::VIDEO:
          RTC_HISTOGRAM_ENUMERATION_SPARSE(
              "WebRTC.PeerConnection.SslCipherSuite.Video", ssl_cipher_suite,
              kSslCipherSuiteMaxValue);
          break;
        case MediaType::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 (MediaType media_type : media_types) {
      switch (media_type) {
        case MediaType::AUDIO:
          RTC_HISTOGRAM_ENUMERATION_SPARSE(
              "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Audio",
              ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue);
          break;
        case MediaType::VIDEO:
          RTC_HISTOGRAM_ENUMERATION_SPARSE(
              "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Video",
              ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue);
          break;
        case MediaType::DATA:
          RTC_HISTOGRAM_ENUMERATION_SPARSE(
              "WebRTC.PeerConnection.SslPeerSignatureAlgorithm.Data",
              ssl_peer_signature_algorithm, kSslSignatureAlgorithmMaxValue);
          break;
        default:
          RTC_DCHECK_NOTREACHED();
          continue;
      }
    }
  }
}

bool PeerConnection::OnTransportChanged(
    absl::string_view mid,
    RtpTransportInternal* rtp_transport,
    scoped_refptr<DtlsTransport> dtls_transport,
    DataChannelTransportInterface* data_channel_transport) {
  RTC_DCHECK_RUN_ON(network_thread());
  RTC_DCHECK(!mid.empty());
  bool ret = true;
  if (ConfiguredForMedia()) {
    for (const auto& transceiver :
         rtp_manager()->transceivers()->UnsafeList()) {
      auto internal = transceiver->internal();
      if (internal->mid() == mid) {
        ret = internal->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(rtp_transport->transport_name())] {
                     RTC_DCHECK_RUN_ON(signaling_thread());
                     SetSctpTransportName(std::move(name));
                   }));
    }
  }

  return ret;
}

void PeerConnection::RunWithObserver(
    absl::AnyInvocable<void(PeerConnectionObserver*) &&> task) {  // NOLINT
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(observer_);
  std::move(task)(observer_);
}

void PeerConnection::RunWithMaybeNullObserver(
    absl::AnyInvocable<void(PeerConnectionObserver*) &&> task)  // NOLINT
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::move(task)(observer_);
}

RTCError PeerConnection::StartSctpTransport(const SctpOptions& options) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(sctp_mid_s_);

  return network_thread()->BlockingCall([this, mid = *sctp_mid_s_, options] {
    scoped_refptr<SctpTransport> sctp_transport =
        transport_controller_n()->GetSctpTransport(mid);
    RTC_DCHECK(sctp_transport);
    if (!sctp_transport || !sctp_transport->Start(options)) {
      return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_MODIFICATION)
                           << "Failed to start SCTP transport.");
    }
    return RTCError::OK();
  });
}

CryptoOptions PeerConnection::GetCryptoOptions() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return configuration_.crypto_options;
}

void PeerConnection::ClearStatsCache() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (legacy_stats_) {
    legacy_stats_->InvalidateCache();
  }
  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().value_or(0);
  });
}

int PeerConnection::FeedbackAccordingToTransportCcCountForTesting() const {
  return worker_thread()->BlockingCall([this]() {
    RTC_DCHECK_RUN_ON(worker_thread());
    return call_->FeedbackAccordingToTransportCcCount().value_or(0);
  });
}

absl::AnyInvocable<void(const CopyOnWriteBuffer& packet, int64_t packet_time_us)
                       const>
PeerConnection::InitializeRtcpCallback() {
  return [this](const CopyOnWriteBuffer& packet, int64_t /*packet_time_us*/) {
    RTC_DCHECK_RUN_ON(network_thread());
    call_ptr_->Receiver()->DeliverRtcpPacket(packet);
  };
}

absl::AnyInvocable<void(const RtpPacketReceived& parsed_packet) const>
PeerConnection::InitializeUnDemuxablePacketHandler() {
  return [this](const RtpPacketReceived& parsed_packet) {
    RTC_DCHECK_RUN_ON(network_thread());
    // 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 correctly parsed.
    call_ptr_->Receiver()->DeliverRtpPacket(
        MediaType::ANY, parsed_packet,
        /*undemuxable_packet_handler=*/
        [](const RtpPacketReceived& packet) { return false; });
  };
}

bool PeerConnection::CanAttemptDtlsStunPiggybacking() {
  return dtls_enabled_ && SSLStreamAdapter::IsBoringSsl() &&
         env_.field_trials().IsEnabled("WebRTC-IceHandshakeDtls");
}

void PeerConnection::RunOnSignalingThread(absl::AnyInvocable<void() &&> task) {
  if (signaling_thread()->IsCurrent()) {
    std::move(task)();
  } else {
    signaling_thread()->PostTask(
        SafeTask(signaling_thread_safety_.flag(), std::move(task)));
  }
}

}  // namespace webrtc
