/*
 *  Copyright 2017 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "pc/jsep_transport_controller.h"

#include <cstddef>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "api/async_dns_resolver.h"
#include "api/candidate.h"
#include "api/dtls_transport_interface.h"
#include "api/environment/environment.h"
#include "api/ice_transport_interface.h"
#include "api/jsep.h"
#include "api/local_network_access_permission.h"
#include "api/make_ref_counted.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_header_extension_id.h"
#include "api/rtp_parameters.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/transport/data_channel_transport_interface.h"
#include "api/transport/ecn_marking.h"
#include "api/transport/enums.h"
#include "api/units/timestamp.h"
#include "media/sctp/sctp_transport_internal.h"
#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/packet_transport_internal.h"
#include "p2p/base/port.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_info.h"
#include "p2p/dtls/dtls_transport.h"
#include "p2p/dtls/dtls_transport_internal.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport.h"
#include "pc/rtp_transport.h"
#include "pc/rtp_transport_internal.h"
#include "pc/sctp_transport.h"
#include "pc/session_description.h"
#include "pc/transport_stats.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/trace_event.h"

namespace webrtc {

JsepTransportController::JsepTransportController(
    const Environment& env,
    TaskQueueBase* signaling_thread,
    Thread* network_thread,
    PortAllocator* port_allocator,
    AsyncDnsResolverFactoryInterface* async_dns_resolver_factory,
    LocalNetworkAccessPermissionFactoryInterface* lna_permission_factory,
    Config config)
    : env_(env),
      signaling_thread_(signaling_thread),
      network_thread_(network_thread),
      port_allocator_(port_allocator),
      async_dns_resolver_factory_(async_dns_resolver_factory),
      lna_permission_factory_(lna_permission_factory),
      transports_(
          [this](absl::string_view mid, JsepTransport* transport) {
            return OnTransportChanged(mid, transport);
          },
          [this]() {
            RTC_DCHECK_RUN_ON(network_thread_);
            UpdateAggregateStates_n();
          }),
      config_(std::move(config)),
      bundles_(config_.bundle_policy),
      role_update_safety_flag_s_(
          PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true,
                                                           signaling_thread_)),
      role_update_safety_flag_n_(role_update_safety_flag_s_) {
  RTC_DCHECK(signaling_thread_);
  RTC_DCHECK(network_thread_);
  RTC_DCHECK(port_allocator_);
  // The `transport_observer` is assumed to be non-null.
  RTC_DCHECK(config_.transport_observer);
  RTC_DCHECK(config_.rtcp_handler);
  RTC_DCHECK(config_.ice_transport_factory);
  RTC_DCHECK(config_.on_dtls_handshake_error);
  RTC_DCHECK(config_.signal_ice_candidates_gathered);
  RTC_DCHECK(config_.signal_ice_connection_state);
  RTC_DCHECK(config_.signal_connection_state);
  RTC_DCHECK(config_.signal_standardized_ice_connection_state);
  RTC_DCHECK(config_.signal_ice_gathering_state);
  RTC_DCHECK(config_.signal_ice_candidate_error);
  RTC_DCHECK(config_.signal_ice_candidates_removed);
  RTC_DCHECK(config_.signal_ice_candidate_pair_changed);
}

JsepTransportController::~JsepTransportController() {
  // Channel destructors may try to send packets, so this needs to happen on
  // the network thread.
  RTC_DCHECK_RUN_ON(network_thread_);
  DestroyAllJsepTransports_n();
}

absl::AnyInvocable<void() &&> JsepTransportController::MakeCloseTask() {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  role_update_safety_flag_s_->SetNotAlive();

  return [this]() mutable {
    RTC_DCHECK_RUN_ON(network_thread_);
    DestroyAllJsepTransports_n();
  };
}

RTCError JsepTransportController::SetLocalDescription(
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  RTC_DCHECK(local_desc);
  TRACE_EVENT0("webrtc", "JsepTransportController::SetLocalDescription");

  flat_map<std::string, TransportState> new_states;
  RTCError error = network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    RTCError err = SetLocalDescription_n(type, local_desc, remote_desc);
    new_states = GetTransportStates_n();
    return err;
  });
  transport_states_ = std::move(new_states);
  return error;
}

// RTC_RUN_ON(network_thread_)
RTCError JsepTransportController::SetLocalDescription_n(
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  // ice_role_ is initialized to ICEROLE_CONTROLLING. Check if we still have the
  // initialized value and might need to set the role. For a non-offer, we'll
  // set the ice role to `controlled`. In the case where `type` is kOffer and
  // the ice_role_ is already 'controlling' we'll still call `SetRole_n()`. That
  // call will either will be a noop or initialization for the transports.
  if (ice_role_ == ICEROLE_CONTROLLING) {
    const IceRole role =
        (type == SdpType::kOffer) ? ICEROLE_CONTROLLING : ICEROLE_CONTROLLED;
    // Note that ApplyDescription_n() will always call DetermineIceRole() where
    // SetIceRole_n() will be called again such as in the case where the answer
    // side takes the controlling role. See "Section 5.1.1" below.
    SetIceRole_n(role);
  }
  return ApplyDescription_n(/*local=*/true, type, local_desc, remote_desc);
}

RTCError JsepTransportController::SetRemoteDescription(
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  RTC_DCHECK(remote_desc);
  TRACE_EVENT0("webrtc", "JsepTransportController::SetRemoteDescription");

  flat_map<std::string, TransportState> new_states;
  RTCError error = network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    RTCError err = SetRemoteDescription_n(type, local_desc, remote_desc);
    new_states = GetTransportStates_n();
    return err;
  });
  transport_states_ = std::move(new_states);
  return error;
}

RTCError JsepTransportController::SetRemoteDescription_n(
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  return ApplyDescription_n(/*local=*/false, type, local_desc, remote_desc);
}

RtpTransportInternal* JsepTransportController::GetRtpTransport(
    absl::string_view mid) const {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    return nullptr;
  }
  return jsep_transport->rtp_transport();
}

DataChannelTransportInterface* JsepTransportController::GetDataChannelTransport(
    absl::string_view mid) const {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    return nullptr;
  }
  return jsep_transport->data_channel_transport();
}

DtlsTransportInternal* JsepTransportController::GetDtlsTransport(
    absl::string_view mid) {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    return nullptr;
  }
  return jsep_transport->rtp_dtls_transport();
}

scoped_refptr<DtlsTransport>
JsepTransportController::LookupDtlsTransportByMid_n(absl::string_view mid) {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    return nullptr;
  }
  return jsep_transport->RtpDtlsTransport();
}

scoped_refptr<DtlsTransport> JsepTransportController::LookupDtlsTransportByMid(
    absl::string_view mid) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  return network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    return LookupDtlsTransportByMid_n(mid);
  });
}

scoped_refptr<SctpTransport> JsepTransportController::GetSctpTransport(
    absl::string_view mid) const {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    return nullptr;
  }
  return jsep_transport->SctpTransport();
}

void JsepTransportController::SetIceConfig(const IceConfig& config) {
  RTC_DCHECK_RUN_ON(network_thread_);
  ice_config_ = config;
  for (auto& dtls : GetDtlsTransports()) {
    dtls->ice_transport()->SetIceConfig(ice_config_);
  }
}

void JsepTransportController::SetNeedsIceRestartFlag() {
  RTC_DCHECK_RUN_ON(network_thread_);
  transports_.ForEachTransport(
      [](JsepTransport& t) { t.SetNeedsIceRestartFlag(); });
}

bool JsepTransportController::NeedsIceRestart(
    absl::string_view transport_name) const {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  auto it = transport_states_.find(std::string(transport_name));
  if (it != transport_states_.end()) {
    return it->second.needs_ice_restart;
  }
  return false;
}

std::optional<SSLRole> JsepTransportController::GetDtlsRole(
    absl::string_view mid) const {
  if (signaling_thread_->IsCurrent()) {
    RTC_DCHECK_RUN_ON(signaling_thread_);
    auto it = transport_states_.find(std::string(mid));
    if (it != transport_states_.end()) {
      return it->second.dtls_role;
    }
    return std::nullopt;
  }

  RTC_DCHECK_RUN_ON(network_thread_);

  const JsepTransport* t = GetJsepTransportForMid(mid);
  if (!t) {
    return std::optional<SSLRole>();
  }
  return t->GetDtlsRole();
}

void JsepTransportController::SetTransportStates(
    flat_map<std::string, TransportState> states) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  transport_states_ = std::move(states);
}

bool JsepTransportController::SetLocalCertificate(
    const scoped_refptr<RTCCertificate>& certificate) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  return network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    return SetLocalCertificate_n(certificate);
  });
}

bool JsepTransportController::SetLocalCertificate_n(
    const scoped_refptr<RTCCertificate>& certificate) {
  RTC_DCHECK_RUN_ON(network_thread_);

  // Can't change a certificate, or set a null certificate.
  if (certificate_ || !certificate) {
    return false;
  }
  certificate_ = certificate;

  // Set certificate for JsepTransport, which verifies it matches the
  // fingerprint in SDP, and DTLS transport.
  // Fallback from DTLS to SDES is not supported.
  transports_.ForEachTransport(
      [&](JsepTransport& t) { t.SetLocalCertificate(certificate_); });

  for (auto& dtls : GetDtlsTransports()) {
    bool set_cert_success = dtls->SetLocalCertificate(certificate_);
    RTC_DCHECK(set_cert_success);
  }
  return true;
}

scoped_refptr<RTCCertificate> JsepTransportController::GetLocalCertificate(
    absl::string_view transport_name) const {
  RTC_DCHECK_RUN_ON(network_thread_);

  const JsepTransport* t = GetJsepTransportByName(transport_name);
  if (!t) {
    return nullptr;
  }
  return t->GetLocalCertificate();
}

std::unique_ptr<SSLCertChain> JsepTransportController::GetRemoteSSLCertChain(
    absl::string_view transport_name) const {
  RTC_DCHECK_RUN_ON(network_thread_);

  // Get the certificate from the RTP transport's DTLS handshake. Should be
  // identical to the RTCP transport's, since they were given the same remote
  // fingerprint.
  auto jsep_transport = GetJsepTransportByName(transport_name);
  if (!jsep_transport) {
    return nullptr;
  }
  auto dtls = jsep_transport->rtp_dtls_transport();
  if (!dtls) {
    return nullptr;
  }

  return dtls->GetRemoteSSLCertChain();
}

std::vector<IceParameters> JsepTransportController::MaybeStartGathering() {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  return network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    MaybeStartGathering_n();
    return port_allocator_->GetPooledIceCredentials();
  });
}

void JsepTransportController::MaybeStartGathering_n() {
  RTC_DCHECK_RUN_ON(network_thread_);
  for (auto& dtls : GetDtlsTransports()) {
    dtls->ice_transport()->MaybeStartGathering();
  }
}

RTCError JsepTransportController::AddRemoteCandidates(
    absl::string_view transport_name,
    const Candidates& candidates) {
  RTC_DCHECK_RUN_ON(network_thread_);
  RTC_DCHECK(VerifyCandidates(candidates).ok());
  auto jsep_transport = GetJsepTransportByName(transport_name);
  if (!jsep_transport) {
    RTC_LOG(LS_WARNING) << "Not adding candidate because the JsepTransport "
                           "doesn't exist. Ignore it.";
    return RTCError::OK();
  }
  return jsep_transport->AddRemoteCandidates(candidates);
}

bool JsepTransportController::RemoveRemoteCandidate(const IceCandidate* c) {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  return network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    return RemoveRemoteCandidate_n(c);
  });
}

bool JsepTransportController::RemoveRemoteCandidate_n(const IceCandidate* c) {
  RTC_DCHECK_RUN_ON(network_thread_);
  std::string mid = c->sdp_mid();
  if (!VerifyCandidate(c->candidate()).ok() || mid.empty()) {
    RTC_LOG(LS_ERROR) << "Candidate invalid or missing sdp_mid: "
                      << c->candidate().ToSensitiveString();
    return false;
  }
  JsepTransport* jsep_transport = GetJsepTransportForMid(mid);
  if (!jsep_transport) {
    RTC_LOG(LS_WARNING) << "No Transport for mid=" << mid;
    return false;
  }
  DtlsTransportInternal* dtls =
      c->candidate().component() == ICE_CANDIDATE_COMPONENT_RTP
          ? jsep_transport->rtp_dtls_transport()
          : jsep_transport->rtcp_dtls_transport();
  if (dtls) {
    dtls->ice_transport()->RemoveRemoteCandidate(c->candidate());
  }
  return true;
}

bool JsepTransportController::GetStats(absl::string_view transport_name,
                                       TransportStats* stats) const {
  RTC_DCHECK_RUN_ON(network_thread_);

  const JsepTransport* transport = GetJsepTransportByName(transport_name);
  if (!transport) {
    return false;
  }
  return transport->GetStats(stats);
}

RTCError JsepTransportController::RollbackTransports() {
  RTC_DCHECK_RUN_ON(signaling_thread_);
  scoped_refptr<PendingTaskSafetyFlag> new_flag =
      PendingTaskSafetyFlag::CreateAttachedToTaskQueue(true, signaling_thread_);
  flat_map<std::string, TransportState> new_states;
  RTCError error = network_thread_->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread_);
    RTCError err = RollbackTransports_n();
    new_states = GetTransportStates_n();
    role_update_safety_flag_n_ = new_flag;
    return err;
  });
  transport_states_ = std::move(new_states);
  role_update_safety_flag_s_->SetNotAlive();
  role_update_safety_flag_s_ = std::move(new_flag);
  return error;
}

RTCError JsepTransportController::RollbackTransports_n() {
  bundles_.Rollback();
  if (!transports_.RollbackTransports()) {
    return RTC_LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                         << "Failed to roll back transport state.");
  }
  return RTCError::OK();
}

scoped_refptr<IceTransportInterface>
JsepTransportController::CreateIceTransport(absl::string_view transport_name,
                                            bool rtcp) {
  int component =
      rtcp ? ICE_CANDIDATE_COMPONENT_RTCP : ICE_CANDIDATE_COMPONENT_RTP;

  IceTransportInit init(env_);
  init.set_port_allocator(port_allocator_);
  init.set_async_dns_resolver_factory(async_dns_resolver_factory_);
  init.set_lna_permission_factory(lna_permission_factory_);
  scoped_refptr<IceTransportInterface> transport =
      config_.ice_transport_factory->CreateIceTransport(
          std::string(transport_name), component, std::move(init));

  // Ideally all of the below should be done as part of construction. That way
  // construction could happen on one thread and then safely be used on another
  // without there being an inherent race in the interface.
  IceTransportInternal* internal = transport->internal();

  internal->AddGatheringStateCallback(this,
                                      [this](IceTransportInternal* transport) {
                                        RTC_DCHECK_RUN_ON(network_thread_);
                                        OnTransportGatheringState_n(transport);
                                      });
  internal->SubscribeCandidateGathered(
      this,
      [this](IceTransportInternal* transport, const Candidate& candidate) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnTransportCandidateGathered_n(transport, candidate);
      });
  internal->SetCandidateErrorCallback(
      [this](IceTransportInternal* transport,
             const IceCandidateErrorEvent& error) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnTransportCandidateError_n(transport, error);
      });
  internal->SetCandidatesRemovedCallback(
      [this](IceTransportInternal* transport, const Candidates& candidates) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnTransportCandidatesRemoved_n(transport, candidates);
      });
  internal->SubscribeRoleConflict(this,
                                  [this](IceTransportInternal* transport) {
                                    RTC_DCHECK_RUN_ON(network_thread_);
                                    OnTransportRoleConflict_n(transport);
                                  });
  internal->SubscribeIceTransportStateChanged(
      this, [this](IceTransportInternal* transport) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnTransportStateChanged_n(transport);
      });
  internal->SetCandidatePairChangeCallback(
      [this](const CandidatePairChangeEvent& event) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnTransportCandidatePairChanged_n(event);
      });

  internal->SetIceRole(ice_role_);
  internal->SetIceConfig(ice_config_);

  return transport;
}

std::unique_ptr<DtlsTransportInternal>
JsepTransportController::CreateDtlsTransport(const ContentInfo& content_info,
                                             bool rtcp) {
  RTC_DCHECK_RUN_ON(network_thread_);

  scoped_refptr<IceTransportInterface> ice =
      CreateIceTransport(content_info.mid(), rtcp);
  std::unique_ptr<DtlsTransportInternal> dtls =
      config_.dtls_transport_factory
          ? config_.dtls_transport_factory->CreateDtlsTransport(
                std::move(ice), config_.crypto_options, config_.ssl_max_version)
          : std::make_unique<DtlsTransportInternalImpl>(
                env_, std::move(ice), config_.crypto_options,
                config_.ssl_max_version);

  // Ideally all of the below should be done as part of construction. That way
  // construction could happen on one thread and then safely be used on another
  // without there being an inherent race in the interface.
  if (certificate_) {
    bool set_cert_success = dtls->SetLocalCertificate(certificate_);
    RTC_DCHECK(set_cert_success);
  }

  // Connect to signals offered by the DTLS and ICE transport.
  dtls->SubscribeWritableState(this,
                               [this](PacketTransportInternal* transport) {
                                 RTC_DCHECK_RUN_ON(network_thread_);
                                 OnTransportWritableState_n(transport);
                               });
  dtls->SubscribeReceivingState(this,
                                [this](PacketTransportInternal* transport) {
                                  RTC_DCHECK_RUN_ON(network_thread_);
                                  OnTransportReceivingState_n(transport);
                                });
  dtls->SubscribeDtlsHandshakeError(
      this, [this](SSLHandshakeError error) { OnDtlsHandshakeError(error); });
  dtls->SubscribeDtlsRoleChange(
      [this](DtlsTransportInternal* transport, SSLRole role) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnDtlsRoleChange_n(transport, role);
      });
  return dtls;
}

std::unique_ptr<RtpTransport>
JsepTransportController::CreateUnencryptedRtpTransport(
    absl::string_view transport_name,
    std::unique_ptr<PacketTransportInternal> rtp_packet_transport,
    std::unique_ptr<PacketTransportInternal> rtcp_packet_transport) {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto unencrypted_rtp_transport = std::make_unique<RtpTransport>(
      rtcp_packet_transport == nullptr, env_.field_trials());
  unencrypted_rtp_transport->SetRtpPacketTransportOwned(
      std::move(rtp_packet_transport));
  if (rtcp_packet_transport) {
    unencrypted_rtp_transport->SetRtcpPacketTransportOwned(
        std::move(rtcp_packet_transport));
  }
  return unencrypted_rtp_transport;
}

std::unique_ptr<DtlsSrtpTransport>
JsepTransportController::CreateDtlsSrtpTransport(
    absl::string_view transport_name,
    std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport,
    std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport) {
  RTC_DCHECK_RUN_ON(network_thread_);
  auto dtls_srtp_transport = std::make_unique<DtlsSrtpTransport>(
      rtcp_dtls_transport == nullptr, env_.field_trials());

  dtls_srtp_transport->SetDtlsTransportsOwned(std::move(rtp_dtls_transport),
                                              std::move(rtcp_dtls_transport));
  // Capturing this in the callback because JsepTransportController will always
  // outlive the DtlsSrtpTransport.
  dtls_srtp_transport->SetOnDtlsStateChange([this]() {
    RTC_DCHECK_RUN_ON(this->network_thread_);
    this->UpdateAggregateStates_n();
  });
  return dtls_srtp_transport;
}

std::unique_ptr<RtpTransport> JsepTransportController::CreateRtpTransport(
    absl::string_view transport_name,
    std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport,
    std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport) {
  std::unique_ptr<RtpTransport> rtp_transport;
  if (config_.disable_encryption) {
    RTC_LOG(LS_INFO)
        << "Creating UnencryptedRtpTransport, because encryption is disabled.";
    rtp_transport = CreateUnencryptedRtpTransport(
        transport_name, std::move(rtp_dtls_transport),
        std::move(rtcp_dtls_transport));
  } else if (config_.rtp_transport_factory) {
    RTC_LOG(LS_INFO) << "Creating RtpTransport from injected factory.";
    rtp_transport = config_.rtp_transport_factory->CreateRtpTransport(
        transport_name, std::move(rtp_dtls_transport),
        std::move(rtcp_dtls_transport));
  } else {
    RTC_LOG(LS_INFO) << "Creating DtlsSrtpTransport.";
    rtp_transport =
        CreateDtlsSrtpTransport(transport_name, std::move(rtp_dtls_transport),
                                std::move(rtcp_dtls_transport));
  }

  rtp_transport->SubscribeRtcpPacketReceived(
      this, [this](CopyOnWriteBuffer packet,
                   std::optional<Timestamp> arrival_time, EcnMarking ecn) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnRtcpPacketReceived_n(std::move(packet), arrival_time, ecn);
      });
  rtp_transport->SetUnDemuxableRtpPacketReceivedHandler(
      [this](RtpPacketReceived& packet) {
        RTC_DCHECK_RUN_ON(network_thread_);
        OnUnDemuxableRtpPacketReceived_n(packet);
      });
  return rtp_transport;
}

std::vector<DtlsTransportInternal*>
JsepTransportController::GetDtlsTransports() {
  RTC_DCHECK_RUN_ON(network_thread_);
  std::vector<DtlsTransportInternal*> dtls_transports;
  transports_.ForEachTransport([&](JsepTransport& t) {
    if (t.rtp_dtls_transport()) {
      dtls_transports.push_back(t.rtp_dtls_transport());
    }
    if (t.rtcp_dtls_transport()) {
      dtls_transports.push_back(t.rtcp_dtls_transport());
    }
  });
  return dtls_transports;
}

std::vector<DtlsTransportInternal*>
JsepTransportController::GetActiveDtlsTransports() {
  RTC_DCHECK_RUN_ON(network_thread_);
  std::vector<DtlsTransportInternal*> dtls_transports;
  transports_.ForEachActiveTransport([&](JsepTransport& t) {
    if (t.rtp_dtls_transport()) {
      dtls_transports.push_back(t.rtp_dtls_transport());
    }
    if (t.rtcp_dtls_transport()) {
      dtls_transports.push_back(t.rtcp_dtls_transport());
    }
  });
  return dtls_transports;
}

RTCError JsepTransportController::ApplyDescription_n(
    bool local,
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  TRACE_EVENT0("webrtc", "JsepTransportController::ApplyDescription_n");

  // Stash away the description object that we'll be applying (since this
  // function is used for both local and remote).
  const SessionDescription* description = local ? local_desc : remote_desc;

  RTC_DCHECK(description);

  RTCError error;
  error =
      ValidateAndMaybeUpdateBundleGroups(local, type, local_desc, remote_desc);
  if (!error.ok()) {
    return error;
  }

  std::map<const ContentGroup*, std::vector<RtpHeaderExtensionId>>
      merged_encrypted_extension_ids_by_bundle;
  if (!bundles_.bundle_groups().empty()) {
    merged_encrypted_extension_ids_by_bundle =
        MergeEncryptedHeaderExtensionIdsForBundles(description);
  }

  for (const ContentInfo& content_info : description->contents()) {
    // Don't create transports for rejected m-lines and bundled m-lines.
    if (content_info.rejected ||
        !bundles_.IsFirstMidInGroup(content_info.mid())) {
      continue;
    }
    error = MaybeCreateJsepTransport(local, content_info, *description);
    if (!error.ok()) {
      return error;
    }
  }

  RTC_DCHECK(description->contents().size() ==
             description->transport_infos().size());
  for (size_t i = 0; i < description->contents().size(); ++i) {
    const ContentInfo& content_info = description->contents()[i];
    const TransportInfo& transport_info = description->transport_infos()[i];

    if (content_info.rejected) {
      // This may cause groups to be removed from |bundles_.bundle_groups()|.
      HandleRejectedContent(content_info);
      continue;
    }

    const ContentGroup* established_bundle_group =
        bundles_.LookupGroupByMid(content_info.mid());

    // For bundle members that are not BUNDLE-tagged (not first in the group),
    // configure their transport to be the same as the BUNDLE-tagged transport.
    if (established_bundle_group &&
        content_info.mid() != *established_bundle_group->FirstContentName()) {
      if (!HandleBundledContent(content_info, *established_bundle_group)) {
        return RTCError(RTCErrorType::INVALID_PARAMETER,
                        "Failed to process the bundled m= section with "
                        "mid='" +
                            content_info.mid() + "'.");
      }
    }

    JsepTransport* transport = GetJsepTransportForMid(content_info.mid());
    if (!transport) {
      return RTC_LOG_ERROR(
          RTCError(RTCErrorType::INVALID_PARAMETER)
          << "Could not find transport for m= section with mid='"
          << content_info.mid() << "'");
    }

    if (established_bundle_group &&
        content_info.mid() != *established_bundle_group->FirstContentName()) {
      continue;  // Skip code below.
    }
    // The code below this is only executed for contents that are the
    // leader of their bundle group, or is unbundled.
    error = ValidateContent(content_info);
    if (!error.ok()) {
      return error;
    }

    std::vector<RtpHeaderExtensionId> extension_ids;
    if (established_bundle_group) {
      // If bundled: Check that this is BUNDLE-tagged (first in the group).
      RTC_DCHECK(content_info.mid() ==
                 *established_bundle_group->FirstContentName());
      auto it = merged_encrypted_extension_ids_by_bundle.find(
          established_bundle_group);
      RTC_DCHECK(it != merged_encrypted_extension_ids_by_bundle.end());
      extension_ids = it->second;
    } else {
      extension_ids = GetEncryptedHeaderExtensionIds(content_info);
    }

    SetIceRole_n(DetermineIceRole(transport, transport_info, type, local));

    JsepTransportDescription jsep_description = CreateJsepTransportDescription(
        content_info, transport_info, extension_ids);
    jsep_description.transport_desc.cryptex =
        content_info.media_description()->cryptex();
    if (local) {
      error =
          transport->SetLocalJsepTransportDescription(jsep_description, type);
    } else {
      error =
          transport->SetRemoteJsepTransportDescription(jsep_description, type);
    }

    if (!error.ok()) {
      return RTC_LOG_ERROR(
          RTCError(RTCErrorType::INVALID_PARAMETER)
          << "Failed to apply the description for m= section with mid='"
          << content_info.mid() << "': " << error.message());
    }
  }
  if (type == SdpType::kAnswer) {
    transports_.CommitTransports();
    bundles_.Commit();
  }
  return RTCError::OK();
}

flat_map<std::string, JsepTransportController::TransportState>
JsepTransportController::GetTransportStates_n() {
  RTC_DCHECK_RUN_ON(network_thread_);
  flat_map<std::string, TransportState> states;
  transports_.ForEachTransport([&](JsepTransport& t) {
    std::optional<SSLRole> role = t.GetDtlsRole();
    bool needs_ice_restart = t.needs_ice_restart();

    TransportState state;
    state.dtls_role = role;
    state.needs_ice_restart = needs_ice_restart;

    bool bundled = false;
    for (const std::unique_ptr<ContentGroup>& bundle_group :
         bundles_.bundle_groups()) {
      const std::string* first_mid = bundle_group->FirstContentName();
      if (first_mid && *first_mid == t.name()) {
        bundled = true;
        for (const std::string& mid : bundle_group->content_names()) {
          states[mid] = state;
        }
        break;
      }
    }
    if (!bundled) {
      states[std::string(t.name())] = state;
    }
  });
  return states;
}

RTCError JsepTransportController::ValidateAndMaybeUpdateBundleGroups(
    bool local,
    SdpType type,
    const SessionDescription* local_desc,
    const SessionDescription* remote_desc) {
  const SessionDescription* description = local ? local_desc : remote_desc;

  RTC_DCHECK(description);

  std::vector<const ContentGroup*> new_bundle_groups =
      description->GetGroupsByName(GROUP_TYPE_BUNDLE);
  // Verify `new_bundle_groups`.
  std::map<std::string, const ContentGroup*> new_bundle_groups_by_mid;
  for (const ContentGroup* new_bundle_group : new_bundle_groups) {
    for (const std::string& content_name : new_bundle_group->content_names()) {
      // The BUNDLE group must not contain a MID that is a member of a different
      // BUNDLE group, or that contains the same MID multiple times.
      if (new_bundle_groups_by_mid.find(content_name) !=
          new_bundle_groups_by_mid.end()) {
        return RTCError(RTCErrorType::INVALID_PARAMETER,
                        "A BUNDLE group contains a MID='" + content_name +
                            "' that is already in a BUNDLE group.");
      }
      new_bundle_groups_by_mid.insert(
          std::make_pair(content_name, new_bundle_group));
      // The BUNDLE group must not contain a MID that no m= section has.
      if (!description->GetContentByName(content_name)) {
        return RTCError(RTCErrorType::INVALID_PARAMETER,
                        "A BUNDLE group contains a MID='" + content_name +
                            "' matching no m= section.");
      }
    }
  }

  if (type == SdpType::kOffer) {
    // For an offer, we need to verify that there is not a conflicting mapping
    // between existing and new bundle groups. For example, if the existing
    // groups are [[1,2],[3,4]] and new are [[1,3],[2,4]] or [[1,2,3,4]], or
    // vice versa. Switching things around like this requires a separate offer
    // that removes the relevant sections from their group, as per RFC 8843,
    // section 7.5.2.
    std::map<const ContentGroup*, const ContentGroup*>
        new_bundle_groups_by_existing_bundle_groups;
    std::map<const ContentGroup*, const ContentGroup*>
        existing_bundle_groups_by_new_bundle_groups;
    for (const ContentGroup* new_bundle_group : new_bundle_groups) {
      for (const std::string& mid : new_bundle_group->content_names()) {
        ContentGroup* existing_bundle_group = bundles_.LookupGroupByMid(mid);
        if (!existing_bundle_group) {
          continue;
        }
        auto it = new_bundle_groups_by_existing_bundle_groups.find(
            existing_bundle_group);
        if (it != new_bundle_groups_by_existing_bundle_groups.end() &&
            it->second != new_bundle_group) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "MID " + mid + " in the offer has changed group.");
        }
        new_bundle_groups_by_existing_bundle_groups.insert(
            std::make_pair(existing_bundle_group, new_bundle_group));
        it = existing_bundle_groups_by_new_bundle_groups.find(new_bundle_group);
        if (it != existing_bundle_groups_by_new_bundle_groups.end() &&
            it->second != existing_bundle_group) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "MID " + mid + " in the offer has changed group.");
        }
        existing_bundle_groups_by_new_bundle_groups.insert(
            std::make_pair(new_bundle_group, existing_bundle_group));
      }
    }
  } else if (type == SdpType::kAnswer) {
    if ((local && remote_desc) || (!local && local_desc)) {
      std::vector<const ContentGroup*> offered_bundle_groups =
          local ? remote_desc->GetGroupsByName(GROUP_TYPE_BUNDLE)
                : local_desc->GetGroupsByName(GROUP_TYPE_BUNDLE);

      std::map<std::string, const ContentGroup*> offered_bundle_groups_by_mid;
      for (const ContentGroup* offered_bundle_group : offered_bundle_groups) {
        for (const std::string& content_name :
             offered_bundle_group->content_names()) {
          offered_bundle_groups_by_mid[content_name] = offered_bundle_group;
        }
      }

      std::map<const ContentGroup*, const ContentGroup*>
          new_bundle_groups_by_offered_bundle_groups;
      for (const ContentGroup* new_bundle_group : new_bundle_groups) {
        if (!new_bundle_group->FirstContentName()) {
          // Empty groups could be a subset of any group.
          continue;
        }
        // The group in the answer (new_bundle_group) must have a corresponding
        // group in the offer (original_group), because the answer groups may
        // only be subsets of the offer groups.
        auto it = offered_bundle_groups_by_mid.find(
            *new_bundle_group->FirstContentName());
        if (it == offered_bundle_groups_by_mid.end()) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "A BUNDLE group was added in the answer that did not "
                          "exist in the offer.");
        }
        const ContentGroup* offered_bundle_group = it->second;
        if (new_bundle_groups_by_offered_bundle_groups.find(
                offered_bundle_group) !=
            new_bundle_groups_by_offered_bundle_groups.end()) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "A MID in the answer has changed group.");
        }
        new_bundle_groups_by_offered_bundle_groups.insert(
            std::make_pair(offered_bundle_group, new_bundle_group));
        for (const std::string& content_name :
             new_bundle_group->content_names()) {
          it = offered_bundle_groups_by_mid.find(content_name);
          // The BUNDLE group in answer should be a subset of offered group.
          if (it == offered_bundle_groups_by_mid.end() ||
              it->second != offered_bundle_group) {
            return RTCError(RTCErrorType::INVALID_PARAMETER,
                            "A BUNDLE group in answer contains a MID='" +
                                content_name +
                                "' that was not in the offered group.");
          }
        }
      }

      for (const auto& bundle_group : bundles_.bundle_groups()) {
        for (const std::string& content_name : bundle_group->content_names()) {
          // An answer that removes m= sections from pre-negotiated BUNDLE group
          // without rejecting it, is invalid.
          auto it = new_bundle_groups_by_mid.find(content_name);
          if (it == new_bundle_groups_by_mid.end()) {
            auto* content_info = description->GetContentByName(content_name);
            if (!content_info || !content_info->rejected) {
              return RTCError(RTCErrorType::INVALID_PARAMETER,
                              "Answer cannot remove m= section with mid='" +
                                  content_name +
                                  "' from already-established BUNDLE group.");
            }
          }
        }
      }
    }
  }

  if (config_.bundle_policy ==
          PeerConnectionInterface::kBundlePolicyMaxBundle &&
      !description->HasGroup(GROUP_TYPE_BUNDLE) &&
      description->contents().size() > 1) {
    return RTCError(RTCErrorType::INVALID_PARAMETER,
                    "max-bundle is used but no bundle group found.");
  }

  bundles_.Update(description, type);

  for (const auto& bundle_group : bundles_.bundle_groups()) {
    if (!bundle_group->FirstContentName())
      continue;

    // The first MID in a BUNDLE group is BUNDLE-tagged.
    auto bundled_content =
        description->GetContentByName(*bundle_group->FirstContentName());
    if (!bundled_content) {
      return RTCError(
          RTCErrorType::INVALID_PARAMETER,
          "An m= section associated with the BUNDLE-tag doesn't exist.");
    }

    // If the `bundled_content` is rejected, other contents in the bundle group
    // must also be rejected.
    if (bundled_content->rejected) {
      for (const auto& content_name : bundle_group->content_names()) {
        auto other_content = description->GetContentByName(content_name);
        if (!other_content) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "A BUNDLE group contains a MID='" + content_name +
                              "' matching no m= section.");
        }
        if (!other_content->rejected) {
          return RTCError(RTCErrorType::INVALID_PARAMETER,
                          "The m= section with mid='" + content_name +
                              "' should be rejected.");
        }
      }
    }
  }
  return RTCError::OK();
}

RTCError JsepTransportController::ValidateContent(
    const ContentInfo& content_info) {
  if (config_.rtcp_mux_policy ==
          PeerConnectionInterface::kRtcpMuxPolicyRequire &&
      content_info.type == MediaProtocolType::kRtp &&
      !content_info.bundle_only &&
      !content_info.media_description()->rtcp_mux()) {
    return RTCError(RTCErrorType::INVALID_PARAMETER,
                    "The m= section with mid='" + content_info.mid() +
                        "' is invalid. RTCP-MUX is not "
                        "enabled when it is required.");
  }
  return RTCError::OK();
}

void JsepTransportController::HandleRejectedContent(
    const ContentInfo& content_info) {
  // If the content is rejected, let the
  // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
  // then destroy the JsepTransport.
  ContentGroup* bundle_group = bundles_.LookupGroupByMid(content_info.mid());
  if (bundle_group && !bundle_group->content_names().empty() &&
      content_info.mid() == *bundle_group->FirstContentName()) {
    // Rejecting a BUNDLE group's first mid means we are rejecting the entire
    // group.
    for (const auto& content_name : bundle_group->content_names()) {
      transports_.RemoveTransportForMid(content_name);
    }
    // Delete the BUNDLE group.
    bundles_.DeleteGroup(bundle_group);
  } else {
    transports_.RemoveTransportForMid(content_info.mid());
    if (bundle_group) {
      // Remove the rejected content from the `bundle_group`.
      bundles_.DeleteMid(bundle_group, content_info.mid());
    }
  }
}

bool JsepTransportController::HandleBundledContent(
    const ContentInfo& content_info,
    const ContentGroup& bundle_group) {
  TRACE_EVENT0("webrtc", "JsepTransportController::HandleBundledContent");
  RTC_DCHECK(bundle_group.FirstContentName());
  auto jsep_transport =
      GetJsepTransportByName(*bundle_group.FirstContentName());
  RTC_DCHECK(jsep_transport);
  // If the content is bundled, let the
  // BaseChannel/SctpTransport change the RtpTransport/DtlsTransport first,
  // then destroy the JsepTransport.
  // TODO(bugs.webrtc.org/9719) For media transport this is far from ideal,
  // because it means that we first create media transport and start
  // connecting it, and then we destroy it. We will need to address it before
  // video path is enabled.
  return transports_.SetTransportForMid(content_info.mid(), jsep_transport);
}

JsepTransportDescription
JsepTransportController::CreateJsepTransportDescription(
    const ContentInfo& content_info,
    const TransportInfo& transport_info,
    const std::vector<RtpHeaderExtensionId>& encrypted_extension_ids) {
  TRACE_EVENT0("webrtc",
               "JsepTransportController::CreateJsepTransportDescription");
  const MediaContentDescription* content_desc =
      content_info.media_description();
  RTC_DCHECK(content_desc);
  bool rtcp_mux_enabled = content_info.type == MediaProtocolType::kSctp
                              ? true
                              : content_desc->rtcp_mux();

  return JsepTransportDescription(rtcp_mux_enabled, encrypted_extension_ids,
                                  transport_info.description);
}

std::vector<RtpHeaderExtensionId>
JsepTransportController::GetEncryptedHeaderExtensionIds(
    const ContentInfo& content_info) {
  const MediaContentDescription* content_desc =
      content_info.media_description();

  if (!config_.crypto_options.srtp.enable_encrypted_rtp_header_extensions) {
    return std::vector<RtpHeaderExtensionId>();
  }

  std::vector<RtpHeaderExtensionId> encrypted_header_extension_ids;
  for (const auto& extension : content_desc->rtp_header_extensions()) {
    if (!extension.encrypt) {
      continue;
    }
    if (!absl::c_linear_search(encrypted_header_extension_ids, extension.id)) {
      encrypted_header_extension_ids.push_back(extension.id);
    }
  }
  return encrypted_header_extension_ids;
}

std::map<const ContentGroup*, std::vector<RtpHeaderExtensionId>>
JsepTransportController::MergeEncryptedHeaderExtensionIdsForBundles(
    const SessionDescription* description) {
  RTC_DCHECK(description);
  RTC_DCHECK(!bundles_.bundle_groups().empty());
  std::map<const ContentGroup*, std::vector<RtpHeaderExtensionId>>
      merged_encrypted_extension_ids_by_bundle;
  // Union the encrypted header IDs in the group when bundle is enabled.
  for (const ContentInfo& content_info : description->contents()) {
    auto group = bundles_.LookupGroupByMid(content_info.mid());
    if (!group)
      continue;
    // Get or create list of IDs for the BUNDLE group.
    std::vector<RtpHeaderExtensionId>& merged_ids =
        merged_encrypted_extension_ids_by_bundle[group];
    // Add IDs not already in the list.
    std::vector<RtpHeaderExtensionId> extension_ids =
        GetEncryptedHeaderExtensionIds(content_info);
    for (RtpHeaderExtensionId id : extension_ids) {
      if (!absl::c_linear_search(merged_ids, id)) {
        merged_ids.push_back(id);
      }
    }
  }
  return merged_encrypted_extension_ids_by_bundle;
}

const JsepTransport* JsepTransportController::GetJsepTransportForMid(
    absl::string_view mid) const {
  return transports_.GetTransportForMid(mid);
}

JsepTransport* JsepTransportController::GetJsepTransportForMid(
    absl::string_view mid) {
  return transports_.GetTransportForMid(mid);
}

const JsepTransport* JsepTransportController::GetJsepTransportByName(
    absl::string_view transport_name) const {
  return transports_.GetTransportByName(transport_name);
}

JsepTransport* JsepTransportController::GetJsepTransportByName(
    absl::string_view transport_name) {
  return transports_.GetTransportByName(transport_name);
}

RTCError JsepTransportController::MaybeCreateJsepTransport(
    bool local,
    const ContentInfo& content_info,
    const SessionDescription& description) {
  JsepTransport* transport = GetJsepTransportByName(content_info.mid());
  if (transport) {
    return RTCError::OK();
  }

  std::unique_ptr<DtlsTransportInternal> rtp_dtls_transport =
      CreateDtlsTransport(content_info, /*rtcp=*/false);
  DtlsTransportInternal* rtp_dtls_transport_ptr = rtp_dtls_transport.get();

  std::unique_ptr<DtlsTransportInternal> rtcp_dtls_transport;
  if (config_.rtcp_mux_policy !=
          PeerConnectionInterface::kRtcpMuxPolicyRequire &&
      content_info.type == MediaProtocolType::kRtp) {
    rtcp_dtls_transport = CreateDtlsTransport(content_info, /*rtcp=*/true);
  }

  std::unique_ptr<RtpTransport> rtp_transport =
      CreateRtpTransport(content_info.mid(), std::move(rtp_dtls_transport),
                         std::move(rtcp_dtls_transport));

  std::unique_ptr<SctpTransportInternal> sctp_transport;
  if (config_.sctp_factory) {
    sctp_transport =
        config_.sctp_factory->CreateSctpTransport(env_, rtp_dtls_transport_ptr);
  }

  scoped_refptr<DtlsTransport> dtls_transport =
      make_ref_counted<DtlsTransport>(rtp_dtls_transport_ptr);

  std::unique_ptr<JsepTransport> jsep_transport =
      std::make_unique<JsepTransport>(
          certificate_, std::move(rtp_transport), std::move(dtls_transport),
          std::move(sctp_transport),
          [&]() {
            RTC_DCHECK_RUN_ON(network_thread_);
            UpdateAggregateStates_n();
          },
          config_.crypto_options.srtp.cryptex_policy);

  // Object hierarchy for objects injected into JsepTransport:
  //
  //  rtp_transport
  //    -> dtls_srtp_transport / unencrypted_rtp_transport
  //      -> rtp_dtls_transport
  //        -> ice->internal()  <- ownership
  //      -> rtcp_dtls_transport
  //        -> ice->internal() <- ownership (rtcp_ice)
  //    -> sctp_transport
  //      -> rtp_dtls_transport <- raw pointer.
  //  dtls_transport (reference counted)
  //    -> rtp_dtls_transport <- raw pointer.
  //
  // TODO(tommi): At this point `jsep_transport` has been constructed
  // and initialized with callbacks subscribed to. Write and call
  // a new method, e.g. jsep_transport->InitializationDone().
  // At that point, all the transport objects should detach any sequence
  // checkers and be ready for use on a different TQ.
  //
  // Since there are still raw pointers handed to some of the transports,
  // and since `dtls_transport` is reference counted, teardown then needs
  // to be explicit as it currently is in ~JsepTransport() where Clear() is
  // called.

  transports_.RegisterTransport(std::move(jsep_transport));
  UpdateAggregateStates_n();
  return RTCError::OK();
}

void JsepTransportController::DestroyAllJsepTransports_n() {
  transports_.DestroyAllTransports();
}

void JsepTransportController::SetIceRole_n(IceRole ice_role) {
  ice_role_ = ice_role;
  auto dtls_transports = GetDtlsTransports();
  for (auto& dtls : dtls_transports) {
    dtls->ice_transport()->SetIceRole(ice_role_);
  }
}

IceRole JsepTransportController::DetermineIceRole(
    JsepTransport* jsep_transport,
    const TransportInfo& transport_info,
    SdpType type,
    bool local) {
  IceRole ice_role = ice_role_;
  auto tdesc = transport_info.description;
  if (local) {
    // The initial offer side may use ICE Lite, in which case, per RFC5245
    // Section 5.1.1, the answer side should take the controlling role if it is
    // in the full ICE mode.
    //
    // When both sides use ICE Lite, the initial offer side must take the
    // controlling role, and this is the default logic implemented in
    // SetLocalDescription in JsepTransportController.
    if (jsep_transport->remote_description() &&
        jsep_transport->remote_description()->transport_desc.ice_mode ==
            ICEMODE_LITE &&
        ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_FULL) {
      ice_role = ICEROLE_CONTROLLING;
    }
  } else {
    // If our role is ICEROLE_CONTROLLED and the remote endpoint
    // supports only ice_lite, this local endpoint should take the CONTROLLING
    // role.
    // TODO(deadbeef): This is a session-level attribute, so it really shouldn't
    // be in a TransportDescription in the first place...
    if (ice_role_ == ICEROLE_CONTROLLED && tdesc.ice_mode == ICEMODE_LITE) {
      ice_role = ICEROLE_CONTROLLING;
    }

    // If we use ICE Lite and the remote endpoint uses the full implementation
    // of ICE, the local endpoint must take the controlled role, and the other
    // side must be the controlling role.
    if (jsep_transport->local_description() &&
        jsep_transport->local_description()->transport_desc.ice_mode ==
            ICEMODE_LITE &&
        ice_role_ == ICEROLE_CONTROLLING && tdesc.ice_mode == ICEMODE_FULL) {
      ice_role = ICEROLE_CONTROLLED;
    }
  }

  return ice_role;
}

void JsepTransportController::OnTransportWritableState_n(
    PacketTransportInternal* transport) {
  RTC_LOG(LS_INFO) << " Transport " << transport->transport_name()
                   << " writability changed to " << transport->writable()
                   << ".";
  UpdateAggregateStates_n();
}

void JsepTransportController::OnTransportReceivingState_n(
    PacketTransportInternal* transport) {
  UpdateAggregateStates_n();
}

void JsepTransportController::OnTransportGatheringState_n(
    IceTransportInternal* transport) {
  UpdateAggregateStates_n();
}

void JsepTransportController::OnTransportCandidateGathered_n(
    IceTransportInternal* transport,
    const Candidate& candidate) {
  // We should never signal peer-reflexive candidates.
  if (candidate.is_prflx()) {
    RTC_DCHECK_NOTREACHED();
    return;
  }

  config_.signal_ice_candidates_gathered(transport->transport_name(),
                                         std::vector<Candidate>{candidate});
}

void JsepTransportController::OnTransportCandidateError_n(
    IceTransportInternal* transport,
    const IceCandidateErrorEvent& event) {
  config_.signal_ice_candidate_error(event);
}
void JsepTransportController::OnTransportCandidatesRemoved_n(
    IceTransportInternal* transport,
    const Candidates& candidates) {
  config_.signal_ice_candidates_removed(transport, candidates);
}
void JsepTransportController::OnTransportCandidatePairChanged_n(
    const CandidatePairChangeEvent& event) {
  RTC_DCHECK(!event.transport_name.empty());
  config_.signal_ice_candidate_pair_changed(event);
}

void JsepTransportController::OnTransportRoleConflict_n(
    IceTransportInternal* transport) {
  // Note: since the role conflict is handled entirely on the network thread,
  // we don't need to worry about role conflicts occurring on two ports at
  // once. The first one encountered should immediately reverse the role.
  IceRole reversed_role = (ice_role_ == ICEROLE_CONTROLLING)
                              ? ICEROLE_CONTROLLED
                              : ICEROLE_CONTROLLING;
  RTC_LOG(LS_INFO) << "Got role conflict; switching to "
                   << (reversed_role == ICEROLE_CONTROLLING ? "controlling"
                                                            : "controlled")
                   << " role.";
  SetIceRole_n(reversed_role);
}

void JsepTransportController::OnTransportStateChanged_n(
    IceTransportInternal* transport) {
  RTC_LOG(LS_INFO) << transport->transport_name() << " Transport "
                   << transport->component()
                   << " state changed. Check if state is complete.";
  UpdateAggregateStates_n();
}

void JsepTransportController::UpdateAggregateStates_n() {
  TRACE_EVENT0("webrtc", "JsepTransportController::UpdateAggregateStates_n");
  auto dtls_transports = GetActiveDtlsTransports();
  IceConnectionState new_connection_state = kIceConnectionConnecting;
  PeerConnectionInterface::IceConnectionState new_ice_connection_state =
      PeerConnectionInterface::IceConnectionState::kIceConnectionNew;
  PeerConnectionInterface::PeerConnectionState new_combined_state =
      PeerConnectionInterface::PeerConnectionState::kNew;
  IceGatheringState new_gathering_state = kIceGatheringNew;
  bool any_failed = false;
  bool all_connected = !dtls_transports.empty();
  bool all_completed = !dtls_transports.empty();
  bool any_gathering = false;
  bool all_done_gathering = !dtls_transports.empty();

  std::map<IceTransportState, int> ice_state_counts;
  std::map<DtlsTransportState, int> dtls_state_counts;

  for (const auto& dtls : dtls_transports) {
    any_failed = any_failed || dtls->ice_transport()->GetState() ==
                                   IceTransportStateInternal::STATE_FAILED;
    all_connected = all_connected && dtls->writable();
    all_completed =
        all_completed && dtls->writable() &&
        dtls->ice_transport()->GetState() ==
            IceTransportStateInternal::STATE_COMPLETED &&
        dtls->ice_transport()->GetIceRole() == ICEROLE_CONTROLLING &&
        dtls->ice_transport()->gathering_state() == kIceGatheringComplete;
    any_gathering = any_gathering || dtls->ice_transport()->gathering_state() !=
                                         kIceGatheringNew;
    all_done_gathering =
        all_done_gathering &&
        dtls->ice_transport()->gathering_state() == kIceGatheringComplete;

    dtls_state_counts[dtls->dtls_state()]++;
    ice_state_counts[dtls->ice_transport()->GetIceTransportState()]++;
  }

  if (any_failed) {
    new_connection_state = kIceConnectionFailed;
  } else if (all_completed) {
    new_connection_state = kIceConnectionCompleted;
  } else if (all_connected) {
    new_connection_state = kIceConnectionConnected;
  }
  if (ice_connection_state_ != new_connection_state) {
    ice_connection_state_ = new_connection_state;
    config_.signal_ice_connection_state(new_connection_state);
  }

  // Compute the current RTCIceConnectionState as described in
  // https://www.w3.org/TR/webrtc/#dom-rtciceconnectionstate.
  // The PeerConnection is responsible for handling the "closed" state.
  int total_ice_checking = ice_state_counts[IceTransportState::kChecking];
  int total_ice_connected = ice_state_counts[IceTransportState::kConnected];
  int total_ice_completed = ice_state_counts[IceTransportState::kCompleted];
  int total_ice_failed = ice_state_counts[IceTransportState::kFailed];
  int total_ice_disconnected =
      ice_state_counts[IceTransportState::kDisconnected];
  int total_ice_closed = ice_state_counts[IceTransportState::kClosed];
  int total_ice_new = ice_state_counts[IceTransportState::kNew];
  int total_ice = dtls_transports.size();

  if (total_ice_failed > 0) {
    // Any RTCIceTransports are in the "failed" state.
    new_ice_connection_state = PeerConnectionInterface::kIceConnectionFailed;
  } else if (total_ice_disconnected > 0) {
    // None of the previous states apply and any RTCIceTransports are in the
    // "disconnected" state.
    new_ice_connection_state =
        PeerConnectionInterface::kIceConnectionDisconnected;
  } else if (total_ice_new + total_ice_closed == total_ice) {
    // None of the previous states apply and all RTCIceTransports are in the
    // "new" or "closed" state, or there are no transports.
    new_ice_connection_state = PeerConnectionInterface::kIceConnectionNew;
  } else if (total_ice_new + total_ice_checking > 0) {
    // None of the previous states apply and any RTCIceTransports are in the
    // "new" or "checking" state.
    new_ice_connection_state = PeerConnectionInterface::kIceConnectionChecking;
  } else if (total_ice_completed + total_ice_closed == total_ice ||
             all_completed) {
    // None of the previous states apply and all RTCIceTransports are in the
    // "completed" or "closed" state.
    //
    // TODO(https://bugs.webrtc.org/10356): The all_completed condition is added
    // to mimic the behavior of the old ICE connection state, and should be
    // removed once we get end-of-candidates signaling in place.
    new_ice_connection_state = PeerConnectionInterface::kIceConnectionCompleted;
  } else if (total_ice_connected + total_ice_completed + total_ice_closed ==
             total_ice) {
    // None of the previous states apply and all RTCIceTransports are in the
    // "connected", "completed" or "closed" state.
    new_ice_connection_state = PeerConnectionInterface::kIceConnectionConnected;
  } else {
    RTC_DCHECK_NOTREACHED();
  }

  if (standardized_ice_connection_state_ != new_ice_connection_state) {
    if (standardized_ice_connection_state_ ==
            PeerConnectionInterface::kIceConnectionChecking &&
        new_ice_connection_state ==
            PeerConnectionInterface::kIceConnectionCompleted) {
      // Ensure that we never skip over the "connected" state.
      config_.signal_standardized_ice_connection_state(
          PeerConnectionInterface::kIceConnectionConnected);
    }
    standardized_ice_connection_state_ = new_ice_connection_state;
    config_.signal_standardized_ice_connection_state(new_ice_connection_state);
  }

  // Compute the current RTCPeerConnectionState as described in
  // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnectionstate.
  // The PeerConnection is responsible for handling the "closed" state.
  // Note that "connecting" is only a valid state for DTLS transports while
  // "checking", "completed" and "disconnected" are only valid for ICE
  // transports.
  int total_connected =
      total_ice_connected + dtls_state_counts[DtlsTransportState::kConnected];
  int total_dtls_connecting =
      dtls_state_counts[DtlsTransportState::kConnecting];
  int total_failed =
      total_ice_failed + dtls_state_counts[DtlsTransportState::kFailed];
  int total_closed =
      total_ice_closed + dtls_state_counts[DtlsTransportState::kClosed];
  int total_new = total_ice_new + dtls_state_counts[DtlsTransportState::kNew];
  int total_transports = total_ice * 2;

  if (total_failed > 0) {
    // Any of the RTCIceTransports or RTCDtlsTransports are in a "failed" state.
    new_combined_state = PeerConnectionInterface::PeerConnectionState::kFailed;
  } else if (total_ice_disconnected > 0) {
    // None of the previous states apply and any RTCIceTransports or
    // RTCDtlsTransports are in the "disconnected" state.
    new_combined_state =
        PeerConnectionInterface::PeerConnectionState::kDisconnected;
  } else if (total_new + total_closed == total_transports) {
    // None of the previous states apply and all RTCIceTransports and
    // RTCDtlsTransports are in the "new" or "closed" state, or there are no
    // transports.
    new_combined_state = PeerConnectionInterface::PeerConnectionState::kNew;
  } else if (total_new + total_dtls_connecting + total_ice_checking > 0) {
    // None of the previous states apply and all RTCIceTransports or
    // RTCDtlsTransports are in the "new", "connecting" or "checking" state.
    new_combined_state =
        PeerConnectionInterface::PeerConnectionState::kConnecting;
  } else if (total_connected + total_ice_completed + total_closed ==
             total_transports) {
    // None of the previous states apply and all RTCIceTransports and
    // RTCDtlsTransports are in the "connected", "completed" or "closed" state.
    new_combined_state =
        PeerConnectionInterface::PeerConnectionState::kConnected;
  } else {
    RTC_DCHECK_NOTREACHED();
  }

  if (combined_connection_state_ != new_combined_state) {
    combined_connection_state_ = new_combined_state;
    config_.signal_connection_state(new_combined_state);
  }

  // Compute the gathering state.
  if (dtls_transports.empty()) {
    new_gathering_state = kIceGatheringNew;
  } else if (all_done_gathering) {
    new_gathering_state = kIceGatheringComplete;
  } else if (any_gathering) {
    new_gathering_state = kIceGatheringGathering;
  }
  if (ice_gathering_state_ != new_gathering_state) {
    ice_gathering_state_ = new_gathering_state;
    config_.signal_ice_gathering_state(new_gathering_state);
  }
}

void JsepTransportController::OnRtcpPacketReceived_n(
    CopyOnWriteBuffer packet,
    std::optional<Timestamp> arrival_time,
    EcnMarking ecn) {
  RTC_DCHECK(config_.rtcp_handler);
  config_.rtcp_handler(std::move(packet),
                       arrival_time.has_value() ? arrival_time->us() : -1);
}

void JsepTransportController::OnUnDemuxableRtpPacketReceived_n(
    const RtpPacketReceived& packet) {
  RTC_DCHECK(config_.un_demuxable_packet_handler);
  config_.un_demuxable_packet_handler(packet);
}

void JsepTransportController::OnDtlsRoleChange_n(
    DtlsTransportInternal* transport,
    SSLRole role) {
  RTC_DCHECK_RUN_ON(network_thread_);
  flat_map<std::string, TransportState> new_states = GetTransportStates_n();
  signaling_thread_->PostTask(
      SafeTask(role_update_safety_flag_n_,
               [this, new_states = std::move(new_states)]() mutable {
                 RTC_DCHECK_RUN_ON(signaling_thread_);
                 transport_states_ = std::move(new_states);
               }));
}

void JsepTransportController::OnDtlsHandshakeError(SSLHandshakeError error) {
  config_.on_dtls_handshake_error(error);
}

bool JsepTransportController::OnTransportChanged(
    absl::string_view mid,
    JsepTransport* jsep_transport) {
  if (config_.transport_observer) {
    if (jsep_transport) {
      return config_.transport_observer->OnTransportChanged(
          mid, jsep_transport->rtp_transport(),
          jsep_transport->RtpDtlsTransport(),
          jsep_transport->data_channel_transport());
    } else {
      return config_.transport_observer->OnTransportChanged(mid, nullptr,
                                                            nullptr, nullptr);
    }
  }
  return false;
}

}  // namespace webrtc
