/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name of Google Inc. nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/compiler_specific.h"
#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/connection_allowlist.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_crypto_algorithm_params.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_object_string.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_string_stringsequence.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_void_function.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_answer_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_certificate.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_configuration.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_data_channel_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_data_channel_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_candidate_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_connection_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_gathering_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_ice_server.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_offer_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_peer_connection_error_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_peer_connection_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_rtp_transceiver_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_session_description_callback.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_session_description_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_signaling_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_mediastreamtrack_string.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/dom_time_stamp.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/connection_allowlist_violation_report_body.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/modules/crypto/crypto_result_impl.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints_impl.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_event.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_track_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_certificate.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_certificate_generator.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_data_channel.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_data_channel_event.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_dtls_transport.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_dtmf_sender.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_error_util.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_ice_transport.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_error_event.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_ice_event.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_receiver.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_transceiver.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_sctp_transport.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_session_description.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_session_description_request_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_session_description_request_promise_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_stats_report.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_track_event.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_promise_impl.h"
#include "third_party/blink/renderer/modules/peerconnection/web_rtc_stats_report_callback_resolver.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
#include "third_party/blink/renderer/platform/heap/cross_thread_persistent.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/instrumentation/instance_counters.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_answer_options_platform.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_ice_candidate_platform.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_offer_options_platform.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_session_description_platform.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_stats.h"
#include "third_party/blink/renderer/platform/peerconnection/rtc_void_request.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
#include "third_party/blink/renderer/platform/scheduler/public/scheduling_policy.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/webrtc/api/data_channel_interface.h"
#include "third_party/webrtc/api/dtls_transport_interface.h"
#include "third_party/webrtc/api/jsep.h"
#include "third_party/webrtc/api/peer_connection_interface.h"
#include "third_party/webrtc/api/priority.h"
#include "third_party/webrtc/rtc_base/ssl_identity.h"

namespace blink {

namespace {

const char kSignalingStateClosedMessage[] =
    "The RTCPeerConnection's signalingState is 'closed'.";
const char kModifiedSdpMessage[] =
    "The SDP does not match the previously generated SDP for this type";

base::LazyInstance<RTCPeerConnection::RtcPeerConnectionHandlerFactoryCallback>::
    Leaky g_create_rpc_peer_connection_handler_callback_ =
        LAZY_INSTANCE_INITIALIZER;

// The maximum number of PeerConnections that can exist simultaneously.
const int64_t kMaxPeerConnections = 500;

bool ThrowExceptionIfSignalingStateClosed(
    webrtc::PeerConnectionInterface::SignalingState state,
    ExceptionState* exception_state) {
  if (state == webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    exception_state->ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       kSignalingStateClosedMessage);
    return true;
  }

  return false;
}

void AsyncCallErrorCallback(ExecutionContext* context,
                            V8RTCPeerConnectionErrorCallback* error_callback,
                            DOMException* exception) {
  DCHECK(error_callback);
  context->GetAgent()->event_loop()->EnqueueMicrotask(BindOnce(
      &V8RTCPeerConnectionErrorCallback::InvokeAndReportException,
      WrapPersistent(error_callback), nullptr, WrapPersistent(exception)));
}

bool CallErrorCallbackIfSignalingStateClosed(
    ExecutionContext* context,
    webrtc::PeerConnectionInterface::SignalingState state,
    V8RTCPeerConnectionErrorCallback* error_callback) {
  if (state == webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    if (error_callback) {
      AsyncCallErrorCallback(context, error_callback,
                             MakeGarbageCollected<DOMException>(
                                 DOMExceptionCode::kInvalidStateError,
                                 kSignalingStateClosedMessage));
    }
    return true;
  }

  return false;
}

bool IsIceCandidateMissingSdpMidAndMLineIndex(
    const RTCIceCandidateInit* candidate) {
  return (candidate->sdpMid().IsNull() &&
          !candidate->sdpMLineIndex().has_value());
}

RTCOfferOptionsPlatform* ConvertToRTCOfferOptionsPlatform(
    const RTCOfferOptions* options) {
  if (!options) {
    return nullptr;
  }
  return MakeGarbageCollected<RTCOfferOptionsPlatform>(
      options->hasOfferToReceiveVideo()
          ? std::max(options->offerToReceiveVideo(), 0)
          : -1,
      options->hasOfferToReceiveAudio()
          ? std::max(options->offerToReceiveAudio(), 0)
          : -1,
      options->hasVoiceActivityDetection() ? options->voiceActivityDetection()
                                           : true,
      options->hasIceRestart() ? options->iceRestart() : false);
}

RTCAnswerOptionsPlatform* ConvertToRTCAnswerOptionsPlatform(
    const RTCAnswerOptions* options) {
  if (!options) {
    return nullptr;
  }
  return MakeGarbageCollected<RTCAnswerOptionsPlatform>(
      options->hasVoiceActivityDetection() ? options->voiceActivityDetection()
                                           : true);
}

RTCIceCandidatePlatform* ConvertToRTCIceCandidatePlatform(
    ExecutionContext* context,
    const RTCIceCandidateInit* candidate) {
  // TODO(guidou): Change default value to -1. crbug.com/614958.
  uint16_t sdp_m_line_index = candidate->sdpMLineIndex().value_or(0);
  if (!candidate->sdpMLineIndex()) {
    UseCounter::Count(context,
                      WebFeature::kRTCIceCandidateDefaultSdpMLineIndex);
  }
  return MakeGarbageCollected<RTCIceCandidatePlatform>(
      candidate->candidate(), candidate->sdpMid(), sdp_m_line_index,
      candidate->usernameFragment(),
      /*url can not be reconstructed*/ String());
}

webrtc::PeerConnectionInterface::IceTransportsType IceTransportPolicyFromEnum(
    V8RTCIceTransportPolicy::Enum policy) {
  switch (policy) {
    case V8RTCIceTransportPolicy::Enum::kRelay:
      return webrtc::PeerConnectionInterface::kRelay;
    case V8RTCIceTransportPolicy::Enum::kAll:
      return webrtc::PeerConnectionInterface::kAll;
  }
  NOTREACHED();
}

bool IsValidStunURL(const KURL& url) {
  if (!url.ProtocolIs("stun") && !url.ProtocolIs("stuns")) {
    return false;
  }
  if (!url.Query().empty()) {
    return false;
  }
  return true;
}

bool IsValidTurnURL(const KURL& url) {
  if (!url.ProtocolIs("turn") && !url.ProtocolIs("turns")) {
    return false;
  }
  if (!url.Query().empty()) {
    Vector<StringView> query_parts = url.Query().SplitSkippingEmpty('=');
    if (query_parts.size() < 2 || query_parts[0] != "transport") {
      return false;
    }
  }
  return true;
}

// Determines if the current context disallows WebRTC. Corresponds to the
// algorithm in https://www.w3.org/TR/CSP3/#should-block-rtc-connection.
bool AreIceCandidatesAdministrativelyProhibited(ExecutionContext* context) {
  const network::ConnectionAllowlists& connection_allowlists =
      context->GetPolicyContainer()->GetPolicies().connection_allowlists;

  bool blocked_by_connection_allowlist =
      connection_allowlists.enforced.has_value() &&
      connection_allowlists.enforced->webrtc_behavior ==
          network::ConnectionAllowlist::WebRtcBehavior::kBlock;
  if (blocked_by_connection_allowlist) {
    return true;
  }

  return false;
}

webrtc::PeerConnectionInterface::RTCConfiguration ParseConfiguration(
    ExecutionContext* context,
    const RTCConfiguration* configuration,
    ExceptionState* exception_state) {
  DCHECK(context);

  webrtc::PeerConnectionInterface::RTCConfiguration web_configuration;

  if (configuration->hasIceTransportPolicy()) {
    UseCounter::Count(context, WebFeature::kRTCConfigurationIceTransportPolicy);
    web_configuration.type = IceTransportPolicyFromEnum(
        configuration->iceTransportPolicy().AsEnum());
  } else if (configuration->hasIceTransports()) {
    UseCounter::Count(context, WebFeature::kRTCConfigurationIceTransports);
    web_configuration.type =
        IceTransportPolicyFromEnum(configuration->iceTransports().AsEnum());
  }

  switch (configuration->bundlePolicy().AsEnum()) {
    case V8RTCBundlePolicy::Enum::kMaxCompat:
      web_configuration.bundle_policy =
          webrtc::PeerConnectionInterface::kBundlePolicyMaxCompat;
      break;
    case V8RTCBundlePolicy::Enum::kMaxBundle:
      web_configuration.bundle_policy =
          webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle;
      break;
    case V8RTCBundlePolicy::Enum::kBalanced:
      break;
  }

  switch (configuration->rtcpMuxPolicy().AsEnum()) {
    case V8RTCRtcpMuxPolicy::Enum::kNegotiate:
      web_configuration.rtcp_mux_policy =
          webrtc::PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
      Deprecation::CountDeprecation(context,
                                    WebFeature::kRtcpMuxPolicyNegotiate);
      break;
    case V8RTCRtcpMuxPolicy::Enum::kRequire:
      break;
  }

  if (RuntimeEnabledFeatures::RtcRtpHeaderEncryptionPolicyEnabled()) {
    switch (configuration->rtpHeaderEncryptionPolicy().AsEnum()) {
      case V8RTCRtpHeaderEncryptionPolicy::Enum::kNegotiate:
        web_configuration.crypto_options.srtp.cryptex_policy =
            webrtc::CryptoOptions::Srtp::CryptexPolicy::kNegotiate;
        break;
      case V8RTCRtpHeaderEncryptionPolicy::Enum::kRequire:
        web_configuration.crypto_options.srtp.cryptex_policy =
            webrtc::CryptoOptions::Srtp::CryptexPolicy::kRequire;
        break;
    }
  }

  // If RTC connections are blocked globally, communication with all ICE servers
  // should be also blocked. The simplest way to accomplish this is to filter
  // them all out before they reach the native layer.
  if (!AreIceCandidatesAdministrativelyProhibited(context)) {
    std::vector<webrtc::PeerConnectionInterface::IceServer>& ice_servers =
        web_configuration.servers;
    for (const RTCIceServer* ice_server : configuration->iceServers()) {
      Vector<String> url_strings;
      std::vector<std::string> converted_urls;
      if (ice_server->hasUrls()) {
        UseCounter::Count(context, WebFeature::kRTCIceServerURLs);
        switch (ice_server->urls()->GetContentType()) {
          case V8UnionStringOrStringSequence::ContentType::kString:
            url_strings.push_back(ice_server->urls()->GetAsString());
            break;
          case V8UnionStringOrStringSequence::ContentType::kStringSequence:
            url_strings = ice_server->urls()->GetAsStringSequence();
            break;
        }
      } else if (ice_server->hasUrl()) {
        UseCounter::Count(context, WebFeature::kRTCIceServerURL);
        url_strings.push_back(ice_server->url());
      } else {
        exception_state->ThrowTypeError("Malformed RTCIceServer");
        return {};
      }

      for (const String& url_string : url_strings) {
        KURL url(NullUrl(), url_string);
        if (!url.IsValid()) {
          exception_state->ThrowDOMException(
              DOMExceptionCode::kSyntaxError,
              StrCat({"'", url_string, "' is not a valid URL."}));
          return {};
        }
        bool is_valid_turn = IsValidTurnURL(url);
        if (!is_valid_turn && !IsValidStunURL(url)) {
          exception_state->ThrowDOMException(
              DOMExceptionCode::kSyntaxError,
              StrCat({"'", url_string, "' is not a valid stun or turn URL."}));
          return {};
        }
        if (is_valid_turn &&
            (!ice_server->hasUsername() || !ice_server->hasCredential())) {
          exception_state->ThrowDOMException(
              DOMExceptionCode::kInvalidAccessError,
              "Both username and credential are "
              "required when the URL scheme is "
              "\"turn\" or \"turns\".");
        }

        converted_urls.push_back(String(url).Utf8());
      }

      auto converted_ice_server = webrtc::PeerConnectionInterface::IceServer();
      converted_ice_server.urls = std::move(converted_urls);
      if (ice_server->hasUsername()) {
        converted_ice_server.username = ice_server->username().Utf8();
      }
      if (ice_server->hasCredential()) {
        converted_ice_server.password = ice_server->credential().Utf8();
      }
      ice_servers.emplace_back(std::move(converted_ice_server));
    }
  }

  web_configuration.certificates = base::ToVector(
      configuration->certificates(),
      [](const auto& certificate) { return certificate->Certificate(); });

  web_configuration.ice_candidate_pool_size =
      configuration->iceCandidatePoolSize();

  if (configuration->hasRtcAudioJitterBufferMaxPackets()) {
    UseCounter::Count(context, WebFeature::kRTCMaxAudioBufferSize);
    web_configuration.audio_jitter_buffer_max_packets =
        static_cast<int>(configuration->rtcAudioJitterBufferMaxPackets());
  }

  if (configuration->hasRtcAudioJitterBufferFastAccelerate()) {
    UseCounter::Count(context, WebFeature::kRTCMaxAudioBufferSize);
    web_configuration.audio_jitter_buffer_fast_accelerate =
        configuration->hasRtcAudioJitterBufferFastAccelerate();
  }

  if (configuration->hasRtcAudioJitterBufferMinDelayMs()) {
    UseCounter::Count(context, WebFeature::kRTCMaxAudioBufferSize);
    web_configuration.audio_jitter_buffer_min_delay_ms =
        static_cast<int>(configuration->rtcAudioJitterBufferMinDelayMs());
  }

  if (configuration->hasAlwaysNegotiateDataChannels()) {
    web_configuration.always_negotiate_data_channels =
        configuration->alwaysNegotiateDataChannels();
  }

  return web_configuration;
}

bool SdpMismatch(String old_sdp, String new_sdp, String attribute) {
  // Look for an attribute that is present in both old and new SDP
  // and is modified which is not allowed.
  String attribute_with_prefix = StrCat({"\na=", attribute, ":"});
  const wtf_size_t new_attribute_pos = new_sdp.find(attribute_with_prefix);
  if (new_attribute_pos == kNotFound) {
    return true;
  }
  const wtf_size_t old_attribute_pos = old_sdp.find(attribute_with_prefix);
  if (old_attribute_pos == kNotFound) {
    return true;
  }
  wtf_size_t old_attribute_end = old_sdp.find("\r\n", old_attribute_pos + 1);
  if (old_attribute_end == kNotFound) {
    old_attribute_end = old_sdp.find('\n', old_attribute_pos + 1);
  }
  wtf_size_t new_attribute_end = new_sdp.find("\r\n", new_attribute_pos + 1);
  if (new_attribute_end == kNotFound) {
    new_attribute_end = new_sdp.find('\n', new_attribute_pos + 1);
  }
  return old_sdp.substr(old_attribute_pos,
                        old_attribute_end - old_attribute_pos) !=
         new_sdp.substr(new_attribute_pos,
                        new_attribute_end - new_attribute_pos);
}

bool IceUfragPwdMismatch(String old_sdp, String new_sdp) {
  return SdpMismatch(old_sdp, new_sdp, "ice-ufrag") ||
         SdpMismatch(old_sdp, new_sdp, "ice-pwd");
}

bool FingerprintMismatch(String old_sdp, String new_sdp) {
  // Check special case of externally generated SDP without fingerprints.
  // It's impossible to generate a valid fingerprint without createOffer
  // or createAnswer, so this only applies when there are no fingerprints.
  // This is allowed.
  const wtf_size_t new_fingerprint_pos = new_sdp.find("\na=fingerprint:");
  if (new_fingerprint_pos == kNotFound) {
    return false;
  }
  // Look for fingerprint having been added. Not allowed.
  const wtf_size_t old_fingerprint_pos = old_sdp.find("\na=fingerprint:");
  if (old_fingerprint_pos == kNotFound) {
    return true;
  }
  // Look for fingerprint being modified. Not allowed.  Handle differences in
  // line endings ('\r\n' vs, '\n' when looking for the end of the fingerprint).
  wtf_size_t old_fingerprint_end =
      old_sdp.find("\r\n", old_fingerprint_pos + 1);
  if (old_fingerprint_end == kNotFound) {
    old_fingerprint_end = old_sdp.find('\n', old_fingerprint_pos + 1);
  }
  wtf_size_t new_fingerprint_end =
      new_sdp.find("\r\n", new_fingerprint_pos + 1);
  if (new_fingerprint_end == kNotFound) {
    new_fingerprint_end = new_sdp.find('\n', new_fingerprint_pos + 1);
  }
  return old_sdp.subview(old_fingerprint_pos,
                         old_fingerprint_end - old_fingerprint_pos) !=
         new_sdp.subview(new_fingerprint_pos,
                         new_fingerprint_end - new_fingerprint_pos);
}

bool ContainsLegacySimulcast(String sdp) {
  // Looks for the non-spec simulcast that іs enabled via SDP munging.
  return sdp.contains("\na=ssrc-group:SIM");
}

bool ContainsLegacyRtpDataChannel(String sdp) {
  // Looks for the non-spec legacy RTP data channel.
  return sdp.contains("google-data/90000");
}

bool ContainsCandidate(String sdp) {
  return sdp.contains("\na=candidate");
}

bool ContainsOpusStereo(String sdp) {
  return sdp.contains("stereo=1");
}

bool ContainsMultiOpus(String sdp) {
  return sdp.contains("multiopus");
}

bool ContainsSctpSnap(String sdp) {
  // SNAP (draft-hancke-tsvwg-snap) is signaled via the a=sctp-init: attribute.
  return sdp.contains("\na=sctp-init:");
}

// Keep in sync with tools/metrics/histograms/metadata/web_rtc/enums.xml
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class GenerateCertificateAlgorithms {
  kEcDsaP256 = 0,
  kRsa1024,
  kRsa2048,
  kRsa4096,
  kRsa8192,
  kRsaOther,
  kMaxValue = kRsaOther,
};

void MeasureGenerateCertificateKeyType(
    const std::optional<webrtc::KeyParams>& key_params) {
  if (!key_params.has_value()) {
    return;
  }
  GenerateCertificateAlgorithms bucket =
      GenerateCertificateAlgorithms::kEcDsaP256;
  if (key_params->type() == webrtc::KT_RSA) {
    switch (key_params->rsa_params().mod_size) {
      case 1024:
        bucket = GenerateCertificateAlgorithms::kRsa1024;
        break;
      case 2048:
        bucket = GenerateCertificateAlgorithms::kRsa2048;
        break;
      case 4096:
        bucket = GenerateCertificateAlgorithms::kRsa4096;
        break;
      case 8192:
        bucket = GenerateCertificateAlgorithms::kRsa8192;
        break;
      default:
        bucket = GenerateCertificateAlgorithms::kRsaOther;
        break;
    }
  }
  UMA_HISTOGRAM_ENUMERATION(
      "WebRTC.PeerConnection.GenerateCertificate.Algorithms", bucket,
      GenerateCertificateAlgorithms::kMaxValue);
}

bool IsSignalingStateAllowedForType(
    webrtc::PeerConnectionInterface::SignalingState state,
    V8RTCSdpType::Enum type) {
  switch (type) {
    case V8RTCSdpType::Enum::kOffer:
      return state == webrtc::PeerConnectionInterface::kStable ||
             state == webrtc::PeerConnectionInterface::kHaveLocalOffer ||
             state == webrtc::PeerConnectionInterface::kHaveRemotePrAnswer;
    case V8RTCSdpType::Enum::kPranswer:
    case V8RTCSdpType::Enum::kAnswer:
      return state == webrtc::PeerConnectionInterface::kHaveLocalPrAnswer ||
             state == webrtc::PeerConnectionInterface::kHaveRemoteOffer;
    case V8RTCSdpType::Enum::kRollback:
      break;
  }
  return false;
}

String GetSdpFromCache(V8RTCSdpType::Enum type,
                       const String& last_offer,
                       const String& last_answer) {
  if (type == V8RTCSdpType::Enum::kOffer) {
    return last_offer;
  }
  if (type == V8RTCSdpType::Enum::kAnswer ||
      type == V8RTCSdpType::Enum::kPranswer) {
    return last_answer;
  }
  return String();
}
}  // namespace

RTCPeerConnection::EventWrapper::EventWrapper(Event* event,
                                              BoolFunction function)
    : event_(event), setup_function_(std::move(function)) {}

bool RTCPeerConnection::EventWrapper::Setup() {
  if (setup_function_) {
    return std::move(setup_function_).Run();
  }
  return true;
}

void RTCPeerConnection::EventWrapper::Trace(Visitor* visitor) const {
  visitor->Trace(event_);
}

RTCPeerConnection* RTCPeerConnection::Create(
    ExecutionContext* context,
    const RTCConfiguration* rtc_configuration,
    ExceptionState& exception_state) {
  if (context->IsContextDestroyed()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotSupportedError,
        "PeerConnections may not be created in detached documents.");
    return nullptr;
  }

  // Count number of PeerConnections that could potentially be impacted by CSP
  auto* content_security_policy = context->GetContentSecurityPolicy();
  if (content_security_policy &&
      content_security_policy->IsActiveForConnections()) {
    UseCounter::Count(context, WebFeature::kRTCPeerConnectionWithActiveCsp);
    // Count number of PeerConnections that would be blocked by CSP connect-src
    // or one of the directive it inherits from.
    // This is intended for evaluating whether introducing a "webrtc-src"
    // on-off switch that inherits from connect-csp would be harmful or not.
    // TODO(crbug.com/1225968): Remove code when decision is made.
    if (!content_security_policy->AllowConnectToSource(
            KURL("https://example.org"), KURL("https://example.org"),
            RedirectStatus::kNoRedirect,
            ReportingDisposition::kSuppressReporting)) {
      UseCounter::Count(context, WebFeature::kRTCPeerConnectionWithBlockingCsp);
    }
  }
  // TODO(https://crbug.com/1318448): figure out if this counter should be
  // retired - the other alternative is removed.
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionConstructorCompliant);

  webrtc::PeerConnectionInterface::RTCConfiguration configuration =
      ParseConfiguration(context, rtc_configuration, &exception_state);
  if (exception_state.HadException()) {
    return nullptr;
  }

  // Make sure no certificates have expired.
  if (!configuration.certificates.empty()) {
    DOMTimeStamp now = ConvertSecondsToDOMTimeStamp(
        base::Time::Now().InSecondsFSinceUnixEpoch());
    for (const webrtc::scoped_refptr<webrtc::RTCCertificate>& certificate :
         configuration.certificates) {
      DOMTimeStamp expires = certificate->Expires();
      if (expires <= now) {
        exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError,
                                          "Expired certificate(s).");
        return nullptr;
      }
    }
  }

  if (RuntimeEnabledFeatures::WebRtcSctpSnapEnabled(context)) {
    configuration.enable_sctp_snap = true;
  }

  RTCPeerConnection* peer_connection = MakeGarbageCollected<RTCPeerConnection>(
      context, std::move(configuration),
      rtc_configuration->encodedInsertableStreams(), exception_state);
  if (exception_state.HadException()) {
    return nullptr;
  }
  return peer_connection;
}

RTCPeerConnection::RTCPeerConnection(
    ExecutionContext* context,
    webrtc::PeerConnectionInterface::RTCConfiguration configuration,
    bool encoded_insertable_streams,
    ExceptionState& exception_state)
    : ActiveScriptWrappable<RTCPeerConnection>({}),
      ExecutionContextLifecycleObserver(context),
      pending_local_description_(nullptr),
      current_local_description_(nullptr),
      pending_remote_description_(nullptr),
      current_remote_description_(nullptr),
      signaling_state_(
          webrtc::PeerConnectionInterface::SignalingState::kStable),
      ice_gathering_state_(webrtc::PeerConnectionInterface::kIceGatheringNew),
      ice_connection_state_(webrtc::PeerConnectionInterface::kIceConnectionNew),
      peer_connection_state_(
          webrtc::PeerConnectionInterface::PeerConnectionState::kNew),
      peer_handler_unregistered_(true),
      closed_(true),
      suppress_events_(true),
      encoded_insertable_streams_(encoded_insertable_streams) {
  LocalDOMWindow* window = To<LocalDOMWindow>(context);

  if (AreIceCandidatesAdministrativelyProhibited(context)) {
    are_ice_candidates_administratively_prohibited_ = true;
  }
  MaybeReportConnectionAllowlistViolation(context);

  InstanceCounters::IncrementCounter(
      InstanceCounters::kRTCPeerConnectionCounter);
  // If we fail, set |m_closed| and |m_stopped| to true, to avoid hitting the
  // assert in the destructor.
  if (InstanceCounters::CounterValue(
          InstanceCounters::kRTCPeerConnectionCounter) > kMaxPeerConnections) {
    exception_state.ThrowDOMException(DOMExceptionCode::kUnknownError,
                                      "Cannot create so many PeerConnections");
    return;
  }

  // WebRTC peer connections are not allowed in fenced frames.
  // Given the complex scaffolding for setting up fenced frames testing, this
  // is tested in the following locations:
  // * third_party/blink/web_tests/external/wpt/fenced-frame/webrtc-peer-connection.https.html
  // * content/browser/fenced_frame/fenced_frame_browsertest.cc
  if (RuntimeEnabledFeatures::
          FencedFramesLocalUnpartitionedDataAccessEnabled() &&
      window->GetFrame()->IsInFencedFrameTree()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "RTCPeerConnection is not allowed in fenced frames.");
    return;
  }

  // Tests might need a custom RtcPeerConnectionHandler implementation.
  PeerConnectionDependencyFactory& dependency_factory =
      PeerConnectionDependencyFactory::From(*context);
  if (!g_create_rpc_peer_connection_handler_callback_.Get().is_null()) {
    peer_handler_ =
        std::move(g_create_rpc_peer_connection_handler_callback_.Get()).Run();
  } else {
    peer_handler_ = dependency_factory.CreateRTCPeerConnectionHandler(
        this, window->GetTaskRunner(TaskType::kInternalMedia),
        encoded_insertable_streams_);
  }

  if (!peer_handler_) {
    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                      "No PeerConnection handler can be "
                                      "created, perhaps WebRTC is disabled?");
    return;
  }

  auto* web_frame =
      static_cast<WebLocalFrame*>(WebFrame::FromCoreFrame(window->GetFrame()));
  if (!peer_handler_->Initialize(context, configuration, web_frame,
                                 exception_state)) {
    DCHECK(exception_state.HadException());
    return;
  }
  // After Initialize() with a real `peer_handler_`, WebRTC threads exist.
  scoped_refptr<base::SingleThreadTaskRunner> worker_thread =
      dependency_factory.GetWebRtcWorkerTaskRunner();
  if (!worker_thread) {
    // This path is only used in some unit test environments with a fake
    // `peer_handler_` that does not ensure WebRTC threads exist.
    worker_thread =
        base::ThreadPool::CreateSingleThreadTaskRunner({base::MayBlock()});
  }
  rtp_contributing_source_cache_.emplace(this, std::move(worker_thread));
  // The RTCPeerConnection was successfully constructed.
  closed_ = false;
  peer_handler_unregistered_ = false;
  suppress_events_ = false;
}

RTCPeerConnection::~RTCPeerConnection() {
  // This checks that close() or stop() is called before the destructor.
  // We are assuming that a wrapper is always created when RTCPeerConnection is
  // created.
  DCHECK(closed_ || peer_handler_unregistered_);
  InstanceCounters::DecrementCounter(
      InstanceCounters::kRTCPeerConnectionCounter);
  DCHECK_GE(InstanceCounters::CounterValue(
                InstanceCounters::kRTCPeerConnectionCounter),
            0);
}

void RTCPeerConnection::Dispose() {
  // Promptly clears the handler so that content doesn't access it in a lazy
  // sweeping phase. Other references to the handler use a weak pointer,
  // preventing access.
  if (peer_handler_) {
    peer_handler_.reset();
  }
  // Memory owned by RTCPeerConnection must not be touched after Dispose().
  // Shut down the cache to cancel any in-flight tasks that may otherwise have
  // used the cache.
  if (rtp_contributing_source_cache_.has_value()) {
    rtp_contributing_source_cache_.value().Shutdown();
  }
}

ScriptPromise<RTCSessionDescriptionInit> RTCPeerConnection::createOffer(
    ScriptState* script_state,
    const RTCOfferOptions* options,
    ExceptionState& exception_state) {
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }
  auto* resolver =
      MakeGarbageCollected<ScriptPromiseResolver<RTCSessionDescriptionInit>>(
          script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  RTCSessionDescriptionRequest* request =
      RTCSessionDescriptionRequestPromiseImpl::Create(this, resolver,
                                                      /*is_offer=*/true);
  ++pending_create_offer_count_;

  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateOffer);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateOfferPromise);
  if (options->hasOfferToReceiveAudio() || options->hasOfferToReceiveVideo()) {
    UseCounter::Count(
        context,
        WebFeature::kRTCPeerConnectionCreateOfferOptionsOfferToReceive);
  }

  auto platform_transceivers = peer_handler_->CreateOffer(
      request, ConvertToRTCOfferOptionsPlatform(options));
  for (auto& platform_transceiver : platform_transceivers) {
    CreateOrUpdateTransceiver(std::move(platform_transceiver));
  }
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::createOffer(
    ScriptState* script_state,
    V8RTCSessionDescriptionCallback* success_callback,
    V8RTCPeerConnectionErrorCallback* error_callback,
    const RTCOfferOptions* options) {
  DCHECK(success_callback);
  DCHECK(error_callback);
  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateOffer);
  UseCounter::Count(
      context, WebFeature::kRTCPeerConnectionCreateOfferLegacyFailureCallback);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionCreateOfferLegacyCompliant);
  if (CallErrorCallbackIfSignalingStateClosed(context, signaling_state_,
                                              error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  RTCSessionDescriptionRequest* request =
      RTCSessionDescriptionRequestImpl::Create(
          GetExecutionContext(), this, success_callback, error_callback,
          /*is_offer=*/true);
  ++pending_create_offer_count_;

  Vector<std::unique_ptr<RTCRtpTransceiverPlatform>> platform_transceivers =
      peer_handler_->CreateOffer(request,
                                 ConvertToRTCOfferOptionsPlatform(options));
  for (auto& platform_transceiver : platform_transceivers) {
    CreateOrUpdateTransceiver(std::move(platform_transceiver));
  }

  return ToResolvedUndefinedPromise(script_state);
}

ScriptPromise<RTCSessionDescriptionInit> RTCPeerConnection::createAnswer(
    ScriptState* script_state,
    const RTCAnswerOptions* options,
    ExceptionState& exception_state) {
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }

  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateAnswer);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateAnswerPromise);

  auto* resolver =
      MakeGarbageCollected<ScriptPromiseResolver<RTCSessionDescriptionInit>>(
          script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  RTCSessionDescriptionRequest* request =
      RTCSessionDescriptionRequestPromiseImpl::Create(this, resolver,
                                                      /*is_offer=*/false);
  ++pending_create_answer_count_;
  peer_handler_->CreateAnswer(request,
                              ConvertToRTCAnswerOptionsPlatform(options));
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::createAnswer(
    ScriptState* script_state,
    V8RTCSessionDescriptionCallback* success_callback,
    V8RTCPeerConnectionErrorCallback* error_callback) {
  DCHECK(success_callback);
  DCHECK(error_callback);
  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionCreateAnswer);
  UseCounter::Count(
      context, WebFeature::kRTCPeerConnectionCreateAnswerLegacyFailureCallback);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionCreateAnswerLegacyCompliant);

  if (CallErrorCallbackIfSignalingStateClosed(context, signaling_state_,
                                              error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  RTCSessionDescriptionRequest* request =
      RTCSessionDescriptionRequestImpl::Create(
          GetExecutionContext(), this, success_callback, error_callback,
          /*is_offer=*/false);
  ++pending_create_answer_count_;
  peer_handler_->CreateAnswer(request, nullptr);
  return ToResolvedUndefinedPromise(script_state);
}

DOMException* RTCPeerConnection::checkSdpForStateErrors(
    ExecutionContext* context,
    const ParsedSessionDescription& parsed_sdp) {
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    return MakeGarbageCollected<DOMException>(
        DOMExceptionCode::kInvalidStateError, kSignalingStateClosedMessage);
  }

  if (parsed_sdp.type() == "offer") {
    if (parsed_sdp.sdp() != last_offer_) {
      if (FingerprintMismatch(last_offer_, parsed_sdp.sdp())) {
        return MakeGarbageCollected<DOMException>(
            DOMExceptionCode::kInvalidModificationError, kModifiedSdpMessage);
      } else {
        UseCounter::Count(context, WebFeature::kRTCLocalSdpModification);
        if (ContainsLegacySimulcast(parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationSimulcast);
        }
        if (IceUfragPwdMismatch(last_offer_, parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationIceUfragPwd);
        }
        if (ContainsOpusStereo(parsed_sdp.sdp()) &&
            !ContainsOpusStereo(last_offer_)) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationOpusStereo);
        }
        // multiopus is never generated by the browser.
        if (ContainsMultiOpus(parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationMultiOpus);
        }
        return nullptr;
        // TODO(https://crbug.com/823036): Return failure for all modification.
      }
    }
  } else if (parsed_sdp.type() == "answer" || parsed_sdp.type() == "pranswer") {
    if (parsed_sdp.sdp() != last_answer_) {
      if (FingerprintMismatch(last_answer_, parsed_sdp.sdp())) {
        return MakeGarbageCollected<DOMException>(
            DOMExceptionCode::kInvalidModificationError, kModifiedSdpMessage);
      } else {
        UseCounter::Count(context, WebFeature::kRTCLocalSdpModification);
        if (ContainsLegacySimulcast(parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationSimulcast);
        }
        if (IceUfragPwdMismatch(last_answer_, parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationIceUfragPwd);
        }
        if (ContainsOpusStereo(parsed_sdp.sdp()) &&
            !ContainsOpusStereo(last_answer_)) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationOpusStereo);
        }
        // multiopus is never generated by the browser.
        if (ContainsMultiOpus(parsed_sdp.sdp())) {
          UseCounter::Count(context,
                            WebFeature::kRTCLocalSdpModificationMultiOpus);
        }
        return nullptr;
        // TODO(https://crbug.com/823036): Return failure for all modification.
      }
    }
  }
  return nullptr;
}

HeapHashSet<Member<RTCIceTransport>> RTCPeerConnection::ActiveIceTransports()
    const {
  HeapHashSet<Member<RTCIceTransport>> active_transports;
  for (auto transceiver : transceivers_) {
    auto* sender = transceiver->sender();
    if (sender) {
      auto* dtls_transport = transceiver->sender()->transport();
      if (dtls_transport) {
        auto* ice_transport = dtls_transport->iceTransport();
        if (ice_transport) {
          active_transports.insert(ice_transport);
        }
      }
    }
  }
  if (sctp_transport_) {
    auto* dtls_transport = sctp_transport_->transport();
    if (dtls_transport) {
      auto* ice_transport = dtls_transport->iceTransport();
      if (ice_transport) {
        active_transports.insert(ice_transport);
      }
    }
  }
  return active_transports;
}

void RTCPeerConnection::GenerateCertificateCompleted(
    ScriptPromiseResolver<RTCCertificate>* resolver,
    webrtc::scoped_refptr<webrtc::RTCCertificate> certificate) {
  if (!certificate) {
    resolver->Reject();
    return;
  }

  resolver->Resolve(
      MakeGarbageCollected<RTCCertificate>(std::move(certificate)));
}

void RTCPeerConnection::UpdateIceConnectionState() {
  auto new_state = ComputeIceConnectionState();
  if (ice_connection_state_ != new_state) {
    peer_handler_->TrackIceConnectionStateChange(new_state);
  }
  ChangeIceConnectionState(new_state);
}

ScriptPromise<IDLUndefined> RTCPeerConnection::setLocalDescription(
    ScriptState* script_state,
    ExceptionState& exception_state) {
  DCHECK(script_state->ContextIsValid());
  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionSetLocalDescription);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionSetLocalDescriptionPromise);

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  auto* request =
      MakeGarbageCollected<RTCVoidRequestPromiseImpl>(this, resolver);
  peer_handler_->SetLocalDescription(request);
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::setLocalDescription(
    ScriptState* script_state,
    const RTCSessionDescriptionInit* session_description_init,
    ExceptionState& exception_state) {
  if (closed_) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }

  DCHECK(script_state->ContextIsValid());
  if (!session_description_init->hasType()) {
    return setLocalDescription(script_state, exception_state);
  }

  String sdp = session_description_init->sdp();
  // https://w3c.github.io/webrtc-pc/#dom-peerconnection-setlocaldescription
  // step 4.4 and 4.5: If SDP is empty, return the last created offer or answer.
  if (sdp.empty()) {
    V8RTCSdpType::Enum type = session_description_init->type().AsEnum();
    sdp = GetSdpFromCache(type, last_offer_, last_answer_);
    if (sdp.empty() && type != V8RTCSdpType::Enum::kRollback) {
      bool has_pending_sdp_creation = false;
      if (type == V8RTCSdpType::Enum::kOffer) {
        has_pending_sdp_creation = pending_create_offer_count_ > 0;
      } else if (type == V8RTCSdpType::Enum::kAnswer ||
                 type == V8RTCSdpType::Enum::kPranswer) {
        has_pending_sdp_creation = pending_create_answer_count_ > 0;
      }

      if (!has_pending_sdp_creation) {
        const bool type_mismatch =
            !IsSignalingStateAllowedForType(signaling_state_, type);

        if (type_mismatch) {
          exception_state.ThrowDOMException(
              DOMExceptionCode::kInvalidStateError,
              "The type of the description does not match the expected signaling "
              "state.");
          return EmptyPromise();
        }
      }

      return setLocalDescription(script_state, exception_state);
    }
  }

  ExecutionContext* context = ExecutionContext::From(script_state);
  ParsedSessionDescription parsed_sdp = ParsedSessionDescription::Parse(
      session_description_init->type().AsString(), sdp);
  if (session_description_init->type() != V8RTCSdpType::Enum::kRollback) {
    DOMException* exception = checkSdpForStateErrors(context, parsed_sdp);
    if (exception) {
      exception_state.ThrowDOMException(
          static_cast<DOMExceptionCode>(exception->code()),
          exception->message());
      return EmptyPromise();
    }
  }

  UseCounter::Count(context, WebFeature::kRTCPeerConnectionSetLocalDescription);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionSetLocalDescriptionPromise);

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  auto* request =
      MakeGarbageCollected<RTCVoidRequestPromiseImpl>(this, resolver);
  peer_handler_->SetLocalDescription(request, std::move(parsed_sdp));
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::setLocalDescription(
    ScriptState* script_state,
    const RTCSessionDescriptionInit* session_description_init,
    V8VoidFunction* success_callback,
    V8RTCPeerConnectionErrorCallback* error_callback) {
  if (CallErrorCallbackIfSignalingStateClosed(
          ExecutionContext::From(script_state), signaling_state_,
          error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context, WebFeature::kRTCPeerConnectionSetLocalDescription);
  if (success_callback && error_callback) {
    UseCounter::Count(
        context,
        WebFeature::kRTCPeerConnectionSetLocalDescriptionLegacyCompliant);
  } else {
    if (!success_callback) {
      UseCounter::Count(
          context,
          WebFeature::
              kRTCPeerConnectionSetLocalDescriptionLegacyNoSuccessCallback);
    }
    if (!error_callback) {
      UseCounter::Count(
          context,
          WebFeature::
              kRTCPeerConnectionSetLocalDescriptionLegacyNoFailureCallback);
    }
  }

  String sdp = session_description_init->sdp();
  // https://w3c.github.io/webrtc-pc/#dom-peerconnection-setlocaldescription
  // step 4.4 and 4.5: If SDP is empty, return the last created offer or answer.
  if (sdp.empty() && session_description_init->hasType()) {
    V8RTCSdpType::Enum type = session_description_init->type().AsEnum();
    sdp = GetSdpFromCache(type, last_offer_, last_answer_);
    if (sdp.empty() && type != V8RTCSdpType::Enum::kRollback) {
      bool has_pending_sdp_creation = false;
      if (type == V8RTCSdpType::Enum::kOffer) {
        has_pending_sdp_creation = pending_create_offer_count_ > 0;
      } else if (type == V8RTCSdpType::Enum::kAnswer ||
                 type == V8RTCSdpType::Enum::kPranswer) {
        has_pending_sdp_creation = pending_create_answer_count_ > 0;
      }

      if (!has_pending_sdp_creation) {
        const bool type_mismatch =
            !IsSignalingStateAllowedForType(signaling_state_, type);

        if (type_mismatch) {
          if (error_callback) {
            DOMException* exception = MakeGarbageCollected<DOMException>(
                DOMExceptionCode::kInvalidStateError,
                "The type of the description does not match the expected "
                "signaling state.");
            AsyncCallErrorCallback(context, error_callback, exception);
          }
          return ToResolvedUndefinedPromise(script_state);
        }
      }

      auto* request = MakeGarbageCollected<RTCVoidRequestImpl>(
          GetExecutionContext(), this, success_callback, error_callback);
      peer_handler_->SetLocalDescription(request);
      return ToResolvedUndefinedPromise(script_state);
    }
  }

  ParsedSessionDescription parsed_sdp = ParsedSessionDescription::Parse(
      session_description_init->hasType()
          ? session_description_init->type().AsString()
          : String(),
      sdp);
  if (!session_description_init->hasType() ||
      session_description_init->type() != V8RTCSdpType::Enum::kRollback) {
    DOMException* exception = checkSdpForStateErrors(context, parsed_sdp);
    if (exception) {
      if (error_callback) {
        AsyncCallErrorCallback(context, error_callback, exception);
      }
      return ToResolvedUndefinedPromise(script_state);
    }
  }
  auto* request = MakeGarbageCollected<RTCVoidRequestImpl>(
      GetExecutionContext(), this, success_callback, error_callback);
  peer_handler_->SetLocalDescription(request, std::move(parsed_sdp));
  return ToResolvedUndefinedPromise(script_state);
}

RTCSessionDescription* RTCPeerConnection::localDescription() const {
  return pending_local_description_ ? pending_local_description_
                                    : current_local_description_;
}

RTCSessionDescription* RTCPeerConnection::currentLocalDescription() const {
  return current_local_description_.Get();
}

RTCSessionDescription* RTCPeerConnection::pendingLocalDescription() const {
  return pending_local_description_.Get();
}

ScriptPromise<IDLUndefined> RTCPeerConnection::setRemoteDescription(
    ScriptState* script_state,
    const RTCSessionDescriptionInit* session_description_init,
    ExceptionState& exception_state) {
  if (closed_) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }

  DCHECK(script_state->ContextIsValid());
  ParsedSessionDescription parsed_sdp =
      ParsedSessionDescription::Parse(session_description_init);
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }

  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionSetRemoteDescription);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionSetRemoteDescriptionPromise);

  if (ContainsLegacyRtpDataChannel(session_description_init->sdp())) {
    UseCounter::Count(context, WebFeature::kRTCLegacyRtpDataChannelNegotiated);
  }

  if (ContainsCandidate(session_description_init->sdp())) {
    DisableBackForwardCache(context);
  }

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  auto* request =
      MakeGarbageCollected<RTCVoidRequestPromiseImpl>(this, resolver);
  peer_handler_->SetRemoteDescription(request, std::move(parsed_sdp));
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::setRemoteDescription(
    ScriptState* script_state,
    const RTCSessionDescriptionInit* session_description_init,
    V8VoidFunction* success_callback,
    V8RTCPeerConnectionErrorCallback* error_callback) {
  if (CallErrorCallbackIfSignalingStateClosed(
          ExecutionContext::From(script_state), signaling_state_,
          error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  DCHECK(script_state->ContextIsValid());
  ParsedSessionDescription parsed_sdp =
      ParsedSessionDescription::Parse(session_description_init);
  ExecutionContext* context = ExecutionContext::From(script_state);
  UseCounter::Count(context,
                    WebFeature::kRTCPeerConnectionSetRemoteDescription);
  if (success_callback && error_callback) {
    UseCounter::Count(
        context,
        WebFeature::kRTCPeerConnectionSetRemoteDescriptionLegacyCompliant);
  } else {
    if (!success_callback) {
      UseCounter::Count(
          context,
          WebFeature::
              kRTCPeerConnectionSetRemoteDescriptionLegacyNoSuccessCallback);
    }
    if (!error_callback) {
      UseCounter::Count(
          context,
          WebFeature::
              kRTCPeerConnectionSetRemoteDescriptionLegacyNoFailureCallback);
    }
  }

  if (ContainsLegacyRtpDataChannel(session_description_init->sdp())) {
    UseCounter::Count(context, WebFeature::kRTCLegacyRtpDataChannelNegotiated);
  }

  if (ContainsCandidate(session_description_init->sdp())) {
    DisableBackForwardCache(context);
  }

  if (CallErrorCallbackIfSignalingStateClosed(context, signaling_state_,
                                              error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  auto* request = MakeGarbageCollected<RTCVoidRequestImpl>(
      GetExecutionContext(), this, success_callback, error_callback);
  peer_handler_->SetRemoteDescription(request, std::move(parsed_sdp));
  return ToResolvedUndefinedPromise(script_state);
}

RTCSessionDescription* RTCPeerConnection::remoteDescription() const {
  return pending_remote_description_ ? pending_remote_description_
                                     : current_remote_description_;
}

RTCSessionDescription* RTCPeerConnection::currentRemoteDescription() const {
  return current_remote_description_.Get();
}

RTCSessionDescription* RTCPeerConnection::pendingRemoteDescription() const {
  return pending_remote_description_.Get();
}

RTCConfiguration* RTCPeerConnection::getConfiguration(
    ScriptState* script_state) const {
  RTCConfiguration* result = RTCConfiguration::Create();
  const auto& webrtc_configuration = peer_handler_->GetConfiguration();

  switch (webrtc_configuration.type) {
    case webrtc::PeerConnectionInterface::kRelay:
      result->setIceTransportPolicy(V8RTCIceTransportPolicy::Enum::kRelay);
      break;
    case webrtc::PeerConnectionInterface::kAll:
      result->setIceTransportPolicy(V8RTCIceTransportPolicy::Enum::kAll);
      break;
    default:
      NOTREACHED();
  }

  switch (webrtc_configuration.bundle_policy) {
    case webrtc::PeerConnectionInterface::kBundlePolicyMaxCompat:
      result->setBundlePolicy(V8RTCBundlePolicy::Enum::kMaxCompat);
      break;
    case webrtc::PeerConnectionInterface::kBundlePolicyMaxBundle:
      result->setBundlePolicy(V8RTCBundlePolicy::Enum::kMaxBundle);
      break;
    case webrtc::PeerConnectionInterface::kBundlePolicyBalanced:
      result->setBundlePolicy(V8RTCBundlePolicy::Enum::kBalanced);
      break;
    default:
      NOTREACHED();
  }

  switch (webrtc_configuration.rtcp_mux_policy) {
    case webrtc::PeerConnectionInterface::kRtcpMuxPolicyNegotiate:
      result->setRtcpMuxPolicy(V8RTCRtcpMuxPolicy::Enum::kNegotiate);
      break;
    case webrtc::PeerConnectionInterface::kRtcpMuxPolicyRequire:
      result->setRtcpMuxPolicy(V8RTCRtcpMuxPolicy::Enum::kRequire);
      break;
    default:
      NOTREACHED();
  }

  if (RuntimeEnabledFeatures::RtcRtpHeaderEncryptionPolicyEnabled()) {
    switch (webrtc_configuration.crypto_options.srtp.cryptex_policy) {
      case webrtc::CryptoOptions::Srtp::CryptexPolicy::kNegotiate:
        result->setRtpHeaderEncryptionPolicy(
            V8RTCRtpHeaderEncryptionPolicy::Enum::kNegotiate);
        break;
      case webrtc::CryptoOptions::Srtp::CryptexPolicy::kRequire:
        result->setRtpHeaderEncryptionPolicy(
            V8RTCRtpHeaderEncryptionPolicy::Enum::kRequire);
        break;
      default:
        NOTREACHED();
    }
  }

  HeapVector<Member<RTCIceServer>> ice_servers;
  ice_servers.reserve(
      base::checked_cast<wtf_size_t>(webrtc_configuration.servers.size()));
  for (const auto& webrtc_server : webrtc_configuration.servers) {
    auto* ice_server = RTCIceServer::Create();

    Vector<String> url_vector;
    url_vector.reserve(
        base::checked_cast<wtf_size_t>(webrtc_server.urls.size()));
    for (const auto& url : webrtc_server.urls) {
      url_vector.emplace_back(url);
    }
    auto* urls = MakeGarbageCollected<V8UnionStringOrStringSequence>(
        std::move(url_vector));

    ice_server->setUrls(urls);
    ice_server->setUsername(String(webrtc_server.username));
    ice_server->setCredential(String(webrtc_server.password));
    ice_servers.push_back(ice_server);
  }
  result->setIceServers(ice_servers);

  HeapVector<blink::Member<RTCCertificate>> certificates;
  certificates.reserve(
      base::checked_cast<wtf_size_t>(webrtc_configuration.certificates.size()));
  for (const auto& webrtc_certificate : webrtc_configuration.certificates) {
    certificates.emplace_back(
        MakeGarbageCollected<RTCCertificate>(webrtc_certificate));
  }
  result->setCertificates(certificates);

  result->setIceCandidatePoolSize(webrtc_configuration.ice_candidate_pool_size);

  const auto* context = ExecutionContext::From(script_state);
  if (RuntimeEnabledFeatures::RtcAudioJitterBufferMaxPacketsEnabled(context)) {
    int audio_jitter_buffer_max_packets =
        webrtc_configuration.audio_jitter_buffer_max_packets;
    result->setRtcAudioJitterBufferMaxPackets(
        static_cast<int32_t>(audio_jitter_buffer_max_packets));
    result->setRtcAudioJitterBufferFastAccelerate(
        webrtc_configuration.audio_jitter_buffer_fast_accelerate);
    int audio_jitter_buffer_min_delay_ms =
        webrtc_configuration.audio_jitter_buffer_min_delay_ms;
    result->setRtcAudioJitterBufferMinDelayMs(
        static_cast<int32_t>(audio_jitter_buffer_min_delay_ms));
  }
  result->setEncodedInsertableStreams(
      peer_handler_->encoded_insertable_streams());
  result->setAlwaysNegotiateDataChannels(
      webrtc_configuration.always_negotiate_data_channels);

  return result;
}

void RTCPeerConnection::setConfiguration(
    ScriptState* script_state,
    const RTCConfiguration* rtc_configuration,
    ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return;
  }

  webrtc::PeerConnectionInterface::RTCConfiguration configuration =
      ParseConfiguration(ExecutionContext::From(script_state),
                         rtc_configuration, &exception_state);

  if (exception_state.HadException()) {
    return;
  }

  if (peer_handler_->encoded_insertable_streams() !=
      rtc_configuration->encodedInsertableStreams()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidModificationError,
        "Attempted to modify the PeerConnection's "
        "configuration in an unsupported way.");
  }

  webrtc::RTCErrorType error = peer_handler_->SetConfiguration(configuration);
  if (error == webrtc::RTCErrorType::NONE) {
    return;
  } else if (error == webrtc::RTCErrorType::INVALID_MODIFICATION) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidModificationError,
        "Attempted to modify the PeerConnection's configuration in an "
        "unsupported way.");
  } else if (error == webrtc::RTCErrorType::SYNTAX_ERROR) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kSyntaxError,
        "The given configuration has a syntax error.");
  } else if (error == webrtc::RTCErrorType::INVALID_PARAMETER) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidAccessError,
        "The given configuration has invalid parameters.");
  } else {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kOperationError,
        "Could not update the PeerConnection with the given configuration.");
  }
}

ScriptPromise<RTCCertificate> RTCPeerConnection::generateCertificate(
    ScriptState* script_state,
    const V8AlgorithmIdentifier* keygen_algorithm,
    ExceptionState& exception_state) {
  // Normalize |keygenAlgorithm| with WebCrypto, making sure it is a recognized
  // AlgorithmIdentifier.
  WebCryptoAlgorithm crypto_algorithm;
  if (!NormalizeAlgorithm(script_state->GetIsolate(), keygen_algorithm,
                          kWebCryptoOperationGenerateKey, crypto_algorithm,
                          exception_state)) {
    return EmptyPromise();
  }

  // Check if |keygenAlgorithm| contains the optional DOMTimeStamp |expires|
  // attribute.
  std::optional<DOMTimeStamp> expires;
  if (keygen_algorithm->IsObject()) {
    Dictionary keygen_algorithm_dict(keygen_algorithm->GetAsObject());
    if (exception_state.HadException()) {
      return EmptyPromise();
    }

    bool has_expires =
        keygen_algorithm_dict.HasProperty("expires", exception_state);
    if (exception_state.HadException()) {
      return EmptyPromise();
    }

    if (has_expires) {
      v8::Local<v8::Value> expires_value;
      keygen_algorithm_dict.Get("expires", expires_value);
      if (expires_value->IsNumber()) {
        double expires_double =
            expires_value
                ->ToNumber(script_state->GetIsolate()->GetCurrentContext())
                .ToLocalChecked()
                ->Value();

        if (base::IsValueInRangeForNumericType<DOMTimeStamp>(expires_double)) {
          expires = static_cast<DOMTimeStamp>(expires_double);
        } else {
          exception_state.ThrowTypeError(
              "The value for 'expires' is outside the valid range.");
          return EmptyPromise();
        }
      } else {
        exception_state.ThrowTypeError("Invalid type for expires attribute.");
        return EmptyPromise();
      }
    }
  }

  // Convert from WebCrypto representation to recognized WebRTCKeyParams. WebRTC
  // supports a small subset of what are valid AlgorithmIdentifiers.
  const char* unsupported_params_string =
      "The 1st argument provided is an AlgorithmIdentifier with a supported "
      "algorithm name, but the parameters are not supported.";
  std::optional<webrtc::KeyParams> key_params;
  switch (crypto_algorithm.Id()) {
    case kWebCryptoAlgorithmIdRsaSsaPkcs1v1_5: {
      // name: "RSASSA-PKCS1-v1_5"
      std::optional<uint32_t> public_exponent =
          crypto_algorithm.RsaHashedKeyGenParams()->PublicExponentAsU32();
      unsigned modulus_length =
          crypto_algorithm.RsaHashedKeyGenParams()->ModulusLengthBits();
      // Parameters must fit in int to be passed to webrtc::KeyParams::RSA. The
      // only recognized "hash" is "SHA-256".
      // TODO(bugs.webrtc.org/364338811): deprecate 1024 bit keys.
      if (public_exponent &&
          base::IsValueInRangeForNumericType<int>(*public_exponent) &&
          base::IsValueInRangeForNumericType<int>(modulus_length) &&
          crypto_algorithm.RsaHashedKeyGenParams()->GetHash().Id() ==
              kWebCryptoAlgorithmIdSha256) {
        key_params =
            webrtc::KeyParams::RSA(base::checked_cast<int>(modulus_length),
                                   base::checked_cast<int>(*public_exponent));
      } else {
        exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                          unsupported_params_string);
        return EmptyPromise();
      }
      break;
    }
    case kWebCryptoAlgorithmIdEcdsa:
      // name: "ECDSA"
      // The only recognized "namedCurve" is "P-256".
      if (crypto_algorithm.EcKeyGenParams()->NamedCurve() ==
          kWebCryptoNamedCurveP256) {
        key_params = webrtc::KeyParams::ECDSA(webrtc::EC_NIST_P256);
      } else {
        exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                          unsupported_params_string);
        return EmptyPromise();
      }
      break;
    default:
      exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                        "The 1st argument provided is an "
                                        "AlgorithmIdentifier, but the "
                                        "algorithm is not supported.");
      return EmptyPromise();
  }
  DCHECK(key_params.has_value());
  MeasureGenerateCertificateKeyType(key_params);

  auto certificate_generator = std::make_unique<RTCCertificateGenerator>();

  // |keyParams| was successfully constructed, but does the certificate
  // generator support these parameters?
  if (!certificate_generator->IsSupportedKeyParams(key_params.value())) {
    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
                                      unsupported_params_string);
    return EmptyPromise();
  }

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<RTCCertificate>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();

  // Helper closure callback for RTCPeerConnection::generateCertificate.
  auto completion_callback =
      CrossThreadBindOnce(RTCPeerConnection::GenerateCertificateCompleted,
                          WrapCrossThreadPersistent(resolver));

  // Generate certificate. The |certificateObserver| will resolve the promise
  // asynchronously upon completion. The observer will manage its own
  // destruction as well as the resolver's destruction.
  scoped_refptr<base::SingleThreadTaskRunner> task_runner =
      ExecutionContext::From(script_state)
          ->GetTaskRunner(blink::TaskType::kInternalMedia);
  if (!expires) {
    certificate_generator->GenerateCertificate(
        key_params.value(), std::move(completion_callback),
        *ExecutionContext::From(script_state), task_runner);
  } else {
    certificate_generator->GenerateCertificateWithExpiration(
        key_params.value(), expires.value(), std::move(completion_callback),
        *ExecutionContext::From(script_state), task_runner);
  }

  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::addIceCandidate(
    ScriptState* script_state,
    const RTCIceCandidateInit* candidate,
    ExceptionState& exception_state) {
  DCHECK(script_state->ContextIsValid());
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                      kSignalingStateClosedMessage);
    return EmptyPromise();
  }

  if (candidate->hasCandidate() && candidate->candidate().empty()) {
    // Temporary mitigation to avoid throwing an exception when candidate is
    // empty or nothing was passed.
    // TODO(crbug.com/978582): Remove this mitigation when the WebRTC layer
    // handles the empty candidate field or the null candidate correctly.
    return ToResolvedUndefinedPromise(script_state);
  }

  RTCIceCandidatePlatform* platform_candidate =
      ConvertToRTCIceCandidatePlatform(ExecutionContext::From(script_state),
                                       candidate);

  if (IsIceCandidateMissingSdpMidAndMLineIndex(candidate)) {
    exception_state.ThrowTypeError(
        "Candidate missing values for both sdpMid and sdpMLineIndex");
    return EmptyPromise();
  }

  DisableBackForwardCache(GetExecutionContext());

  // If WebRTC is blocked globally, all candidates are "administratively
  // prohibited" per spec, so we skip adding them.
  if (are_ice_candidates_administratively_prohibited_) {
    return EmptyPromise();
  }

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();
  auto* request =
      MakeGarbageCollected<RTCVoidRequestPromiseImpl>(this, resolver);
  peer_handler_->AddIceCandidate(request, std::move(platform_candidate));
  return promise;
}

ScriptPromise<IDLUndefined> RTCPeerConnection::addIceCandidate(
    ScriptState* script_state,
    const RTCIceCandidateInit* candidate,
    V8VoidFunction* success_callback,
    V8RTCPeerConnectionErrorCallback* error_callback,
    ExceptionState& exception_state) {
  DCHECK(script_state->ContextIsValid());
  DCHECK(success_callback);
  DCHECK(error_callback);

  if (CallErrorCallbackIfSignalingStateClosed(
          ExecutionContext::From(script_state), signaling_state_,
          error_callback)) {
    return ToResolvedUndefinedPromise(script_state);
  }

  if (IsIceCandidateMissingSdpMidAndMLineIndex(candidate)) {
    exception_state.ThrowTypeError(
        "Candidate missing values for both sdpMid and sdpMLineIndex");
    return EmptyPromise();
  }

  RTCIceCandidatePlatform* platform_candidate =
      ConvertToRTCIceCandidatePlatform(ExecutionContext::From(script_state),
                                       candidate);

  // Temporary mitigation to avoid throwing an exception when candidate is
  // empty.
  // TODO(crbug.com/978582): Remove this mitigation when the WebRTC layer
  // handles the empty candidate field or the null candidate correctly.
  if (platform_candidate->Candidate().empty()) {
    return ToResolvedUndefinedPromise(script_state);
  }

  DisableBackForwardCache(GetExecutionContext());

  auto* request = MakeGarbageCollected<RTCVoidRequestImpl>(
      GetExecutionContext(), this, success_callback, error_callback);
  peer_handler_->AddIceCandidate(request, std::move(platform_candidate));
  return ToResolvedUndefinedPromise(script_state);
}

V8RTCSignalingState RTCPeerConnection::signalingState() const {
  switch (signaling_state_) {
    case webrtc::PeerConnectionInterface::SignalingState::kStable:
      return V8RTCSignalingState(V8RTCSignalingState::Enum::kStable);
    case webrtc::PeerConnectionInterface::SignalingState::kHaveLocalOffer:
      return V8RTCSignalingState(V8RTCSignalingState::Enum::kHaveLocalOffer);
    case webrtc::PeerConnectionInterface::SignalingState::kHaveLocalPrAnswer:
      return V8RTCSignalingState(V8RTCSignalingState::Enum::kHaveLocalPranswer);
    case webrtc::PeerConnectionInterface::SignalingState::kHaveRemoteOffer:
      return V8RTCSignalingState(V8RTCSignalingState::Enum::kHaveRemoteOffer);
    case webrtc::PeerConnectionInterface::SignalingState::kHaveRemotePrAnswer:
      return V8RTCSignalingState(
          V8RTCSignalingState::Enum::kHaveRemotePranswer);
    case webrtc::PeerConnectionInterface::SignalingState::kClosed:
      return V8RTCSignalingState(V8RTCSignalingState::Enum::kClosed);
  }
  NOTREACHED();
}

V8RTCIceGatheringState RTCPeerConnection::iceGatheringState() const {
  switch (ice_gathering_state_) {
    case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew:
      return V8RTCIceGatheringState(V8RTCIceGatheringState::Enum::kNew);
    case webrtc::PeerConnectionInterface::IceGatheringState::
        kIceGatheringGathering:
      return V8RTCIceGatheringState(V8RTCIceGatheringState::Enum::kGathering);
    case webrtc::PeerConnectionInterface::IceGatheringState::
        kIceGatheringComplete:
      return V8RTCIceGatheringState(V8RTCIceGatheringState::Enum::kComplete);
  }
  NOTREACHED();
}

V8RTCIceConnectionState RTCPeerConnection::iceConnectionState() const {
  if (closed_) {
    return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kClosed);
  }
  switch (ice_connection_state_) {
    case webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionNew:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kNew);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionChecking:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kChecking);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionConnected:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kConnected);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionCompleted:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kCompleted);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionFailed:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kFailed);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionDisconnected:
      return V8RTCIceConnectionState(
          V8RTCIceConnectionState::Enum::kDisconnected);
    case webrtc::PeerConnectionInterface::IceConnectionState::
        kIceConnectionClosed:
      return V8RTCIceConnectionState(V8RTCIceConnectionState::Enum::kClosed);
    case webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionMax:
      // Should not happen.
      break;
  }
  NOTREACHED();
}

V8RTCPeerConnectionState RTCPeerConnection::connectionState() const {
  if (closed_) {
    return V8RTCPeerConnectionState(V8RTCPeerConnectionState::Enum::kClosed);
  }
  switch (peer_connection_state_) {
    case webrtc::PeerConnectionInterface::PeerConnectionState::kNew:
      return V8RTCPeerConnectionState(V8RTCPeerConnectionState::Enum::kNew);
    case webrtc::PeerConnectionInterface::PeerConnectionState::kConnecting:
      return V8RTCPeerConnectionState(
          V8RTCPeerConnectionState::Enum::kConnecting);
    case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
      return V8RTCPeerConnectionState(
          V8RTCPeerConnectionState::Enum::kConnected);
    case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
      return V8RTCPeerConnectionState(V8RTCPeerConnectionState::Enum::kFailed);
    case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
      return V8RTCPeerConnectionState(
          V8RTCPeerConnectionState::Enum::kDisconnected);
    case webrtc::PeerConnectionInterface::PeerConnectionState::kClosed:
      return V8RTCPeerConnectionState(V8RTCPeerConnectionState::Enum::kClosed);
  }
  NOTREACHED();
}

std::optional<bool> RTCPeerConnection::canTrickleIceCandidates() const {
  if (closed_ || !remoteDescription()) {
    return std::nullopt;
  }
  webrtc::PeerConnectionInterface* native_connection =
      peer_handler_->NativePeerConnection();
  if (!native_connection) {
    return std::nullopt;
  }
  std::optional<bool> can_trickle =
      native_connection->can_trickle_ice_candidates();
  if (!can_trickle) {
    return std::nullopt;
  }
  return *can_trickle;
}

void RTCPeerConnection::restartIce() {
  if (closed_ || are_ice_candidates_administratively_prohibited_) {
    return;
  }
  peer_handler_->RestartIce();
}

void RTCPeerConnection::addStream(ScriptState* script_state,
                                  MediaStream* stream,
                                  ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return;
  }

  MediaStreamVector streams;
  streams.push_back(stream);
  for (const auto& track : stream->getTracks()) {
    addTrack(track, streams, IGNORE_EXCEPTION);
  }

  stream->RegisterObserver(this);
}

void RTCPeerConnection::removeStream(MediaStream* stream,
                                     ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return;
  }
  for (const auto& track : stream->getTracks()) {
    auto* sender = FindSenderForTrackAndStream(track, stream);
    if (!sender) {
      continue;
    }
    removeTrack(sender, IGNORE_EXCEPTION);
  }
  stream->UnregisterObserver(this);
}

MediaStreamVector RTCPeerConnection::getLocalStreams() const {
  MediaStreamVector local_streams;
  for (const auto& transceiver : transceivers_) {
    if (!transceiver->DirectionHasSend()) {
      continue;
    }
    for (const auto& stream : transceiver->sender()->streams()) {
      if (!local_streams.Contains(stream)) {
        local_streams.push_back(stream);
      }
    }
  }
  return local_streams;
}

MediaStreamVector RTCPeerConnection::getRemoteStreams() const {
  MediaStreamVector remote_streams;
  for (const auto& transceiver : transceivers_) {
    if (!transceiver->DirectionHasRecv()) {
      continue;
    }
    for (const auto& stream : transceiver->receiver()->streams()) {
      if (!remote_streams.Contains(stream)) {
        remote_streams.push_back(stream);
      }
    }
  }
  return remote_streams;
}

ScriptPromise<RTCStatsReport> RTCPeerConnection::getStats(
    ScriptState* script_state,
    MediaStreamTrack* selector,
    ExceptionState& exception_state) {
  if (!selector) {
    ExecutionContext* context = ExecutionContext::From(script_state);
    UseCounter::Count(context, WebFeature::kRTCPeerConnectionGetStats);

    if (!peer_handler_) {
      LOG(ERROR) << "Internal error: peer_handler_ has been discarded";
      exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
                                        "Internal error: release in progress");
      return EmptyPromise();
    }
    auto* resolver =
        MakeGarbageCollected<ScriptPromiseResolver<RTCStatsReport>>(
            script_state, exception_state.GetContext());
    auto promise = resolver->Promise();
    if (peer_handler_unregistered_) {
      LOG(ERROR) << "Internal error: context is destroyed";
      // This is needed to have the resolver release its internal resources
      // while leaving the associated promise pending as specified.
      resolver->Detach();
    } else {
      peer_handler_->GetStats(
          CrossThreadBindOnce(WebRTCStatsReportCallbackResolver,
                              WrapCrossThreadPersistent(resolver)));
    }
    return promise;
  }

  // Find the sender or receiver that represent the selector.
  size_t track_uses = 0u;
  RTCRtpSender* track_sender = nullptr;
  for (const auto& sender : rtp_senders_) {
    if (sender->track() == selector) {
      ++track_uses;
      track_sender = sender;
    }
  }
  RTCRtpReceiver* track_receiver = nullptr;
  for (const auto& receiver : rtp_receivers_) {
    if (receiver->track() == selector) {
      ++track_uses;
      track_receiver = receiver;
    }
  }
  if (track_uses == 0u) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidAccessError,
        "There is no sender or receiver for the track.");
    return EmptyPromise();
  }
  if (track_uses > 1u) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidAccessError,
        "There are more than one sender or receiver for the track.");
    return EmptyPromise();
  }
  // There is just one use of the track, a sender or receiver.
  if (track_sender) {
    DCHECK(!track_receiver);
    return track_sender->getStats(script_state);
  }
  DCHECK(track_receiver);
  return track_receiver->getStats(script_state);
}

const HeapVector<Member<RTCRtpTransceiver>>&
RTCPeerConnection::getTransceivers() const {
  return transceivers_;
}

const HeapVector<Member<RTCRtpSender>>& RTCPeerConnection::getSenders() const {
  return rtp_senders_;
}

const HeapVector<Member<RTCRtpReceiver>>& RTCPeerConnection::getReceivers()
    const {
  return rtp_receivers_;
}

RtpContributingSourceCache& RTCPeerConnection::GetRtpContributingSourceCache() {
  DCHECK(rtp_contributing_source_cache_.has_value());
  return rtp_contributing_source_cache_.value();
}

std::optional<webrtc::RtpTransceiverInit> ValidateRtpTransceiverInit(
    ExecutionContext* execution_context,
    ExceptionState& exception_state,
    const RTCRtpTransceiverInit* init,
    const String kind) {
  auto webrtc_init = ToRtpTransceiverInit(execution_context, init, kind);
  // Validate sendEncodings.
  for (auto& encoding : webrtc_init.send_encodings) {
    if (encoding.rid.length() > 16) {
      exception_state.ThrowTypeError("Illegal length of rid");
      return std::nullopt;
    }
    // Allowed characters: a-z 0-9 _ and -
    if (encoding.rid.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM"
                                       "NOPQRSTUVWXYZ0123456789-_") !=
        std::string::npos) {
      exception_state.ThrowTypeError("Illegal character in rid");
      return std::nullopt;
    }
  }
  return webrtc_init;
}

RTCRtpTransceiver* RTCPeerConnection::addTransceiver(
    const V8UnionMediaStreamTrackOrString* track_or_kind,
    const RTCRtpTransceiverInit* init,
    ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return nullptr;
  }
  webrtc::RTCErrorOr<std::unique_ptr<RTCRtpTransceiverPlatform>> result =
      webrtc::RTCError(webrtc::RTCErrorType::UNSUPPORTED_OPERATION);
  switch (track_or_kind->GetContentType()) {
    case V8UnionMediaStreamTrackOrString::ContentType::kMediaStreamTrack: {
      MediaStreamTrack* track = track_or_kind->GetAsMediaStreamTrack();

      auto webrtc_init = ValidateRtpTransceiverInit(
          GetExecutionContext(), exception_state, init, track->kind());
      if (!webrtc_init) {
        return nullptr;
      }

      RegisterTrack(track);
      result = peer_handler_->AddTransceiverWithTrack(track->Component(),
                                                      std::move(*webrtc_init));
      break;
    }
    case V8UnionMediaStreamTrackOrString::ContentType::kString: {
      const String& kind_string = track_or_kind->GetAsString();
      // TODO(hbos): Make webrtc::MediaType an allowed identifier in
      // rtc_peer_connection.cc and use that instead of a boolean.
      String kind;
      if (kind_string == "audio") {
        kind = webrtc::MediaStreamTrackInterface::kAudioKind;
      } else if (kind_string == "video") {
        kind = webrtc::MediaStreamTrackInterface::kVideoKind;
      } else {
        exception_state.ThrowTypeError(
            "The argument provided as parameter 1 is not a valid "
            "MediaStreamTrack kind ('audio' or 'video').");
        return nullptr;
      }

      auto webrtc_init = ValidateRtpTransceiverInit(
          GetExecutionContext(), exception_state, init, kind);
      if (!webrtc_init) {
        return nullptr;
      }

      result = peer_handler_->AddTransceiverWithKind(std::move(kind),
                                                     std::move(*webrtc_init));
      break;
    }
  }
  if (!result.ok()) {
    ThrowExceptionFromRTCError(result.error(), exception_state);
    return nullptr;
  }
  return CreateOrUpdateTransceiver(result.MoveValue());
}

RTCRtpSender* RTCPeerConnection::addTrack(MediaStreamTrack* track,
                                          MediaStreamVector streams,
                                          ExceptionState& exception_state) {
  DCHECK(track);
  DCHECK(track->Component());
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return nullptr;
  }
  for (const auto& sender : rtp_senders_) {
    if (sender->track() == track) {
      exception_state.ThrowDOMException(
          DOMExceptionCode::kInvalidAccessError,
          "A sender already exists for the track.");
      return nullptr;
    }
  }

  MediaStreamDescriptorVector descriptors(streams.size());
  for (wtf_size_t i = 0; i < streams.size(); ++i) {
    descriptors[i] = streams[i]->Descriptor();
  }
  webrtc::RTCErrorOr<std::unique_ptr<RTCRtpTransceiverPlatform>>
      error_or_transceiver =
          peer_handler_->AddTrack(track->Component(), descriptors);
  if (!error_or_transceiver.ok()) {
    ThrowExceptionFromRTCError(error_or_transceiver.error(), exception_state);
    return nullptr;
  }

  auto platform_transceiver = error_or_transceiver.MoveValue();

  // The track must be known to the peer connection when performing
  // CreateOrUpdateSender() below.
  RegisterTrack(track);

  auto stream_ids = platform_transceiver->Sender()->StreamIds();
  RTCRtpTransceiver* transceiver =
      CreateOrUpdateTransceiver(std::move(platform_transceiver));
  RTCRtpSender* sender = transceiver->sender();
  // Newly created senders have no streams set, we have to set it ourselves.
  sender->set_streams(streams);
  // The native sender may have filtered out duplicates.
  DCHECK_LE(stream_ids.size(), streams.size());
  return sender;
}

void RTCPeerConnection::removeTrack(RTCRtpSender* sender,
                                    ExceptionState& exception_state) {
  DCHECK(sender);
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return;
  }
  auto it = FindSender(*sender->web_sender());
  if (it == rtp_senders_.end()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidAccessError,
        "The sender was not created by this peer connection.");
    return;
  }

  auto error_or_transceiver = peer_handler_->RemoveTrack(sender->web_sender());
  if (!error_or_transceiver.ok()) {
    ThrowExceptionFromRTCError(error_or_transceiver.error(), exception_state);
    return;
  }
  if (!error_or_transceiver.value()) {
    // There is no transceiver to update - the operation was cancelled, such
    // as if the transceiver was rolled back.
    return;
  }
  CreateOrUpdateTransceiver(error_or_transceiver.MoveValue());
}

RTCSctpTransport* RTCPeerConnection::sctp() const {
  return sctp_transport_.Get();
}

RTCDataChannel* RTCPeerConnection::createDataChannel(
    ScriptState* script_state,
    String label,
    const RTCDataChannelInit* data_channel_dict,
    ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return nullptr;
  }

  webrtc::DataChannelInit init;
  init.ordered = data_channel_dict->ordered();
  ExecutionContext* context = ExecutionContext::From(script_state);
  if (data_channel_dict->hasMaxPacketLifeTime()) {
    UseCounter::Count(
        context,
        WebFeature::kRTCPeerConnectionCreateDataChannelMaxPacketLifeTime);
    init.maxRetransmitTime = data_channel_dict->maxPacketLifeTime();
  }
  if (data_channel_dict->hasMaxRetransmits()) {
    UseCounter::Count(
        context, WebFeature::kRTCPeerConnectionCreateDataChannelMaxRetransmits);
    init.maxRetransmits = data_channel_dict->maxRetransmits();
  }
  init.protocol = data_channel_dict->protocol().Utf8();
  init.negotiated = data_channel_dict->negotiated();
  if (init.negotiated && data_channel_dict->hasId()) {
    init.id = data_channel_dict->id();
  }
  if (data_channel_dict->hasPriority()) {
    init.priority = [&] {
      switch (data_channel_dict->priority().AsEnum()) {
        case V8RTCPriorityType::Enum::kVeryLow:
          return webrtc::PriorityValue(webrtc::Priority::kVeryLow);
        case V8RTCPriorityType::Enum::kLow:
          return webrtc::PriorityValue(webrtc::Priority::kLow);
        case V8RTCPriorityType::Enum::kMedium:
          return webrtc::PriorityValue(webrtc::Priority::kMedium);
        case V8RTCPriorityType::Enum::kHigh:
          return webrtc::PriorityValue(webrtc::Priority::kHigh);
      }
    }();
  }
  // Checks from WebRTC specification section 6.1
  // If [[DataChannelLabel]] is longer than 65535 bytes, throw a
  // TypeError.
  if (label.Utf8().length() > 65535) {
    exception_state.ThrowTypeError("RTCDataChannel label too long");
    return nullptr;
  }
  // If [[DataChannelProtocol]] is longer than 65535 bytes long, throw a
  // TypeError.
  if (init.protocol.length() > 65535) {
    exception_state.ThrowTypeError("RTCDataChannel protocol too long");
    return nullptr;
  }
  // If [[Negotiated]] is true and [[DataChannelId]] is null, throw a TypeError.
  if (init.negotiated && init.id == -1) {
    exception_state.ThrowTypeError(
        "RTCDataChannel must have id set if negotiated is true");
    return nullptr;
  }
  // If both [[MaxPacketLifeTime]] and [[MaxRetransmits]] attributes are set
  // (not null), throw a TypeError.
  if (init.maxRetransmitTime >= 0 && init.maxRetransmits >= 0) {
    exception_state.ThrowTypeError(
        "RTCDataChannel cannot have both max retransmits and max lifetime");
    return nullptr;
  }
  // If [[DataChannelId]] is equal to 65535, which is greater than the maximum
  // allowed ID of 65534 but still qualifies as an unsigned short, throw a
  // TypeError.
  if (init.id >= 65535) {
    exception_state.ThrowTypeError("RTCDataChannel cannot have id > 65534");
    return nullptr;
  }
  // Further checks of DataChannelId are done in the webrtc layer.

  webrtc::scoped_refptr<webrtc::DataChannelInterface> webrtc_channel =
      peer_handler_->CreateDataChannel(label, init);
  if (!webrtc_channel) {
    exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
                                      "RTCDataChannel creation failed");
    return nullptr;
  }
  auto* channel = MakeGarbageCollected<RTCDataChannel>(
      GetExecutionContext(), std::move(webrtc_channel));

  return channel;
}

MediaStreamTrack* RTCPeerConnection::GetTrackForTesting(
    MediaStreamComponent* component) const {
  auto it = tracks_.find(component);
  if (it != tracks_.end()) {
    return it->value.Get();
  } else {
    return nullptr;
  }
}

RTCRtpSender* RTCPeerConnection::FindSenderForTrackAndStream(
    MediaStreamTrack* track,
    MediaStream* stream) {
  for (const auto& rtp_sender : rtp_senders_) {
    if (rtp_sender->track() == track) {
      auto streams = rtp_sender->streams();
      if (streams.size() == 1u && streams[0] == stream) {
        return rtp_sender.Get();
      }
    }
  }
  return nullptr;
}

HeapVector<Member<RTCRtpSender>>::iterator RTCPeerConnection::FindSender(
    const RTCRtpSenderPlatform& web_sender) {
  return std::ranges::find_if(rtp_senders_, [&](const auto& sender) {
    return sender->web_sender()->Id() == web_sender.Id();
  });
}

HeapVector<Member<RTCRtpReceiver>>::iterator RTCPeerConnection::FindReceiver(
    const RTCRtpReceiverPlatform& platform_receiver) {
  return std::ranges::find_if(rtp_receivers_, [&](const auto& receiver) {
    return receiver->platform_receiver()->Id() == platform_receiver.Id();
  });
}

HeapVector<Member<RTCRtpTransceiver>>::iterator
RTCPeerConnection::FindTransceiverById(uintptr_t id) {
  return std::ranges::find_if(transceivers_, [&](const auto& transceiver) {
    return transceiver->platform_transceiver()->Id() == id;
  });
}

HeapVector<Member<RTCRtpTransceiver>>::iterator
RTCPeerConnection::FindTransceiver(
    const RTCRtpTransceiverPlatform& platform_transceiver) {
  return FindTransceiverById(platform_transceiver.Id());
}

RTCRtpSender* RTCPeerConnection::CreateOrUpdateSender(
    std::unique_ptr<RTCRtpSenderPlatform> rtp_sender_platform,
    String kind) {
  // The track corresponding to |web_track| must already be known to us by being
  // in |tracks_|, as is a prerequisite of CreateOrUpdateSender().
  MediaStreamComponent* component = rtp_sender_platform->Track();
  MediaStreamTrack* track = nullptr;
  if (component) {
    track = tracks_.at(component);
    DCHECK(track);
  }

  // Create or update sender. If the web sender has stream IDs the sender's
  // streams need to be set separately outside of this method.
  auto sender_it = FindSender(*rtp_sender_platform);
  RTCRtpSender* sender;
  if (sender_it == rtp_senders_.end()) {
    // Create new sender (with empty stream set).
    sender = MakeGarbageCollected<RTCRtpSender>(
        this, std::move(rtp_sender_platform), kind, track, MediaStreamVector(),
        encoded_insertable_streams_,
        GetExecutionContext()->GetTaskRunner(TaskType::kInternalMedia));
    rtp_senders_.push_back(sender);
  } else {
    // Update existing sender (not touching the stream set).
    sender = *sender_it;
    DCHECK_EQ(sender->web_sender()->Id(), rtp_sender_platform->Id());
    sender->SetTrack(track);
  }
  sender->set_transport(CreateOrUpdateDtlsTransport(
      sender->web_sender()->DtlsTransport(),
      sender->web_sender()->DtlsTransportInformation()));
  return sender;
}

RTCRtpReceiver* RTCPeerConnection::CreateOrUpdateReceiver(
    std::unique_ptr<RTCRtpReceiverPlatform> platform_receiver) {
  auto receiver_it = FindReceiver(*platform_receiver);
  // Create track.
  MediaStreamTrack* track;
  if (receiver_it == rtp_receivers_.end()) {
    track = MakeGarbageCollected<MediaStreamTrackImpl>(
        GetExecutionContext(), platform_receiver->Track());
    RegisterTrack(track);
  } else {
    track = (*receiver_it)->track();
  }

  // Create or update receiver. If the web receiver has stream IDs the
  // receiver's streams need to be set separately outside of this method.
  RTCRtpReceiver* receiver;
  if (receiver_it == rtp_receivers_.end()) {
    // Create new receiver.
    receiver = MakeGarbageCollected<RTCRtpReceiver>(
        this, std::move(platform_receiver), track, MediaStreamVector(),
        encoded_insertable_streams_,
        GetExecutionContext()->GetTaskRunner(TaskType::kInternalMedia));
    // Receiving tracks should be muted by default. SetReadyState() propagates
    // the related state changes to ensure it is muted on all layers. It also
    // fires events - which is not desired - but because they fire synchronously
    // there are no listeners to detect this so this is indistinguishable from
    // having constructed the track in an already muted state.
    receiver->track()->Component()->Source()->SetReadyState(
        MediaStreamSource::kReadyStateMuted);
    rtp_receivers_.push_back(receiver);
  } else {
    // Update existing receiver is a no-op.
    receiver = *receiver_it;
    DCHECK_EQ(receiver->platform_receiver()->Id(), platform_receiver->Id());
    DCHECK_EQ(receiver->track(), track);  // Its track should never change.
  }
  receiver->set_transport(CreateOrUpdateDtlsTransport(
      receiver->platform_receiver()->DtlsTransport(),
      receiver->platform_receiver()->DtlsTransportInformation()));
  return receiver;
}

RTCRtpTransceiver* RTCPeerConnection::CreateOrUpdateTransceiver(
    std::unique_ptr<RTCRtpTransceiverPlatform> platform_transceiver) {
  String kind = (platform_transceiver->Receiver()->Track()->GetSourceType() ==
                 MediaStreamSource::kTypeAudio)
                    ? "audio"
                    : "video";
  RTCRtpSender* sender =
      CreateOrUpdateSender(platform_transceiver->Sender(), kind);
  RTCRtpReceiver* receiver =
      CreateOrUpdateReceiver(platform_transceiver->Receiver());

  RTCRtpTransceiver* transceiver;
  auto transceiver_it = FindTransceiver(*platform_transceiver);
  if (transceiver_it == transceivers_.end()) {
    // Create new tranceiver.
    transceiver = MakeGarbageCollected<RTCRtpTransceiver>(
        this, std::move(platform_transceiver), sender, receiver);
    transceivers_.push_back(transceiver);
  } else {
    // Update existing transceiver.
    transceiver = *transceiver_it;
    // The sender and receiver have already been updated above.
    DCHECK_EQ(transceiver->sender(), sender);
    DCHECK_EQ(transceiver->receiver(), receiver);
    transceiver->UpdateMembers();
  }
  return transceiver;
}

RTCDtlsTransport* RTCPeerConnection::CreateOrUpdateDtlsTransport(
    webrtc::scoped_refptr<webrtc::DtlsTransportInterface> native_transport,
    const webrtc::DtlsTransportInformation& information) {
  if (!native_transport) {
    return nullptr;
  }
  auto& transport = dtls_transports_by_native_transport_
                        .insert(native_transport.get(), nullptr)
                        .stored_value->value;
  if (!transport) {
    RTCIceTransport* ice_transport =
        CreateOrUpdateIceTransport(native_transport->ice_transport());
    transport = MakeGarbageCollected<RTCDtlsTransport>(
        GetExecutionContext(), std::move(native_transport), ice_transport);
  }
  transport->ChangeState(information);
  return transport.Get();
}

RTCIceTransport* RTCPeerConnection::CreateOrUpdateIceTransport(
    webrtc::scoped_refptr<webrtc::IceTransportInterface> ice_transport) {
  if (!ice_transport) {
    return nullptr;
  }
  auto& transport =
      ice_transports_by_native_transport_.insert(ice_transport.get(), nullptr)
          .stored_value->value;
  if (!transport) {
    transport = RTCIceTransport::Create(GetExecutionContext(),
                                        std::move(ice_transport), this);
  }
  return transport.Get();
}

RTCDTMFSender* RTCPeerConnection::createDTMFSender(
    MediaStreamTrack* track,
    ExceptionState& exception_state) {
  if (ThrowExceptionIfSignalingStateClosed(signaling_state_,
                                           &exception_state)) {
    return nullptr;
  }
  if (track->kind() != "audio") {
    exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
                                      "track.kind is not 'audio'.");
    return nullptr;
  }
  RTCRtpSender* found_rtp_sender = nullptr;
  for (const auto& rtp_sender : rtp_senders_) {
    if (rtp_sender->track() == track) {
      found_rtp_sender = rtp_sender;
      break;
    }
  }
  if (!found_rtp_sender) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kSyntaxError,
        "No RTCRtpSender is available for the track provided.");
    return nullptr;
  }
  RTCDTMFSender* dtmf_sender = found_rtp_sender->dtmf();
  if (!dtmf_sender) {
    exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
                                      "Unable to create DTMF sender for track");
    return nullptr;
  }
  return dtmf_sender;
}

void RTCPeerConnection::close() {
  suppress_events_ = true;
  pending_create_offer_count_ = 0;
  pending_create_answer_count_ = 0;
  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    return;
  }
  CloseInternal();
}

void RTCPeerConnection::RegisterTrack(MediaStreamTrack* track) {
  DCHECK(track);
  tracks_.insert(track->Component(), track);
}

void RTCPeerConnection::NoteSdpCreated(const RTCSessionDescriptionInit& desc) {
  if (desc.type() == V8RTCSdpType::Enum::kOffer) {
    last_offer_ = desc.sdp();
    DCHECK_GT(pending_create_offer_count_, 0);
    --pending_create_offer_count_;
  } else if (desc.type() == V8RTCSdpType::Enum::kAnswer) {
    last_answer_ = desc.sdp();
    DCHECK_GT(pending_create_answer_count_, 0);
    --pending_create_answer_count_;
  }
}

void RTCPeerConnection::NoteCreateOfferFailed() {
  DCHECK_GT(pending_create_offer_count_, 0);
  --pending_create_offer_count_;
}

void RTCPeerConnection::NoteCreateAnswerFailed() {
  DCHECK_GT(pending_create_answer_count_, 0);
  --pending_create_answer_count_;
}

void RTCPeerConnection::OnStreamAddTrack(MediaStream* stream,
                                         MediaStreamTrack* track,
                                         ExceptionState& exception_state) {
  MediaStreamVector streams;
  streams.push_back(stream);
  addTrack(track, streams, exception_state);
}

void RTCPeerConnection::OnStreamRemoveTrack(MediaStream* stream,
                                            MediaStreamTrack* track,
                                            ExceptionState& exception_state) {
  auto* sender = FindSenderForTrackAndStream(track, stream);
  if (sender) {
    removeTrack(sender, exception_state);
  }
}

void RTCPeerConnection::NegotiationNeeded() {
  DCHECK(!closed_);
  MaybeDispatchEvent(Event::Create(event_type_names::kNegotiationneeded));
}

void RTCPeerConnection::DidGenerateICECandidate(
    RTCIceCandidatePlatform* platform_candidate) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  DCHECK(platform_candidate);
  RTCIceCandidate* ice_candidate = RTCIceCandidate::Create(platform_candidate);
  MaybeDispatchEvent(RTCPeerConnectionIceEvent::Create(ice_candidate));
}

void RTCPeerConnection::DidFailICECandidate(const String& address,
                                            std::optional<uint16_t> port,
                                            const String& host_candidate,
                                            const String& url,
                                            int error_code,
                                            const String& error_text) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  MaybeDispatchEvent(RTCPeerConnectionIceErrorEvent::Create(
      address, port, host_candidate, url, error_code, error_text));
}

void RTCPeerConnection::DidChangeSessionDescriptions(
    RTCSessionDescriptionPlatform* pending_local_description,
    RTCSessionDescriptionPlatform* current_local_description,
    RTCSessionDescriptionPlatform* pending_remote_description,
    RTCSessionDescriptionPlatform* current_remote_description) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  pending_local_description_ =
      pending_local_description
          ? RTCSessionDescription::Create(pending_local_description)
          : nullptr;
  current_local_description_ =
      current_local_description
          ? RTCSessionDescription::Create(current_local_description)
          : nullptr;
  pending_remote_description_ =
      pending_remote_description
          ? RTCSessionDescription::Create(pending_remote_description)
          : nullptr;
  current_remote_description_ =
      current_remote_description
          ? RTCSessionDescription::Create(current_remote_description)
          : nullptr;
}

void RTCPeerConnection::DidChangeIceGatheringState(
    webrtc::PeerConnectionInterface::IceGatheringState new_state) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  ChangeIceGatheringState(new_state);
}

void RTCPeerConnection::DidChangePeerConnectionState(
    webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  ChangePeerConnectionState(new_state);
}

void RTCPeerConnection::DidModifySctpTransport(
    WebRTCSctpTransportSnapshot snapshot) {
  if (!snapshot.transport) {
    sctp_transport_ = nullptr;
    return;
  }
  if (!sctp_transport_ ||
      sctp_transport_->native_transport() != snapshot.transport) {
    sctp_transport_ = MakeGarbageCollected<RTCSctpTransport>(
        GetExecutionContext(), snapshot.transport);
  }
  if (!sctp_transport_->transport() ||
      sctp_transport_->transport()->native_transport() !=
          snapshot.sctp_transport_state.dtls_transport()) {
    sctp_transport_->SetTransport(CreateOrUpdateDtlsTransport(
        snapshot.sctp_transport_state.dtls_transport(),
        snapshot.dtls_transport_state));
  }
  sctp_transport_->ChangeState(snapshot.sctp_transport_state);
}

void RTCPeerConnection::DidModifyTransceivers(
    webrtc::PeerConnectionInterface::SignalingState signaling_state,
    Vector<std::unique_ptr<RTCRtpTransceiverPlatform>> platform_transceivers,
    Vector<uintptr_t> removed_transceiver_ids,
    bool is_remote_description_or_rollback) {
  HeapVector<Member<MediaStreamTrack>> mute_tracks;
  HeapVector<std::pair<Member<MediaStream>, Member<MediaStreamTrack>>>
      remove_list;
  HeapVector<std::pair<Member<MediaStream>, Member<MediaStreamTrack>>> add_list;
  HeapVector<Member<RTCRtpTransceiver>> track_events;
  MediaStreamVector previous_streams = getRemoteStreams();
  // Remove transceivers and update their states to reflect that they are
  // necessarily stopped.
  for (auto id : removed_transceiver_ids) {
    auto it = FindTransceiverById(id);
    if (it != transceivers_.end()) {
      // All streams are removed on stop, update `remove_list` if necessary.
      auto* track = (*it)->receiver()->track();
      for (const auto& stream : (*it)->receiver()->streams()) {
        if (stream->getTracks().Contains(track)) {
          remove_list.push_back(std::make_pair(stream, track));
        }
      }
      (*it)->OnTransceiverStopped();
      transceivers_.erase(it);
    }
  }
  for (auto& platform_transceiver : platform_transceivers) {
    auto it = FindTransceiver(*platform_transceiver);
    bool previously_had_recv =
        (it != transceivers_.end()) ? (*it)->FiredDirectionHasRecv() : false;
    RTCRtpTransceiver* transceiver =
        CreateOrUpdateTransceiver(std::move(platform_transceiver));

    size_t add_list_prev_size = add_list.size();
    // "Set the associated remote streams".
    // https://w3c.github.io/webrtc-pc/#set-associated-remote-streams
    SetAssociatedMediaStreams(
        transceiver->receiver(),
        transceiver->platform_transceiver()->Receiver()->StreamIds(),
        &remove_list, &add_list);
    // The transceiver is now up-to-date. Check if the receiver's track is now
    // considered added or removed (though a receiver's track is never truly
    // removed). A track event indicates either that the track was "added" in
    // the sense that FiredDirectionHasRecv() changed, or that a new remote
    // stream was added containing the receiver's track.
    if (is_remote_description_or_rollback &&
        ((!previously_had_recv && transceiver->FiredDirectionHasRecv()) ||
         add_list_prev_size != add_list.size()) &&
        transceiver->currentDirection() !=
            V8RTCRtpTransceiverDirection::Enum::kStopped) {
      // "Process the addition of a remote track".
      // https://w3c.github.io/webrtc-pc/#process-remote-track-addition
      track_events.push_back(transceiver);
    }
    if (previously_had_recv && !transceiver->FiredDirectionHasRecv()) {
      // "Process the removal of a remote track".
      // https://w3c.github.io/webrtc-pc/#process-remote-track-removal
      if (!transceiver->receiver()->track()->muted()) {
        mute_tracks.push_back(transceiver->receiver()->track());
      }
    }
  }
  // Update the rtp_senders_ and rtp_receivers_ members to only contain
  // senders and receivers that are in the current set of transceivers.
  rtp_senders_.clear();
  rtp_receivers_.clear();
  for (auto& transceiver : transceivers_) {
    rtp_senders_.push_back(transceiver->sender());
    rtp_receivers_.push_back(transceiver->receiver());
  }

  MediaStreamVector current_streams = getRemoteStreams();

  // Modify and fire "pc.onsignalingchange" synchronously.
  if (signaling_state_ == webrtc::PeerConnectionInterface::kHaveLocalOffer &&
      signaling_state == webrtc::PeerConnectionInterface::kHaveRemoteOffer) {
    // Inject missing kStable in case of implicit rollback.
    ChangeSignalingState(webrtc::PeerConnectionInterface::kStable, true);
  }
  ChangeSignalingState(signaling_state, true);

  // Mute the tracks, this fires "track.onmute" synchronously.
  for (auto& track : mute_tracks) {
    track->Component()->Source()->SetReadyState(
        MediaStreamSource::kReadyStateMuted);
  }
  // Remove/add tracks to streams, this fires "stream.onremovetrack" and
  // "stream.onaddtrack" synchronously.
  for (auto& pair : remove_list) {
    auto& stream = pair.first;
    auto& track = pair.second;
    if (stream->getTracks().Contains(track)) {
      stream->RemoveTrackAndFireEvents(
          track,
          MediaStreamDescriptorClient::DispatchEventTiming::kImmediately);
    }
  }
  for (auto& pair : add_list) {
    auto& stream = pair.first;
    auto& track = pair.second;
    if (!stream->getTracks().Contains(track)) {
      stream->AddTrackAndFireEvents(
          track,
          MediaStreamDescriptorClient::DispatchEventTiming::kImmediately);
    }
  }

  // Legacy APIs: "pc.onaddstream" and "pc.onremovestream".
  for (const auto& current_stream : current_streams) {
    if (!previous_streams.Contains(current_stream)) {
      MaybeDispatchEvent(MakeGarbageCollected<MediaStreamEvent>(
          event_type_names::kAddstream, current_stream));
    }
  }
  for (const auto& previous_stream : previous_streams) {
    if (!current_streams.Contains(previous_stream)) {
      MaybeDispatchEvent(MakeGarbageCollected<MediaStreamEvent>(
          event_type_names::kRemovestream, previous_stream));
    }
  }

  // Fire "pc.ontrack" synchronously.
  for (auto& transceiver : track_events) {
    auto* track_event = MakeGarbageCollected<RTCTrackEvent>(
        transceiver->receiver(), transceiver->receiver()->track(),
        transceiver->receiver()->streams(), transceiver);
    // Only log events that are actually dispatched to JavaScript, matching
    // the condition in MaybeDispatchEvent().
    if (!suppress_events_) {
      peer_handler_->TrackOnTrack(*track_event);
    }
    MaybeDispatchEvent(track_event);
  }

  // Transceiver modifications can cause changes in the set of ICE
  // transports, which may affect ICE transport state.
  // Note - this must be done every time the set of ICE transports happens.
  // At the moment this only happens in SLD/SRD, and this function is called
  // whenever these functions complete.
  UpdateIceConnectionState();
}

void RTCPeerConnection::SetAssociatedMediaStreams(
    RTCRtpReceiver* receiver,
    const Vector<String>& stream_ids,
    HeapVector<std::pair<Member<MediaStream>, Member<MediaStreamTrack>>>*
        remove_list,
    HeapVector<std::pair<Member<MediaStream>, Member<MediaStreamTrack>>>*
        add_list) {
  MediaStreamVector known_streams = getRemoteStreams();

  MediaStreamVector streams;
  for (const auto& stream_id : stream_ids) {
    MediaStream* curr_stream = nullptr;
    for (const auto& known_stream : known_streams) {
      if (known_stream->id() == stream_id) {
        curr_stream = known_stream;
        break;
      }
    }
    if (!curr_stream) {
      curr_stream = MediaStream::Create(
          GetExecutionContext(),
          MakeGarbageCollected<MediaStreamDescriptor>(
              static_cast<String>(stream_id), MediaStreamComponentVector(),
              MediaStreamComponentVector()));
    }
    streams.push_back(curr_stream);
  }

  const MediaStreamVector& prev_streams = receiver->streams();
  if (remove_list) {
    for (const auto& stream : prev_streams) {
      if (!streams.Contains(stream)) {
        remove_list->push_back(std::make_pair(stream, receiver->track()));
      }
    }
  }
  if (add_list) {
    for (const auto& stream : streams) {
      if (!prev_streams.Contains(stream)) {
        add_list->push_back(std::make_pair(stream, receiver->track()));
      }
    }
  }
  receiver->set_streams(std::move(streams));
}

void RTCPeerConnection::DidAddRemoteDataChannel(
    webrtc::scoped_refptr<webrtc::DataChannelInterface> channel) {
  DCHECK(!closed_);
  DCHECK(GetExecutionContext()->IsContextThread());
  DCHECK(sctp_transport_);

  if (signaling_state_ ==
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    return;
  }

  auto* blink_channel = MakeGarbageCollected<RTCDataChannel>(
      GetExecutionContext(), std::move(channel));
  blink_channel->SetStateToOpenWithoutEvent(
      static_cast<int>(sctp_transport_->maxMessageSize()));
  MaybeDispatchEvent(MakeGarbageCollected<RTCDataChannelEvent>(
      event_type_names::kDatachannel, blink_channel));
  // The event handler might have closed the channel.
  if (blink_channel->readyState() == V8RTCDataChannelState::Enum::kOpen) {
    blink_channel->DispatchOpenEvent();
  }
}

void RTCPeerConnection::DidNoteInterestingUsage(int usage_pattern) {
  if (!GetExecutionContext()) {
    return;
  }
  ukm::SourceId source_id = GetExecutionContext()->UkmSourceID();
  ukm::builders::WebRTC_AddressHarvesting(source_id)
      .SetUsagePattern(usage_pattern)
      .Record(GetExecutionContext()->UkmRecorder());
}

void RTCPeerConnection::UnregisterPeerConnectionHandler() {
  if (peer_handler_unregistered_) {
    DCHECK(scheduled_events_.empty())
        << "Undelivered events can cause memory leaks due to "
        << "WrapPersistent(this) in setup function callbacks";
    return;
  }

  peer_handler_unregistered_ = true;
  ice_connection_state_ = webrtc::PeerConnectionInterface::kIceConnectionClosed;
  signaling_state_ = webrtc::PeerConnectionInterface::SignalingState::kClosed;

  peer_handler_->CloseAndUnregister();
  dispatch_scheduled_events_task_handle_.Cancel();
  scheduled_events_.clear();
  feature_handle_for_scheduler_.reset();
}

void RTCPeerConnection::ClosePeerConnection() {
  DCHECK(signaling_state_ !=
         webrtc::PeerConnectionInterface::SignalingState::kClosed);
  CloseInternal();
}

const AtomicString& RTCPeerConnection::InterfaceName() const {
  return event_target_names::kRTCPeerConnection;
}

ExecutionContext* RTCPeerConnection::GetExecutionContext() const {
  return ExecutionContextLifecycleObserver::GetExecutionContext();
}

void RTCPeerConnection::ContextDestroyed() {
  suppress_events_ = true;
  if (!closed_) {
    CloseInternal();
  }
  UnregisterPeerConnectionHandler();
}

void RTCPeerConnection::ChangeSignalingState(
    webrtc::PeerConnectionInterface::SignalingState signaling_state,
    bool dispatch_event_immediately) {
  if (signaling_state_ == signaling_state) {
    return;
  }
  if (signaling_state_ !=
      webrtc::PeerConnectionInterface::SignalingState::kClosed) {
    signaling_state_ = signaling_state;
    Event* event = Event::Create(event_type_names::kSignalingstatechange);
    if (dispatch_event_immediately) {
      MaybeDispatchEvent(event);
    } else {
      ScheduleDispatchEvent(event);
    }
  }
}

void RTCPeerConnection::ChangeIceGatheringState(
    webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state) {
  if (ice_connection_state_ !=
      webrtc::PeerConnectionInterface::kIceConnectionClosed) {
    ScheduleDispatchEvent(
        Event::Create(event_type_names::kIcegatheringstatechange),
        BindOnce(&RTCPeerConnection::SetIceGatheringState, WrapPersistent(this),
                 ice_gathering_state));
    if (ice_gathering_state ==
        webrtc::PeerConnectionInterface::kIceGatheringComplete) {
      // If ICE gathering is completed, generate a null ICE candidate, to
      // signal end of candidates.
      ScheduleDispatchEvent(RTCPeerConnectionIceEvent::Create(nullptr));
    }
  }
}

bool RTCPeerConnection::SetIceGatheringState(
    webrtc::PeerConnectionInterface::IceGatheringState ice_gathering_state) {
  if (ice_connection_state_ !=
          webrtc::PeerConnectionInterface::kIceConnectionClosed &&
      ice_gathering_state_ != ice_gathering_state) {
    ice_gathering_state_ = ice_gathering_state;
    return true;
  }
  return false;
}

void RTCPeerConnection::ChangeIceConnectionState(
    webrtc::PeerConnectionInterface::IceConnectionState ice_connection_state) {
  if (closed_) {
    return;
  }
  if (ice_connection_state_ == ice_connection_state) {
    return;
  }
  ice_connection_state_ = ice_connection_state;
  MaybeDispatchEvent(
      Event::Create(event_type_names::kIceconnectionstatechange));
}

webrtc::PeerConnectionInterface::IceConnectionState
RTCPeerConnection::ComputeIceConnectionState() {
  if (closed_) {
    return webrtc::PeerConnectionInterface::kIceConnectionClosed;
  }
  if (are_ice_candidates_administratively_prohibited_ ||
      HasAnyFailedIceTransport()) {
    return webrtc::PeerConnectionInterface::kIceConnectionFailed;
  }
  if (HasAnyDisconnectedIceTransport()) {
    return webrtc::PeerConnectionInterface::kIceConnectionDisconnected;
  }
  if (HasAllNewOrClosedIceTransports()) {
    return webrtc::PeerConnectionInterface::kIceConnectionNew;
  }
  if (HasAnyNewOrCheckingIceTransport()) {
    return webrtc::PeerConnectionInterface::kIceConnectionChecking;
  }
  if (HasAllCompletedOrClosedIceTransports()) {
    return webrtc::PeerConnectionInterface::kIceConnectionCompleted;
  }
  if (HasAllConnectedCompletedOrClosedIceTransports()) {
    return webrtc::PeerConnectionInterface::kIceConnectionConnected;
  }

  return ice_connection_state_;
}

bool RTCPeerConnection::HasAnyFailedIceTransport() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() == webrtc::IceTransportState::kFailed) {
      return true;
    }
  }
  return false;
}

bool RTCPeerConnection::HasAnyDisconnectedIceTransport() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() == webrtc::IceTransportState::kDisconnected) {
      return true;
    }
  }
  return false;
}

bool RTCPeerConnection::HasAllNewOrClosedIceTransports() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() != webrtc::IceTransportState::kNew &&
        transport->GetState() != webrtc::IceTransportState::kClosed) {
      return false;
    }
  }
  return true;
}

bool RTCPeerConnection::HasAnyNewOrCheckingIceTransport() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() == webrtc::IceTransportState::kNew ||
        transport->GetState() == webrtc::IceTransportState::kChecking) {
      return true;
    }
  }
  return false;
}

bool RTCPeerConnection::HasAllCompletedOrClosedIceTransports() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() != webrtc::IceTransportState::kCompleted &&
        transport->GetState() != webrtc::IceTransportState::kClosed) {
      return false;
    }
  }
  return true;
}

bool RTCPeerConnection::HasAllConnectedCompletedOrClosedIceTransports() const {
  for (auto& transport : ActiveIceTransports()) {
    if (transport->GetState() != webrtc::IceTransportState::kConnected &&
        transport->GetState() != webrtc::IceTransportState::kCompleted &&
        transport->GetState() != webrtc::IceTransportState::kClosed) {
      return false;
    }
  }
  return true;
}

void RTCPeerConnection::ChangePeerConnectionState(
    webrtc::PeerConnectionInterface::PeerConnectionState
        peer_connection_state) {
  if (are_ice_candidates_administratively_prohibited_ &&
      peer_connection_state !=
          webrtc::PeerConnectionInterface::PeerConnectionState::kClosed) {
    peer_connection_state =
        webrtc::PeerConnectionInterface::PeerConnectionState::kFailed;
  }
  if (peer_connection_state_ !=
      webrtc::PeerConnectionInterface::PeerConnectionState::kClosed) {
    ScheduleDispatchEvent(
        Event::Create(event_type_names::kConnectionstatechange),
        BindOnce(&RTCPeerConnection::SetPeerConnectionState,
                 WrapPersistent(this), peer_connection_state));
  }
  // The first time the connection gets established iѕ used to trigger
  // some measurements.
  if (peer_connection_state ==
          webrtc::PeerConnectionInterface::PeerConnectionState::kConnected &&
      !was_ever_connected_) {
    was_ever_connected_ = true;
    RTCSessionDescription* local = currentLocalDescription();
    RTCSessionDescription* remote = currentRemoteDescription();
    if (local && remote && ContainsSctpSnap(local->sdp()) &&
        ContainsSctpSnap(remote->sdp())) {
      UseCounter::Count(GetExecutionContext(),
                        WebFeature::kRTCSctpSnapNegotiated);
    }
  }
}

bool RTCPeerConnection::SetPeerConnectionState(
    webrtc::PeerConnectionInterface::PeerConnectionState
        peer_connection_state) {
  if (peer_connection_state_ !=
          webrtc::PeerConnectionInterface::PeerConnectionState::kClosed &&
      peer_connection_state_ != peer_connection_state) {
    peer_connection_state_ = peer_connection_state;
    return true;
  }
  return false;
}

void RTCPeerConnection::CloseInternal() {
  DCHECK(signaling_state_ !=
         webrtc::PeerConnectionInterface::SignalingState::kClosed);
  peer_handler_->Close();
  closed_ = true;

  ChangeIceConnectionState(
      webrtc::PeerConnectionInterface::kIceConnectionClosed);
  SetPeerConnectionState(
      webrtc::PeerConnectionInterface::PeerConnectionState::kClosed);
  ChangeSignalingState(webrtc::PeerConnectionInterface::SignalingState::kClosed,
                       false);
  for (auto& transceiver : transceivers_) {
    transceiver->OnTransceiverStopped();
  }
  if (sctp_transport_) {
    sctp_transport_->Close();
  }
  // Closing a transport can invalidate its weak map entry, so iterate over a
  // copy of the transports list.
  auto dtls_transports_copy = dtls_transports_by_native_transport_;
  for (auto& dtls_transport_iter : dtls_transports_copy) {
    // Since "value" is a WeakPtr, check if it's still valid.
    if (dtls_transport_iter.value) {
      dtls_transport_iter.value->Close();
    }
  }

  feature_handle_for_scheduler_.reset();
}

void RTCPeerConnection::MaybeDispatchEvent(Event* event) {
  if (suppress_events_) {
    return;
  }
  DispatchEvent(*event);
}

void RTCPeerConnection::ScheduleDispatchEvent(Event* event) {
  ScheduleDispatchEvent(event, BoolFunction());
}

void RTCPeerConnection::ScheduleDispatchEvent(Event* event,
                                              BoolFunction setup_function) {
  if (peer_handler_unregistered_) {
    DCHECK(scheduled_events_.empty())
        << "Undelivered events can cause memory leaks due to "
        << "WrapPersistent(this) in setup function callbacks";
    return;
  }
  if (suppress_events_) {
    // If suppressed due to closing we also want to ignore the event, but we
    // don't need to crash.
    return;
  }

  scheduled_events_.push_back(
      MakeGarbageCollected<EventWrapper>(event, std::move(setup_function)));

  if (dispatch_scheduled_events_task_handle_.IsActive()) {
    return;
  }

  if (auto* context = GetExecutionContext()) {
    if (dispatch_events_task_created_callback_for_testing_) {
      context->GetTaskRunner(TaskType::kNetworking)
          ->PostTask(
              FROM_HERE,
              std::move(dispatch_events_task_created_callback_for_testing_));
    }

    // WebRTC spec specifies kNetworking as task source.
    // https://www.w3.org/TR/webrtc/#operation
    dispatch_scheduled_events_task_handle_ = PostCancellableTask(
        *context->GetTaskRunner(TaskType::kNetworking), FROM_HERE,
        BindOnce(&RTCPeerConnection::DispatchScheduledEvents,
                 WrapPersistent(this)));
  }
}

void RTCPeerConnection::DispatchScheduledEvents() {
  if (peer_handler_unregistered_) {
    DCHECK(scheduled_events_.empty())
        << "Undelivered events can cause memory leaks due to "
        << "WrapPersistent(this) in setup function callbacks";
    return;
  }
  if (suppress_events_) {
    // If suppressed due to closing we also want to ignore the event, but we
    // don't need to crash.
    return;
  }

  HeapVector<Member<EventWrapper>> events;
  events.swap(scheduled_events_);

  for (auto& event : events) {
    if (event->Setup()) {
      DispatchEvent(*event->event_.Release());
    }
  }

  events.clear();
}

void RTCPeerConnection::Trace(Visitor* visitor) const {
  visitor->Trace(pending_local_description_);
  visitor->Trace(current_local_description_);
  visitor->Trace(pending_remote_description_);
  visitor->Trace(current_remote_description_);
  visitor->Trace(tracks_);
  visitor->Trace(rtp_senders_);
  visitor->Trace(rtp_receivers_);
  visitor->Trace(transceivers_);
  visitor->Trace(scheduled_events_);
  visitor->Trace(dtls_transports_by_native_transport_);
  visitor->Trace(ice_transports_by_native_transport_);
  visitor->Trace(sctp_transport_);
  EventTarget::Trace(visitor);
  ExecutionContextLifecycleObserver::Trace(visitor);
  MediaStreamObserver::Trace(visitor);
}

// static
void RTCPeerConnection::SetRtcPeerConnectionHandlerFactoryForTesting(
    RtcPeerConnectionHandlerFactoryCallback callback) {
  DCHECK(g_create_rpc_peer_connection_handler_callback_.Get().is_null());
  g_create_rpc_peer_connection_handler_callback_.Get() = std::move(callback);
}

int RTCPeerConnection::PeerConnectionCount() {
  return InstanceCounters::CounterValue(
      InstanceCounters::kRTCPeerConnectionCounter);
}

int RTCPeerConnection::PeerConnectionCountLimit() {
  return kMaxPeerConnections;
}

void RTCPeerConnection::DisableBackForwardCache(ExecutionContext* context) {
  LocalDOMWindow* window = To<LocalDOMWindow>(context);
  // Two features are registered here:
  // - `kWebRTC`: a non-sticky feature that will disable BFCache for any page.
  // It will be reset after the `RTCPeerConnection` is closed.
  // - `kWebRTCSticky`: a sticky feature that will only disable BFCache for the
  // page containing "Cache-Control: no-store" header. It won't be reset even if
  // the `RTCPeerConnection` is closed.
  feature_handle_for_scheduler_ =
      window->GetFrame()->GetFrameScheduler()->RegisterFeature(
          SchedulingPolicy::Feature::kWebRTC,
          SchedulingPolicy{SchedulingPolicy::DisableBackForwardCache()});
  window->GetFrame()->GetFrameScheduler()->RegisterStickyFeature(
      SchedulingPolicy::Feature::kWebRTCSticky,
      SchedulingPolicy{SchedulingPolicy::DisableBackForwardCache()});
}

void RTCPeerConnection::MaybeReportConnectionAllowlistViolation(
    ExecutionContext* context) {
  const network::ConnectionAllowlists& connection_allowlists =
      context->GetPolicyContainer()->GetPolicies().connection_allowlists;

  bool blocked_by_connection_allowlist_report_only =
      connection_allowlists.report_only.has_value() &&
      connection_allowlists.report_only->webrtc_behavior ==
          network::ConnectionAllowlist::WebRtcBehavior::kBlock;
  if (blocked_by_connection_allowlist_report_only) {
    ConnectionAllowlistViolationReportBody::QueueWebRTCReport(
        V8ConnectionAllowlistDisposition::Enum::kReport, *context);
  }

  if (are_ice_candidates_administratively_prohibited_) {
    ConnectionAllowlistViolationReportBody::QueueWebRTCReport(
        V8ConnectionAllowlistDisposition::Enum::kEnforce, *context);
    base::UmaHistogramBoolean(
        "WebRTC.PeerConnection.BlockedByConnectionAllowlist", true);
  }
}

}  // namespace blink
