Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  sdp_offer_answer.cc

  Sprache: C
 

/*
 *  Copyright 2020 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/sdp_offer_answer.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <queue>
#include <set>
#include <span>
#include <string>
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/cleanup/cleanup.h"
#include "absl/functional/any_invocable.h"
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "api/candidate.h"
#include "api/crypto/crypto_options.h"
#include "api/environment/environment.h"
#include "api/jsep.h"
#include "api/make_ref_counted.h"
#include "api/media_stream_interface.h"
#include "api/media_types.h"
#include "api/payload_type.h"
#include "api/peer_connection_interface.h"
#include "api/rtc_error.h"
#include "api/rtp_parameters.h"
#include "api/rtp_receiver_interface.h"
#include "api/rtp_sender_interface.h"
#include "api/rtp_transceiver_direction.h"
#include "api/rtp_transceiver_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/set_local_description_observer_interface.h"
#include "api/set_remote_description_observer_interface.h"
#include "api/uma_metrics.h"
#include "api/video/builtin_video_bitrate_allocator_factory.h"
#include "api/video/video_codec_constants.h"
#include "media/base/codec.h"
#include "media/base/codec_comparators.h"
#include "media/base/media_channel.h"
#include "media/base/media_constants.h"
#include "media/base/media_engine.h"
#include "media/base/rid_description.h"
#include "media/base/stream_params.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "p2p/base/p2p_transport_channel.h"
#include "p2p/base/port_allocator.h"
#include "p2p/base/transport_description.h"
#include "p2p/base/transport_description_factory.h"
#include "p2p/base/transport_info.h"
#include "pc/channel_interface.h"
#include "pc/codec_vendor.h"
#include "pc/connection_context.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport_controller.h"
#include "pc/legacy_stats_collector.h"
#include "pc/media_options.h"
#include "pc/media_session.h"
#include "pc/media_stream.h"
#include "pc/media_stream_observer.h"
#include "pc/media_stream_proxy.h"
#include "pc/peer_connection_internal.h"
#include "pc/peer_connection_message_handler.h"
#include "pc/rtp_media_utils.h"
#include "pc/rtp_receiver.h"
#include "pc/rtp_sender.h"
#include "pc/rtp_sender_proxy.h"
#include "pc/rtp_transceiver.h"
#include "pc/rtp_transmission_manager.h"
#include "pc/rtp_transport_internal.h"
#include "pc/scoped_operations_batcher.h"
#include "pc/sdp_munging_detector.h"
#include "pc/session_description.h"
#include "pc/simulcast_description.h"
#include "pc/stream_collection.h"
#include "pc/transceiver_list.h"
#include "pc/usage_pattern.h"
#include "pc/webrtc_session_description_factory.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_map.h"
#include "rtc_base/containers/flat_set.h"
#include "rtc_base/crypto_random.h"
#include "rtc_base/logging.h"
#include "rtc_base/operations_chain.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/system/plan_b_only.h"
#include "rtc_base/thread.h"
#include "rtc_base/trace_event.h"
#include "rtc_base/weak_ptr.h"
#include "system_wrappers/include/metrics.h"

namespace webrtc {
namespace {

void MaybeHandleLocallyRejectedTransceiver(
    ContentSource source,
    const SessionDescriptionInterface& new_session,
    const ContentInfo& new_content,
    scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> transceiver,
    ScopedOperationsBatcher& worker_tasks) {
  if (source != ContentSource::CS_LOCAL || !new_content.rejected) {
    return;
  }
  if (new_session.GetType() == SdpType::kOffer) {
    // If the RtpTransceiver API was used, it would already have made the
    // transceiver stopping. But if the rejection was caused by SDP
    // munging then we need to ensure the transceiver is stopping here.
    if (!transceiver->internal()->stopping()) {
      worker_tasks.AddWithFinalizer(
          transceiver->internal()->StopStandardAsync());
    }
    RTC_DCHECK(transceiver->internal()->stopping());
  } else {
    RTC_DCHECK(new_session.GetType() == SdpType::kAnswer ||
               new_session.GetType() == SdpType::kPrAnswer);
    // When RtpTransceiver API is used, rejection happens in the offer and
    // the transceiver will already be stopped at local answer time
    // (calling stop between SRD(offer) and SLD(answer) would not reject
    // the content in the answer - instead this would trigger a follow-up
    // O/A exchange). So if the content was rejected but the transceiver
    // is not already stopped, SDP munging has happened and we need to
    // ensure the transceiver is stopped.
    if (!transceiver->internal()->stopped()) {
      worker_tasks.Add(transceiver->internal()->GetStopTransceiverProcedure());
    }
    RTC_DCHECK(transceiver->internal()->stopped());
  }
}

struct DtlsTransportAndName {
  scoped_refptr<DtlsTransport> transport;
  std::optional<std::string> transport_name;
};

typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions;

// Error messages
const char kInvalidSdp[] = "Invalid session description.";
const char kInvalidCandidates[] = "Description contains invalid candidates.";
const char kBundleWithoutRtcpMux[] =
    "rtcp-mux must be enabled when BUNDLE "
    "is enabled.";
const char kMlineMismatchInAnswer[] =
    "The order of m-lines in answer doesn't match order in offer. Rejecting "
    "answer.";
const char kMlineMismatchInSubsequentOffer[] =
    "The order of m-lines in subsequent offer doesn't match order from "
    "previous offer/answer.";
const char kSdpWithoutIceUfragPwd[] =
    "Called with SDP without ice-ufrag and ice-pwd.";
const char kSdpWithoutDtlsFingerprint[] =
    "Called with SDP without DTLS fingerprint.";
const char kSdpWithoutCrypto[] = "Called with SDP without crypto setup.";
const char kSframeNotInOffer[] =
    "Remote answer has a=sframe for a media section but the corresponding "
    "offer did not include a=sframe.";

const char kSessionError[] = "Session error code: ";
const char kSessionErrorDesc[] = "Session error description: ";

// The length of RTCP CNAMEs.
const int kRtcpCnameLength = 16;

// The maximum length of the MID attribute.
constexpr size_t kMidMaxSize = 16;

const char kDefaultStreamId[] = "default";
// NOTE: Duplicated in peer_connection.cc:
const char kDefaultAudioSenderId[] = "defaulta0";
const char kDefaultVideoSenderId[] = "defaultv0";

void NoteAddIceCandidateResult(int result) {
  RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.AddIceCandidate", result,
                            kAddIceCandidateMax);
}

flat_map<std::string, const ContentGroup*> GetBundleGroupsByMid(
    const SessionDescription* desc) {
  std::vector<const ContentGroup*> bundle_groups =
      desc->GetGroupsByName(GROUP_TYPE_BUNDLE);
  flat_map<std::string, const ContentGroup*> bundle_groups_by_mid;
  for (const ContentGroup* bundle_group : bundle_groups) {
    for (const std::string& content_name : bundle_group->content_names()) {
      bundle_groups_by_mid[content_name] = bundle_group;
    }
  }
  return bundle_groups_by_mid;
}

// Helper function to look up DTLS transports for all transceivers in a single
// blocking call to the network thread.
flat_map<std::string, DtlsTransportAndName> GetDtlsTransports(
    const TransceiverList& transceivers,
    Thread* network_thread,
    JsepTransportController* transport_controller) {
  const auto& transceiver_list = transceivers.ListRef();
  std::vector<std::string> mids_to_lookup;
  mids_to_lookup.reserve(transceiver_list.size());
  for (const auto& transceiver : transceiver_list) {
    if (const auto mid = transceiver->internal()->mid()) {
      mids_to_lookup.push_back(*mid);
    }
  }
  if (mids_to_lookup.empty()) {
    return flat_map<std::string, DtlsTransportAndName>();
  }
  return network_thread->BlockingCall([&] {
    RTC_DCHECK_RUN_ON(network_thread);
    std::vector<std::pair<std::string, DtlsTransportAndName>> entries;
    entries.reserve(mids_to_lookup.size());
    for (const auto& mid : mids_to_lookup) {
      // Here we essentially look up the same transport twice because
      // the `transport_controller` doesn't have a public method that allows us
      // to look up the JsepTransport object. This could be improved.
      auto transport = transport_controller->LookupDtlsTransportByMid_n(mid);
      auto internal_transport = transport_controller->GetDtlsTransport(mid);
      entries.emplace_back(
          mid, DtlsTransportAndName{
                   transport, internal_transport
                                  ? std::optional<std::string>(
                                        internal_transport->transport_name())
                                  : std::nullopt});
    }
    return flat_map<std::string, DtlsTransportAndName>(std::move(entries));
  });
}

// Returns true if `new_desc` requests an ICE restart (i.e., new ufrag/pwd).
bool CheckForRemoteIceRestart(const SessionDescriptionInterface* old_desc,
                              const SessionDescriptionInterface* new_desc,
                              absl::string_view content_name) {
  if (!old_desc) {
    return false;
  }
  const SessionDescription* new_sd = new_desc->description();
  const SessionDescription* old_sd = old_desc->description();
  const ContentInfo* cinfo = new_sd->GetContentByName(content_name);
  if (!cinfo || cinfo->rejected) {
    return false;
  }
  // If the content isn't rejected, check if ufrag and password has changed.
  const TransportDescription* new_transport_desc =
      new_sd->GetTransportDescriptionByName(content_name);
  const TransportDescription* old_transport_desc =
      old_sd->GetTransportDescriptionByName(content_name);
  if (!new_transport_desc || !old_transport_desc) {
    // No transport description exists. This is not an ICE restart.
    return false;
  }
  if (IceCredentialsChanged(
          old_transport_desc->ice_ufrag, old_transport_desc->ice_pwd,
          new_transport_desc->ice_ufrag, new_transport_desc->ice_pwd)) {
    RTC_LOG(LS_INFO) << "Remote peer requests ICE restart for " << content_name
                     << ".";
    return true;
  }
  return false;
}

// Generates a string error message for SetLocalDescription/SetRemoteDescription
// from an RTCError.
std::string GetSetDescriptionErrorMessage(ContentSource source,
                                          SdpType type,
                                          const RTCError& error) {
  StringBuilder oss;
  oss << "Failed to set " << (source == CS_LOCAL ? "local" : "remote") << " "
      << SdpTypeToString(type) << " sdp: ";
  RTC_DCHECK(!absl::StartsWith(error.message(), oss.str())) << error.message();
  oss << error.message();
  return oss.Release();
}

std::string GetStreamIdsString(std::span<const std::string> stream_ids) {
  std::string output = "streams=[";
  const char* separator = "";
  for (const auto& stream_id : stream_ids) {
    output.append(separator).append(stream_id);
    separator = ", ";
  }
  output.append("]");
  return output;
}

const ContentInfo* FindTransceiverMSection(
    RtpTransceiver* transceiver,
    const SessionDescriptionInterface* session_description) {
  return transceiver->mid()
             ? session_description->description()->GetContentByName(
                   *transceiver->mid())
             : nullptr;
}

// If the direction is "recvonly" or "inactive", treat the description
// as containing no streams.
// See: https://code.google.com/p/webrtc/issues/detail?id=5054
std::vector<StreamParams> GetActiveStreams(
    const MediaContentDescription* desc) {
  return RtpTransceiverDirectionHasSend(desc->direction())
             ? desc->streams()
             : std::vector<StreamParams>();
}

// Logic to decide if an m= section can be recycled. This means that the new
// m= section is not rejected, but the old local or remote m= section is
// rejected. `old_content_one` and `old_content_two` refer to the m= section
// of the old remote and old local descriptions in no particular order.
// We need to check both the old local and remote because either
// could be the most current from the latest negotation.
bool IsMediaSectionBeingRecycled(SdpType type,
                                 const ContentInfo& content,
                                 const ContentInfo* old_content_one,
                                 const ContentInfo* old_content_two) {
  return type == SdpType::kOffer && !content.rejected &&
         ((old_content_one && old_content_one->rejected) ||
          (old_content_two && old_content_two->rejected));
}

// Verify that the order of media sections in `new_desc` matches
// `current_desc`. The number of m= sections in `new_desc` should be no
// less than `current_desc`. In the case of checking an answer's
// `new_desc`, the `current_desc` is the last offer that was set as the
// local or remote. In the case of checking an offer's `new_desc` we
// check against the local and remote descriptions stored from the last
// negotiation, because either of these could be the most up to date for
// possible rejected m sections. These are the `current_desc` and
// `secondary_current_desc`.
bool MediaSectionsInSameOrder(const SessionDescription& current_desc,
                              const SessionDescription* secondary_current_desc,
                              const SessionDescription& new_desc,
                              const SdpType type) {
  if (current_desc.contents().size() > new_desc.contents().size()) {
    return false;
  }

  for (size_t i = 0; i < current_desc.contents().size(); ++i) {
    const ContentInfo* secondary_content_info = nullptr;
    if (secondary_current_desc &&
        i < secondary_current_desc->contents().size()) {
      secondary_content_info = &secondary_current_desc->contents()[i];
    }
    if (IsMediaSectionBeingRecycled(type, new_desc.contents()[i],
                                    ¤t_desc.contents()[i],
                                    secondary_content_info)) {
      // For new offer descriptions, if the media section can be recycled, it's
      // valid for the MID and media type to change.
      continue;
    }
    if (new_desc.contents()[i].mid() != current_desc.contents()[i].mid()) {
      return false;
    }
    const MediaContentDescription* new_desc_mdesc =
        new_desc.contents()[i].media_description();
    const MediaContentDescription* current_desc_mdesc =
        current_desc.contents()[i].media_description();
    if (new_desc_mdesc->type() != current_desc_mdesc->type()) {
      return false;
    }
  }
  return true;
}

bool MediaSectionsHaveSameCount(const SessionDescription& desc1,
                                const SessionDescription& desc2) {
  return desc1.contents().size() == desc2.contents().size();
}

// Checks that the remote answer follows the rules from
// https://datatracker.ietf.org/doc/html/rfc3264#section-6.1
RTCError VerifyDirectionsInAnswer(const SessionDescription* local_offer,
                                  const SessionDescription* remote_answer) {
  RTC_DCHECK(local_offer);
  RTC_DCHECK(remote_answer);

  const ContentInfos& local_contents = local_offer->contents();
  const ContentInfos& remote_contents = remote_answer->contents();
  RTC_DCHECK(local_contents.size() == remote_contents.size());

  for (size_t i = 0; i < local_contents.size(); i++) {
    if (remote_contents[i].rejected) {
      continue;
    }
    RtpTransceiverDirection local_direction =
        local_contents[i].media_description()->direction();
    RtpTransceiverDirection remote_direction =
        remote_contents[i].media_description()->direction();

    if (!RtpTransceiverDirectionHasRecv(local_direction) &&
        RtpTransceiverDirectionHasSend(remote_direction)) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "Incompatible receive direction");
    }
    if (!RtpTransceiverDirectionHasSend(local_direction) &&
        RtpTransceiverDirectionHasRecv(remote_direction)) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "Incompatible send direction");
    }
  }
  return RTCError::OK();
}

// Checks that no answer m-section introduces a=sframe that was not present in
// the corresponding offer m-section.
RTCError VerifySframeInAnswer(const SessionDescription* local_offer,
                              const SessionDescription* remote_answer) {
  RTC_DCHECK(local_offer);
  RTC_DCHECK(remote_answer);

  const ContentInfos& offer_contents = local_offer->contents();
  const ContentInfos& answer_contents = remote_answer->contents();

  for (size_t i = 0; i < answer_contents.size() && i < offer_contents.size();
       ++i) {
    if (answer_contents[i].rejected) {
      continue;
    }
    const MediaContentDescription* answer_media =
        answer_contents[i].media_description();
    const MediaContentDescription* offer_media =
        offer_contents[i].media_description();
    if (answer_media->sframe_enabled() && !offer_media->sframe_enabled()) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << kSframeNotInOffer);
    }
  }
  return RTCError::OK();
}

// Checks that each non-rejected content has a DTLS
// fingerprint, unless it's in a BUNDLE group, in which case only the
// BUNDLE-tag section (first media section/description in the BUNDLE group)
// needs a ufrag and pwd. Mismatches, such as replying with a DTLS fingerprint
// to SDES keys, will be caught in JsepTransport negotiation, and backstopped
// by Channel's `srtp_required` check.
RTCError VerifyCrypto(
    const SessionDescription* desc,
    bool dtls_enabled,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  for (const ContentInfo& content_info : desc->contents()) {
    if (content_info.rejected) {
      continue;
    }
    const auto mid = content_info.mid();
    auto it = bundle_groups_by_mid.find(mid);
    const ContentGroup* bundle =
        it != bundle_groups_by_mid.end() ? it->second : nullptr;
    if (bundle && mid != *(bundle->FirstContentName())) {
      // This isn't the first media section in the BUNDLE group, so it's not
      // required to have crypto attributes, since only the crypto attributes
      // from the first section actually get used.
      continue;
    }

    // If the content isn't rejected or bundled into another m= section, crypto
    // must be present.
    const MediaContentDescription* media = content_info.media_description();
    const TransportInfo* tinfo = desc->GetTransportInfoByName(mid);
    if (!media || !tinfo) {
      // Something is not right.
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << kInvalidSdp);
    }
    if (dtls_enabled) {
      if (!tinfo->description.identity_fingerprint) {
        return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << kSdpWithoutDtlsFingerprint);
      }
    } else {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << kSdpWithoutCrypto);
    }
  }
  return RTCError::OK();
}

// Checks that each non-rejected content has ice-ufrag and ice-pwd set, unless
// it's in a BUNDLE group, in which case only the BUNDLE-tag section (first
// media section/description in the BUNDLE group) needs a ufrag and pwd.
bool VerifyIceUfragPwdPresent(
    const SessionDescription* desc,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  for (const ContentInfo& content_info : desc->contents()) {
    if (content_info.rejected) {
      continue;
    }
    const auto mid = content_info.mid();
    auto it = bundle_groups_by_mid.find(mid);
    const ContentGroup* bundle =
        it != bundle_groups_by_mid.end() ? it->second : nullptr;
    if (bundle && mid != *(bundle->FirstContentName())) {
      // This isn't the first media section in the BUNDLE group, so it's not
      // required to have ufrag/password, since only the ufrag/password from
      // the first section actually get used.
      continue;
    }

    // If the content isn't rejected or bundled into another m= section,
    // ice-ufrag and ice-pwd must be present.
    const TransportInfo* tinfo = desc->GetTransportInfoByName(mid);
    if (!tinfo) {
      // Something is not right.
      RTC_LOG(LS_ERROR) << kInvalidSdp;
      return false;
    }
    if (tinfo->description.ice_ufrag.empty() ||
        tinfo->description.ice_pwd.empty()) {
      RTC_LOG(LS_ERROR) << "Session description must have ice ufrag and pwd.";
      return false;
    }
  }
  return true;
}

RTCError ValidateMids(const SessionDescription& description) {
  flat_set<absl::string_view> mids;
  for (const ContentInfo& content : description.contents()) {
    if (content.mid().empty()) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "A media section is missing a MID attribute.");
    }
    if (content.mid().size() > kMidMaxSize) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "The MID attribute exceeds the maximum supported "
                          "length of 16 characters.");
    }
    if (!mids.insert(content.mid()).second) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "Duplicate a=mid value '" << content.mid() << "'.");
    }
  }
  return RTCError::OK();
}

RTCError FindDuplicateCodecParameters(
    const RtpCodecParameters codec_parameters,
    std::map<int, RtpCodecParameters>& payload_to_codec_parameters) {
  auto existing_codec_parameters =
      payload_to_codec_parameters.find(codec_parameters.payload_type);
  if (existing_codec_parameters != payload_to_codec_parameters.end() &&
      codec_parameters != existing_codec_parameters->second) {
    StringBuilder sb;
    sb << "A BUNDLE group contains a codec collision between "
       << absl::StrCat(codec_parameters) << " and "
       << absl::StrCat(existing_codec_parameters->second)
       << ". All codecs must share the same type, "
          "encoding name, clock rate and parameters.";

    return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER) << sb.Release());
  }
  payload_to_codec_parameters.insert(
      std::make_pair(codec_parameters.payload_type, codec_parameters));
  return RTCError::OK();
}

RTCError ValidateBundledPayloadTypes(const SessionDescription& description) {
  // https://www.rfc-editor.org/rfc/rfc8843#name-payload-type-pt-value-reuse
  // ... all codecs associated with the payload type number MUST share an
  // identical codec configuration. This means that the codecs MUST share
  // the same media type, encoding name, clock rate, and any parameter
  // that can affect the codec configuration and packetization.
  std::vector<const ContentGroup*> bundle_groups =
      description.GetGroupsByName(GROUP_TYPE_BUNDLE);
  for (const ContentGroup* bundle_group : bundle_groups) {
    std::map<int, RtpCodecParameters> payload_to_codec_parameters;
    for (const std::string& content_name : bundle_group->content_names()) {
      const ContentInfo* content_description =
          description.GetContentByName(content_name);
      if (!content_description) {
        return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "A BUNDLE group contains a MID='" << content_name
                         << "' matching no m= section.");
      }
      const MediaContentDescription* media_description =
          content_description->media_description();
      RTC_DCHECK(media_description);
      if (content_description->rejected || !media_description ||
          !media_description->has_codecs()) {
        continue;
      }
      const auto type = media_description->type();
      if (type == MediaType::AUDIO || type == MediaType::VIDEO) {
        for (const auto& c : media_description->codecs()) {
          auto error = FindDuplicateCodecParameters(
              c.ToCodecParameters(), payload_to_codec_parameters);
          if (!error.ok()) {
            return error;
          }
        }
      }
    }
  }
  return RTCError::OK();
}

RTCError FindDuplicateHeaderExtensionIds(
    const RtpExtension extension,
    std::map<int, RtpExtension>& id_to_extension) {
  auto existing_extension = id_to_extension.find(extension.id);
  if (existing_extension != id_to_extension.end() &&
      !(extension.uri == existing_extension->second.uri &&
        extension.encrypt == existing_extension->second.encrypt)) {
    return LOG_ERROR(
        RTCError(RTCErrorType::INVALID_PARAMETER)
        << "A BUNDLE group contains a codec collision for "
           "header extension id="
        << extension.id
        << ". The id must be the same across all bundled media descriptions");
  }
  id_to_extension.insert(std::make_pair(extension.id, extension));
  return RTCError::OK();
}

RTCError ValidateBundledRtpHeaderExtensions(
    const SessionDescription& description) {
  // https://www.rfc-editor.org/rfc/rfc8843#name-rtp-header-extensions-consi
  // ... the identifier used for a given extension MUST identify the same
  // extension across all the bundled media descriptions.
  std::vector<const ContentGroup*> bundle_groups =
      description.GetGroupsByName(GROUP_TYPE_BUNDLE);
  for (const ContentGroup* bundle_group : bundle_groups) {
    std::map<int, RtpExtension> id_to_extension;
    for (const std::string& content_name : bundle_group->content_names()) {
      const ContentInfo* content_description =
          description.GetContentByName(content_name);
      if (!content_description) {
        return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "A BUNDLE group contains a MID='" << content_name
                         << "' matching no m= section.");
      }
      const MediaContentDescription* media_description =
          content_description->media_description();
      RTC_DCHECK(media_description);
      if (content_description->rejected || !media_description ||
          !media_description->has_codecs()) {
        continue;
      }

      for (const auto& extension : media_description->rtp_header_extensions()) {
        auto error =
            FindDuplicateHeaderExtensionIds(extension, id_to_extension);
        if (!error.ok()) {
          return error;
        }
      }
    }
  }
  return RTCError::OK();
}

RTCError ValidateRtpHeaderExtensionsForSpecSimulcast(
    const SessionDescription& description) {
  for (const ContentInfo& content : description.contents()) {
    if (content.type != MediaProtocolType::kRtp || content.rejected) {
      continue;
    }
    const auto media_description = content.media_description();
    if (!media_description->HasSimulcast()) {
      continue;
    }
    auto extensions = media_description->rtp_header_extensions();
    auto it = absl::c_find_if(extensions, [](const RtpExtension& ext) {
      return ext.uri == RtpExtension::kRidUri;
    });
    if (it == extensions.end()) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "The media section with MID='" << content.mid()
                       << "' negotiates simulcast but does not negotiate "
                          "the RID RTP header extension.");
    }
  }
  return RTCError::OK();
}

RTCError ValidateSsrcGroups(const SessionDescription& description) {
  for (const ContentInfo& content : description.contents()) {
    if (content.type != MediaProtocolType::kRtp) {
      continue;
    }
    for (const StreamParams& stream : content.media_description()->streams()) {
      for (const SsrcGroup& group : stream.ssrc_groups) {
        // Validate the number of SSRCs for standard SSRC group semantics such
        // as FID and FEC-FR and the non-standard SIM group.
        if ((group.semantics == kFidSsrcGroupSemantics &&
             group.ssrcs.size() != 2) ||
            (group.semantics == kFecFrSsrcGroupSemantics &&
             group.ssrcs.size() != 2) ||
            (group.semantics == kSimSsrcGroupSemantics &&
             group.ssrcs.size() > kMaxSimulcastStreams)) {
          return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                           << "The media section with MID='" << content.mid()
                           << "' has a ssrc-group with semantics "
                           << group.semantics
                           << " and an unexpected number of SSRCs.");
        }
      }
    }
  }
  return RTCError::OK();
}

RTCError ValidatePayloadTypes(const SessionDescription& description) {
  for (const ContentInfo& content : description.contents()) {
    if (content.type != MediaProtocolType::kRtp) {
      continue;
    }
    const auto media_description = content.media_description();
    RTC_DCHECK(media_description);
    if (content.rejected || !media_description ||
        !media_description->has_codecs()) {
      continue;
    }
    const auto type = media_description->type();
    if (type == MediaType::AUDIO || type == MediaType::VIDEO) {
      for (const auto& codec : media_description->codecs()) {
        if (!PayloadType::IsValid(codec.id, media_description->rtcp_mux())) {
          return LOG_ERROR(
              RTCError(RTCErrorType::INVALID_PARAMETER)
              << "The media section with MID='" << content.mid()
              << "' used an invalid payload type " << codec.id << " for codec '"
              << codec.name << ", rtcp-mux:"
              << (media_description->rtcp_mux() ? "enabled" : "disabled"));
        }
      }
    }
  }
  return RTCError::OK();
}

bool IsValidOfferToReceiveMedia(int value) {
  typedef PeerConnectionInterface::RTCOfferAnswerOptions Options;
  return (value >= Options::kUndefined) &&
         (value <= Options::kMaxOfferToReceiveMedia);
}

bool ValidateOfferAnswerOptions(
    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) {
  return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) &&
         IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video);
}

// This method will extract any send encodings that were sent by the remote
// connection. This is currently only relevant for Simulcast scenario (where
// the number of layers may be communicated by the server).
std::vector<RtpEncodingParameters> GetSendEncodingsFromRemoteDescription(
    const MediaContentDescription& desc) {
  if (!desc.HasSimulcast()) {
    return {};
  }
  std::vector<RtpEncodingParameters> result;
  const SimulcastDescription& simulcast = desc.simulcast_description();

  // This is a remote description, the parameters we are after should appear
  // as receive streams.
  for (const auto& alternatives : simulcast.receive_layers()) {
    if (result.size() >= kMaxSimulcastStreams) {
      RTC_LOG(LS_WARNING)
          << "Excessive simulcast layers in remote description. Clamping to "
          << kMaxSimulcastStreams;
      break;
    }
    RTC_DCHECK(!alternatives.empty());
    // There is currently no way to specify or choose from alternatives.
    // We will always use the first alternative, which is the most preferred.
    const SimulcastLayer& layer = alternatives[0];
    RtpEncodingParameters parameters;
    parameters.rid = layer.rid;
    parameters.active = !layer.is_paused;
    // If a payload type has been specified for this rid, set the codec
    // corresponding to that payload type.
    auto rid_desc = std::find_if(
        desc.receive_rids().begin(), desc.receive_rids().end(),
        [&layer](const RidDescription& rid) { return rid.rid == layer.rid; });
    if (rid_desc != desc.receive_rids().end() && !rid_desc->codecs.empty()) {
      parameters.codec = rid_desc->codecs[0].ToCodecParameters();
    }
    result.push_back(parameters);
  }

  return result;
}

RTCError UpdateSimulcastLayerStatusInSender(
    const std::vector<SimulcastLayer>& layers,
    scoped_refptr<RtpSenderInternal> sender) {
  RTC_DCHECK(sender);
  RTC_LOG_THREAD_BLOCK_COUNT();
  // In practice simulcast is supported only for video. However, this can
  // currently get called for non-video senders in tests.
  // See the `external/wpt/webrtc/simulcast/negotiation-encodings.https.html`
  // test in chromium.
  RtpParameters parameters = sender->GetParametersInternalWithAllLayers();
  std::vector<std::string> disabled_layers;
  bool changed = false;

  // The simulcast envelope cannot be changed, only the status of the streams.
  // So we will iterate over the send encodings rather than the layers.
  for (RtpEncodingParameters& encoding : parameters.encodings) {
    auto iter = std::find_if(layers.begin(), layers.end(),
                             [&encoding](const SimulcastLayer& layer) {
                               return layer.rid == encoding.rid;
                             });
    // A layer that cannot be found may have been removed by the remote party.
    if (iter == layers.end()) {
      disabled_layers.push_back(encoding.rid);
      continue;
    }

    if (encoding.active != !iter->is_paused) {
      encoding.active = !iter->is_paused;
      changed = true;
    }
  }

  RTCError result = RTCError::OK();
  if (changed) {
    result = sender->SetParametersInternalWithAllLayers(parameters);
    if (!result.ok()) {
      return result;
    }
  }

  if (!disabled_layers.empty()) {
    bool need_disable = false;
    for (const auto& rid : disabled_layers) {
      auto iter = std::find_if(
          parameters.encodings.begin(), parameters.encodings.end(),
          [&rid](const RtpEncodingParameters& enc) { return enc.rid == rid; });
      if (iter != parameters.encodings.end() && iter->active) {
        need_disable = true;
        break;
      }
    }

    if (need_disable) {
      result = sender->DisableEncodingLayers(disabled_layers);
    }
  }

  return result;
}

bool SimulcastIsRejected(const ContentInfo* local_content,
                         const MediaContentDescription& answer_media_desc,
                         bool enable_encrypted_rtp_header_extensions) {
  bool simulcast_offered = local_content &&
                           local_content->media_description() &&
                           local_content->media_description()->HasSimulcast();
  bool simulcast_answered = answer_media_desc.HasSimulcast();
  bool rids_supported = RtpExtension::FindHeaderExtensionByUri(
      answer_media_desc.rtp_header_extensions(), RtpExtension::kRidUri,
      enable_encrypted_rtp_header_extensions
          ? RtpExtension::Filter::kPreferEncryptedExtension
          : RtpExtension::Filter::kDiscardEncryptedExtension);
  return simulcast_offered && (!simulcast_answered || !rids_supported);
}

bool GetInitialSimulcastLayersAndRejectionStatus(
    ContentSource source,
    const MediaContentDescription& media_desc,
    const ContentInfo* old_local_content,
    bool enable_encrypted_rtp_header_extensions,
    std::vector<SimulcastLayer>& initial_simulcast_layers) {
  if (media_desc.HasSimulcast()) {
    initial_simulcast_layers =
        source == CS_LOCAL
            ? media_desc.simulcast_description().send_layers().GetAllLayers()
            : media_desc.simulcast_description()
                  .receive_layers()
                  .GetAllLayers();
  }

  return SimulcastIsRejected(old_local_content, media_desc,
                             enable_encrypted_rtp_header_extensions);
}

RTCError DisableSimulcastInSender(scoped_refptr<RtpSenderInternal> sender) {
  RTC_DCHECK(sender);
  RTC_LOG_THREAD_BLOCK_COUNT();
  RtpParameters parameters = sender->GetParametersInternalWithAllLayers();
  if (parameters.encodings.size() <= 1) {
    return RTCError::OK();
  }

  std::vector<std::string> disabled_layers;
  for (size_t i = 1; i < parameters.encodings.size(); ++i) {
    if (parameters.encodings[i].active) {
      disabled_layers.push_back(parameters.encodings[i].rid);
    }
  }

  if (!disabled_layers.empty()) {
    return sender->DisableEncodingLayers(disabled_layers);
  }
  return RTCError::OK();
}

// The SDP parser used to populate these values by default for the 'content
// name' if an a=mid line was absent.
PLAN_B_ONLY std::string GetDefaultMidForPlanB(MediaType media_type) {
  switch (media_type) {
    case MediaType::AUDIO:
      return CN_AUDIO;
    case MediaType::VIDEO:
      return CN_VIDEO;
    case MediaType::DATA:
      return CN_DATA;
    case MediaType::UNSUPPORTED:
      return "not supported";
    default:
      // Fall through to RTC_CHECK_NOTREACHED
      break;
  }
  RTC_DCHECK_NOTREACHED();
  return "";
}

// Add options to |[audio/video]_media_description_options| from `senders`.
PLAN_B_ONLY void AddPlanBRtpSenderOptions(
    const std::vector<
        scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
    MediaDescriptionOptions* audio_media_description_options,
    MediaDescriptionOptions* video_media_description_options,
    int num_sim_layers) {
  for (const auto& sender : senders) {
    if (sender->media_type() == MediaType::AUDIO) {
      if (audio_media_description_options) {
        audio_media_description_options->AddAudioSender(
            sender->id(), sender->internal()->stream_ids());
      }
    } else {
      RTC_DCHECK(sender->media_type() == MediaType::VIDEO);
      if (video_media_description_options) {
        video_media_description_options->AddVideoSender(
            sender->id(), sender->internal()->stream_ids(), {},
            SimulcastLayerList(), num_sim_layers);
      }
    }
  }
}

MediaDescriptionOptions GetMediaDescriptionOptionsForTransceiver(
    RtpTransceiver* transceiver,
    absl::string_view mid,
    bool is_create_offer) {
  // NOTE: a stopping transceiver should be treated as a stopped one in
  // createOffer as specified in
  // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
  bool stopped =
      is_create_offer ? transceiver->stopping() : transceiver->stopped();
  MediaDescriptionOptions media_description_options(
      transceiver->media_type(), mid, transceiver->direction(), stopped);
  media_description_options.codec_preferences =
      transceiver->filtered_codec_preferences();
  media_description_options.header_extensions =
      transceiver->GetHeaderExtensionsToNegotiate();
  media_description_options.sframe_enabled =
      transceiver->SframeEnabled().value_or(false);
  // This behavior is specified in JSEP. The gist is that:
  // 1. The MSID is included if the RtpTransceiver's direction is sendonly or
  //    sendrecv.
  // 2. If the MSID is included, then it must be included in any subsequent
  //    offer/answer exactly the same until the RtpTransceiver is stopped.
  if (stopped || (!RtpTransceiverDirectionHasSend(transceiver->direction()) &&
                  !transceiver->has_ever_been_used_to_send())) {
    return media_description_options;
  }

  SenderOptions sender_options;
  sender_options.track_id = transceiver->sender()->id();
  sender_options.stream_ids = transceiver->sender()->stream_ids();

  // The following sets up RIDs and Simulcast.
  // RIDs are included if Simulcast is requested or if any RID was specified.
  RtpParameters send_parameters =
      transceiver->sender_internal()->GetParametersInternalWithAllLayers();
  bool has_rids = std::any_of(send_parameters.encodings.begin(),
                              send_parameters.encodings.end(),
                              [](const RtpEncodingParameters& encoding) {
                                return !encoding.rid.empty();
                              });

  std::vector<RidDescription> send_rids;
  SimulcastLayerList send_layers;
  for (const RtpEncodingParameters& encoding : send_parameters.encodings) {
    if (encoding.rid.empty()) {
      continue;
    }
    auto send_rid = RidDescription(encoding.rid, RidDirection::kSend);
    if (encoding.codec) {
      auto send_codecs = transceiver->sender_internal()->GetSendCodecs();
      for (const Codec& codec : send_codecs) {
        if (IsSameRtpCodecIgnoringLevel(codec, *encoding.codec)) {
          send_rid.codecs.push_back(codec);
          break;
        }
      }
    }
    send_rids.push_back(send_rid);
    send_layers.AddLayer(SimulcastLayer(encoding.rid, !encoding.active));
  }

  if (has_rids) {
    sender_options.rids = send_rids;
  }

  sender_options.simulcast_layers = send_layers;
  // When RIDs are configured, we must set num_sim_layers to 0 to.
  // Otherwise, num_sim_layers must be 1 because either there is no
  // simulcast, or simulcast is acheived by munging the SDP.
  sender_options.num_sim_layers = has_rids ? 0 : 1;
  media_description_options.sender_options.push_back(sender_options);

  return media_description_options;
}

// Returns the ContentInfo at mline index `i`, or null if none exists.
const ContentInfo* GetContentByIndex(const SessionDescriptionInterface* sdesc,
                                     size_t i) {
  if (!sdesc) {
    return nullptr;
  }
  const ContentInfos& contents = sdesc->description()->contents();
  return (i < contents.size() ? &contents[i] : nullptr);
}

// From `rtc_options`, fill parts of `session_options` shared by all generated
// m= sectionss (in other words, nothing that involves a map/array).
void ExtractSharedMediaSessionOptions(
    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
    MediaSessionOptions* session_options) {
  session_options->vad_enabled = rtc_options.voice_activity_detection;
  session_options->bundle_enabled = rtc_options.use_rtp_mux;
  session_options->raw_packetization_for_video =
      rtc_options.raw_packetization_for_video;
}

// Generate a RTCP CNAME when a PeerConnection is created.
std::string GenerateRtcpCname() {
  std::string cname;
  if (!CreateRandomString(kRtcpCnameLength, &cname)) {
    RTC_LOG(LS_ERROR) << "Failed to generate CNAME.";
    RTC_DCHECK_NOTREACHED();
  }
  return cname;
}

// Check if we can send `new_stream` on a PeerConnection.
bool CanAddLocalMediaStream(StreamCollectionInterface* current_streams,
                            MediaStreamInterface* new_stream) {
  if (!new_stream || !current_streams) {
    return false;
  }
  if (current_streams->find(new_stream->id()) != nullptr) {
    RTC_LOG(LS_ERROR) << "MediaStream with ID " << new_stream->id()
                      << " is already added.";
    return false;
  }
  return true;
}

bool ContentHasHeaderExtension(const ContentInfo& content_info,
                               absl::string_view header_extension_uri) {
  for (const RtpExtension& rtp_header_extension :
       content_info.media_description()->rtp_header_extensions()) {
    if (rtp_header_extension.uri == header_extension_uri) {
      return true;
    }
  }
  return false;
}

// Matches the given host with the wildcard host. A wildcard `*` matches the
// current character and anything that comes after it. For instance, 127.0.*
// matches any string that starts with `127.0.`. For IPv6 addresses, the
// wildcard host should also start with `[` and end with `]` or a wildcard.
bool WildcardHostPrefixMatch(absl::string_view host,
                             absl::string_view wildcard_host) {
  for (size_t i = 0; i < std::min(host.size(), wildcard_host.size()); ++i) {
    if (host[i] == wildcard_host[i]) {
      continue;
    }
    return wildcard_host[i] == '*';
  }
  return host.size() == wildcard_host.size();
}

}  // namespace

void UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
    const SessionDescription* description,
    TransceiverList* transceivers) {
  // This integrates the RTP Header Extension Control API and local SDP munging
  // for backward compability reasons. If something was enabled in the local
  // description via SDP munging, consider it non-stopped in the API as well
  // so that is shows up in subsequent offers/answers.
  RTC_DCHECK(description);
  RTC_DCHECK(transceivers);
  for (const auto& content : description->contents()) {
    auto transceiver = transceivers->FindByMid(content.mid());
    if (!transceiver) {
      continue;
    }
    auto extension_capabilities = transceiver->GetHeaderExtensionsToNegotiate();
    // Set the capability of every extension we see here to "sendrecv".
    for (auto& ext : content.media_description()->rtp_header_extensions()) {
      auto it = absl::c_find_if(extension_capabilities,
                                [&ext](const RtpHeaderExtensionCapability c) {
                                  return ext.uri == c.uri;
                                });
      if (it != extension_capabilities.end()) {
        it->direction = RtpTransceiverDirection::kSendRecv;
      }
    }
    transceiver->SetHeaderExtensionsToNegotiate(extension_capabilities);
  }
}


// This class stores state related to a SetRemoteDescription operation, captures
// and reports potential errors that might occur and makes sure to notify the
// observer of the operation and the operations chain of completion.
class SdpOfferAnswerHandler::RemoteDescriptionOperation {
 public:
  RemoteDescriptionOperation(
      SdpOfferAnswerHandler* handler,
      std::unique_ptr<SessionDescriptionInterface> desc,
      scoped_refptr<SetRemoteDescriptionObserverInterface> observer,
      std::function<void()> operations_chain_callback)
      : handler_(handler),
        desc_(std::move(desc)),
        observer_(std::move(observer)),
        operations_chain_callback_(std::move(operations_chain_callback)),
        type_(desc_->GetType()),
        unified_plan_(handler_->IsUnifiedPlan()) {}

  ~RemoteDescriptionOperation() {
    RTC_DCHECK_RUN_ON(handler_->signaling_thread());
    SignalCompletion();
    operations_chain_callback_();
  }

  bool ok() const { return error_.ok(); }

  // Notifies the observer that the operation is complete and releases the
  // reference to the observer.
  void SignalCompletion() {
    if (!observer_)
      return;

    if (!error_.ok() && type_ != static_cast<SdpType>(-1)) {
      std::string error_message =
          GetSetDescriptionErrorMessage(CS_REMOTE, type_, error_);
      RTC_LOG(LS_ERROR) << error_message;
      error_.set_message(error_message);
    }

    observer_->OnSetRemoteDescriptionComplete(error_);
    observer_ = nullptr;  // Only fire the notification once.
  }

  // If a session error has occurred the PeerConnection is in a possibly
  // inconsistent state so fail right away.
  bool HaveSessionError() {
    RTC_DCHECK(ok());
    if (handler_->session_error() != SessionError::kNone)
      InternalError(handler_->GetSessionErrorMsg());
    return !ok();
  }

  // Returns true if the operation was a rollback operation. If this function
  // returns true, the caller should consider the operation complete. Otherwise
  // proceed to the next step.
  bool MaybeRollback() {
    RTC_DCHECK_RUN_ON(handler_->signaling_thread());
    RTC_DCHECK(ok());
    if (type_ != SdpType::kRollback) {
      // Check if we can do an implicit rollback.
      if (type_ == SdpType::kOffer && unified_plan_ &&
          handler_->pc_->configuration()->enable_implicit_rollback &&
          handler_->signaling_state() ==
              PeerConnectionInterface::kHaveLocalOffer) {
        handler_->Rollback(type_);
      }
      return false;
    }

    if (unified_plan_) {
      error_ = handler_->Rollback(type_);
    } else if (type_ == SdpType::kRollback) {
      Unsupported("Rollback not supported in Plan B");
    }

    return true;
  }

  // Report to UMA the format of the received offer or answer.
  void ReportOfferAnswerUma() {
    RTC_DCHECK(ok());
    if (type_ == SdpType::kOffer || type_ == SdpType::kAnswer) {
      handler_->pc_->ReportSdpBundleUsage(*desc_);
    }
  }

  // Checks if the session description for the operation is valid. If not, the
  // function captures error information and returns false. Note that if the
  // return value is false, the operation should be considered done.
  bool IsDescriptionValid() {
    RTC_DCHECK_RUN_ON(handler_->signaling_thread());
    RTC_DCHECK(ok());
    RTC_DCHECK(bundle_groups_by_mid_.empty()) << "Already called?";
    bundle_groups_by_mid_ = GetBundleGroupsByMid(description());
    error_ = handler_->ValidateSessionDescription(desc_.get(), CS_REMOTE,
                                                  bundle_groups_by_mid_);
    return ok();
  }

  // Transfers ownership of the session description object over to `handler_`.
  bool ReplaceRemoteDescriptionAndCheckError() {
    RTC_DCHECK_RUN_ON(handler_->signaling_thread());
    RTC_DCHECK(ok());
    RTC_DCHECK(desc_);
    RTC_DCHECK(!replaced_remote_description_);
#if RTC_DCHECK_IS_ON
    const auto* existing_remote_description = handler_->remote_description();
#endif

    error_ = handler_->ReplaceRemoteDescription(std::move(desc_), type_,
                                                &replaced_remote_description_);

    if (ok()) {
#if RTC_DCHECK_IS_ON
      // Sanity check that our `old_remote_description()` method always returns
      // the same value as `remote_description()` did before the call to
      // ReplaceRemoteDescription.
      RTC_DCHECK_EQ(existing_remote_description, old_remote_description());
#endif
    } else {
      SetAsSessionError();
    }

    return ok();
  }

  bool UpdateChannels() {
    RTC_DCHECK(ok());
    RTC_DCHECK(!desc_) << "ReplaceRemoteDescription hasn't been called";

    const auto* remote_description = handler_->remote_description();

    const SessionDescription* session_desc = remote_description->description();

    // Transport and Media channels will be created only when offer is set.
    if (unified_plan_) {
      error_ = handler_->UpdateTransceiversAndDataChannels(
          CS_REMOTE, *remote_description, handler_->local_description(),
          old_remote_description(), bundle_groups_by_mid_);
    } else {
      // Media channels will be created only when offer is set. These may use
      // new transports just created by PushdownTransportDescription.
      if (type_ == SdpType::kOffer) {
        // TODO(mallinath) - Handle CreateChannel failure, as new local
        // description is applied. Restore back to old description.
        RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
        error_ = handler_->CreateChannels(*session_desc);
        RTC_ALLOW_PLAN_B_DEPRECATION_END();
      }
      // Remove unused channels if MediaContentDescription is rejected.
      handler_->RemoveUnusedChannels(session_desc);
    }

    return ok();
  }

  bool UpdateSessionState() {
    RTC_DCHECK(ok());
    error_ = handler_->UpdateSessionState(
        type_, CS_REMOTE, handler_->remote_description()->description(),
        bundle_groups_by_mid_);
    if (!ok())
      SetAsSessionError();
    return ok();
  }

  bool UseCandidatesInRemoteDescription() {
    RTC_DCHECK(ok());
    if (handler_->local_description() &&
        !handler_->UseCandidatesInRemoteDescription()) {
      InvalidParam(kInvalidCandidates);
    }
    return ok();
  }

  // Convenience getter for desc_->GetType().
  SdpType type() const { return type_; }
  bool unified_plan() const { return unified_plan_; }
  SessionDescription* description() { return desc_->description(); }

  const SessionDescriptionInterface* old_remote_description() const {
    RTC_DCHECK(!desc_) << "Called before replacing the remote description";
    if (type_ == SdpType::kAnswer)
      return replaced_remote_description_.get();
    return replaced_remote_description_
               ? replaced_remote_description_.get()
               : handler_->current_remote_description();
  }

  // Returns a reference to a cached map of bundle groups ordered by mid.
  // Note that this will only be valid after a successful call to
  // `IsDescriptionValid`.
  const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid()
      const {
    RTC_DCHECK(ok());
    return bundle_groups_by_mid_;
  }

 private:
  // Convenience methods for populating the embedded `error_` object.
  void Unsupported(absl::string_view message) {
    SetError(RTCErrorType::UNSUPPORTED_OPERATION, message);
  }

  void InvalidParam(absl::string_view message) {
    SetError(RTCErrorType::INVALID_PARAMETER, message);
  }

  void InternalError(absl::string_view message) {
    SetError(RTCErrorType::INTERNAL_ERROR, message);
  }

  void SetError(RTCErrorType type, absl::string_view message) {
    RTC_DCHECK(ok()) << "Overwriting an existing error?";
    error_ = RTCError(type, message);
  }

  // Called when the PeerConnection could be in an inconsistent state and we set
  // the session error so that future calls to
  // SetLocalDescription/SetRemoteDescription fail.
  void SetAsSessionError() {
    RTC_DCHECK(!ok());
    handler_->SetSessionError(SessionError::kContent, error_.message());
  }

  SdpOfferAnswerHandler* const handler_;
  std::unique_ptr<SessionDescriptionInterface> desc_;
  // Keeps the replaced session description object alive while the operation
  // is taking place since methods that depend on `old_remote_description()`
  // for updating the state, need it.
  std::unique_ptr<SessionDescriptionInterface> replaced_remote_description_;
  scoped_refptr<SetRemoteDescriptionObserverInterface> observer_;
  std::function<void()> operations_chain_callback_;
  RTCError error_ = RTCError::OK();
  flat_map<std::string, const ContentGroup*> bundle_groups_by_mid_;
  const SdpType type_;
  const bool unified_plan_;
};
// Used by parameterless SetLocalDescription() to create an offer or answer.
// Upon completion of creating the session description, SetLocalDescription() is
// invoked with the result.
class SdpOfferAnswerHandler::ImplicitCreateSessionDescriptionObserver
    : public CreateSessionDescriptionObserver {
 public:
  ImplicitCreateSessionDescriptionObserver(
      WeakPtr<SdpOfferAnswerHandler> sdp_handler,
      scoped_refptr<SetLocalDescriptionObserverInterface>
          set_local_description_observer)
      : sdp_handler_(std::move(sdp_handler)),
        set_local_description_observer_(
            std::move(set_local_description_observer)) {}
  ~ImplicitCreateSessionDescriptionObserver() override {
    RTC_DCHECK(was_called_);
  }

  void SetOperationCompleteCallback(
      std::function<void()> operation_complete_callback) {
    operation_complete_callback_ = std::move(operation_complete_callback);
  }

  bool was_called() const { return was_called_; }

  void OnSuccess(SessionDescriptionInterface* desc_ptr) override {
    RTC_DCHECK(!was_called_);
    std::unique_ptr<SessionDescriptionInterface> desc(desc_ptr);
    was_called_ = true;

    // Abort early if `pc_` is no longer valid.
    if (!sdp_handler_) {
      operation_complete_callback_();
      return;
    }
    // DoSetLocalDescription() is a synchronous operation that invokes
    // `set_local_description_observer_` with the result.
    sdp_handler_->DoSetLocalDescription(
        std::move(desc), std::move(set_local_description_observer_));
    operation_complete_callback_();
  }

  void OnFailure(RTCError error) override {
    RTC_DCHECK(!was_called_);
    was_called_ = true;
    set_local_description_observer_->OnSetLocalDescriptionComplete(RTCError(
        error.type(), std::string("SetLocalDescription failed to create "
                                  "session description - ") +
                          error.message()));
    operation_complete_callback_();
  }

 private:
  bool was_called_ = false;
  WeakPtr<SdpOfferAnswerHandler> sdp_handler_;
  scoped_refptr<SetLocalDescriptionObserverInterface>
      set_local_description_observer_;
  std::function<void()> operation_complete_callback_;
};

// Wraps a CreateSessionDescriptionObserver and an OperationsChain operation
// complete callback. When the observer is invoked, the wrapped observer is
// invoked followed by invoking the completion callback.
class CreateSessionDescriptionObserverOperationWrapper
    : public CreateSessionDescriptionObserver {
 public:
  CreateSessionDescriptionObserverOperationWrapper(
      scoped_refptr<CreateSessionDescriptionObserver> observer,
      std::function<void()> operation_complete_callback)
      : observer_(std::move(observer)),
        operation_complete_callback_(std::move(operation_complete_callback)) {
    RTC_DCHECK(observer_);
  }
  ~CreateSessionDescriptionObserverOperationWrapper() override {
#if RTC_DCHECK_IS_ON
    RTC_DCHECK(was_called_);
#endif
  }

  void OnSuccess(SessionDescriptionInterface* desc) override {
#if RTC_DCHECK_IS_ON
    RTC_DCHECK(!was_called_);
    was_called_ = true;
#endif  // RTC_DCHECK_IS_ON
    // Completing the operation before invoking the observer allows the observer
    // to execute SetLocalDescription() without delay.
    operation_complete_callback_();
    desc->RelinquishThreadOwnership();
    observer_->OnSuccess(desc);
  }

  void OnFailure(RTCError error) override {
#if RTC_DCHECK_IS_ON
    RTC_DCHECK(!was_called_);
    was_called_ = true;
#endif  // RTC_DCHECK_IS_ON
    operation_complete_callback_();
    observer_->OnFailure(std::move(error));
  }

 private:
#if RTC_DCHECK_IS_ON
  bool was_called_ = false;
#endif  // RTC_DCHECK_IS_ON
  scoped_refptr<CreateSessionDescriptionObserver> observer_;
  std::function<void()> operation_complete_callback_;
};

// Wraps a session description observer so a Clone of the last created
// offer/answer can be stored.
// This object can be used only once.
class CreateDescriptionObserverWrapperWithCreationCallback
    : public CreateSessionDescriptionObserver {
 public:
  CreateDescriptionObserverWrapperWithCreationCallback(
      absl::AnyInvocable<void(std::unique_ptr<SessionDescriptionInterface>) &&>
          callback,
      scoped_refptr<CreateSessionDescriptionObserver> observer)
      : callback_(std::move(callback)), observer_(observer) {
    RTC_DCHECK(observer_);
  }
  void OnSuccess(SessionDescriptionInterface* desc) override {
    // `OnSuccess` will be called on the signaling thread. That's what we want
    // for SdpOfferAnswerHandler since that's where public methods are called.
    // The wrapped observer might have different threading needs. Calling the
    // `Clone()` method will require accessing internal state of `desc` and
    // therefore will (at least semantically) attach its state to the current
    // thread. So, since we need to call Clone(), keep `desc` as the copy that
    // SdpOfferAnswerHandler will own, and give a pristine clone to the
    // observer.
    auto clone = desc->Clone();
    std::move(callback_)(absl::WrapUnique(desc));
    observer_->OnSuccess(clone.release());
  }
  void OnFailure(RTCError error) override {
    std::move(callback_)(nullptr);
    observer_->OnFailure(std::move(error));
  }

 private:
  absl::AnyInvocable<void(std::unique_ptr<SessionDescriptionInterface>) &&>
      callback_;
  scoped_refptr<CreateSessionDescriptionObserver> observer_;
};

// Wrapper for SetSessionDescriptionObserver that invokes the success or failure
// callback in a posted message handled by the peer connection. This introduces
// a delay that prevents recursive API calls by the observer, but this also
// means that the PeerConnection can be modified before the observer sees the
// result of the operation. This is ill-advised for synchronizing states.
//
// Implements both the SetLocalDescriptionObserverInterface and the
// SetRemoteDescriptionObserverInterface.
class SdpOfferAnswerHandler::SetSessionDescriptionObserverAdapter
    : public SetLocalDescriptionObserverInterface,
      public SetRemoteDescriptionObserverInterface {
 public:
  SetSessionDescriptionObserverAdapter(
      WeakPtr<SdpOfferAnswerHandler> handler,
      scoped_refptr<SetSessionDescriptionObserver> inner_observer)
      : handler_(std::move(handler)),
        inner_observer_(std::move(inner_observer)) {}

  // SetLocalDescriptionObserverInterface implementation.
  void OnSetLocalDescriptionComplete(RTCError error) override {
    OnSetDescriptionComplete(std::move(error));
  }
  // SetRemoteDescriptionObserverInterface implementation.
  void OnSetRemoteDescriptionComplete(RTCError error) override {
    OnSetDescriptionComplete(std::move(error));
  }

 private:
  void OnSetDescriptionComplete(RTCError error) {
    if (!handler_)
      return;
    if (error.ok()) {
      handler_->pc_->message_handler()->PostSetSessionDescriptionSuccess(
          inner_observer_.get());
    } else {
      handler_->pc_->message_handler()->PostSetSessionDescriptionFailure(
          inner_observer_.get(), std::move(error));
    }
  }

  WeakPtr<SdpOfferAnswerHandler> handler_;
  scoped_refptr<SetSessionDescriptionObserver> inner_observer_;
};

class SdpOfferAnswerHandler::LocalIceCredentialsToReplace {
 public:
  // Sets the ICE credentials that need restarting to the ICE credentials of
  // the current and pending descriptions.
  void SetIceCredentialsFromLocalDescriptions(
      const SessionDescriptionInterface* current_local_description,
      const SessionDescriptionInterface* pending_local_description) {
    ice_credentials_.clear();
    if (current_local_description) {
      AppendIceCredentialsFromSessionDescription(*current_local_description);
    }
    if (pending_local_description) {
      AppendIceCredentialsFromSessionDescription(*pending_local_description);
    }
  }

  void ClearIceCredentials() { ice_credentials_.clear(); }

  // Returns true if we have ICE credentials that need restarting.
  bool HasIceCredentials() const { return !ice_credentials_.empty(); }

  // Returns true if `local_description` shares no ICE credentials with the
  // ICE credentials that need restarting.
  bool SatisfiesIceRestart(
      const SessionDescriptionInterface& local_description) const {
    for (const auto& transport_info :
         local_description.description()->transport_infos()) {
      if (ice_credentials_.find(std::make_pair(
              transport_info.description.ice_ufrag,
              transport_info.description.ice_pwd)) != ice_credentials_.end()) {
        return false;
      }
    }
    return true;
  }

 private:
  void AppendIceCredentialsFromSessionDescription(
      const SessionDescriptionInterface& desc) {
    for (const auto& transport_info : desc.description()->transport_infos()) {
      ice_credentials_.insert(
          std::make_pair(transport_info.description.ice_ufrag,
                         transport_info.description.ice_pwd));
    }
  }

  std::set<std::pair<std::string, std::string>> ice_credentials_;
};

SdpOfferAnswerHandler::SdpOfferAnswerHandler(const Environment& env,
                                             PeerConnectionSdpMethods* pc,
                                             ConnectionContext* context)
    : env_(env),
      pc_(pc),
      context_(context),
      local_streams_(StreamCollection::Create()),
      remote_streams_(StreamCollection::Create()),
      operations_chain_(OperationsChain::Create()),
      rtcp_cname_(GenerateRtcpCname()),
      local_ice_credentials_to_replace_(new LocalIceCredentialsToReplace()),
      pt_suggester_(pc_->configuration()->bundle_policy),
      weak_ptr_factory_(this) {
  operations_chain_->SetOnChainEmptyCallback(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr()]() {
        if (!this_weak_ptr)
          return;
        this_weak_ptr->OnOperationsChainEmpty();
      });
}

SdpOfferAnswerHandler::~SdpOfferAnswerHandler() {}

// Static
std::unique_ptr<SdpOfferAnswerHandler> SdpOfferAnswerHandler::Create(
    const Environment& env,
    PeerConnectionSdpMethods* pc,
    const PeerConnectionInterface::RTCConfiguration& configuration,
    std::unique_ptr<RTCCertificateGeneratorInterface> cert_generator,
    std::unique_ptr<VideoBitrateAllocatorFactory>
        video_bitrate_allocator_factory,
    ConnectionContext* context,
    CodecLookupHelper* codec_lookup_helper) {
  auto handler = absl::WrapUnique(new SdpOfferAnswerHandler(env, pc, context));
  handler->Initialize(configuration, std::move(cert_generator),
                      std::move(video_bitrate_allocator_factory), context,
                      codec_lookup_helper);
  return handler;
}

void SdpOfferAnswerHandler::Initialize(
    const PeerConnectionInterface::RTCConfiguration& configuration,
    std::unique_ptr<RTCCertificateGeneratorInterface> cert_generator,
    std::unique_ptr<VideoBitrateAllocatorFactory>
        video_bitrate_allocator_factory,
    ConnectionContext* context,
    CodecLookupHelper* codec_lookup_helper) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  // 100 kbps is used by default, but can be overriden by a non-standard
  // RTCConfiguration value (not available on Web).
  video_options_.screencast_min_bitrate_kbps =
      configuration.screencast_min_bitrate.value_or(100);

  audio_options_.audio_jitter_buffer_max_packets =
      configuration.audio_jitter_buffer_max_packets;

  audio_options_.audio_jitter_buffer_fast_accelerate =
      configuration.audio_jitter_buffer_fast_accelerate;

  audio_options_.audio_jitter_buffer_min_delay_ms =
      configuration.audio_jitter_buffer_min_delay_ms;

  // Obtain a certificate from RTCConfiguration if any were provided (optional).
  scoped_refptr<RTCCertificate> certificate;
  if (!configuration.certificates.empty()) {
    // TODO(hbos,torbjorng): Decide on certificate-selection strategy instead of
    // just picking the first one. The decision should be made based on the DTLS
    // handshake. The DTLS negotiations need to know about all certificates.
    certificate = configuration.certificates[0];
  }

  webrtc_session_desc_factory_ =
      std::make_unique<WebRtcSessionDescriptionFactory>(
          context, this, pc_->session_id(), pc_->dtls_enabled(),
          std::move(cert_generator), std::move(certificate),
          [this](const scoped_refptr<RTCCertificate>& certificate) {
            RTC_DCHECK_RUN_ON(signaling_thread());
            transport_controller_s()->SetLocalCertificate(certificate);
          },
          codec_lookup_helper, pc_->env());

  if (pc_->options()->disable_encryption) {
    RTC_LOG(LS_INFO)
        << "Disabling encryption. This should only be done in tests.";
    webrtc_session_desc_factory_->SetInsecureForTesting();
  }

  webrtc_session_desc_factory_->set_enable_encrypted_rtp_header_extensions(
      pc_->GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions);
  webrtc_session_desc_factory_->set_is_unified_plan(IsUnifiedPlan());

  video_bitrate_allocator_factory_ = std::move(video_bitrate_allocator_factory);
  if (!video_bitrate_allocator_factory_) {
    video_bitrate_allocator_factory_ =
        CreateBuiltinVideoBitrateAllocatorFactory();
  }
  codec_lookup_helper_ = codec_lookup_helper;

  max_sctp_streams_ = configuration.max_sctp_streams;
}

// ==================================================================
// Access to pc_ variables
const MediaEngineInterface* SdpOfferAnswerHandler::media_engine() const {
  RTC_DCHECK(context_);
  return context_->media_engine();
}

TransceiverList* SdpOfferAnswerHandler::transceivers() {
  if (!pc_->rtp_manager()) {
    return nullptr;
  }
  return pc_->rtp_manager()->transceivers();
}

const TransceiverList* SdpOfferAnswerHandler::transceivers() const {
  if (!pc_->rtp_manager()) {
    return nullptr;
  }
  return pc_->rtp_manager()->transceivers();
}
JsepTransportController* SdpOfferAnswerHandler::transport_controller_s() {
  return pc_->transport_controller_s();
}
JsepTransportController* SdpOfferAnswerHandler::transport_controller_n() {
  return pc_->transport_controller_n();
}
const JsepTransportController* SdpOfferAnswerHandler::transport_controller_s()
    const {
  return pc_->transport_controller_s();
}
const JsepTransportController* SdpOfferAnswerHandler::transport_controller_n()
    const {
  return pc_->transport_controller_n();
}
DataChannelController* SdpOfferAnswerHandler::data_channel_controller() {
  return pc_->data_channel_controller();
}
const DataChannelController* SdpOfferAnswerHandler::data_channel_controller()
    const {
  return pc_->data_channel_controller();
}
PortAllocator* SdpOfferAnswerHandler::port_allocator() {
  return pc_->port_allocator();
}
const PortAllocator* SdpOfferAnswerHandler::port_allocator() const {
  return pc_->port_allocator();
}
RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() {
  return pc_->rtp_manager();
}
const RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() const {
  return pc_->rtp_manager();
}

// ===================================================================

void SdpOfferAnswerHandler::PrepareForShutdown() {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  weak_ptr_factory_.InvalidateWeakPtrs();
}

void SdpOfferAnswerHandler::UpdateCachedIceCredentials(
    std::vector<IceParameters> credentials) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  cached_pooled_ice_credentials_ = std::move(credentials);
}

void SdpOfferAnswerHandler::Close() {
  RTC_LOG_THREAD_BLOCK_COUNT();
  ChangeSignalingState(PeerConnectionInterface::kClosed);
}

void SdpOfferAnswerHandler::RestartIce() {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  local_ice_credentials_to_replace_->SetIceCredentialsFromLocalDescriptions(
      current_local_description(), pending_local_description());
  UpdateNegotiationNeeded();
}

Thread* SdpOfferAnswerHandler::signaling_thread() const {
  return context_->signaling_thread();
}

Thread* SdpOfferAnswerHandler::network_thread() const {
  return context_->network_thread();
}

void SdpOfferAnswerHandler::CreateOffer(
    CreateSessionDescriptionObserver* observer,
    const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
       observer_refptr =
           scoped_refptr<CreateSessionDescriptionObserver>(observer),
       options](std::function<void()> operations_chain_callback) {
        // Abort early if `this_weak_ptr` is no longer valid.
        if (!this_weak_ptr) {
          observer_refptr->OnFailure(
              RTCError(RTCErrorType::INTERNAL_ERROR,
                       "CreateOffer failed because the session was shut down"));
          operations_chain_callback();
          return;
        }
        // The operation completes asynchronously when the wrapper is invoked.
        auto observer_wrapper =
            make_ref_counted<CreateSessionDescriptionObserverOperationWrapper>(
                std::move(observer_refptr),
                std::move(operations_chain_callback));
        this_weak_ptr->DoCreateOffer(options, observer_wrapper);
      });
}

void SdpOfferAnswerHandler::SetLocalDescription(
    scoped_refptr<SetSessionDescriptionObserver> observer,
    std::unique_ptr<SessionDescriptionInterface> desc) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(desc);
  RTC_DCHECK(observer);
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
       observer_refptr = observer, desc = std::move(desc)](
          std::function<void()> operations_chain_callback) mutable {
        // Abort early if `this_weak_ptr` is no longer valid.
        if (!this_weak_ptr) {
          // For consistency with SetSessionDescriptionObserverAdapter whose
          // posted messages doesn't get processed when the PC is destroyed, we
          // do not inform `observer_refptr` that the operation failed.
          operations_chain_callback();
          return;
        }
        // SetSessionDescriptionObserverAdapter takes care of making sure the
        // `observer_refptr` is invoked in a posted message.
        this_weak_ptr->DoSetLocalDescription(
            std::move(desc),
            make_ref_counted<SetSessionDescriptionObserverAdapter>(
                this_weak_ptr, observer_refptr));
        // For backwards-compatability reasons, we declare the operation as
        // completed here (rather than in a post), so that the operation chain
        // is not blocked by this operation when the observer is invoked. This
        // allows the observer to trigger subsequent offer/answer operations
        // synchronously if the operation chain is now empty.
        operations_chain_callback();
      });
}

void SdpOfferAnswerHandler::SetLocalDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(desc);
  RTC_DCHECK(observer);
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), observer,
       desc = std::move(desc)](
          std::function<void()> operations_chain_callback) mutable {
        // Abort early if `this_weak_ptr` is no longer valid.
        if (!this_weak_ptr) {
          observer->OnSetLocalDescriptionComplete(RTCError(
              RTCErrorType::INTERNAL_ERROR,
              "SetLocalDescription failed because the session was shut down"));
          operations_chain_callback();
          return;
        }
        this_weak_ptr->DoSetLocalDescription(std::move(desc), observer);
        // DoSetLocalDescription() is implemented as a synchronous operation.
        // The `observer` will already have been informed that it completed, and
        // we can mark this operation as complete without any loose ends.
        operations_chain_callback();
      });
}

void SdpOfferAnswerHandler::SetLocalDescription(
    scoped_refptr<SetSessionDescriptionObserver> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  scoped_refptr<SetLocalDescriptionObserverInterface> adapter =
      make_ref_counted<SetSessionDescriptionObserverAdapter>(
          weak_ptr_factory_.GetWeakPtr(), std::move(observer));
  SetLocalDescription(std::move(adapter));
}

void SdpOfferAnswerHandler::SetLocalDescription(
    scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(observer);
  // The `create_sdp_observer` handles performing DoSetLocalDescription() with
  // the resulting description as well as completing the operation.
  auto create_sdp_observer =
      make_ref_counted<ImplicitCreateSessionDescriptionObserver>(
          weak_ptr_factory_.GetWeakPtr(), observer);
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
       create_sdp_observer](std::function<void()> operations_chain_callback) {
        // The `create_sdp_observer` is responsible for completing the
        // operation.
        create_sdp_observer->SetOperationCompleteCallback(
            std::move(operations_chain_callback));
        // Abort early if `this_weak_ptr` is no longer valid. This triggers the
        // same code path as if DoCreateOffer() or DoCreateAnswer() failed.
        if (!this_weak_ptr) {
          create_sdp_observer->OnFailure(RTCError(
              RTCErrorType::INTERNAL_ERROR,
              "SetLocalDescription failed because the session was shut down"));
          return;
        }
        switch (this_weak_ptr->signaling_state()) {
          case PeerConnectionInterface::kStable:
          case PeerConnectionInterface::kHaveLocalOffer:
          case PeerConnectionInterface::kHaveRemotePrAnswer:
            // TODO(hbos): If [LastCreatedOffer] exists and still represents the
            // current state of the system, use that instead of creating another
            // offer.
            this_weak_ptr->DoCreateOffer(
                PeerConnectionInterface::RTCOfferAnswerOptions(),
                create_sdp_observer);
            break;
          case PeerConnectionInterface::kHaveLocalPrAnswer:
          case PeerConnectionInterface::kHaveRemoteOffer:
            // TODO(hbos): If [LastCreatedAnswer] exists and still represents
            // the current state of the system, use that instead of creating
            // another answer.
            this_weak_ptr->DoCreateAnswer(
                PeerConnectionInterface::RTCOfferAnswerOptions(),
                create_sdp_observer);
            break;
          case PeerConnectionInterface::kClosed:
            create_sdp_observer->OnFailure(RTCError(
                RTCErrorType::INVALID_STATE,
                "SetLocalDescription called when PeerConnection is closed."));
            break;
        }
      });
}

RTCError SdpOfferAnswerHandler::ApplyLocalDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::ApplyLocalDescription");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(desc);

  // Invalidate the stats caches to make sure that they get
  // updated the next time getStats() gets called, as updating the session
  // description affects the stats.
  pc_->ClearStatsCache();

  // Take a reference to the old local description since it's used below to
  // compare against the new local description. When setting the new local
  // description, grab ownership of the replaced session description in case it
  // is the same as `old_local_description`, to keep it alive for the duration
  // of the method.
  const SessionDescriptionInterface* old_local_description =
      local_description();
  std::unique_ptr<SessionDescriptionInterface> replaced_local_description;
  SdpType type = desc->GetType();
  if (type == SdpType::kAnswer) {
    replaced_local_description = pending_local_description_
                                     ? std::move(pending_local_description_)
                                     : std::move(current_local_description_);
    current_local_description_ = std::move(desc);
    pending_local_description_ = nullptr;
    current_remote_description_ = std::move(pending_remote_description_);
  } else {
    replaced_local_description = std::move(pending_local_description_);
    pending_local_description_ = std::move(desc);
  }
  if (!initial_offerer_) {
    initial_offerer_.emplace(type == SdpType::kOffer);
  }
  // The session description to apply now must be accessed by
  // `local_description()`.
  RTC_DCHECK(local_description());

  if (!is_caller_) {
    if (remote_description()) {
      // Remote description was applied first, so this PC is the callee.
      is_caller_ = false;
    } else {
      // Local description is applied first, so this PC is the caller.
      is_caller_ = true;
    }
  }

  RTCError error = PushdownTransportDescription(CS_LOCAL, type);
  if (!error.ok()) {
    return error;
  }

  if (IsUnifiedPlan()) {
    error = UpdateTransceiversAndDataChannels(
        CS_LOCAL, *local_description(), old_local_description,
        remote_description(), bundle_groups_by_mid);
    if (!error.ok()) {
      RTC_LOG(LS_ERROR) << error.message() << " (" << type << ")";
      return error;
    }
    if (ConfiguredForMedia()) {
      std::vector<scoped_refptr<RtpTransceiverInterface>> remove_list;
      std::vector<scoped_refptr<MediaStreamInterface>> removed_streams;
      flat_map<std::string, DtlsTransportAndName> dtls_transports_by_mid =
          GetDtlsTransports(*transceivers(), context_->network_thread(),
                            transport_controller_s());

      for (const auto& transceiver_ext : transceivers()->List()) {
        auto transceiver = transceiver_ext->internal();
        if (transceiver->stopped()) {
          continue;
        }

        // 2.2.7.1.1.(6-9): Set sender and receiver's transport slots.
        // Note that code paths that don't set MID won't be able to use
        // information about DTLS transports.
        if (transceiver->mid()) {
          auto it = dtls_transports_by_mid.find(*transceiver->mid());
          RTC_DCHECK(it != dtls_transports_by_mid.end());
          transceiver->SetTransport(it->second.transport,
                                    it->second.transport_name);
        }

        const ContentInfo* content =
            FindMediaSectionForTransceiver(transceiver, local_description());
        if (!content) {
          continue;
        }
        const MediaContentDescription* media_desc =
            content->media_description();
        // 2.2.7.1.6: If description is of type "answer" or "pranswer", then run
        // the following steps:
        if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
          // 2.2.7.1.6.1: If direction is "sendonly" or "inactive", and
          // transceiver's [[FiredDirection]] slot is either "sendrecv" or
          // "recvonly", process the removal of a remote track for the media
          // description, given transceiver, removeList, and muteTracks.
          if (!RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
              (transceiver->fired_direction() &&
               RtpTransceiverDirectionHasRecv(
                   *transceiver->fired_direction()))) {
            ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
                                        &removed_streams);
          }
          // 2.2.7.1.6.2: Set transceiver's [[CurrentDirection]] and
          // [[FiredDirection]] slots to direction.
          transceiver->set_current_direction(media_desc->direction());
          transceiver->set_fired_direction(media_desc->direction());
        }
        transceiver->set_receptive(
            RtpTransceiverDirectionHasRecv(media_desc->direction()));
        transceiver->ApplySframeEnabled(media_desc->sframe_enabled());
      }
      pc_->RunWithObserver([&](auto observer) {
        for (const auto& transceiver : remove_list) {
          observer->OnRemoveTrack(transceiver->receiver());
        }
        for (const auto& stream : removed_streams) {
          observer->OnRemoveStream(stream);
        }
      });
    }
  } else {
    // Media channels will be created only when offer is set. These may use new
    // transports just created by PushdownTransportDescription.
    if (type == SdpType::kOffer) {
      // TODO(bugs.webrtc.org/4676) - Handle CreateChannel failure, as new local
      // description is applied. Restore back to old description.
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      error = CreateChannels(*local_description()->description());
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
      if (!error.ok()) {
        RTC_LOG(LS_ERROR) << error.message() << " (" << type << ")";
        return error;
      }
    }
    // Plan B transport synchronization.
    flat_map<std::string, DtlsTransportAndName> dtls_transports_by_mid =
        GetDtlsTransports(*transceivers(), context_->network_thread(),
                          transport_controller_s());
    for (const auto& transceiver_ext : transceivers()->List()) {
      auto transceiver = transceiver_ext->internal();
      if (transceiver->mid()) {
        auto it = dtls_transports_by_mid.find(*transceiver->mid());
        if (it != dtls_transports_by_mid.end()) {
          transceiver->SetTransport(it->second.transport,
                                    it->second.transport_name);
        }
      }
    }
    // Remove unused channels if MediaContentDescription is rejected.
    RemoveUnusedChannels(local_description()->description());
  }

  error = UpdateSessionState(type, CS_LOCAL, local_description()->description(),
                             bundle_groups_by_mid);
  if (!error.ok()) {
    RTC_LOG(LS_ERROR) << error.message() << " (" << type << ")";
    return error;
  }

  // Now that we have a local description, we can push down remote candidates.
  UseCandidatesInRemoteDescription();

  pending_ice_restarts_.clear();
  if (session_error() != SessionError::kNone) {
    return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                     << GetSessionErrorMsg());
  }

  // Validate SSRCs, we do not allow duplicates.
  if (ConfiguredForMedia()) {
    std::set<uint32_t> used_ssrcs;
    for (const auto& content : local_description()->description()->contents()) {
      for (const auto& stream : content.media_description()->streams()) {
        for (uint32_t ssrc : stream.ssrcs) {
          auto result = used_ssrcs.insert(ssrc);
          if (!result.second) {
            return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                             << "Duplicate ssrc " << ssrc << " is not allowed");
          }
        }
      }
    }
  }

  if (IsUnifiedPlan()) {
    UpdateSenderSsrcsFromLocalDescription();
  } else {
    // Plan B semantics.

    // Update state and SSRC of local MediaStreams and DataChannels based on the
    // local session description.
    const ContentInfo* audio_content =
        GetFirstAudioContent(local_description()->description());
    if (audio_content) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      if (audio_content->rejected) {
        RemoveSenders(MediaType::AUDIO);
      } else {
        const MediaContentDescription* audio_desc =
            audio_content->media_description();
        UpdateLocalSendersPlanB(audio_desc->streams(), audio_desc->type());
      }
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
    }

    const ContentInfo* video_content =
        GetFirstVideoContent(local_description()->description());
    if (video_content) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      if (video_content->rejected) {
        RemoveSenders(MediaType::VIDEO);
      } else {
        const MediaContentDescription* video_desc =
            video_content->media_description();
        UpdateLocalSendersPlanB(video_desc->streams(), video_desc->type());
      }
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
    }
  }

  // This function does nothing with data content.

  if (type == SdpType::kAnswer &&
      local_ice_credentials_to_replace_->SatisfiesIceRestart(
          *current_local_description_)) {
    local_ice_credentials_to_replace_->ClearIceCredentials();
  }

  if (IsUnifiedPlan()) {
    UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
        local_description()->description(), transceivers());
  }

  return RTCError::OK();
}

void SdpOfferAnswerHandler::UpdateSenderSsrcsFromLocalDescription() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());
  if (!ConfiguredForMedia()) {
    return;
  }
  ScopedOperationsBatcher worker_tasks(context_->worker_thread());
  // We must use List and not ListInternal here because
  // transceivers()->StableState() is indexed by the non-internal refptr.
  for (const auto& transceiver_ext : transceivers()->List()) {
    RtpTransceiver* transceiver = transceiver_ext->internal();
    if (transceiver->stopped()) {
      continue;
    }
    const ContentInfo* content =
        FindMediaSectionForTransceiver(transceiver, local_description());
    if (content == nullptr) {
      continue;
    }
    scoped_refptr<RtpSenderInternal> sender = transceiver->sender_internal();
    if (content->rejected || !transceiver->HasChannel() ||
        transceiver->channel_local_streams().empty()) {
      // 0 is a special value meaning "this sender has no associated send
      // stream". Need to call this so the sender won't attempt to configure
      // a no longer existing stream and run into DCHECKs in the lower
      // layers.
      worker_tasks.AddWithFinalizer(sender->SetSsrcTask(0));
    } else {
      const std::vector<StreamParams>& streams =
          transceiver->channel_local_streams();
      sender->set_stream_ids(streams[0].stream_ids());
      std::vector<RtpEncodingParameters> encodings =
          sender->init_send_encodings();
      worker_tasks.AddWithFinalizer(
          sender->SetSsrcTask(streams[0].first_ssrc()));
      if (!encodings.empty()) {
        transceivers()
            ->StableState(transceiver_ext)
            ->SetInitSendEncodings(std::move(encodings));
      }
    }
  }
}

void SdpOfferAnswerHandler::SetRemoteDescription(
    scoped_refptr<SetSessionDescriptionObserver> observer,
    std::unique_ptr<SessionDescriptionInterface> desc) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(observer);
  RTC_DCHECK(desc);
  SetRemoteDescription(
      std::move(desc),
      make_ref_counted<SetSessionDescriptionObserverAdapter>(
          weak_ptr_factory_.GetWeakPtr(), std::move(observer)));
}

void SdpOfferAnswerHandler::SetRemoteDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    scoped_refptr<SetRemoteDescriptionObserverInterface> observer) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(desc);
  RTC_DCHECK(observer);
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(), observer,
       desc = std::move(desc)](
          std::function<void()> operations_chain_callback) mutable {
        // Abort early if `this_weak_ptr` is no longer valid.
        if (!this_weak_ptr) {
          observer->OnSetRemoteDescriptionComplete(RTCError(
              RTCErrorType::INTERNAL_ERROR,
              "SetRemoteDescription failed because the session was shut down"));
          operations_chain_callback();
          return;
        }

        this_weak_ptr->DoSetRemoteDescription(
            std::make_unique<RemoteDescriptionOperation>(
                this_weak_ptr.get(), std::move(desc), std::move(observer),
                std::move(operations_chain_callback)));
      });
}

RTCError SdpOfferAnswerHandler::ReplaceRemoteDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    SdpType sdp_type,
    std::unique_ptr<SessionDescriptionInterface>* replaced_description) {
  RTC_DCHECK(replaced_description);
  if (sdp_type == SdpType::kAnswer) {
    *replaced_description = pending_remote_description_
                                ? std::move(pending_remote_description_)
                                : std::move(current_remote_description_);
    current_remote_description_ = std::move(desc);
    pending_remote_description_ = nullptr;
    current_local_description_ = std::move(pending_local_description_);
  } else {
    *replaced_description = std::move(pending_remote_description_);
    pending_remote_description_ = std::move(desc);
  }

  // The session description to apply now must be accessed by
  // `remote_description()`.
  const SessionDescription* session_desc = remote_description()->description();

  const auto* local = local_description();

  // NOTE: This will perform a BlockingCall() to the network thread.
  return transport_controller_s()->SetRemoteDescription(
      sdp_type, local ? local->description() : nullptr, session_desc);
}

void SdpOfferAnswerHandler::ApplyRemoteDescription(
    std::unique_ptr<RemoteDescriptionOperation> operation) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::ApplyRemoteDescription");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(operation->description());

  // Invalidate the stats caches to make sure that they get
  // updated next time getStats() gets called, as updating the session
  // description affects the stats.
  pc_->ClearStatsCache();

  if (!operation->ReplaceRemoteDescriptionAndCheckError())
    return;

  if (!operation->UpdateChannels())
    return;

  // NOTE: Candidates allocation will be initiated only when
  // SetLocalDescription is called.
  if (!operation->UpdateSessionState())
    return;

  if (!operation->UseCandidatesInRemoteDescription())
    return;

  if (operation->old_remote_description()) {
    for (const ContentInfo& content :
         operation->old_remote_description()->description()->contents()) {
      // Check if this new SessionDescription contains new ICE ufrag and
      // password that indicates the remote peer requests an ICE restart.
      // TODO(deadbeef): When we start storing both the current and pending
      // remote description, this should reset pending_ice_restarts and compare
      // against the current description.
      if (CheckForRemoteIceRestart(operation->old_remote_description(),
                                   remote_description(), content.mid())) {
        if (operation->type() == SdpType::kOffer) {
          pending_ice_restarts_.insert(std::string(content.mid()));
        }
      } else {
        // We retain all received candidates only if ICE is not restarted.
        // When ICE is restarted, all previous candidates belong to an old
        // generation and should not be kept.
        // TODO(deadbeef): This goes against the W3C spec which says the remote
        // description should only contain candidates from the last set remote
        // description plus any candidates added since then. We should remove
        // this once we're sure it won't break anything.
        WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
            operation->old_remote_description(), content.mid(),
            mutable_remote_description());
      }
    }
  }

  if (operation->HaveSessionError())
    return;

  // We have now decided that the operation will be applied.
  pt_suggester_.Update(remote_description()->description(),
                       /* local= */ false, remote_description()->GetType());

  // Set the the ICE connection state to connecting since the connection may
  // become writable with peer reflexive candidates before any remote candidate
  // is signaled.
  // TODO(pthatcher): This is a short-term solution for crbug/446908. A real fix
  // is to have a new signal the indicates a change in checking state from the
  // transport and expose a new checking() member from transport that can be
  // read to determine the current checking state. The existing SignalConnecting
  // actually means "gathering candidates", so cannot be be used here.
  if (remote_description()->GetType() != SdpType::kOffer &&
      remote_description()->number_of_mediasections() > 0u &&
      pc_->ice_connection_state_internal() ==
          PeerConnectionInterface::kIceConnectionNew) {
    pc_->SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking);
  }

  if (operation->unified_plan()) {
    ApplyRemoteDescriptionUpdateTransceiverState(operation->type());
  }
  remote_peer_supports_msid_ =
      remote_description()->description()->msid_signaling() !=
      kMsidSignalingNotUsed;

  if (!operation->unified_plan()) {
    RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
    PlanBUpdateSendersAndReceivers(
        GetFirstAudioContent(remote_description()->description()),
        GetFirstAudioContentDescription(remote_description()->description()),
        GetFirstVideoContent(remote_description()->description()),
        GetFirstVideoContentDescription(remote_description()->description()));
    RTC_ALLOW_PLAN_B_DEPRECATION_END();

    // Plan B transport synchronization.
    flat_map<std::string, DtlsTransportAndName> dtls_transports_by_mid =
        GetDtlsTransports(*transceivers(), context_->network_thread(),
                          transport_controller_s());
    for (const auto& transceiver_ext : transceivers()->List()) {
      auto transceiver = transceiver_ext->internal();
      if (transceiver->mid()) {
        auto it = dtls_transports_by_mid.find(*transceiver->mid());
        if (it != dtls_transports_by_mid.end()) {
          transceiver->SetTransport(it->second.transport,
                                    it->second.transport_name);
        }
      }
    }
  }

  if (operation->type() == SdpType::kAnswer) {
    if (local_ice_credentials_to_replace_->SatisfiesIceRestart(
            *current_local_description_)) {
      local_ice_credentials_to_replace_->ClearIceCredentials();
    }

    RemoveStoppedTransceivers();
  }

  // Consider the operation complete at this point.
  operation->SignalCompletion();

  SetRemoteDescriptionPostProcess(operation->type() == SdpType::kAnswer);
}

void SdpOfferAnswerHandler::ApplyRemoteDescriptionUpdateTransceiverState(
    SdpType sdp_type) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());
  if (!ConfiguredForMedia()) {
    return;
  }
  std::vector<scoped_refptr<RtpTransceiverInterface>>
      now_receiving_transceivers;
  std::vector<scoped_refptr<RtpTransceiverInterface>> remove_list;
  std::vector<scoped_refptr<MediaStreamInterface>> added_streams;
  std::vector<scoped_refptr<MediaStreamInterface>> removed_streams;
  ScopedOperationsBatcher worker_tasks(context_->worker_thread());
  flat_map<std::string, DtlsTransportAndName> dtls_transports_by_mid =
      GetDtlsTransports(*transceivers(), context_->network_thread(),
                        transport_controller_s());

  for (const auto& transceiver_ext : transceivers()->List()) {
    const auto transceiver = transceiver_ext->internal();
    const ContentInfo* content =
        FindMediaSectionForTransceiver(transceiver, remote_description());
    if (!content) {
      continue;
    }
    const MediaContentDescription* media_desc = content->media_description();
    RtpTransceiverDirection local_direction =
        RtpTransceiverDirectionReversed(media_desc->direction());
    // Remember the previous remote streams if this is a remote offer. This
    // makes it possible to rollback modifications to the streams.
    if (sdp_type == SdpType::kOffer) {
      transceivers()
          ->StableState(transceiver_ext)
          ->SetRemoteStreamIds(transceiver->receiver()->stream_ids());
    }
    // Roughly the same as steps 2.2.8.6 of section 4.4.1.6 "Set the
    // RTCSessionDescription: Set the associated remote streams given
    // transceiver.[[Receiver]], msids, addList, and removeList".
    // https://w3c.github.io/webrtc-pc/#set-the-rtcsessiondescription
    if (RtpTransceiverDirectionHasRecv(local_direction)) {
      std::vector<std::string> stream_ids;
      if (!media_desc->streams().empty()) {
        // The remote description has signaled the stream IDs.
        stream_ids = media_desc->streams()[0].stream_ids();
      }

      RTC_LOG(LS_INFO) << "Processing the MSIDs for MID=" << content->mid()
                       << " (" << GetStreamIdsString(stream_ids) << ").";
      SetAssociatedRemoteStreams(transceiver->receiver_internal(), stream_ids,
                                 &added_streams, &removed_streams);
      // From the WebRTC specification, steps 2.2.8.5/6 of section 4.4.1.6
      // "Set the RTCSessionDescription: If direction is sendrecv or recvonly,
      // and transceiver's current direction is neither sendrecv nor recvonly,
      // process the addition of a remote track for the media description.
      if (!transceiver->fired_direction() ||
          !RtpTransceiverDirectionHasRecv(*transceiver->fired_direction())) {
        RTC_LOG(LS_INFO) << "Processing the addition of a remote track for MID="
                         << content->mid() << ".";
        // Since the transceiver is passed to the user in an
        // OnTrack event, we must use the proxied transceiver.
        now_receiving_transceivers.push_back(transceiver_ext);
      }
    } else {
      transceiver->set_receptive(false);
    }
    // 2.2.8.1.9: If direction is "sendonly" or "inactive", and transceiver's
    // [[FiredDirection]] slot is either "sendrecv" or "recvonly", process the
    // removal of a remote track for the media description, given transceiver,
    // removeList, and muteTracks.
    if (!RtpTransceiverDirectionHasRecv(local_direction) &&
        (transceiver->fired_direction() &&
         RtpTransceiverDirectionHasRecv(*transceiver->fired_direction()))) {
      ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
                                  &removed_streams);
    }
    // 2.2.8.1.10: Set transceiver's [[FiredDirection]] slot to direction.
    if (sdp_type == SdpType::kOffer) {
      // Remember the previous fired direction if this is a remote offer. This
      // makes it possible to rollback modifications to [[FiredDirection]],
      // which is necessary for "ontrack" to fire in or after rollback.
      transceivers()
          ->StableState(transceiver_ext)
          ->SetFiredDirection(transceiver->fired_direction());
    }
    transceiver->set_fired_direction(local_direction);
    // 2.2.8.1.11: If description is of type "answer" or "pranswer", then run
    // the following steps:
    if (sdp_type == SdpType::kPrAnswer || sdp_type == SdpType::kAnswer) {
      // 2.2.8.1.11.1: Set transceiver's [[CurrentDirection]] slot to
      // direction.
      transceiver->set_current_direction(local_direction);
      // 2.2.8.1.11.[3-6]: Set the transport internal slots.
      if (transceiver->mid()) {
        auto it = dtls_transports_by_mid.find(*transceiver->mid());
        RTC_DCHECK(it != dtls_transports_by_mid.end());
        transceiver->SetTransport(it->second.transport,
                                  it->second.transport_name);
      }
    }
    // 2.2.8.1.12: If the media description is rejected, and transceiver is
    // not already stopped, stop the RTCRtpTransceiver transceiver.
    if (content->rejected && !transceiver->stopped()) {
      RTC_LOG(LS_INFO) << "Stopping transceiver for MID=" << content->mid()
                       << " since the media section was rejected.";
      worker_tasks.Add(transceiver->GetStopTransceiverProcedure());
    }
    // If the local offer included Sframe but the remote answer does not,
    // stop the transceiver since Sframe cannot be downgraded.
    if (sdp_type == SdpType::kPrAnswer || sdp_type == SdpType::kAnswer) {
      const ContentInfo* local_content =
          FindMediaSectionForTransceiver(transceiver, local_description());
      if (local_content && !content->rejected &&
          local_content->media_description()->sframe_enabled() &&
          !media_desc->sframe_enabled() && !transceiver->stopped()) {
        RTC_LOG(LS_INFO) << "Stopping transceiver for MID=" << content->mid()
                         << " since the remote answer does not include Sframe.";
        transceiver->ClearChannel();
        worker_tasks.Add(transceiver->GetStopTransceiverProcedure());
      }
    }
    if (!content->rejected && RtpTransceiverDirectionHasRecv(local_direction)) {
      if (!media_desc->streams().empty() &&
          media_desc->streams()[0].has_ssrcs()) {
        uint32_t ssrc = media_desc->streams()[0].first_ssrc();
        worker_tasks.Add(
            transceiver->receiver_internal()->GetSetupForMediaChannel(ssrc));
      } else {
        worker_tasks.Add(transceiver->receiver_internal()
                             ->GetSetupForUnsignaledMediaChannel());
      }
    }
  }

  worker_tasks.Run();

  // Once all processing has finished, fire off callbacks.
  pc_->RunWithObserver([&](auto observer) {
    for (const auto& transceiver : now_receiving_transceivers) {
      pc_->legacy_stats()->AddTrack(transceiver->receiver()->track().get());
      observer->OnTrack(transceiver);
      observer->OnAddTrack(transceiver->receiver(),
                           transceiver->receiver()->streams());
    }
    for (const auto& stream : added_streams) {
      observer->OnAddStream(stream);
    }
    for (const auto& transceiver : remove_list) {
      observer->OnRemoveTrack(transceiver->receiver());
    }
    for (const auto& stream : removed_streams) {
      observer->OnRemoveStream(stream);
    }
  });
}

void SdpOfferAnswerHandler::PlanBUpdateSendersAndReceivers(
    const ContentInfo* audio_content,
    const AudioContentDescription* audio_desc,
    const ContentInfo* video_content,
    const VideoContentDescription* video_desc) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(!IsUnifiedPlan());

  // We wait to signal new streams until we finish processing the description,
  // since only at that point will new streams have all their tracks.
  scoped_refptr<StreamCollection> new_streams(StreamCollection::Create());

  // TODO(steveanton): When removing RTP senders/receivers in response to a
  // rejected media section, there is some cleanup logic that expects the
  // voice/ video channel to still be set. But in this method the voice/video
  // channel would have been destroyed by the SetRemoteDescription caller
  // above so the cleanup that relies on them fails to run. The RemoveSenders
  // calls should be moved to right before the DestroyChannel calls to fix
  // this.

  // Find all audio rtp streams and create corresponding remote AudioTracks
  // and MediaStreams.
  if (audio_content) {
    if (audio_content->rejected) {
      RemoveSenders(MediaType::AUDIO);
    } else {
      bool default_audio_track_needed =
          !remote_peer_supports_msid_ &&
          RtpTransceiverDirectionHasSend(audio_desc->direction());
      UpdateRemoteSendersListPlanB(GetActiveStreams(audio_desc),
                                   default_audio_track_needed,
                                   audio_desc->type(), new_streams.get());
    }
  }

  // Find all video rtp streams and create corresponding remote VideoTracks
  // and MediaStreams.
  if (video_content) {
    if (video_content->rejected) {
      RemoveSenders(MediaType::VIDEO);
    } else {
      bool default_video_track_needed =
          !remote_peer_supports_msid_ &&
          RtpTransceiverDirectionHasSend(video_desc->direction());
      UpdateRemoteSendersListPlanB(GetActiveStreams(video_desc),
                                   default_video_track_needed,
                                   video_desc->type(), new_streams.get());
    }
  }

  // Iterate new_streams and notify the observer about new MediaStreams.
  pc_->RunWithObserver([&](auto observer) {
    for (size_t i = 0; i < new_streams->count(); ++i) {
      MediaStreamInterface* new_stream = new_streams->at(i);
      pc_->legacy_stats()->AddStream(new_stream);
      observer->OnAddStream(scoped_refptr<MediaStreamInterface>(new_stream));
    }
  });

  UpdateEndedRemoteMediaStreams();
}

void SdpOfferAnswerHandler::ReportInitialSdpMunging(bool had_local_description,
                                                    SdpType type) {
  // Report SDP munging of the initial call to setLocalDescription separately.
  if (!had_local_description) {
    switch (type) {
      case SdpType::kOffer:
        RTC_HISTOGRAM_ENUMERATION(
            "WebRTC.PeerConnection.SdpMunging.Offer.Initial",
            last_sdp_munging_type_, SdpMungingType::kMaxValue);
        break;
      case SdpType::kAnswer:
        RTC_HISTOGRAM_ENUMERATION(
            "WebRTC.PeerConnection.SdpMunging.Answer.Initial",
            last_sdp_munging_type_, SdpMungingType::kMaxValue);
        break;
      case SdpType::kPrAnswer:
        RTC_HISTOGRAM_ENUMERATION(
            "WebRTC.PeerConnection.SdpMunging.PrAnswer.Initial",
            last_sdp_munging_type_, SdpMungingType::kMaxValue);
        break;
      case SdpType::kRollback:
        // Rollback does not have SDP so can not be munged.
        break;
    }
  }
}

void SdpOfferAnswerHandler::DoSetLocalDescription(
    std::unique_ptr<SessionDescriptionInterface> desc,
    scoped_refptr<SetLocalDescriptionObserverInterface> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::DoSetLocalDescription");
  RTC_DCHECK(desc);
  RTC_DCHECK(observer);

  // If a session error has occurred the PeerConnection is in a possibly
  // inconsistent state so fail right away.
  if (session_error() != SessionError::kNone) {
    std::string error_message = GetSessionErrorMsg();
    RTC_LOG(LS_ERROR) << "SetLocalDescription: " << error_message;
    observer->OnSetLocalDescriptionComplete(
        RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
    return;
  }

  // For SLD we support only explicit rollback.
  if (desc->GetType() == SdpType::kRollback) {
    if (IsUnifiedPlan()) {
      observer->OnSetLocalDescriptionComplete(Rollback(desc->GetType()));
    } else {
      observer->OnSetLocalDescriptionComplete(
          RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
                   "Rollback not supported in Plan B"));
    }
    return;
  }

  flat_map<std::string, const ContentGroup*> bundle_groups_by_mid =
      GetBundleGroupsByMid(desc->description());
  RTCError error =
      ValidateSessionDescription(desc.get(), CS_LOCAL, bundle_groups_by_mid);
  if (!error.ok()) {
    std::string error_message =
        GetSetDescriptionErrorMessage(CS_LOCAL, desc->GetType(), error);
    RTC_LOG(LS_ERROR) << error_message;
    observer->OnSetLocalDescriptionComplete(
        RTCError(error.type(), std::move(error_message)));
    return;
  }

  SessionDescriptionInterface* last_created_desc =
      desc->GetType() == SdpType::kOffer ? last_created_offer_.get()
                                         : last_created_answer_.get();

  // Determine if SDP munging was done. This is not yet acted upon.
  bool had_local_description = !!local_description();
  SdpMungingType sdp_munging_type =
      DetermineSdpMungingType(desc.get(), last_created_desc);

  if (!disable_sdp_munging_checks_) {
    bool reject_error = false;
    if (HasUfragSdpMunging(desc.get(), last_created_desc)) {
      has_sdp_munged_ufrag_ = true;
      if (pc_->trials().IsEnabled("WebRTC-NoSdpMangleUfrag")) {
        RTC_LOG(LS_ERROR) << "Rejecting SDP because of ufrag modification";
        reject_error = true;
      }
    } else {
      reject_error = !IsSdpMungingAllowed(sdp_munging_type, pc_->trials());
    }
    SdpMungingOutcome outcome = reject_error ? SdpMungingOutcome::kRejected
                                             : SdpMungingOutcome::kAccepted;
    RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.SdpMunging.Outcome",
                              static_cast<int>(outcome),
                              static_cast<int>(SdpMungingOutcome::kMaxValue));
    if (reject_error) {
      observer->OnSetLocalDescriptionComplete(
          RTCError(RTCErrorType::INVALID_MODIFICATION,
                   "SDP is modified in a non-acceptable way"));
      last_sdp_munging_type_ = sdp_munging_type;
      ReportInitialSdpMunging(had_local_description, desc->GetType());
      RTC_HISTOGRAM_ENUMERATION_SPARSE(
          "WebRTC.PeerConnection.SdpMunging.SdpOutcome.Rejected",
          sdp_munging_type, SdpMungingType::kMaxValue);
      return;
    }
    if (sdp_munging_type != kNoModification) {
      RTC_HISTOGRAM_ENUMERATION_SPARSE(
          "WebRTC.PeerConnection.SdpMunging.SdpOutcome.Accepted",
          sdp_munging_type, SdpMungingType::kMaxValue);
    }
  }

  // Workaround for isses.webrtc.org/412904801 - detect if packetization:raw
  // was added in SDP munging, and if so, push it back into the codec vendor.
  std::vector<std::pair<Codec, Codec>> codecs_mangled_to_raw;
  // Ignore if other mungings have been applied.
  if (last_created_desc &&
      last_created_desc->description()->contents().size() ==
          desc->description()->contents().size()) {
    for (size_t i = 0; i < desc->description()->contents().size(); ++i) {
      auto last_created_codecs = last_created_desc->description()
                                     ->contents()[i]
                                     .media_description()
                                     ->codecs();
      auto new_codecs =
          desc->description()->contents()[i].media_description()->codecs();
      if (last_created_codecs.size() != new_codecs.size()) {
        continue;  // Ignore added codecs
      }
      for (size_t j = 0; j < new_codecs.size(); ++j) {
        if (new_codecs[j].type == Codec::Type::kVideo &&
            new_codecs[j].name == last_created_codecs[j].name &&
            new_codecs[j].id == last_created_codecs[j].id &&
            !last_created_codecs[j].packetization &&
            new_codecs[j].packetization == kPacketizationParamRaw) {
          codecs_mangled_to_raw.push_back(
              std::pair(last_created_codecs[j], new_codecs[j]));
        }
      }
    }
  }

  // Grab the description type before moving ownership to
  // ApplyLocalDescription, which may destroy it before returning.
  const SdpType type = desc->GetType();

  error = ApplyLocalDescription(std::move(desc), bundle_groups_by_mid);
  // `desc` may be destroyed at this point.

  if (!error.ok()) {
    // If ApplyLocalDescription fails, the PeerConnection could be in an
    // inconsistent state, so act conservatively here and set the session error
    // so that future calls to SetLocalDescription/SetRemoteDescription fail.
    SetSessionError(SessionError::kContent, error.message());
    std::string error_message =
        GetSetDescriptionErrorMessage(CS_LOCAL, type, error);
    RTC_LOG(LS_ERROR) << error_message;
    observer->OnSetLocalDescriptionComplete(
        RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
    return;
  }
  RTC_DCHECK(local_description());

  // Workaround for issues.webrtc.org/412904801 part 2.
  // If application was successful, we change the codec vendor's codec
  // tables according to the mangle.
  // Note that this depends on there being a single codec vendor for all MIDs.
  codec_lookup_helper_->GetCodecVendor()->ModifyVideoCodecs(
      codecs_mangled_to_raw);

  if (local_description()->GetType() == SdpType::kAnswer) {
    RemoveStoppedTransceivers();

    // TODO(deadbeef): We already had to hop to the network thread for
    // MaybeStartGathering...
    context_->network_thread()->BlockingCall(
        [this] { port_allocator()->DiscardCandidatePool(); });
  }

  // Clear last created offer/answer and update SDP munging type.
  last_created_offer_.reset(nullptr);
  last_created_answer_.reset(nullptr);
  last_sdp_munging_type_ = sdp_munging_type;

  // Report SDP munging of the initial call to setLocalDescription separately.
  ReportInitialSdpMunging(had_local_description,
                          local_description()->GetType());

  observer->OnSetLocalDescriptionComplete(RTCError::OK());
  pc_->NoteUsageEvent(UsageEvent::SET_LOCAL_DESCRIPTION_SUCCEEDED);

  // Check if negotiation is needed. We must do this after informing the
  // observer that SetLocalDescription() has completed to ensure negotiation
  // is not needed prior to the promise resolving.
  if (IsUnifiedPlan()) {
    bool was_negotiation_needed = is_negotiation_needed_;
    UpdateNegotiationNeeded();
    if (signaling_state() == PeerConnectionInterface::kStable &&
        was_negotiation_needed && is_negotiation_needed_) {
      // Legacy version.
      pc_->RunWithObserver(
          [&](auto observer) { observer->OnRenegotiationNeeded(); });
      // Spec-compliant version; the event may get invalidated before firing.
      GenerateNegotiationNeededEvent();
    }
  }

  // MaybeStartGathering needs to be called after informing the observer so
  // that we don't signal any candidates before signaling that
  // SetLocalDescription completed.
  cached_pooled_ice_credentials_ =
      transport_controller_s()->MaybeStartGathering();
}

void SdpOfferAnswerHandler::DoCreateOffer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& options,
    scoped_refptr<CreateSessionDescriptionObserver> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::DoCreateOffer");

  if (!observer) {
    RTC_LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
    return;
  }

  if (pc_->IsClosed()) {
    std::string error = "CreateOffer called when PeerConnection is closed.";
    RTC_LOG(LS_ERROR) << error;
    pc_->message_handler()->PostCreateSessionDescriptionFailure(
        observer.get(),
        RTCError(RTCErrorType::INVALID_STATE, std::move(error)));
    return;
  }

  // If a session error has occurred the PeerConnection is in a possibly
  // inconsistent state so fail right away.
  if (session_error() != SessionError::kNone) {
    std::string error_message = GetSessionErrorMsg();
    RTC_LOG(LS_ERROR) << "CreateOffer: " << error_message;
    pc_->message_handler()->PostCreateSessionDescriptionFailure(
        observer.get(),
        RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
    return;
  }

  if (!ValidateOfferAnswerOptions(options)) {
    std::string error = "CreateOffer called with invalid options.";
    RTC_LOG(LS_ERROR) << error;
    pc_->message_handler()->PostCreateSessionDescriptionFailure(
        observer.get(),
        RTCError(RTCErrorType::INVALID_PARAMETER, std::move(error)));
    return;
  }

  // Legacy handling for offer_to_receive_audio and offer_to_receive_video.
  // Specified in WebRTC section 4.4.3.2 "Legacy configuration extensions".
  if (IsUnifiedPlan()) {
    RTCError error = HandleLegacyOfferOptions(options);
    if (!error.ok()) {
      pc_->message_handler()->PostCreateSessionDescriptionFailure(
          observer.get(), std::move(error));
      return;
    }
  }

  MediaSessionOptions session_options;
  GetOptionsForOffer(options, &session_options);
  auto observer_wrapper =
      make_ref_counted<CreateDescriptionObserverWrapperWithCreationCallback>(
          [this](std::unique_ptr<SessionDescriptionInterface> desc) {
            RTC_DCHECK_RUN_ON(signaling_thread());
            last_created_offer_ = std::move(desc);
          },
          std::move(observer));
  webrtc_session_desc_factory_->CreateOffer(observer_wrapper.get(), options,
                                            session_options);
}

void SdpOfferAnswerHandler::CreateAnswer(
    CreateSessionDescriptionObserver* observer,
    const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::CreateAnswer");
  RTC_DCHECK_RUN_ON(signaling_thread());
  // Chain this operation. If asynchronous operations are pending on the chain,
  // this operation will be queued to be invoked, otherwise the contents of the
  // lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
       observer_refptr =
           scoped_refptr<CreateSessionDescriptionObserver>(observer),
       options](std::function<void()> operations_chain_callback) {
        // Abort early if `this_weak_ptr` is no longer valid.
        if (!this_weak_ptr) {
          observer_refptr->OnFailure(RTCError(
              RTCErrorType::INTERNAL_ERROR,
              "CreateAnswer failed because the session was shut down"));
          operations_chain_callback();
          return;
        }
        // The operation completes asynchronously when the wrapper is invoked.
        auto observer_wrapper =
            make_ref_counted<CreateSessionDescriptionObserverOperationWrapper>(
                std::move(observer_refptr),
                std::move(operations_chain_callback));
        this_weak_ptr->DoCreateAnswer(options, observer_wrapper);
      });
}

void SdpOfferAnswerHandler::DoCreateAnswer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& options,
    scoped_refptr<CreateSessionDescriptionObserver> observer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::DoCreateAnswer");
  if (!observer) {
    RTC_LOG(LS_ERROR) << "CreateAnswer - observer is NULL.";
    return;
  }

  // If a session error has occurred the PeerConnection is in a possibly
  // inconsistent state so fail right away.
  if (session_error() != SessionError::kNone) {
    std::string error_message = GetSessionErrorMsg();
    RTC_LOG(LS_ERROR) << "CreateAnswer: " << error_message;
    pc_->message_handler()->PostCreateSessionDescriptionFailure(
        observer.get(),
        RTCError(RTCErrorType::INTERNAL_ERROR, std::move(error_message)));
    return;
  }

  if (!(signaling_state_ == PeerConnectionInterface::kHaveRemoteOffer ||
        signaling_state_ == PeerConnectionInterface::kHaveLocalPrAnswer)) {
    std::string error =
        "PeerConnection cannot create an answer in a state other than "
        "have-remote-offer or have-local-pranswer.";
    RTC_LOG(LS_ERROR) << error;
    pc_->message_handler()->PostCreateSessionDescriptionFailure(
        observer.get(),
        RTCError(RTCErrorType::INVALID_STATE, std::move(error)));
    return;
  }

  // The remote description should be set if we're in the right state.
  RTC_DCHECK(remote_description());

  if (IsUnifiedPlan()) {
    if (options.offer_to_receive_audio !=
        PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
      RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_audio is not "
                             "supported with Unified Plan semantics. Use the "
                             "RtpTransceiver API instead.";
    }
    if (options.offer_to_receive_video !=
        PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
      RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_video is not "
                             "supported with Unified Plan semantics. Use the "
                             "RtpTransceiver API instead.";
    }
  }

  MediaSessionOptions session_options;
  GetOptionsForAnswer(options, &session_options);
  auto observer_wrapper =
      make_ref_counted<CreateDescriptionObserverWrapperWithCreationCallback>(
          [this](std::unique_ptr<SessionDescriptionInterface> desc) {
            RTC_DCHECK_RUN_ON(signaling_thread());
            last_created_answer_ = std::move(desc);
          },
          std::move(observer));
  webrtc_session_desc_factory_->CreateAnswer(observer_wrapper.get(),
                                             session_options);
}

void SdpOfferAnswerHandler::DoSetRemoteDescription(
    std::unique_ptr<RemoteDescriptionOperation> operation) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::DoSetRemoteDescription");

  if (!operation->ok())
    return;

  if (operation->HaveSessionError())
    return;

  if (operation->MaybeRollback())
    return;

  operation->ReportOfferAnswerUma();

  // Handle remote descriptions missing a=mid lines for interop with legacy
  // end points.
  FillInMissingRemoteMids(operation->description());
  if (!operation->IsDescriptionValid())
    return;

  ApplyRemoteDescription(std::move(operation));
}

// Called after a DoSetRemoteDescription operation completes.
void SdpOfferAnswerHandler::SetRemoteDescriptionPostProcess(bool was_answer) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK(remote_description());

  if (was_answer) {
    // TODO(deadbeef): We already had to hop to the network thread for
    // MaybeStartGathering...
    context_->network_thread()->BlockingCall(
        [this] { port_allocator()->DiscardCandidatePool(); });
  }

  pc_->NoteUsageEvent(UsageEvent::SET_REMOTE_DESCRIPTION_SUCCEEDED);

  // Check if negotiation is needed. We must do this after informing the
  // observer that SetRemoteDescription() has completed to ensure negotiation
  // is not needed prior to the promise resolving.
  if (IsUnifiedPlan()) {
    bool was_negotiation_needed = is_negotiation_needed_;
    UpdateNegotiationNeeded();
    if (signaling_state() == PeerConnectionInterface::kStable &&
        was_negotiation_needed && is_negotiation_needed_) {
      // Legacy version.
      pc_->RunWithObserver(
          [&](auto observer) { observer->OnRenegotiationNeeded(); });
      // Spec-compliant version; the event may get invalidated before firing.
      GenerateNegotiationNeededEvent();
    }
  }
}

void SdpOfferAnswerHandler::SetAssociatedRemoteStreams(
    scoped_refptr<RtpReceiverInternal> receiver,
    const std::vector<std::string>& stream_ids,
    std::vector<scoped_refptr<MediaStreamInterface>>* added_streams,
    std::vector<scoped_refptr<MediaStreamInterface>>* removed_streams) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::vector<scoped_refptr<MediaStreamInterface>> media_streams;
  for (const std::string& stream_id : stream_ids) {
    scoped_refptr<MediaStreamInterface> stream(
        remote_streams_->find(stream_id));
    if (!stream) {
      stream = MediaStreamProxy::Create(Thread::Current(),
                                        MediaStream::Create(stream_id));
      remote_streams_->AddStream(stream);
      added_streams->push_back(stream);
    }
    media_streams.push_back(stream);
  }
  // Special case: "a=msid" missing, use random stream ID.
  if (media_streams.empty() &&
      !(remote_description()->description()->msid_signaling() &
        kMsidSignalingMediaSection)) {
    if (!missing_msid_default_stream_) {
      missing_msid_default_stream_ = MediaStreamProxy::Create(
          Thread::Current(), MediaStream::Create(CreateRandomUuid()));
      added_streams->push_back(missing_msid_default_stream_);
    }
    media_streams.push_back(missing_msid_default_stream_);
  }
  std::vector<scoped_refptr<MediaStreamInterface>> previous_streams =
      receiver->streams();
  // SetStreams() will add/remove the receiver's track to/from the streams.
  // This differs from the spec - the spec uses an "addList" and "removeList"
  // to update the stream-track relationships in a later step. We do this
  // earlier, changing the order of things, but the end-result is the same.
  // TODO(hbos): When we remove remote_streams(), use set_stream_ids()
  // instead. https://crbug.com/webrtc/9480
  receiver->SetStreams(media_streams);
  RemoveRemoteStreamsIfEmpty(previous_streams, removed_streams);
}

bool SdpOfferAnswerHandler::AddIceCandidate(const IceCandidate* ice_candidate) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  const AddIceCandidateResult result = AddIceCandidateInternal(ice_candidate);
  NoteAddIceCandidateResult(result);
  // If the return value is kAddIceCandidateFailNotReady, the candidate has
  // been added, although not 'ready', but that's a success.
  return result == kAddIceCandidateSuccess ||
         result == kAddIceCandidateFailNotReady;
}

AddIceCandidateResult SdpOfferAnswerHandler::AddIceCandidateInternal(
    const IceCandidate* ice_candidate) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::AddIceCandidate");
  if (pc_->IsClosed()) {
    RTC_LOG(LS_ERROR) << "AddIceCandidate: PeerConnection is closed.";
    return kAddIceCandidateFailClosed;
  }

  if (!remote_description()) {
    RTC_LOG(LS_ERROR) << "AddIceCandidate: ICE candidates can't be added "
                         "without any remote session description.";
    return kAddIceCandidateFailNoRemoteDescription;
  }

  if (!ice_candidate) {
    RTC_LOG(LS_ERROR) << "AddIceCandidate: Candidate is null.";
    return kAddIceCandidateFailNullCandidate;
  }

  bool valid = false;
  bool ready = ReadyToUseRemoteCandidate(ice_candidate, nullptr, &valid);
  if (!valid) {
    return kAddIceCandidateFailNotValid;
  }

  // Add this candidate to the remote session description.
  if (!mutable_remote_description()->AddCandidate(ice_candidate)) {
    RTC_LOG(LS_ERROR) << "AddIceCandidate: Candidate cannot be used.";
    return kAddIceCandidateFailInAddition;
  }

  if (!ready) {
    RTC_LOG(LS_INFO) << "AddIceCandidate: Not ready to use candidate.";
    return kAddIceCandidateFailNotReady;
  }

  if (!UseCandidate(ice_candidate)) {
    return kAddIceCandidateFailNotUsable;
  }

  pc_->NoteUsageEvent(UsageEvent::ADD_ICE_CANDIDATE_SUCCEEDED);

  return kAddIceCandidateSuccess;
}

void SdpOfferAnswerHandler::AddIceCandidate(
    std::unique_ptr<IceCandidate> candidate,
    std::function<void(RTCError)> callback) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::AddIceCandidate");
  RTC_DCHECK_RUN_ON(signaling_thread());
  // Chain this operation. If asynchronous operations are pending on the
  // chain, this operation will be queued to be invoked, otherwise the
  // contents of the lambda will execute immediately.
  operations_chain_->ChainOperation(
      [this_weak_ptr = weak_ptr_factory_.GetWeakPtr(),
       candidate = std::move(candidate), callback = std::move(callback)](
          std::function<void()> operations_chain_callback) {
        auto result =
            this_weak_ptr
                ? this_weak_ptr->AddIceCandidateInternal(candidate.get())
                : kAddIceCandidateFailClosed;
        NoteAddIceCandidateResult(result);
        operations_chain_callback();
        switch (result) {
          case AddIceCandidateResult::kAddIceCandidateSuccess:
          case AddIceCandidateResult::kAddIceCandidateFailNotReady:
            // Success!
            callback(RTCError::OK());
            break;
          case AddIceCandidateResult::kAddIceCandidateFailClosed:
            // Note that the spec says to just abort without resolving the
            // promise in this case, but this layer must return an RTCError.
            callback(RTCError(
                RTCErrorType::INVALID_STATE,
                "AddIceCandidate failed because the session was shut down"));
            break;
          case AddIceCandidateResult::kAddIceCandidateFailNoRemoteDescription:
            // Spec: "If remoteDescription is null return a promise rejected
            // with a newly created InvalidStateError."
            callback(RTCError(RTCErrorType::INVALID_STATE,
                              "The remote description was null"));
            break;
          case AddIceCandidateResult::kAddIceCandidateFailNullCandidate:
            // TODO(https://crbug.com/935898): Handle end-of-candidates instead
            // of treating null candidate as an error.
            callback(RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
                              "Error processing ICE candidate"));
            break;
          case AddIceCandidateResult::kAddIceCandidateFailNotValid:
          case AddIceCandidateResult::kAddIceCandidateFailInAddition:
          case AddIceCandidateResult::kAddIceCandidateFailNotUsable:
            // Spec: "If candidate could not be successfully added [...] Reject
            // p with a newly created OperationError and abort these steps."
            // UNSUPPORTED_OPERATION maps to OperationError.
            callback(RTCError(RTCErrorType::UNSUPPORTED_OPERATION,
                              "Error processing ICE candidate"));
            break;
          default:
            RTC_DCHECK_NOTREACHED();
        }
      });
}

bool SdpOfferAnswerHandler::RemoveIceCandidate(const IceCandidate* candidate) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::RemoveIceCandidate");
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (pc_->IsClosed() || !remote_description() || !candidate) {
    RTC_LOG(LS_ERROR) << "RemoveIceCandidate: PeerConnection is closed.";
    return false;
  }

  bool removed = mutable_remote_description()->RemoveCandidate(candidate);
  // Remove the candidates from the transport controller.
  // This involves a hop to the network thread - should we rather do this
  // asynchronously?
  transport_controller_s()->RemoveRemoteCandidate(candidate);
  return removed;
}

void SdpOfferAnswerHandler::AddLocalIceCandidate(
    const IceCandidate* candidate) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (local_description()) {
    mutable_local_description()->AddCandidate(candidate);
  }
}

void SdpOfferAnswerHandler::RemoveLocalIceCandidates(
    absl::string_view mid,
    const std::vector<Candidate>& candidates) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (local_description()) {
    for (const auto& c : candidates) {
      IceCandidate ice_candidate(mid, -1, c);
      mutable_local_description()->RemoveCandidate(&ice_candidate);
    }
  }
}

const SessionDescriptionInterface* SdpOfferAnswerHandler::local_description()
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return pending_local_description_ ? pending_local_description_.get()
                                    : current_local_description_.get();
}

const SessionDescriptionInterface* SdpOfferAnswerHandler::remote_description()
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return pending_remote_description_ ? pending_remote_description_.get()
                                     : current_remote_description_.get();
}

const SessionDescriptionInterface*
SdpOfferAnswerHandler::current_local_description() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return current_local_description_.get();
}

const SessionDescriptionInterface*
SdpOfferAnswerHandler::current_remote_description() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return current_remote_description_.get();
}

const SessionDescriptionInterface*
SdpOfferAnswerHandler::pending_local_description() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return pending_local_description_.get();
}

const SessionDescriptionInterface*
SdpOfferAnswerHandler::pending_remote_description() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return pending_remote_description_.get();
}

PeerConnectionInterface::SignalingState SdpOfferAnswerHandler::signaling_state()
    const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return signaling_state_;
}

void SdpOfferAnswerHandler::ChangeSignalingState(
    PeerConnectionInterface::SignalingState signaling_state) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::ChangeSignalingState");
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (signaling_state_ == signaling_state) {
    return;
  }
  RTC_LOG(LS_INFO) << "Session: " << pc_->session_id() << " Old state: "
                   << PeerConnectionInterface::AsString(signaling_state_)
                   << " New state: "
                   << PeerConnectionInterface::AsString(signaling_state);
  signaling_state_ = signaling_state;
  pc_->RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    observer->OnSignalingChange(signaling_state_);
  });
}

RTCError SdpOfferAnswerHandler::UpdateSessionState(
    SdpType type,
    ContentSource source,
    const SessionDescription* description,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  RTC_DCHECK_RUN_ON(signaling_thread());

  // If there's already a pending error then no state transition should
  // happen. But all call-sites should be verifying this before calling us!
  RTC_DCHECK(session_error() == SessionError::kNone);

  // If this is answer-ish we're ready to let media flow.
  if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
    EnableSending();
  }

  // Update the signaling state according to the specified state machine (see
  // https://w3c.github.io/webrtc-pc/#rtcsignalingstate-enum).
  if (type == SdpType::kOffer) {
    ChangeSignalingState(source == CS_LOCAL
                             ? PeerConnectionInterface::kHaveLocalOffer
                             : PeerConnectionInterface::kHaveRemoteOffer);
  } else if (type == SdpType::kPrAnswer) {
    ChangeSignalingState(source == CS_LOCAL
                             ? PeerConnectionInterface::kHaveLocalPrAnswer
                             : PeerConnectionInterface::kHaveRemotePrAnswer);
  } else {
    RTC_DCHECK(type == SdpType::kAnswer);
    ChangeSignalingState(PeerConnectionInterface::kStable);
    if (ConfiguredForMedia()) {
      transceivers()->DiscardStableStates();
    }
  }

  // Update internal objects according to the session description's media
  // descriptions.
  return PushdownMediaDescription(type, source, bundle_groups_by_mid);
}

bool SdpOfferAnswerHandler::ShouldFireNegotiationNeededEvent(
    uint32_t event_id) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  // Plan B? Always fire to conform with useless legacy behavior.
  if (!IsUnifiedPlan()) {
    return true;
  }
  // The event ID has been invalidated. Either negotiation is no longer needed
  // or a newer negotiation needed event has been generated.
  if (event_id != negotiation_needed_event_id_) {
    return false;
  }
  // The chain is no longer empty, update negotiation needed when it becomes
  // empty. This should generate a newer negotiation needed event, making this
  // one obsolete.
  if (!operations_chain_->IsEmpty()) {
    // Since we just suppressed an event that would have been fired, if
    // negotiation is still needed by the time the chain becomes empty again,
    // we must make sure to generate another event if negotiation is needed
    // then. This happens when `is_negotiation_needed_` goes from false to
    // true, so we set it to false until UpdateNegotiationNeeded() is called.
    is_negotiation_needed_ = false;
    update_negotiation_needed_on_empty_chain_ = true;
    return false;
  }
  // We must not fire if the signaling state is no longer "stable". If
  // negotiation is still needed when we return to "stable", a new negotiation
  // needed event will be generated, so this one can safely be suppressed.
  if (signaling_state_ != PeerConnectionInterface::kStable) {
    return false;
  }
  // All checks have passed - please fire "negotiationneeded" now!
  return true;
}

scoped_refptr<StreamCollectionInterface>
SdpOfferAnswerHandler::local_streams() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_CHECK(!IsUnifiedPlan()) << "local_streams is not available with Unified "
                                 "Plan SdpSemantics. Please use GetSenders "
                                 "instead.";
  return local_streams_;
}

scoped_refptr<StreamCollectionInterface>
SdpOfferAnswerHandler::remote_streams() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_CHECK(!IsUnifiedPlan()) << "remote_streams is not available with Unified "
                                 "Plan SdpSemantics. Please use GetReceivers "
                                 "instead.";
  return remote_streams_;
}

bool SdpOfferAnswerHandler::AddStream(MediaStreamInterface* local_stream) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_CHECK(!IsUnifiedPlan()) << "AddStream is not available with Unified Plan "
                                 "SdpSemantics. Please use AddTrack instead.";
  if (pc_->IsClosed()) {
    return false;
  }
  if (!CanAddLocalMediaStream(local_streams_.get(), local_stream)) {
    return false;
  }

  local_streams_->AddStream(scoped_refptr<MediaStreamInterface>(local_stream));
  auto observer = std::make_unique<MediaStreamObserver>(
      local_stream,
      [this](AudioTrackInterface* audio_track,
             MediaStreamInterface* media_stream) {
        RTC_DCHECK_RUN_ON(signaling_thread());
        OnAudioTrackAdded(audio_track, media_stream);
      },
      [this](AudioTrackInterface* audio_track,
             MediaStreamInterface* media_stream) {
        RTC_DCHECK_RUN_ON(signaling_thread());
        OnAudioTrackRemoved(audio_track, media_stream);
      },
      [this](VideoTrackInterface* video_track,
             MediaStreamInterface* media_stream) {
        RTC_DCHECK_RUN_ON(signaling_thread());
        OnVideoTrackAdded(video_track, media_stream);
      },
      [this](VideoTrackInterface* video_track,
             MediaStreamInterface* media_stream) {
        RTC_DCHECK_RUN_ON(signaling_thread());
        OnVideoTrackRemoved(video_track, media_stream);
      });
  stream_observers_.push_back(std::move(observer));

  for (const auto& track : local_stream->GetAudioTracks()) {
    rtp_manager()->AddTrackPlanB(track.get(), local_stream);
  }
  for (const auto& track : local_stream->GetVideoTracks()) {
    rtp_manager()->AddTrackPlanB(track.get(), local_stream);
  }

  pc_->legacy_stats()->AddStream(local_stream);
  UpdateNegotiationNeeded();
  return true;
}

void SdpOfferAnswerHandler::RemoveStream(MediaStreamInterface* local_stream) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_CHECK(!IsUnifiedPlan()) << "RemoveStream is not available with Unified "
                                 "Plan SdpSemantics. Please use RemoveTrack "
                                 "instead.";
  TRACE_EVENT0("webrtc""PeerConnection::RemoveStream");
  if (!pc_->IsClosed()) {
    for (const auto& track : local_stream->GetAudioTracks()) {
      rtp_manager()->RemoveTrackPlanB(track.get(), local_stream);
    }
    for (const auto& track : local_stream->GetVideoTracks()) {
      rtp_manager()->RemoveTrackPlanB(track.get(), local_stream);
    }
  }
  local_streams_->RemoveStream(local_stream);
  std::erase_if(
      stream_observers_,
      [local_stream](const std::unique_ptr<MediaStreamObserver>& observer) {
        return observer->stream()->id().compare(local_stream->id()) == 0;
      });

  if (pc_->IsClosed()) {
    return;
  }
  UpdateNegotiationNeeded();
}

void SdpOfferAnswerHandler::OnAudioTrackAdded(AudioTrackInterface* track,
                                              MediaStreamInterface* stream) {
  RTC_DCHECK(!IsUnifiedPlan());
  if (pc_->IsClosed()) {
    return;
  }
  rtp_manager()->AddTrackPlanB(track, stream);
  UpdateNegotiationNeeded();
}

void SdpOfferAnswerHandler::OnAudioTrackRemoved(AudioTrackInterface* track,
                                                MediaStreamInterface* stream) {
  RTC_DCHECK(!IsUnifiedPlan());
  if (pc_->IsClosed()) {
    return;
  }
  rtp_manager()->RemoveTrackPlanB(track, stream);
  UpdateNegotiationNeeded();
}

void SdpOfferAnswerHandler::OnVideoTrackAdded(VideoTrackInterface* track,
                                              MediaStreamInterface* stream) {
  RTC_DCHECK(!IsUnifiedPlan());
  if (pc_->IsClosed()) {
    return;
  }
  rtp_manager()->AddTrackPlanB(track, stream);
  UpdateNegotiationNeeded();
}

void SdpOfferAnswerHandler::OnVideoTrackRemoved(VideoTrackInterface* track,
                                                MediaStreamInterface* stream) {
  RTC_DCHECK(!IsUnifiedPlan());
  if (pc_->IsClosed()) {
    return;
  }
  rtp_manager()->RemoveTrackPlanB(track, stream);
  UpdateNegotiationNeeded();
}

RTCError SdpOfferAnswerHandler::Rollback(SdpType desc_type) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::Rollback");
  auto state = signaling_state();
  if (state != PeerConnectionInterface::kHaveLocalOffer &&
      state != PeerConnectionInterface::kHaveRemoteOffer) {
    return LOG_ERROR(RTCError(RTCErrorType::INVALID_STATE)
                     << "Called in wrong signalingState: "
                     << PeerConnectionInterface::AsString(signaling_state()));
  }
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());
  std::vector<scoped_refptr<RtpTransceiverInterface>>
      now_receiving_transceivers;
  std::vector<scoped_refptr<MediaStreamInterface>> all_added_streams;
  std::vector<scoped_refptr<MediaStreamInterface>> all_removed_streams;
  std::vector<scoped_refptr<RtpReceiverInterface>> removed_receivers;
  // Keep to-be-removed transceivers alive until after tasks for them have been
  // run.
  std::vector<RtpTransceiverProxyRefPtr> transceivers_to_remove;
  absl::Cleanup cleanup_remove = [&] {
    for (const auto& transceiver : transceivers_to_remove) {
      transceivers()->Remove(transceiver);
    }
  };
  ScopedOperationsBatcher worker_tasks(context_->worker_thread());
  ScopedOperationsBatcher network_tasks(context_->network_thread());

  for (auto&& transceivers_stable_state_pair : transceivers()->StableStates()) {
    auto transceiver = transceivers_stable_state_pair.first;
    auto stable_state = transceivers_stable_state_pair.second;

    if (stable_state.did_set_fired_direction()) {
      // If this rollback triggers going from not receiving to receiving again,
      // we need to fire "ontrack".
      bool previously_fired_direction_is_recv =
          transceiver->fired_direction().has_value() &&
          RtpTransceiverDirectionHasRecv(*transceiver->fired_direction());
      bool currently_fired_direction_is_recv =
          stable_state.fired_direction().has_value() &&
          RtpTransceiverDirectionHasRecv(
              stable_state.fired_direction().value());
      if (!previously_fired_direction_is_recv &&
          currently_fired_direction_is_recv) {
        now_receiving_transceivers.push_back(transceiver);
      }

      transceiver->internal()->set_fired_direction(
          stable_state.fired_direction());
    }

    // https://github.com/w3c/webrtc-pc/issues/3081
    transceiver->internal()->set_receptive(
        transceiver->internal()->current_direction() &&
        RtpTransceiverDirectionHasRecv(
            *transceiver->internal()->current_direction()));

    if (stable_state.remote_stream_ids()) {
      std::vector<scoped_refptr<MediaStreamInterface>> added_streams;
      std::vector<scoped_refptr<MediaStreamInterface>> removed_streams;
      SetAssociatedRemoteStreams(transceiver->internal()->receiver_internal(),
                                 stable_state.remote_stream_ids().value(),
                                 &added_streams, &removed_streams);
      all_added_streams.insert(all_added_streams.end(), added_streams.begin(),
                               added_streams.end());
      all_removed_streams.insert(all_removed_streams.end(),
                                 removed_streams.begin(),
                                 removed_streams.end());
      if (!stable_state.has_m_section() && !stable_state.newly_created()) {
        continue;
      }
    }

    // Due to the above `continue` statement, the below code only runs if there
    // is a change in mid association (has_m_section), if the transceiver was
    // newly created (newly_created) or if remote streams were not set.

    RTC_DCHECK(transceiver->internal()->mid().has_value());
    network_tasks.Add(transceiver->internal()->GetClearChannelNetworkTask());
    worker_tasks.Add(transceiver->internal()->GetDeleteChannelWorkerTask(
        /*stop_senders=*/false));

    if (signaling_state() == PeerConnectionInterface::kHaveRemoteOffer &&
        transceiver->receiver()) {
      removed_receivers.push_back(transceiver->receiver());
    }
    if (stable_state.newly_created()) {
      if (transceiver->internal()->reused_for_addtrack()) {
        transceiver->internal()->set_created_by_addtrack(true);
      } else {
        worker_tasks.Add(
            transceiver->internal()->GetStopTransceiverProcedure());
        transceivers_to_remove.push_back(transceiver);
      }
    }
    auto sender_internal = transceiver->internal()->sender_internal();
    if (stable_state.init_send_encodings()) {
      sender_internal->set_init_send_encodings(
          stable_state.init_send_encodings().value());
    }
    transceiver->internal()->SetTransport(nullptr, std::nullopt);
    if (stable_state.has_m_section()) {
      transceiver->internal()->set_mid(stable_state.mid());
      transceiver->internal()->set_mline_index(stable_state.mline_index());
    }
  }

  RTCError e = network_tasks.Run();
  RTC_DCHECK(e.ok());  // only void tasks queued.
  e = worker_tasks.Run();
  RTC_DCHECK(e.ok());  // only void tasks queued.
  e = transport_controller_s()->RollbackTransports();
  if (!e.ok()) {
    return e;
  }
  transceivers()->DiscardStableStates();
  pending_local_description_.reset();
  pending_remote_description_.reset();
  ChangeSignalingState(PeerConnectionInterface::kStable);

  // Once all processing has finished, fire off callbacks.
  pc_->RunWithObserver([&](auto observer) {
    for (const auto& transceiver : now_receiving_transceivers) {
      observer->OnTrack(transceiver);
      observer->OnAddTrack(transceiver->receiver(),
                           transceiver->receiver()->streams());
    }
    for (const auto& receiver : removed_receivers) {
      observer->OnRemoveTrack(receiver);
    }
    for (const auto& stream : all_added_streams) {
      observer->OnAddStream(stream);
    }
    for (const auto& stream : all_removed_streams) {
      observer->OnRemoveStream(stream);
    }
  });

  // The assumption is that in case of implicit rollback
  // UpdateNegotiationNeeded gets called in SetRemoteDescription.
  if (desc_type == SdpType::kRollback) {
    UpdateNegotiationNeeded();
    if (is_negotiation_needed_) {
      // Legacy version.
      pc_->RunWithObserver(
          [&](auto observer) { observer->OnRenegotiationNeeded(); });
      // Spec-compliant version; the event may get invalidated before firing.
      GenerateNegotiationNeededEvent();
    }
  }
  return RTCError::OK();
}

bool SdpOfferAnswerHandler::IsUnifiedPlan() const {
  return pc_->IsUnifiedPlan();
}

void SdpOfferAnswerHandler::OnOperationsChainEmpty() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (pc_->IsClosed() || !update_negotiation_needed_on_empty_chain_)
    return;
  update_negotiation_needed_on_empty_chain_ = false;
  // Firing when chain is empty is only supported in Unified Plan to avoid
  // Plan B regressions. (In Plan B, onnegotiationneeded is already broken
  // anyway, so firing it even more might just be confusing.)
  if (IsUnifiedPlan()) {
    UpdateNegotiationNeeded();
  }
}

std::optional<bool> SdpOfferAnswerHandler::is_caller() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return is_caller_;
}

bool SdpOfferAnswerHandler::HasNewIceCredentials() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return local_ice_credentials_to_replace_->HasIceCredentials();
}

bool SdpOfferAnswerHandler::IceRestartPending(
    const std::string& content_name) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return pending_ice_restarts_.find(content_name) !=
         pending_ice_restarts_.end();
}

bool SdpOfferAnswerHandler::NeedsIceRestart(
    const std::string& content_name) const {
  return pc_->NeedsIceRestart(content_name);
}

std::optional<SSLRole> SdpOfferAnswerHandler::GetDtlsRole(
    const std::string& mid) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  return transport_controller_s()->GetDtlsRole(mid);
}

void SdpOfferAnswerHandler::UpdateNegotiationNeeded() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!IsUnifiedPlan()) {
    pc_->RunWithObserver(
        [&](auto observer) { observer->OnRenegotiationNeeded(); });
    GenerateNegotiationNeededEvent();
    return;
  }

  // In the spec, a task is queued here to run the following steps - this is
  // meant to ensure we do not fire onnegotiationneeded prematurely if
  // multiple changes are being made at once. In order to support Chromium's
  // implementation where the JavaScript representation of the PeerConnection
  // lives on a separate thread though, the queuing of a task is instead
  // performed by the PeerConnectionObserver posting from the signaling thread
  // to the JavaScript main thread that negotiation is needed. And because the
  // Operations Chain lives on the WebRTC signaling thread,
  // ShouldFireNegotiationNeededEvent() must be called before firing the event
  // to ensure the Operations Chain is still empty and the event has not been
  // invalidated.

  // If connection's [[IsClosed]] slot is true, abort these steps.
  if (pc_->IsClosed())
    return;

  // If connection's signaling state is not "stable", abort these steps.
  if (signaling_state() != PeerConnectionInterface::kStable)
    return;

  // NOTE
  // The negotiation-needed flag will be updated once the state transitions to
  // "stable", as part of the steps for setting an RTCSessionDescription.

  // If the result of checking if negotiation is needed is false, clear the
  // negotiation-needed flag by setting connection's [[NegotiationNeeded]]
  // slot to false, and abort these steps.
  bool is_negotiation_needed = CheckIfNegotiationIsNeeded();
  if (!is_negotiation_needed) {
    is_negotiation_needed_ = false;
    // Invalidate any negotiation needed event that may previosuly have been
    // generated.
    ++negotiation_needed_event_id_;
    return;
  }

  // If connection's [[NegotiationNeeded]] slot is already true, abort these
  // steps.
  if (is_negotiation_needed_)
    return;

  // Set connection's [[NegotiationNeeded]] slot to true.
  is_negotiation_needed_ = true;

  // Queue a task that runs the following steps:
  // If connection's [[IsClosed]] slot is true, abort these steps.
  // If connection's [[NegotiationNeeded]] slot is false, abort these steps.
  // Fire an event named negotiationneeded at connection.
  pc_->RunWithObserver(
      [&](auto observer) { observer->OnRenegotiationNeeded(); });
  // Fire the spec-compliant version; when ShouldFireNegotiationNeededEvent()
  // is used in the task queued by the observer, this event will only fire
  // when the chain is empty.
  GenerateNegotiationNeededEvent();
}

std::optional<SSLRole> SdpOfferAnswerHandler::GuessSslRole() const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!pc_->sctp_mid())
    return std::nullopt;

  // TODO(bugs.webrtc.org/13668): This guesswork is guessing wrong (returning
  // SSL_CLIENT = ACTIVE) if remote offer has role ACTIVE, but we'll be able
  // to detect that by looking at the SDP.
  //
  // The phases of establishing an SCTP session are:
  //
  // Offerer:
  //
  // * Before negotiation: Neither is_caller nor sctp_mid exists.
  // * After setting an offer as local description: is_caller is known (true),
  //   sctp_mid is known, but we don't know the SSL role for sure (or if we'll
  //   eventually get an SCTP session).
  // * After setting an answer as the remote description: We know is_caller,
  //   sctp_mid and that we'll get the SCTP channel established (m-section
  //   wasn't rejected).
  // * Special case: The SCTP  m-section was rejected: Close datachannels.
  // * We MAY know the SSL role if we offered actpass and got back active or
  //   passive; if the other end is a webrtc implementation, it will be active.
  // * After the TLS handshake: We have a definitive answer on the SSL role.
  //
  // Answerer:
  //
  // * After setting an offer as remote description: We know is_caller (false).
  // * If there was an SCTP session, we know the SCTP mid. We also know the
  //   SSL role, since if the remote offer was actpass or passive, we'll answer
  //   active, and if the remote offer was active, we're passive.
  // * Special case: No SCTP m= line. We don't know for sure if the remote
  //   doesn't support it or just didn't offer it. Not sure what we do in this
  //   case (logic would suggest fire a `negotiationneeded` event and generate a
  //   subsequent offer, but this needs to be tested).
  // * After the TLS handshake: We know that TLS obeyed the protocol. There
  //   should be an error surfaced somewhere if it didn't.
  // * "Guessing" should always be correct if we get an SCTP session and are not
  //   the offerer.

  return is_caller() ? SSL_SERVER : SSL_CLIENT;
}

bool SdpOfferAnswerHandler::CheckIfNegotiationIsNeeded() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  // 1. If any implementation-specific negotiation is required, as described
  // at the start of this section, return true.

  // 2. If connection.[[LocalIceCredentialsToReplace]] is not empty, return
  // true.
  if (local_ice_credentials_to_replace_->HasIceCredentials()) {
    return true;
  }

  // 3. Let description be connection.[[CurrentLocalDescription]].
  const SessionDescriptionInterface* description = current_local_description();
  if (!description)
    return true;

  // 4. If connection has created any RTCDataChannels, and no m= section in
  // description has been negotiated yet for data, return true.
  if (data_channel_controller()->HasUsedDataChannels()) {
    const ContentInfo* data_content =
        GetFirstDataContent(description->description()->contents());
    if (!data_content) {
      return true;
    }
    // The remote end might have rejected the data content.
    const ContentInfo* remote_data_content =
        current_remote_description()
            ? current_remote_description()->description()->GetContentByName(
                  data_content->mid())
            : nullptr;
    if (remote_data_content && remote_data_content->rejected) {
      return true;
    }
  }
  if (!ConfiguredForMedia()) {
    return false;
  }

  // 5. For each transceiver in connection's set of transceivers, perform the
  // following checks:
  for (const auto& transceiver : transceivers()->ListInternal()) {
    const ContentInfo* current_local_msection =
        FindTransceiverMSection(transceiver, description);

    const ContentInfo* current_remote_msection =
        FindTransceiverMSection(transceiver, current_remote_description());

    // 5.4 If transceiver is stopped and is associated with an m= section,
    // but the associated m= section is not yet rejected in
    // connection.[[CurrentLocalDescription]] or
    // connection.[[CurrentRemoteDescription]], return true.
    if (transceiver->stopped()) {
      RTC_DCHECK(transceiver->stopping());
      if (current_local_msection && !current_local_msection->rejected &&
          ((current_remote_msection && !current_remote_msection->rejected) ||
           !current_remote_msection)) {
        return true;
      }
      continue;
    }

    // 5.1 If transceiver.[[Stopping]] is true and transceiver.[[Stopped]] is
    // false, return true.
    if (transceiver->stopping() && !transceiver->stopped())
      return true;

    // 5.2 If transceiver isn't stopped and isn't yet associated with an m=
    // section in description, return true.
    if (!current_local_msection)
      return true;

    const MediaContentDescription* current_local_media_description =
        current_local_msection->media_description();
    // 5.3 If transceiver isn't stopped and is associated with an m= section
    // in description then perform the following checks:

    // If the transceiver's Sframe state differs from the negotiated state
    // in the current local description, negotiation is needed.
    if (transceiver->SframeEnabled().has_value() &&
        transceiver->SframeEnabled().value() !=
            current_local_media_description->sframe_enabled()) {
      return true;
    }

    // 5.3.1 If transceiver.[[Direction]] is "sendrecv" or "sendonly", and the
    // associated m= section in description either doesn't contain a single
    // "a=msid" line, or the number of MSIDs from the "a=msid" lines in this
    // m= section, or the MSID values themselves, differ from what is in
    // transceiver.sender.[[AssociatedMediaStreamIds]], return true.
    if (RtpTransceiverDirectionHasSend(transceiver->direction())) {
      if (current_local_media_description->streams().empty())
        return true;

      std::vector<std::string> msection_msids;
      for (const auto& stream : current_local_media_description->streams()) {
        for (const std::string& msid : stream.stream_ids())
          msection_msids.push_back(msid);
      }

      std::vector<std::string> transceiver_msids =
          transceiver->sender()->stream_ids();
      if (msection_msids.size() != transceiver_msids.size())
        return true;

      absl::c_sort(transceiver_msids);
      absl::c_sort(msection_msids);
      if (transceiver_msids != msection_msids)
        return true;
    }

    // 5.3.2 If description is of type "offer", and the direction of the
    // associated m= section in neither connection.[[CurrentLocalDescription]]
    // nor connection.[[CurrentRemoteDescription]] matches
    // transceiver.[[Direction]], return true.
    if (description->GetType() == SdpType::kOffer) {
      if (!current_remote_description())
        return true;

      if (!current_remote_msection)
        return true;

      RtpTransceiverDirection current_local_direction =
          current_local_media_description->direction();
      RtpTransceiverDirection current_remote_direction =
          current_remote_msection->media_description()->direction();
      if (transceiver->direction() != current_local_direction &&
          transceiver->direction() !=
              RtpTransceiverDirectionReversed(current_remote_direction)) {
        return true;
      }
    }

    // 5.3.3 If description is of type "answer", and the direction of the
    // associated m= section in the description does not match
    // transceiver.[[Direction]] intersected with the offered direction (as
    // described in [JSEP] (section 5.3.1.)), return true.
    if (description->GetType() == SdpType::kAnswer) {
      if (!remote_description())
        return true;

      const ContentInfo* offered_remote_msection =
          FindTransceiverMSection(transceiver, remote_description());

      RtpTransceiverDirection offered_direction =
          offered_remote_msection
              ? offered_remote_msection->media_description()->direction()
              : RtpTransceiverDirection::kInactive;

      if (current_local_media_description->direction() !=
          (RtpTransceiverDirectionIntersection(
              transceiver->direction(),
              RtpTransceiverDirectionReversed(offered_direction)))) {
        return true;
      }
    }
  }
  // If all the preceding checks were performed and true was not returned,
  // nothing remains to be negotiated; return false.
  return false;
}

void SdpOfferAnswerHandler::GenerateNegotiationNeededEvent() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  ++negotiation_needed_event_id_;
  pc_->RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    observer->OnNegotiationNeededEvent(negotiation_needed_event_id_);
  });
}

RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
    const SessionDescriptionInterface* sdesc,
    ContentSource source,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  // An assumption is that a check for session error is done at a higher level.
  RTC_DCHECK_EQ(SessionError::kNone, session_error());

  if (!sdesc || !sdesc->description()) {
    return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER) << kInvalidSdp);
  }

  SdpType type = sdesc->GetType();
  if ((source == CS_LOCAL && !ExpectSetLocalDescription(type)) ||
      (source == CS_REMOTE && !ExpectSetRemoteDescription(type))) {
    return LOG_ERROR(RTCError(RTCErrorType::INVALID_STATE)
                     << "Called in wrong state: "
                     << PeerConnectionInterface::AsString(signaling_state()));
  }

  RTCError error = ValidateMids(*sdesc->description());
  if (!error.ok()) {
    return error;
  }

  // Verify crypto settings.
  if (pc_->dtls_enabled()) {
    RTCError crypto_error = VerifyCrypto(
        sdesc->description(), pc_->dtls_enabled(), bundle_groups_by_mid);
    if (!crypto_error.ok()) {
      return crypto_error;
    }
  }

  // Verify ice-ufrag and ice-pwd.
  if (!VerifyIceUfragPwdPresent(sdesc->description(), bundle_groups_by_mid)) {
    return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                     << kSdpWithoutIceUfragPwd);
  }

  // Validate that there are no collisions of bundled payload types.
  error = ValidateBundledPayloadTypes(*sdesc->description());
  RTC_HISTOGRAM_BOOLEAN("WebRTC.PeerConnection.ValidBundledPayloadTypes",
                        error.ok());
  if (!error.ok()) {
    RTC_LOG(LS_ERROR) << "Bundled payload type collision: " << error.message();
    if (env_.field_trials().IsEnabled(
            "WebRTC-SdpBundlePayloadTypeCollisionCheck")) {
      return error;
    }
  }

  // Validate that there are no collisions of bundled header extensions ids.
  error = ValidateBundledRtpHeaderExtensions(*sdesc->description());
  if (!error.ok()) {
    return error;
  }

  // Validate the SSRC groups.
  error = ValidateSsrcGroups(*sdesc->description());
  if (!error.ok()) {
    return error;
  }

  if (!pc_->ValidateBundleSettings(sdesc->description(),
                                   bundle_groups_by_mid)) {
    return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                     << kBundleWithoutRtcpMux);
  }

  error = ValidatePayloadTypes(*sdesc->description());
  if (!error.ok()) {
    return error;
  }

  // TODO(skvlad): When the local rtcp-mux policy is Require, reject any
  // m-lines that do not rtcp-mux enabled.

  // Verify m-lines in Answer when compared against Offer.
  if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
    // With an answer we want to compare the new answer session description
    // with the offer's session description from the current negotiation.
    const SessionDescription* offer_desc =
        (source == CS_LOCAL) ? remote_description()->description()
                             : local_description()->description();
    if (!MediaSectionsHaveSameCount(*offer_desc, *sdesc->description()) ||
        !MediaSectionsInSameOrder(*offer_desc, nullptr, *sdesc->description(),
                                  type)) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << kMlineMismatchInAnswer);
    }
  } else {
    // The re-offers should respect the order of m= sections in current
    // description. See RFC3264 Section 8 paragraph 4 for more details.
    // With a re-offer, either the current local or current remote
    // descriptions could be the most up to date, so we would like to check
    // against both of them if they exist. It could be the case that one of
    // them has a 0 port for a media section, but the other does not. This is
    // important to check against in the case that we are recycling an m=
    // section.
    const SessionDescription* current_desc = nullptr;
    const SessionDescription* secondary_current_desc = nullptr;
    if (local_description()) {
      current_desc = local_description()->description();
      if (remote_description()) {
        secondary_current_desc = remote_description()->description();
      }
    } else if (remote_description()) {
      current_desc = remote_description()->description();
    }
    if (current_desc &&
        !MediaSectionsInSameOrder(*current_desc, secondary_current_desc,
                                  *sdesc->description(), type)) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << kMlineMismatchInSubsequentOffer);
    }
  }

  if (IsUnifiedPlan()) {
    // Ensure that each audio and video media section has at most one
    // "StreamParams". This will return an error if receiving a session
    // description from a "Plan B" endpoint which adds multiple tracks of the
    // same type. With Unified Plan, there can only be at most one track per
    // media section.
    for (const ContentInfo& content : sdesc->description()->contents()) {
      const MediaContentDescription& desc = *content.media_description();
      if ((desc.type() == MediaType::AUDIO ||
           desc.type() == MediaType::VIDEO) &&
          desc.streams().size() > 1u) {
        return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                         << "Media section has more than one track specified "
                            "with a=ssrc lines which is not supported with "
                            "Unified Plan.");
      }
    }
    // Validate spec-simulcast which only works if the remote end negotiated the
    // mid and rid header extension.
    error = ValidateRtpHeaderExtensionsForSpecSimulcast(*sdesc->description());
    if (!error.ok()) {
      return error;
    }

    if (source == CS_REMOTE &&
        (type == SdpType::kPrAnswer || type == SdpType::kAnswer)) {
      RTC_DCHECK(local_description());
      error = VerifyDirectionsInAnswer(local_description()->description(),
                                       sdesc->description());
      if (!error.ok() && !env_.field_trials().IsDisabled(
                             "WebRTC-EnforceTransceiverDirection")) {
        return error;
      }
    }
  }

  // Validate Sframe consistency: reject remote answers that introduce
  // a=sframe for media sections where the local offer did not include it.
  if (source == CS_REMOTE &&
      (type == SdpType::kPrAnswer || type == SdpType::kAnswer)) {
    RTC_DCHECK(local_description());
    error = VerifySframeInAnswer(local_description()->description(),
                                 sdesc->description());
    if (!error.ok()) {
      return error;
    }
  }

  return RTCError::OK();
}

RTCError SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels(
    ContentSource source,
    const SessionDescriptionInterface& new_session,
    const SessionDescriptionInterface* old_local_description,
    const SessionDescriptionInterface* old_remote_description,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  TRACE_EVENT0("webrtc",
               "SdpOfferAnswerHandler::UpdateTransceiversAndDataChannels");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());

  if (new_session.GetType() == SdpType::kOffer) {
    // If the BUNDLE policy is max-bundle, then we know for sure that all
    // transports will be bundled from the start. Return an error if
    // max-bundle is specified but the session description does not have a
    // BUNDLE group.
    if (pc_->configuration()->bundle_policy ==
            PeerConnectionInterface::kBundlePolicyMaxBundle &&
        bundle_groups_by_mid.empty()) {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "max-bundle configured but session description has "
                          "no BUNDLE group");
    }
  }

  // Thread safety requires specific execution order for tasks:
  // - Construction: Worker tasks before Network tasks.
  //   (Network depends on Worker setup).
  // - Destruction: Network tasks before Worker tasks.
  //   (Worker cleanup may depend on Network teardown).
  // The batchers below are declared and executed to enforce these orders.
  ScopedOperationsBatcher network_teardown_tasks(context_->network_thread());
  ScopedOperationsBatcher worker_tasks(context_->worker_thread());
  ScopedOperationsBatcher network_init_tasks(context_->network_thread());
  const ContentInfos& new_contents = new_session.description()->contents();
  struct TransceiverUpdate {
    scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> transceiver;
    const ContentInfo& content;
  };
  std::vector<TransceiverUpdate> transceivers_to_update;

  for (size_t i = 0; i < new_contents.size(); ++i) {
    const ContentInfo& new_content = new_contents[i];
    MediaType media_type = new_content.media_description()->type();
    mid_generator_.AddKnownId(new_content.mid());
    if (media_type == MediaType::AUDIO || media_type == MediaType::VIDEO) {
      const ContentInfo* old_local_content = nullptr;
      if (old_local_description &&
          i < old_local_description->description()->contents().size()) {
        old_local_content =
            &old_local_description->description()->contents()[i];
      }
      const ContentInfo* old_remote_content = nullptr;
      if (old_remote_description &&
          i < old_remote_description->description()->contents().size()) {
        old_remote_content =
            &old_remote_description->description()->contents()[i];
      }
      auto transceiver_or_error = AssociateTransceiver(
          source, new_session.GetType(), i, new_content, old_local_content,
          old_remote_content, worker_tasks);
      if (!transceiver_or_error.ok()) {
        // In the case where a transceiver is rejected locally prior to being
        // associated, we don't expect to find a transceiver, but might find it
        // in the case where state is still "stopping", not "stopped".
        if (new_content.rejected) {
          continue;
        }
        return transceiver_or_error.MoveError();
      }
      transceivers_to_update.push_back(
          {transceiver_or_error.MoveValue(), new_content});
    } else if (media_type == MediaType::DATA) {
      const std::optional<std::string> data_mid = pc_->sctp_mid();
      if (data_mid && new_content.mid() != data_mid.value()) {
        // Ignore all but the first data section.
        RTC_LOG(LS_INFO) << "Ignoring data media section with MID="
                         << new_content.mid();
        continue;
      }
      auto it = bundle_groups_by_mid.find(new_content.mid());
      const ContentGroup* bundle_group =
          it != bundle_groups_by_mid.end() ? it->second : nullptr;
      RTCError error =
          UpdateDataChannelTransport(source, new_content, bundle_group);
      if (!error.ok()) {
        return error;
      }
    } else if (media_type == MediaType::UNSUPPORTED) {
      RTC_LOG(LS_INFO) << "Ignoring unsupported media type";
    } else {
      return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                       << "Unknown section type.");
    }
  }

  // Run transceiver creation tasks to ensure transceivers are fully constructed
  // before UpdateTransceiverChannel is called.
  RTCError error = worker_tasks.Run();
  if (!error.ok()) {
    return error;
  }

  for (TransceiverUpdate& update : transceivers_to_update) {
    auto it = bundle_groups_by_mid.find(update.content.mid());
    const ContentGroup* bundle_group =
        it != bundle_groups_by_mid.end() ? it->second : nullptr;

    UpdateTransceiverChannel(update.transceiver, update.content, bundle_group,
                             network_teardown_tasks, worker_tasks,
                             network_init_tasks);
    // Handle locally rejected content. This code path is only needed for apps
    // that SDP munge. Remote rejected content is handled in
    // ApplyRemoteDescriptionUpdateTransceiverState().
    // Do not use std::move here to ensure that the transceiver stays alive
    // in the `transceivers_to_update` vector until the end of this function.
    // This guarantees that it outlives the execution of `worker_tasks.Run()`.
    MaybeHandleLocallyRejectedTransceiver(source, new_session, update.content,
                                          update.transceiver, worker_tasks);
  }

  error = network_teardown_tasks.Run();
  RTC_DCHECK(error.ok());  // Teardown tasks cannot fail.
  error = worker_tasks.Run();
  RTC_DCHECK(error.ok());  // Cleanup and construction tasks cannot fail.
  return network_init_tasks.Run();
}

RTCErrorOr<scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
SdpOfferAnswerHandler::AssociateTransceiver(
    ContentSource source,
    SdpType type,
    size_t mline_index,
    const ContentInfo& content,
    const ContentInfo* old_local_content,
    const ContentInfo* old_remote_content,
    ScopedOperationsBatcher& worker_tasks) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::AssociateTransceiver");
  RTC_DCHECK(IsUnifiedPlan());
#if RTC_DCHECK_IS_ON
  // If this is an offer then the m= section might be recycled. If the m=
  // section is being recycled (defined as: rejected in the current local or
  // remote description and not rejected in new description), the transceiver
  // should have been removed by RemoveStoppedtransceivers()->
  if (IsMediaSectionBeingRecycled(type, content, old_local_content,
                                  old_remote_content)) {
    const auto old_mid = (old_local_content && old_local_content->rejected)
                             ? old_local_content->mid()
                             : old_remote_content->mid();
    auto old_transceiver = transceivers()->FindByMid(old_mid);
    // The transceiver should be disassociated in RemoveStoppedTransceivers()
    RTC_DCHECK(!old_transceiver);
  }
#endif

  bool newly_created = false;
  const MediaContentDescription* media_desc = content.media_description();
  std::vector<SimulcastLayer> initial_simulcast_layers;
  bool simulcast_rejected = GetInitialSimulcastLayersAndRejectionStatus(
      source, *media_desc, old_local_content,
      pc_->GetCryptoOptions().srtp.enable_encrypted_rtp_header_extensions,
      initial_simulcast_layers);

  auto transceiver = transceivers()->FindByMid(content.mid());
  if (source == CS_LOCAL) {
    // Find the RtpTransceiver that corresponds to this m= section, using the
    // mapping between transceivers and m= section indices established when
    // creating the offer.
    if (!transceiver) {
      transceiver = transceivers()->FindByMLineIndex(mline_index);
    }
    if (!transceiver) {
      // This may happen normally when media sections are rejected.
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                       << "Transceiver not found based on m-line index");
    }
  } else {
    RTC_DCHECK_EQ(source, CS_REMOTE);
    // If the m= section is sendrecv or recvonly, and there are RtpTransceivers
    // of the same type...
    // When simulcast is requested, a transceiver cannot be associated because
    // AddTrack cannot be called to initialize it.
    if (!transceiver &&
        RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
        !media_desc->HasSimulcast()) {
      transceiver = FindAvailableTransceiverToReceive(media_desc->type());
    }

    // If no RtpTransceiver was found in the previous step, create one with a
    // recvonly direction.
    if (!transceiver) {
      newly_created = true;
      RTC_LOG(LS_INFO) << "Adding " << MediaTypeToString(media_desc->type())
                       << " transceiver for MID=" << content.mid()
                       << " at i=" << mline_index
                       << " in response to the remote description.";
      std::string sender_id = CreateRandomUuid();
      std::vector<RtpEncodingParameters> send_encodings =
          GetSendEncodingsFromRemoteDescription(*media_desc);
      std::string receiver_id;
      if (!media_desc->streams().empty()) {
        receiver_id = media_desc->streams()[0].id;
      } else {
        receiver_id = CreateRandomUuid();
      }
      // TODO: bugs.webrtc.org/42222804 - Batch up the worker thread blocking
      // call that's embedded in the construction of the transceiver.
      transceiver = rtp_manager()->CreateAndAddTransceiver(
          pc_->configuration()->media_config, audio_options_, video_options_,
          pc_->GetCryptoOptions(), video_bitrate_allocator_factory_.get(),
          media_desc->type(), nullptr, {}, send_encodings,
          /*header_extensions_to_negotiate=*/{}, simulcast_rejected,
          initial_simulcast_layers, worker_tasks, sender_id, receiver_id);
      transceiver->internal()->set_direction(
          RtpTransceiverDirection::kRecvOnly);
      transceiver->internal()->ApplySframeEnabled(media_desc->sframe_enabled());
      if (type == SdpType::kOffer) {
        transceivers()->StableState(transceiver)->set_newly_created();
      }
    }

    RTC_DCHECK(transceiver);

    // Check if the offer indicated simulcast but the answer rejected it.
    // This can happen when simulcast is not supported on the remote party.
    if (!newly_created && simulcast_rejected) {
      // TODO: bugs.webrtc.org/42222804 - Optimize this path to avoid blocking
      // calls for existing transceivers.
      RTCError error =
          DisableSimulcastInSender(transceiver->internal()->sender_internal());
      if (!error.ok()) {
        RTC_LOG(LS_ERROR) << "Failed to remove rejected simulcast.";
        return std::move(error);
      }
    }
  }

  if (transceiver->media_type() != media_desc->type()) {
    return LOG_ERROR(
        RTCError(RTCErrorType::INVALID_PARAMETER)
        << "Transceiver type does not match media description type.");
  }

  if (!newly_created && media_desc->HasSimulcast()) {
    // TODO: bugs.webrtc.org/42222804 - Optimize this path to avoid blocking
    // calls for existing transceivers.
    RTCError error = UpdateSimulcastLayerStatusInSender(
        initial_simulcast_layers, transceiver->internal()->sender_internal());
    if (!error.ok()) {
      RTC_LOG(LS_ERROR) << "Failed updating status for simulcast layers.";
      return std::move(error);
    }
  }
  if (type == SdpType::kOffer) {
    bool state_changes = transceiver->internal()->mid() != content.mid() ||
                         transceiver->internal()->mline_index() != mline_index;
    if (state_changes) {
      transceivers()
          ->StableState(transceiver)
          ->SetMSectionIfUnset(transceiver->internal()->mid(),
                               transceiver->internal()->mline_index());
    }
  }
  // Associate the found or created RtpTransceiver with the m= section by
  // setting the value of the RtpTransceiver's mid property to the MID of the m=
  // section, and establish a mapping between the transceiver and the index of
  // the m= section.
  transceiver->internal()->set_mid(std::string(content.mid()));
  transceiver->internal()->set_mline_index(mline_index);
  return std::move(transceiver);
}

void SdpOfferAnswerHandler::UpdateTransceiverChannel(
    scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> transceiver,
    const ContentInfo& content,
    const ContentGroup* bundle_group,
    ScopedOperationsBatcher& network_teardown_tasks,
    ScopedOperationsBatcher& worker_tasks,
    ScopedOperationsBatcher& network_init_tasks) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::UpdateTransceiverChannel");
  RTC_DCHECK(IsUnifiedPlan());
  RTC_DCHECK(transceiver);
  if (content.rejected) {
    if (transceiver->internal()->HasChannel()) {
      network_teardown_tasks.Add(
          transceiver->internal()->GetClearChannelNetworkTask());
      worker_tasks.Add(transceiver->internal()->GetDeleteChannelWorkerTask(
          /*stop_senders=*/false));
    }
  } else {
    if (!transceiver->internal()->HasChannel()) {
      transceiver->internal()->CreateChannel(
          content.mid(), pc_->call_ptr(), pc_->configuration()->media_config,
          pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
          video_options(), video_bitrate_allocator_factory_.get(),
          [mid = content.mid(), this]() {
            RTC_DCHECK_RUN_ON(network_thread());
            return transport_controller_n()->GetRtpTransport(mid);
          },
          worker_tasks, network_init_tasks);
    }
  }
}

RTCError SdpOfferAnswerHandler::UpdateDataChannelTransport(
    ContentSource source,
    const ContentInfo& content,
    const ContentGroup* bundle_group) {
  if (content.rejected) {
    RTC_LOG(LS_INFO) << "Rejected data channel transport with mid="
                     << content.mid();

    StringBuilder sb;
    sb << "Rejected data channel transport with mid=" << content.mid();
    RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA, sb.Release());
    error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
    pc_->DestroyDataChannelTransport(error);
  } else if (!pc_->CreateDataChannelTransport(content.mid())) {
    return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                     << "Failed to create data channel.");
  }
  return RTCError::OK();
}

bool SdpOfferAnswerHandler::ExpectSetLocalDescription(SdpType type) {
  PeerConnectionInterface::SignalingState state = signaling_state();
  if (type == SdpType::kOffer) {
    return (state == PeerConnectionInterface::kStable) ||
           (state == PeerConnectionInterface::kHaveLocalOffer);
  } else {
    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
    return (state == PeerConnectionInterface::kHaveRemoteOffer) ||
           (state == PeerConnectionInterface::kHaveLocalPrAnswer);
  }
}

bool SdpOfferAnswerHandler::ExpectSetRemoteDescription(SdpType type) {
  PeerConnectionInterface::SignalingState state = signaling_state();
  if (type == SdpType::kOffer) {
    return (state == PeerConnectionInterface::kStable) ||
           (state == PeerConnectionInterface::kHaveRemoteOffer);
  } else {
    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
    return (state == PeerConnectionInterface::kHaveLocalOffer) ||
           (state == PeerConnectionInterface::kHaveRemotePrAnswer);
  }
}

void SdpOfferAnswerHandler::FillInMissingRemoteMids(
    SessionDescription* new_remote_description) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(new_remote_description);
  const ContentInfos no_infos;
  const ContentInfos& local_contents =
      (local_description() ? local_description()->description()->contents()
                           : no_infos);
  const ContentInfos& remote_contents =
      (remote_description() ? remote_description()->description()->contents()
                            : no_infos);
  for (size_t i = 0; i < new_remote_description->contents().size(); ++i) {
    ContentInfo& content = new_remote_description->contents()[i];
    if (!content.mid().empty()) {
      continue;
    }
    std::string new_mid;
    absl::string_view source_explanation;
    if (IsUnifiedPlan()) {
      if (i < local_contents.size()) {
        new_mid = local_contents[i].mid();
        source_explanation = "from the matching local media section";
      } else if (i < remote_contents.size()) {
        new_mid = remote_contents[i].mid();
        source_explanation = "from the matching previous remote media section";
      } else {
        new_mid = mid_generator_.GenerateString();
        source_explanation = "generated just now";
      }
    } else {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      new_mid = GetDefaultMidForPlanB(content.media_description()->type());
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
      source_explanation = "to match pre-existing behavior";
    }
    RTC_DCHECK(!new_mid.empty());
    content.set_mid(new_mid);
    new_remote_description->transport_infos()[i].content_name = new_mid;
    RTC_LOG(LS_INFO) << "SetRemoteDescription: Remote media section at i=" << i
                     << " is missing an a=mid line. Filling in the value '"
                     << new_mid << "' " << source_explanation << ".";
  }
}

scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
SdpOfferAnswerHandler::FindAvailableTransceiverToReceive(
    MediaType media_type) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());
  // From JSEP section 5.10 (Applying a Remote Description):
  // If the m= section is sendrecv or recvonly, and there are RtpTransceivers of
  // the same type that were added to the PeerConnection by addTrack and are not
  // associated with any m= section and are not stopped, find the first such
  // RtpTransceiver.
  for (auto transceiver : transceivers()->List()) {
    if (transceiver->media_type() == media_type &&
        transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
        !transceiver->stopped()) {
      return transceiver;
    }
  }
  return nullptr;
}

const ContentInfo* SdpOfferAnswerHandler::FindMediaSectionForTransceiver(
    const RtpTransceiver* transceiver,
    const SessionDescriptionInterface* sdesc) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(transceiver);
  RTC_DCHECK(sdesc);
  if (IsUnifiedPlan()) {
    if (!transceiver->mid()) {
      // This transceiver is not associated with a media section yet.
      return nullptr;
    }
    return sdesc->description()->GetContentByName(*transceiver->mid());
  } else {
    // Plan B only allows at most one audio and one video section, so use the
    // first media section of that type.
    return GetFirstMediaContent(sdesc->description()->contents(),
                                transceiver->media_type());
  }
}

void SdpOfferAnswerHandler::GetOptionsForOffer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);

  if (IsUnifiedPlan()) {
    GetOptionsForUnifiedPlanOffer(offer_answer_options, session_options);
  } else {
    RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
    GetOptionsForPlanBOffer(offer_answer_options, session_options);
    RTC_ALLOW_PLAN_B_DEPRECATION_END();
  }

  // Apply ICE restart flag and renomination flag.
  bool ice_restart = offer_answer_options.ice_restart || HasNewIceCredentials();
  for (auto& options : session_options->media_description_options) {
    options.transport_options.ice_restart = ice_restart;
    options.transport_options.enable_ice_renomination =
        pc_->configuration()->enable_ice_renomination;
  }

  session_options->rtcp_cname = rtcp_cname_;
  session_options->crypto_options = pc_->GetCryptoOptions();
  session_options->pooled_ice_credentials = cached_pooled_ice_credentials_;
  session_options->offer_extmap_allow_mixed =
      pc_->configuration()->offer_extmap_allow_mixed;

  // Allow fallback for using obsolete SCTP syntax.
  // Note that the default in `session_options` is true, while
  // the default in `options` is false.
  session_options->use_obsolete_sctp_sdp =
      offer_answer_options.use_obsolete_sctp_sdp;
  // draft-hancke-tsvwg-snap.
  session_options->use_sctp_snap = pc_->trials().IsEnabled("WebRTC-Sctp-Snap");
}

void SdpOfferAnswerHandler::GetOptionsForPlanBOffer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  bool offer_new_data_description =
      data_channel_controller()->HasUsedDataChannels();
  bool send_audio = false;
  bool send_video = false;
  bool recv_audio = false;
  bool recv_video = false;
  if (ConfiguredForMedia()) {
    // Figure out transceiver directional preferences.
    send_audio =
        !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
    send_video =
        !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();

    // By default, generate sendrecv/recvonly m= sections.
    recv_audio = true;
    recv_video = true;
  }
  // By default, only offer a new m= section if we have media to send with it.
  bool offer_new_audio_description = send_audio;
  bool offer_new_video_description = send_video;
  if (ConfiguredForMedia()) {
    // The "offer_to_receive_X" options allow those defaults to be overridden.
    if (offer_answer_options.offer_to_receive_audio !=
        PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
      recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
      offer_new_audio_description =
          offer_new_audio_description ||
          (offer_answer_options.offer_to_receive_audio > 0);
    }
    if (offer_answer_options.offer_to_receive_video !=
        RTCOfferAnswerOptions::kUndefined) {
      recv_video = (offer_answer_options.offer_to_receive_video > 0);
      offer_new_video_description =
          offer_new_video_description ||
          (offer_answer_options.offer_to_receive_video > 0);
    }
  }
  std::optional<size_t> audio_index;
  std::optional<size_t> video_index;
  std::optional<size_t> data_index;
  // If a current description exists, generate m= sections in the same order,
  // using the first audio/video/data section that appears and rejecting
  // extraneous ones.
  if (local_description()) {
    GenerateMediaDescriptionOptions(
        local_description(),
        RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
        RtpTransceiverDirectionFromSendRecv(send_video, recv_video),
        &audio_index, &video_index, &data_index, session_options);
  }

  if (ConfiguredForMedia()) {
    // Add audio/video/data m= sections to the end if needed.
    if (!audio_index && offer_new_audio_description) {
      MediaDescriptionOptions options(
          MediaType::AUDIO, GetDefaultMidForPlanB(MediaType::AUDIO),
          RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio), false);
      options.header_extensions =
          media_engine()->voice().GetRtpHeaderExtensions(&pc_->trials());
      session_options->media_description_options.push_back(options);
      audio_index = session_options->media_description_options.size() - 1;
    }
    if (!video_index && offer_new_video_description) {
      MediaDescriptionOptions options(
          MediaType::VIDEO, GetDefaultMidForPlanB(MediaType::VIDEO),
          RtpTransceiverDirectionFromSendRecv(send_video, recv_video), false);
      options.header_extensions =
          media_engine()->video().GetRtpHeaderExtensions(&pc_->trials());
      session_options->media_description_options.push_back(options);
      video_index = session_options->media_description_options.size() - 1;
    }
    MediaDescriptionOptions* audio_media_description_options =
        !audio_index
            ? nullptr
            : &session_options->media_description_options[*audio_index];
    MediaDescriptionOptions* video_media_description_options =
        !video_index
            ? nullptr
            : &session_options->media_description_options[*video_index];

    AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
                             audio_media_description_options,
                             video_media_description_options,
                             offer_answer_options.num_simulcast_layers);
  }
  if (!data_index && offer_new_data_description) {
    session_options->media_description_options.push_back(
        GetMediaDescriptionOptionsForActiveData(
            GetDefaultMidForPlanB(MediaType::DATA)));
  }
}

void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanOffer(
    const RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  // Rules for generating an offer are dictated by JSEP sections 5.2.1 (Initial
  // Offers) and 5.2.2 (Subsequent Offers).
  RTC_DCHECK_EQ(session_options->media_description_options.size(), 0);
  const ContentInfos no_infos;
  const ContentInfos& local_contents =
      (local_description() ? local_description()->description()->contents()
                           : no_infos);
  const ContentInfos& remote_contents =
      (remote_description() ? remote_description()->description()->contents()
                            : no_infos);
  // The mline indices that can be recycled. New transceivers should reuse these
  // slots first.
  std::queue<size_t> recycleable_mline_indices;
  // First, go through each media section that exists in either the local or
  // remote description and generate a media section in this offer for the
  // associated transceiver. If a media section can be recycled, generate a
  // default, rejected media section here that can be later overwritten.
  for (size_t i = 0;
       i < std::max(local_contents.size(), remote_contents.size()); ++i) {
    // Either `local_content` or `remote_content` is non-null.
    const ContentInfo* local_content =
        (i < local_contents.size() ? &local_contents[i] : nullptr);
    const ContentInfo* current_local_content =
        GetContentByIndex(current_local_description(), i);
    const ContentInfo* remote_content =
        (i < remote_contents.size() ? &remote_contents[i] : nullptr);
    const ContentInfo* current_remote_content =
        GetContentByIndex(current_remote_description(), i);
    bool had_been_rejected =
        (current_local_content && current_local_content->rejected) ||
        (current_remote_content && current_remote_content->rejected);
    const absl::string_view mid =
        local_content ? local_content->mid() : remote_content->mid();
    MediaType media_type =
        (local_content ? local_content->media_description()->type()
                       : remote_content->media_description()->type());
    if (media_type == MediaType::AUDIO || media_type == MediaType::VIDEO) {
      // A media section is considered eligible for recycling if it is marked as
      // rejected in either the current local or current remote description.
      auto transceiver = transceivers()->FindByMid(mid);
      if (!transceiver) {
        // No associated transceiver. The media section has been stopped.
        recycleable_mline_indices.push(i);
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(media_type, mid,
                                    RtpTransceiverDirection::kInactive,
                                    /*stopped=*/true));
      } else {
        // NOTE: a stopping transceiver should be treated as a stopped one in
        // createOffer as specified in
        // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
        if (had_been_rejected && transceiver->stopping()) {
          session_options->media_description_options.push_back(
              MediaDescriptionOptions(transceiver->media_type(), mid,
                                      RtpTransceiverDirection::kInactive,
                                      /*stopped=*/true));
          recycleable_mline_indices.push(i);
        } else {
          session_options->media_description_options.push_back(
              GetMediaDescriptionOptionsForTransceiver(
                  transceiver->internal(), mid,
                  /*is_create_offer=*/true));
          // CreateOffer shouldn't really cause any state changes in
          // PeerConnection, but we need a way to match new transceivers to new
          // media sections in SetLocalDescription and JSEP specifies this is
          // done by recording the index of the media section generated for the
          // transceiver in the offer.
          transceiver->internal()->set_mline_index(i);
        }
      }
    } else if (media_type == MediaType::UNSUPPORTED) {
      RTC_DCHECK(local_content->rejected);
      session_options->media_description_options.push_back(
          MediaDescriptionOptions(media_type, mid,
                                  RtpTransceiverDirection::kInactive,
                                  /*stopped=*/true));
    } else {
      RTC_CHECK_EQ(MediaType::DATA, media_type);
      if (had_been_rejected) {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForRejectedData(mid));
      } else {
        const auto data_mid = pc_->sctp_mid();
        if (data_mid.has_value() && mid == data_mid.value()) {
          session_options->media_description_options.push_back(
              GetMediaDescriptionOptionsForActiveData(mid));
        } else {
          if (!data_mid.has_value()) {
            RTC_LOG(LS_ERROR) << "Datachannel transport not available: " << mid;
          }
          session_options->media_description_options.push_back(
              GetMediaDescriptionOptionsForRejectedData(mid));
        }
      }
    }
  }

  // Add a datachannel m-line first if explicitly asked even when no data
  // channels exist.
  if (pc_->configuration()->always_negotiate_data_channels &&
      !pc_->sctp_mid()) {
    MaybeNegotiateSctp(session_options);
  }

  // Next, look for transceivers that are newly added (that is, are not stopped
  // and not associated). Reuse media sections marked as recyclable first,
  // otherwise append to the end of the offer. New media sections should be
  // added in the order they were added to the PeerConnection.
  if (ConfiguredForMedia()) {
    for (const auto& transceiver : transceivers()->ListInternal()) {
      if (transceiver->mid() || transceiver->stopping()) {
        continue;
      }
      size_t mline_index;
      if (!recycleable_mline_indices.empty()) {
        mline_index = recycleable_mline_indices.front();
        recycleable_mline_indices.pop();
        session_options->media_description_options[mline_index] =
            GetMediaDescriptionOptionsForTransceiver(
                transceiver, mid_generator_.GenerateString(),
                /*is_create_offer=*/true);
      } else {
        mline_index = session_options->media_description_options.size();
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForTransceiver(
                transceiver, mid_generator_.GenerateString(),
                /*is_create_offer=*/true));
      }
      // See comment above for why CreateOffer changes the transceiver's state.
      transceiver->set_mline_index(mline_index);
    }
  }
  // Lastly, add a m-section if we have requested local data channels and an
  // m section does not already exist.
  if (!pc_->configuration()->always_negotiate_data_channels &&
      !pc_->sctp_mid() && data_channel_controller()->HasDataChannels()) {
    MaybeNegotiateSctp(session_options);
  }
}

void SdpOfferAnswerHandler::MaybeNegotiateSctp(
    MediaSessionOptions* session_options) {
  // Attempt to recycle a stopped m-line.
  // TODO(crbug.com/1442604): sctp_mid() should return the mid if one was
  // ever created but rejected.
  for (size_t i = 0; i < session_options->media_description_options.size();
       i++) {
    auto media_description = session_options->media_description_options[i];
    if (media_description.type == MediaType::DATA &&
        media_description.stopped) {
      session_options->media_description_options[i] =
          GetMediaDescriptionOptionsForActiveData(media_description.mid);
      return;
    }
  }
  // Generate a new m-line.
  session_options->media_description_options.push_back(
      GetMediaDescriptionOptionsForActiveData(mid_generator_.GenerateString()));
}

void SdpOfferAnswerHandler::GetOptionsForAnswer(
    const RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);

  if (IsUnifiedPlan()) {
    GetOptionsForUnifiedPlanAnswer(offer_answer_options, session_options);
  } else {
    RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
    GetOptionsForPlanBAnswer(offer_answer_options, session_options);
    RTC_ALLOW_PLAN_B_DEPRECATION_END();
  }

  // Apply ICE renomination flag.
  for (auto& options : session_options->media_description_options) {
    options.transport_options.enable_ice_renomination =
        pc_->configuration()->enable_ice_renomination;
  }

  session_options->rtcp_cname = rtcp_cname_;
  session_options->crypto_options = pc_->GetCryptoOptions();
  session_options->pooled_ice_credentials = cached_pooled_ice_credentials_;
  // draft-hancke-tsvwg-snap.
  session_options->use_sctp_snap = pc_->trials().IsEnabled("WebRTC-Sctp-Snap");
}

void SdpOfferAnswerHandler::GetOptionsForPlanBAnswer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  bool send_audio = false;
  bool recv_audio = false;
  bool send_video = false;
  bool recv_video = false;

  if (ConfiguredForMedia()) {
    // Figure out transceiver directional preferences.
    send_audio =
        !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
    send_video =
        !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();

    // By default, generate sendrecv/recvonly m= sections. The direction is also
    // restricted by the direction in the offer.
    recv_audio = true;
    recv_video = true;

    // The "offer_to_receive_X" options allow those defaults to be overridden.
    if (offer_answer_options.offer_to_receive_audio !=
        RTCOfferAnswerOptions::kUndefined) {
      recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
    }
    if (offer_answer_options.offer_to_receive_video !=
        RTCOfferAnswerOptions::kUndefined) {
      recv_video = (offer_answer_options.offer_to_receive_video > 0);
    }
  }

  std::optional<size_t> audio_index;
  std::optional<size_t> video_index;
  std::optional<size_t> data_index;

  // Generate m= sections that match those in the offer.
  // Note that mediasession.cc will handle intersection our preferred
  // direction with the offered direction.
  GenerateMediaDescriptionOptions(
      remote_description(),
      RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
      RtpTransceiverDirectionFromSendRecv(send_video, recv_video), &audio_index,
      &video_index, &data_index, session_options);

  MediaDescriptionOptions* audio_media_description_options =
      !audio_index ? nullptr
                   : &session_options->media_description_options[*audio_index];
  MediaDescriptionOptions* video_media_description_options =
      !video_index ? nullptr
                   : &session_options->media_description_options[*video_index];

  if (ConfiguredForMedia()) {
    AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
                             audio_media_description_options,
                             video_media_description_options,
                             offer_answer_options.num_simulcast_layers);
  }
}

void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanAnswer(
    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
    MediaSessionOptions* session_options) {
  // Rules for generating an answer are dictated by JSEP sections 5.3.1 (Initial
  // Answers) and 5.3.2 (Subsequent Answers).
  RTC_DCHECK(remote_description());
  RTC_DCHECK(remote_description()->GetType() == SdpType::kOffer);
  for (const ContentInfo& content :
       remote_description()->description()->contents()) {
    MediaType media_type = content.media_description()->type();
    if (media_type == MediaType::AUDIO || media_type == MediaType::VIDEO) {
      auto transceiver = transceivers()->FindByMid(content.mid());
      if (transceiver) {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForTransceiver(
                transceiver->internal(), content.mid(),
                /*is_create_offer=*/false));
      } else {
        // This should only happen with rejected transceivers.
        RTC_DCHECK(content.rejected);
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(media_type, content.mid(),
                                    RtpTransceiverDirection::kInactive,
                                    /*stopped=*/true));
      }
    } else if (media_type == MediaType::UNSUPPORTED) {
      RTC_DCHECK(content.rejected);
      session_options->media_description_options.push_back(
          MediaDescriptionOptions(media_type, content.mid(),
                                  RtpTransceiverDirection::kInactive,
                                  /*stopped=*/true));
    } else {
      RTC_CHECK_EQ(MediaType::DATA, media_type);
      // Reject all data sections if data channels are disabled.
      // Reject a data section if it has already been rejected.
      // Reject all data sections except for the first one.
      auto sctp_mid = pc_->sctp_mid();
      if (content.rejected ||
          (sctp_mid.has_value() && content.mid() != *sctp_mid)) {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForRejectedData(content.mid()));
      } else {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForActiveData(content.mid()));
      }
    }
  }
}

const char* SdpOfferAnswerHandler::SessionErrorToString(
    SessionError error) const {
  switch (error) {
    case SessionError::kNone:
      return "ERROR_NONE";
    case SessionError::kContent:
      return "ERROR_CONTENT";
    case SessionError::kTransport:
      return "ERROR_TRANSPORT";
  }
  RTC_DCHECK_NOTREACHED();
  return "";
}

std::string SdpOfferAnswerHandler::GetSessionErrorMsg() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  StringBuilder desc;
  desc << kSessionError << SessionErrorToString(session_error()) << ". ";
  desc << kSessionErrorDesc << session_error_desc() << ".";
  return desc.Release();
}

void SdpOfferAnswerHandler::SetSessionError(SessionError error,
                                            const std::string& error_desc) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (error != session_error_) {
    session_error_ = error;
    session_error_desc_ = error_desc;
  }
}

RTCError SdpOfferAnswerHandler::HandleLegacyOfferOptions(
    const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(IsUnifiedPlan());

  if (options.offer_to_receive_audio == 0) {
    RemoveRecvDirectionFromReceivingTransceiversOfType(MediaType::AUDIO);
  } else if (options.offer_to_receive_audio == 1) {
    AddUpToOneReceivingTransceiverOfType(MediaType::AUDIO);
  } else if (options.offer_to_receive_audio > 1) {
    return LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
                     << "offer_to_receive_audio > 1 is not supported.");
  }

  if (options.offer_to_receive_video == 0) {
    RemoveRecvDirectionFromReceivingTransceiversOfType(MediaType::VIDEO);
  } else if (options.offer_to_receive_video == 1) {
    AddUpToOneReceivingTransceiverOfType(MediaType::VIDEO);
  } else if (options.offer_to_receive_video > 1) {
    return LOG_ERROR(RTCError(RTCErrorType::UNSUPPORTED_PARAMETER)
                     << "offer_to_receive_video > 1 is not supported.");
  }

  return RTCError::OK();
}

void SdpOfferAnswerHandler::RemoveRecvDirectionFromReceivingTransceiversOfType(
    MediaType media_type) {
  for (const auto& transceiver : GetReceivingTransceiversOfType(media_type)) {
    RtpTransceiverDirection new_direction =
        RtpTransceiverDirectionWithRecvSet(transceiver->direction(), false);
    if (new_direction != transceiver->direction()) {
      RTC_LOG(LS_INFO) << "Changing " << MediaTypeToString(media_type)
                       << " transceiver (MID="
                       << transceiver->mid().value_or("<not set>") << ") from "
                       << RtpTransceiverDirectionToString(
                              transceiver->direction())
                       << " to "
                       << RtpTransceiverDirectionToString(new_direction)
                       << " since CreateOffer specified offer_to_receive=0";
      transceiver->internal()->set_direction(new_direction);
    }
  }
}

void SdpOfferAnswerHandler::AddUpToOneReceivingTransceiverOfType(
    MediaType media_type) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (GetReceivingTransceiversOfType(media_type).empty()) {
    RTC_LOG(LS_INFO)
        << "Adding one recvonly " << MediaTypeToString(media_type)
        << " transceiver since CreateOffer specified offer_to_receive=1";
    RtpTransceiverInit init;
    init.direction = RtpTransceiverDirection::kRecvOnly;
    pc_->AddTransceiver(media_type, nullptr, init,
                        /*update_negotiation_needed=*/false);
  }
}

std::vector<scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
SdpOfferAnswerHandler::GetReceivingTransceiversOfType(MediaType media_type) {
  std::vector<scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
      receiving_transceivers;
  for (const auto& transceiver : transceivers()->List()) {
    if (!transceiver->stopped() && transceiver->media_type() == media_type &&
        RtpTransceiverDirectionHasRecv(transceiver->direction())) {
      receiving_transceivers.push_back(transceiver);
    }
  }
  return receiving_transceivers;
}

void SdpOfferAnswerHandler::ProcessRemovalOfRemoteTrack(
    scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>> transceiver,
    std::vector<scoped_refptr<RtpTransceiverInterface>>* remove_list,
    std::vector<scoped_refptr<MediaStreamInterface>>* removed_streams) {
  RTC_DCHECK(transceiver->mid());
  RTC_LOG(LS_INFO) << "Processing the removal of a track for MID="
                   << *transceiver->mid();
  std::vector<scoped_refptr<MediaStreamInterface>> previous_streams =
      transceiver->internal()->receiver_internal()->streams();
  // This will remove the remote track from the streams.
  transceiver->internal()->receiver_internal()->set_stream_ids({});
  remove_list->push_back(transceiver);
  RemoveRemoteStreamsIfEmpty(previous_streams, removed_streams);
}

void SdpOfferAnswerHandler::RemoveRemoteStreamsIfEmpty(
    const std::vector<scoped_refptr<MediaStreamInterface>>& remote_streams,
    std::vector<scoped_refptr<MediaStreamInterface>>* removed_streams) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  // TODO(https://crbug.com/webrtc/9480): When we use stream IDs instead of
  // streams, see if the stream was removed by checking if this was the last
  // receiver with that stream ID.
  for (const auto& remote_stream : remote_streams) {
    if (remote_stream->GetAudioTracks().empty() &&
        remote_stream->GetVideoTracks().empty()) {
      remote_streams_->RemoveStream(remote_stream.get());
      removed_streams->push_back(remote_stream);
    }
  }
}

void SdpOfferAnswerHandler::RemoveSenders(MediaType media_type) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(!IsUnifiedPlan());
  UpdateLocalSendersPlanB(std::vector<StreamParams>(), media_type);
  UpdateRemoteSendersListPlanB(std::vector<StreamParams>(), false, media_type,
                               nullptr);
}

void SdpOfferAnswerHandler::UpdateLocalSendersPlanB(
    const std::vector<StreamParams>& streams,
    MediaType media_type) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::UpdateLocalSendersPlanB");
  RTC_DCHECK(!IsUnifiedPlan());
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::vector<RtpSenderInfo>* current_senders =
      rtp_manager()->GetLocalSenderInfos(media_type);

  // Find removed tracks. I.e., tracks where the track id, stream id or ssrc
  // don't match the new StreamParam.
  for (auto sender_it = current_senders->begin();
       sender_it != current_senders->end();
       /* incremented manually */) {
    const RtpSenderInfo& info = *sender_it;
    const StreamParams* params = GetStreamBySsrc(streams, info.first_ssrc);
    if (!params || params->id != info.sender_id ||
        params->first_stream_id() != info.stream_id) {
      rtp_manager()->OnLocalSenderRemoved(info, media_type);
      sender_it = current_senders->erase(sender_it);
    } else {
      ++sender_it;
    }
  }

  // Find new and active senders.
  for (const StreamParams& params : streams) {
    // The sync_label is the MediaStream label and the `stream.id` is the
    // sender id.
    bool found = absl::c_any_of(*current_senders, [&](const autoinfo) {
      return info.stream_id == params.first_stream_id() &&
             info.sender_id == params.id;
    });
    if (!found) {
      current_senders->push_back(RtpSenderInfo(params.first_stream_id(),
                                               params.id, params.first_ssrc()));
      rtp_manager()->OnLocalSenderAdded(current_senders->back(), media_type);
    }
  }
}

void SdpOfferAnswerHandler::UpdateRemoteSendersListPlanB(
    const StreamParamsVec& streams,
    bool default_sender_needed,
    MediaType media_type,
    StreamCollection* new_streams) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::UpdateRemoteSendersListPlanB");
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(!IsUnifiedPlan());

  std::vector<RtpSenderInfo>* current_senders =
      rtp_manager()->GetRemoteSenderInfos(media_type);

  // Find removed senders. I.e., senders where the sender id or ssrc don't match
  // the new StreamParam.
  for (auto sender_it = current_senders->begin();
       sender_it != current_senders->end();
       /* incremented manually */) {
    const RtpSenderInfo& info = *sender_it;
    const StreamParams* params = GetStreamBySsrc(streams, info.first_ssrc);
    std::string params_stream_id;
    if (params) {
      params_stream_id =
          (!params->first_stream_id().empty() ? params->first_stream_id()
                                              : kDefaultStreamId);
    }
    bool sender_exists = params && params->id == info.sender_id &&
                         params_stream_id == info.stream_id;
    // If this is a default track, and we still need it, don't remove it.
    if ((info.stream_id == kDefaultStreamId && default_sender_needed) ||
        sender_exists) {
      ++sender_it;
    } else {
      rtp_manager()->OnRemoteSenderRemovedPlanB(
          info, remote_streams_->find(info.stream_id), media_type);
      sender_it = current_senders->erase(sender_it);
    }
  }

  // Find new and active senders.
  for (const StreamParams& params : streams) {
    if (!params.has_ssrcs()) {
      // The remote endpoint has streams, but didn't signal ssrcs. For an active
      // sender, this means it is coming from a Unified Plan endpoint,so we just
      // create a default.
      default_sender_needed = true;
      break;
    }

    // `params.id` is the sender id and the stream id uses the first of
    // `params.stream_ids`. The remote description could come from a Unified
    // Plan endpoint, with multiple or no stream_ids() signaled. Since this is
    // not supported in Plan B, we just take the first here and create the
    // default stream ID if none is specified.
    std::string stream_id = (!params.first_stream_id().empty()
                                 ? std::string(params.first_stream_id())
                                 : kDefaultStreamId);
    const std::string& sender_id = params.id;
    uint32_t ssrc = params.first_ssrc();

    scoped_refptr<MediaStreamInterface> stream(
        remote_streams_->find(stream_id));
    if (!stream) {
      // This is a new MediaStream. Create a new remote MediaStream.
      stream = MediaStreamProxy::Create(Thread::Current(),
                                        MediaStream::Create(stream_id));
      remote_streams_->AddStream(stream);
      new_streams->AddStream(stream);
    }
    bool found = absl::c_any_of(*current_senders, [&](const autoinfo) {
      return info.stream_id == stream_id && info.sender_id == sender_id;
    });
    if (!found) {
      current_senders->push_back(RtpSenderInfo(stream_id, sender_id, ssrc));
      rtp_manager()->OnRemoteSenderAddedPlanB(current_senders->back(),
                                              stream.get(), media_type);
    }
  }

  // Add default sender if necessary.
  if (default_sender_needed) {
    scoped_refptr<MediaStreamInterface> default_stream(
        remote_streams_->find(kDefaultStreamId));
    if (!default_stream) {
      // Create the new default MediaStream.
      default_stream = MediaStreamProxy::Create(
          Thread::Current(), MediaStream::Create(kDefaultStreamId));
      remote_streams_->AddStream(default_stream);
      new_streams->AddStream(default_stream);
    }
    absl::string_view default_sender_id = (media_type == MediaType::AUDIO)
                                              ? kDefaultAudioSenderId
                                              : kDefaultVideoSenderId;
    bool found = absl::c_any_of(*current_senders, [&](const autoinfo) {
      return info.stream_id == kDefaultStreamId &&
             info.sender_id == default_sender_id;
    });

    if (!found) {
      current_senders->push_back(
          RtpSenderInfo(kDefaultStreamId, default_sender_id, /*ssrc=*/0));
      rtp_manager()->OnRemoteSenderAddedPlanB(current_senders->back(),
                                              default_stream.get(), media_type);
    }
  }
}

void SdpOfferAnswerHandler::EnableSending() {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::EnableSending");
  RTC_DCHECK_RUN_ON(signaling_thread());
  if (!ConfiguredForMedia()) {
    return;
  }
  for (const auto& transceiver : transceivers()->ListInternal()) {
    if (transceiver->HasChannel()) {
      transceiver->EnableChannel(true);
    }
  }
}

RTCError SdpOfferAnswerHandler::PushdownMediaDescription(
    SdpType type,
    ContentSource source,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::PushdownMediaDescription");
  RTC_DCHECK_RUN_ON(signaling_thread());
  const SessionDescriptionInterface* sdesc =
      (source == CS_LOCAL ? local_description() : remote_description());
  RTC_DCHECK(sdesc);

  if (ConfiguredForMedia()) {
    // Batch up operations for the worker thread.
    ScopedOperationsBatcher batcher(context_->worker_thread());

    // Note: This may perform a BlockingCall over to the network thread, which
    // we'll also do in a loop below.
    UpdatePayloadTypeDemuxingState(source, bundle_groups_by_mid, batcher);

    // Push down the new SDP media section for each audio/video transceiver.
    auto rtp_transceivers = transceivers()->ListInternal();
    std::vector<std::pair<RtpTransceiver*, const MediaContentDescription*>>
        channels;
    std::optional<RtcpFeedbackType> preferred_rtcp_cc_ack_type;
    bool all_rtp_have_same_cc_ack_type = true;
    bool any_rtp_has_cc_ack_type = false;
    std::optional<RtcpFeedbackType> first_rtp_cc_ack_type;

    for (const auto& transceiver : rtp_transceivers) {
      const ContentInfo* content_info =
          FindMediaSectionForTransceiver(transceiver, sdesc);
      if (!transceiver->HasChannel() || !content_info ||
          content_info->rejected) {
        continue;
      }
      const MediaContentDescription* content_desc =
          content_info->media_description();
      if (!content_desc) {
        continue;
      }

      if (!first_rtp_cc_ack_type.has_value()) {
        first_rtp_cc_ack_type = content_desc->preferred_rtcp_cc_ack_type();
      }

      if (content_desc->preferred_rtcp_cc_ack_type().has_value()) {
        any_rtp_has_cc_ack_type = true;
      }

      if (first_rtp_cc_ack_type != content_desc->preferred_rtcp_cc_ack_type()) {
        all_rtp_have_same_cc_ack_type = false;
      }

      transceiver->OnNegotiationUpdate(type, content_desc);
      channels.push_back(std::make_pair(transceiver, content_desc));
    }

    if (any_rtp_has_cc_ack_type && all_rtp_have_same_cc_ack_type) {
      preferred_rtcp_cc_ack_type = first_rtp_cc_ack_type;
    } else if (any_rtp_has_cc_ack_type && !all_rtp_have_same_cc_ack_type) {
      RTC_LOG(LS_ERROR) << "Warning: Inconsistent congestion control feedback "
                           "types, ignoring all.";
    }

    for (const auto& [transceiver, content] : channels) {
      if (source == CS_LOCAL) {
        transceiver->SetChannelLocalContent(content, type, batcher);
      } else {
        transceiver->SetChannelRemoteContent(content, type, batcher);
      }
    }

    RTCError error = batcher.Run();
    if (!error.ok()) {
      return error;
    }

    // If local and remote are both set, we assume that it's safe to trigger
    // CCFB.
    if (pc_->trials().IsEnabled("WebRTC-RFC8888CongestionControlFeedback")) {
      if ((type == SdpType::kAnswer || type == SdpType::kPrAnswer) &&
          local_description() && remote_description()) {
        std::optional<RtcpFeedbackType> remote_preferred_rtcp_cc_ack_type;
        bool remote_all_rtp_have_same_cc_ack_type = true;
        bool remote_any_rtp_has_cc_ack_type = false;
        std::optional<RtcpFeedbackType> remote_first_rtp_cc_ack_type;
        // Verify that the remote agrees on congestion control feedback format.
        for (const auto& content :
             remote_description()->description()->contents()) {
          if (content.type != MediaProtocolType::kRtp || content.rejected ||
              content.media_description() == nullptr) {
            continue;
          }

          if (!remote_first_rtp_cc_ack_type.has_value()) {
            remote_first_rtp_cc_ack_type =
                content.media_description()->preferred_rtcp_cc_ack_type();
          }

          if (content.media_description()
                  ->preferred_rtcp_cc_ack_type()
                  .has_value()) {
            remote_any_rtp_has_cc_ack_type = true;
          }

          if (remote_first_rtp_cc_ack_type !=
              content.media_description()->preferred_rtcp_cc_ack_type()) {
            remote_all_rtp_have_same_cc_ack_type = false;
          }
        }

        if (remote_any_rtp_has_cc_ack_type &&
            remote_all_rtp_have_same_cc_ack_type) {
          remote_preferred_rtcp_cc_ack_type = remote_first_rtp_cc_ack_type;
        } else if (remote_any_rtp_has_cc_ack_type &&
                   !remote_all_rtp_have_same_cc_ack_type) {
          RTC_LOG(LS_ERROR) << "Warning: Inconsistent remote congestion "
                               "control feedback types, ignoring all.";
        }

        if (preferred_rtcp_cc_ack_type.has_value()) {
          if (preferred_rtcp_cc_ack_type == remote_preferred_rtcp_cc_ack_type) {
            // The call and the congestion controller live on the worker thread.
            context_->worker_thread()->PostTask(
                [call = pc_->call_ptr(),
                 preferred_rtcp_cc_ack_type = *preferred_rtcp_cc_ack_type] {
                  call->SetPreferredRtcpCcAckType(preferred_rtcp_cc_ack_type);
                });
          } else {
            RTC_LOG(LS_WARNING)
                << "Inconsistent Congestion Control feedback types: "
                << preferred_rtcp_cc_ack_type << " vs "
                << remote_preferred_rtcp_cc_ack_type << " Using default.";
          }
        }
      }
    }
  }
  // Need complete offer/answer with an SCTP m= section before starting SCTP,
  // according to https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-19
  if (pc_->sctp_mid() && local_description() && remote_description()) {
    auto local_sctp_description =
        GetFirstSctpDataContentDescription(local_description()->description());
    auto remote_sctp_description =
        GetFirstSctpDataContentDescription(remote_description()->description());
    if (local_sctp_description && remote_sctp_description) {
      int max_message_size;
      // A remote max message size of zero means "any size supported".
      // We configure the connection with our own max message size.
      if (remote_sctp_description->max_message_size() == 0) {
        max_message_size = local_sctp_description->max_message_size();
      } else {
        max_message_size =
            std::min(local_sctp_description->max_message_size(),
                     remote_sctp_description->max_message_size());
      }
      RTCError error = pc_->StartSctpTransport(
          {.local_port = local_sctp_description->port(),
           .remote_port = remote_sctp_description->port(),
           .max_message_size = max_message_size,
           .max_sctp_streams = max_sctp_streams_,
           .local_init = local_sctp_description->sctp_init(),
           .remote_init = remote_sctp_description->sctp_init()});
      if (!error.ok()) {
        return error;
      }
    }
  }

  return RTCError::OK();
}

RTCError SdpOfferAnswerHandler::PushdownTransportDescription(
    ContentSource source,
    SdpType type) {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::PushdownTransportDescription");
  RTC_DCHECK_RUN_ON(signaling_thread());

  if (source == CS_LOCAL) {
    const SessionDescriptionInterface* sdesc = local_description();
    RTC_DCHECK(sdesc);
    // Inform our local bundle manager of the changes
    pt_suggester_.Update(sdesc->description(), /* local= */ true, type);
    const auto* remote = remote_description();
    return transport_controller_s()->SetLocalDescription(
        type, sdesc->description(), remote ? remote->description() : nullptr);
  } else {
    const SessionDescriptionInterface* sdesc = remote_description();
    RTC_DCHECK(sdesc);
    // Inform our local bundle manager of the changes
    pt_suggester_.Update(sdesc->description(), /* local= */ false, type);
    const auto* local = local_description();
    return transport_controller_s()->SetRemoteDescription(
        type, local ? local->description() : nullptr, sdesc->description());
  }
}

void SdpOfferAnswerHandler::RemoveStoppedTransceivers() {
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::RemoveStoppedTransceivers");
  RTC_DCHECK_RUN_ON(signaling_thread());
  // 3.2.10.1: For each transceiver in the connection's set of transceivers
  //           run the following steps:
  if (!IsUnifiedPlan())
    return;
  if (!ConfiguredForMedia()) {
    return;
  }
  // Traverse a copy of the transceiver list.
  auto transceiver_list = transceivers()->List();
  for (auto transceiver : transceiver_list) {
    // 3.2.10.1.1: If transceiver is stopped, associated with an m= section
    //             and the associated m= section is rejected in
    //             connection.[[CurrentLocalDescription]] or
    //             connection.[[CurrentRemoteDescription]], remove the
    //             transceiver from the connection's set of transceivers.
    if (!transceiver->stopped()) {
      continue;
    }
    const ContentInfo* local_content = FindMediaSectionForTransceiver(
        transceiver->internal(), local_description());
    const ContentInfo* remote_content = FindMediaSectionForTransceiver(
        transceiver->internal(), remote_description());
    if ((local_content && local_content->rejected) ||
        (remote_content && remote_content->rejected)) {
      RTC_LOG(LS_INFO) << "Dissociating transceiver"
                          " since the media section is being recycled.";
      transceiver->internal()->set_mid(std::nullopt);
      transceiver->internal()->set_mline_index(std::nullopt);
    } else if (!local_content && !remote_content) {
      // TODO(bugs.webrtc.org/11973): Consider if this should be removed already
      // See https://github.com/w3c/webrtc-pc/issues/2576
      RTC_LOG(LS_INFO)
          << "Dropping stopped transceiver that was never associated";
    }
    transceivers()->Remove(transceiver);
  }
}

void SdpOfferAnswerHandler::RemoveUnusedChannels(
    const SessionDescription* desc) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK(!IsUnifiedPlan());

  if (ConfiguredForMedia()) {
    // Destroy video channel first since it may have a pointer to the
    // voice channel.
    const ContentInfo* video_info = GetFirstVideoContent(desc);
    if (!video_info || video_info->rejected) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      rtp_manager()->GetVideoTransceiver()->internal()->ClearChannel();
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
    }

    const ContentInfo* audio_info = GetFirstAudioContent(desc);
    if (!audio_info || audio_info->rejected) {
      RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN();
      rtp_manager()->GetAudioTransceiver()->internal()->ClearChannel();
      RTC_ALLOW_PLAN_B_DEPRECATION_END();
    }
  }
  const ContentInfo* data_info = GetFirstDataContent(desc);
  if (!data_info) {
    RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA,
                   "No data channel section in the description.");
    error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
    pc_->DestroyDataChannelTransport(error);
  } else if (data_info->rejected) {
    StringBuilder sb;
    sb << "Rejected data channel with mid=" << data_info->mid() << ".";

    RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA, sb.Release());
    error.set_error_detail(RTCErrorDetailType::DATA_CHANNEL_FAILURE);
    pc_->DestroyDataChannelTransport(error);
  }
}

void SdpOfferAnswerHandler::UpdateEndedRemoteMediaStreams() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  std::vector<scoped_refptr<MediaStreamInterface>> streams_to_remove;
  for (size_t i = 0; i < remote_streams_->count(); ++i) {
    MediaStreamInterface* stream = remote_streams_->at(i);
    if (stream->GetAudioTracks().empty() && stream->GetVideoTracks().empty()) {
      streams_to_remove.push_back(scoped_refptr<MediaStreamInterface>(stream));
    }
  }

  pc_->RunWithObserver([&](auto observer) {
    RTC_DCHECK_RUN_ON(signaling_thread());
    for (auto& stream : streams_to_remove) {
      remote_streams_->RemoveStream(stream.get());
      observer->OnRemoveStream(std::move(stream));
    }
  });
}

bool SdpOfferAnswerHandler::UseCandidatesInRemoteDescription() {
  RTC_DCHECK_RUN_ON(signaling_thread());
  auto* remote_desc = remote_description();
  if (!remote_desc) {
    return true;
  }
  bool ret = true;

  for (size_t m = 0; m < remote_desc->number_of_mediasections(); ++m) {
    const IceCandidateCollection* candidates = remote_desc->candidates(m);
    for (size_t n = 0; n < candidates->count(); ++n) {
      const IceCandidate* candidate = candidates->at(n);
      bool valid = false;
      if (!ReadyToUseRemoteCandidate(candidate, remote_desc, &valid)) {
        if (valid) {
          RTC_LOG(LS_INFO)
              << "UseCandidatesInRemoteDescription: Not ready to use "
                 "candidate.";
        }
        continue;
      }
      ret = UseCandidate(candidate);
      if (!ret) {
        break;
      }
    }
  }
  return ret;
}

bool SdpOfferAnswerHandler::UseCandidate(const IceCandidate* candidate) {
  RTC_DCHECK_RUN_ON(signaling_thread());

  Thread::ScopedDisallowBlockingCalls no_blocking_calls;

  RTCErrorOr<const ContentInfo*> result =
      FindContentInfo(remote_description(), candidate);
  if (!result.ok())
    return false;

  const Candidate& c = candidate->candidate();
  RTCError error = VerifyCandidate(c);
  if (!error.ok()) {
    RTC_LOG(LS_WARNING) << "Invalid candidate: " << c.ToString();
    return true;
  }

  pc_->AddRemoteCandidate(result.value()->mid(), c);

  return true;
}

// We need to check the local/remote description for the Transport instead of
// the session, because a new Transport added during renegotiation may have
// them unset while the session has them set from the previous negotiation.
// Not doing so may trigger the auto generation of transport description and
// mess up DTLS identity information, ICE credential, etc.
bool SdpOfferAnswerHandler::ReadyToUseRemoteCandidate(
    const IceCandidate* candidate,
    const SessionDescriptionInterface* remote_desc,
    bool* valid) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  *valid = true;

  const SessionDescriptionInterface* current_remote_desc =
      remote_desc ? remote_desc : remote_description();

  if (!current_remote_desc) {
    return false;
  }

  RTCErrorOr<const ContentInfo*> result =
      FindContentInfo(current_remote_desc, candidate);
  if (!result.ok()) {
    RTC_LOG(LS_ERROR) << "ReadyToUseRemoteCandidate: Invalid candidate. "
                      << result.error().message();

    *valid = false;
    return false;
  }

  if (has_sdp_munged_ufrag_) {
    // The format is
    // restricted_addresses:<host-with-wildcard>:<port>|<other-host>:<other-port>
    // The host can contain wildcards that match anything that comes after it.
    // The port should either be a wildcard or a specific port.
    const std::string restricted_addresses =
        pc_->trials().Lookup("WebRTC-NoSdpMangleUfragRestrictedAddresses");
    const std::string port = candidate->candidate().address().PortAsString();
    const std::string host = candidate->candidate().address().HostAsURIString();
    const std::vector<absl::string_view> restricted_address_list =
        absl::StrSplit(restricted_addresses, '|');
    bool allowed = true;
    for (const absl::string_view restricted_address : restricted_address_list) {
      const std::pair<absl::string_view, absl::string_view> address =
          absl::StrSplit(restricted_address, ':');
      if ((address.second == port || address.second == "*") &&
          WildcardHostPrefixMatch(host, address.first)) {
        RTC_LOG(LS_ERROR) << "ReadyToUseRemoteCandidate: Candidate not valid "
                             "because of SDP munging.";
        *valid = false;
        // There might be other types of SDP munging, but here we're only
        // interested in IceUfrag and IcePwd.
        SdpMungingType sdp_munging_type =
            last_sdp_munging_type_ == SdpMungingType::kIcePwd
                ? SdpMungingType::kIcePwd
                : SdpMungingType::kIceUfrag;
        RTC_HISTOGRAM_ENUMERATION_SPARSE(
            "WebRTC.PeerConnection.RestrictedCandidates.SdpMungingType",
            sdp_munging_type, SdpMungingType::kMaxValue);
        RTC_HISTOGRAM_ENUMERATION_SPARSE(
            "WebRTC.PeerConnection.RestrictedCandidates.Port",
            candidate->candidate().address().port(), 65536);
        allowed = false;
        break;
      }
    }
    RTC_HISTOGRAM_BOOLEAN(
        "WebRTC.PeerConnection.RestrictedCandidates.MungeAllowed", allowed);
    return allowed;
  }
  return true;
}

RTCErrorOr<const ContentInfo*> SdpOfferAnswerHandler::FindContentInfo(
    const SessionDescriptionInterface* description,
    const IceCandidate* candidate) {
  RTC_LOG_THREAD_BLOCK_COUNT();
  if (!candidate->sdp_mid().empty()) {
    auto& contents = description->description()->contents();
    auto it =
        absl::c_find_if(contents, [candidate](const ContentInfo& content_info) {
          return content_info.mid() == candidate->sdp_mid();
        });
    if (it == contents.end()) {
      return LOG_ERROR(
          RTCError(RTCErrorType::INVALID_PARAMETER)
          << "Mid " << candidate->sdp_mid()
          << " specified but no media section with that mid found.");
    } else {
      return &*it;
    }
  } else if (candidate->sdp_mline_index() >= 0) {
    size_t mediacontent_index =
        static_cast<size_t>(candidate->sdp_mline_index());
    size_t content_size = description->description()->contents().size();
    if (mediacontent_index < content_size) {
      return &description->description()->contents()[mediacontent_index];
    } else {
      return LOG_ERROR(RTCError(RTCErrorType::INVALID_RANGE)
                       << "Media line index (" << candidate->sdp_mline_index()
                       << ") out of range (number of mlines: " << content_size
                       << ").");
    }
  }

  return LOG_ERROR(RTCError(RTCErrorType::INVALID_PARAMETER)
                   << "Neither sdp_mline_index nor sdp_mid specified.");
}

RTCError SdpOfferAnswerHandler::CreateChannels(const SessionDescription& desc) {
  RTC_DCHECK(!IsUnifiedPlan());
  RTC_LOG_THREAD_BLOCK_COUNT();
  TRACE_EVENT0("webrtc""SdpOfferAnswerHandler::CreateChannels");
  // Creating the media channels. Transports should already have been created
  // at this point.
  RTC_DCHECK_RUN_ON(signaling_thread());

  ScopedOperationsBatcher worker_tasks(context_->worker_thread());
  ScopedOperationsBatcher network_tasks(network_thread());

  const ContentInfo* voice = GetFirstAudioContent(&desc);
  if (voice && !voice->rejected &&
      !rtp_manager()->GetAudioTransceiver()->internal()->HasChannel()) {
    rtp_manager()->GetAudioTransceiver()->internal()->CreateChannel(
        voice->mid(), pc_->call_ptr(), pc_->configuration()->media_config,
        pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
        video_options(), video_bitrate_allocator_factory_.get(),
        [mid = voice->mid(), this]() {
          RTC_DCHECK_RUN_ON(network_thread());
          return transport_controller_n()->GetRtpTransport(mid);
        },
        worker_tasks, network_tasks);
  }

  const ContentInfo* video = GetFirstVideoContent(&desc);
  if (video && !video->rejected &&
      !rtp_manager()->GetVideoTransceiver()->internal()->HasChannel()) {
    rtp_manager()->GetVideoTransceiver()->internal()->CreateChannel(
        video->mid(), pc_->call_ptr(), pc_->configuration()->media_config,
        pc_->SrtpRequired(), pc_->GetCryptoOptions(), audio_options(),
        video_options(), video_bitrate_allocator_factory_.get(),
        [mid = video->mid(), this]() {
          RTC_DCHECK_RUN_ON(network_thread());
          return transport_controller_n()->GetRtpTransport(mid);
        },
        worker_tasks, network_tasks);
  }

  const ContentInfo* data = GetFirstDataContent(&desc);
  if (data && !data->rejected &&
      !pc_->CreateDataChannelTransport(data->mid())) {
    return LOG_ERROR(RTCError(RTCErrorType::INTERNAL_ERROR)
                     << "Failed to create data channel.");
  }

  RTCError error = worker_tasks.Run();
  if (!error.ok()) {
    return error;
  }
  return network_tasks.Run();
}

void SdpOfferAnswerHandler::GetMediaChannelTeardownTasks(
    ScopedOperationsBatcher& network_tasks,
    ScopedOperationsBatcher& worker_tasks) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  RTC_DCHECK_DISALLOW_THREAD_BLOCKING_CALLS();
  if (!transceivers()) {
    return;
  }
  auto list = transceivers()->List();
  for (const auto& transceiver : list) {
    if (transceiver->media_type() == MediaType::VIDEO) {
      network_tasks.Add(transceiver->internal()->GetClearChannelNetworkTask());
      worker_tasks.Add(transceiver->internal()->GetDeleteChannelWorkerTask(
          /*stop_senders=*/true));
    }
  }
  for (const auto& transceiver : list) {
    if (transceiver->media_type() == MediaType::AUDIO) {
      network_tasks.Add(transceiver->internal()->GetClearChannelNetworkTask());
      worker_tasks.Add(transceiver->internal()->GetDeleteChannelWorkerTask(
          /*stop_senders=*/true));
    }
  }
}

void SdpOfferAnswerHandler::GenerateMediaDescriptionOptions(
    const SessionDescriptionInterface* session_desc,
    RtpTransceiverDirection audio_direction,
    RtpTransceiverDirection video_direction,
    std::optional<size_t>* audio_index,
    std::optional<size_t>* video_index,
    std::optional<size_t>* data_index,
    MediaSessionOptions* session_options) {
  RTC_DCHECK_RUN_ON(signaling_thread());
  for (const ContentInfo& content : session_desc->description()->contents()) {
    if (IsAudioContent(&content)) {
      // If we already have an audio m= section, reject this extra one.
      if (*audio_index) {
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(MediaType::AUDIO, content.mid(),
                                    RtpTransceiverDirection::kInactive,
                                    /*stopped=*/true));
      } else {
        bool stopped = (audio_direction == RtpTransceiverDirection::kInactive);
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(MediaType::AUDIO, content.mid(),
                                    audio_direction, stopped));
        *audio_index = session_options->media_description_options.size() - 1;
      }
      session_options->media_description_options.back().header_extensions =
          media_engine()->voice().GetRtpHeaderExtensions(&pc_->trials());
    } else if (IsVideoContent(&content)) {
      // If we already have an video m= section, reject this extra one.
      if (*video_index) {
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(MediaType::VIDEO, content.mid(),
                                    RtpTransceiverDirection::kInactive,
                                    /*stopped=*/true));
      } else {
        bool stopped = (video_direction == RtpTransceiverDirection::kInactive);
        session_options->media_description_options.push_back(
            MediaDescriptionOptions(MediaType::VIDEO, content.mid(),
                                    video_direction, stopped));
        *video_index = session_options->media_description_options.size() - 1;
      }
      session_options->media_description_options.back().header_extensions =
          media_engine()->video().GetRtpHeaderExtensions(&pc_->trials());
    } else if (IsUnsupportedContent(&content)) {
      session_options->media_description_options.push_back(
          MediaDescriptionOptions(MediaType::UNSUPPORTED, content.mid(),
                                  RtpTransceiverDirection::kInactive,
                                  /*stopped=*/true));
    } else {
      RTC_DCHECK(IsDataContent(&content));
      // If we already have an data m= section, reject this extra one.
      if (*data_index) {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForRejectedData(content.mid()));
      } else {
        session_options->media_description_options.push_back(
            GetMediaDescriptionOptionsForActiveData(content.mid()));
        *data_index = session_options->media_description_options.size() - 1;
      }
    }
  }
}

MediaDescriptionOptions
SdpOfferAnswerHandler::GetMediaDescriptionOptionsForActiveData(
    absl::string_view mid) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  // Direction for data sections is meaningless, but legacy endpoints might
  // expect sendrecv.
  MediaDescriptionOptions options(MediaType::DATA, mid,
                                  RtpTransceiverDirection::kSendRecv,
                                  /*stopped=*/false);
  return options;
}

MediaDescriptionOptions
SdpOfferAnswerHandler::GetMediaDescriptionOptionsForRejectedData(
    absl::string_view mid) const {
  RTC_DCHECK_RUN_ON(signaling_thread());
  MediaDescriptionOptions options(MediaType::DATA, mid,
                                  RtpTransceiverDirection::kInactive,
                                  /*stopped=*/true);
  return options;
}

void SdpOfferAnswerHandler::UpdatePayloadTypeDemuxingState(
    ContentSource source,
    const flat_map<std::string, const ContentGroup*>& bundle_groups_by_mid,
    ScopedOperationsBatcher& worker_tasks) {
  TRACE_EVENT0("webrtc",
               "SdpOfferAnswerHandler::UpdatePayloadTypeDemuxingState");
  *Useof thiscodeis java.lang.StringIndexOutOfBoundsException: Range [40, 39) out of bounds for length 62
  // We may need to delete any created default streams and disable creation of
  // new ones on the basis of payload type. This is needed to avoid SSRC
  in Call'  in the case that a transceiverhas
   the SSRC
  // signaled in the corresponding Unified Plan "m=" section. Specifically, we nt64_t   ClipTimestamp(
  // need to disable payload type based demuxing when two bundled "m=" sections
  // are using the same payload type(s). For more context
  // see https://bugs.chromium.org/p/webrtc/issues/detail?id=11477
const SessionDescriptionInterface*sdesc =
      (source 
  java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
    std:set< ;
(std: >){
    bool                       ",new java.lang.StringIndexOutOfBoundsException: Range [38, 37) out of bounds for length 52
    bool pt_demuxing_possible_video = true;
  };
  std::map<const ContentGroup*, PayloadTypes> payload_types_by_bundle;
  // If the MID is missing from *any* receiving m= section, this is set to true.
  bool mid_header_extension_missing_audio = false;
  bool mid_header_extension_missing_video = false;
  for (auto& content_info : sdesc->description()->contents()) {
    auto it = bundle_groups_by_mid.find(content_info.mid());
    const ContentGroup* bundle_group =
        it != bundle_groups_by_mid.end() ? it->second : nullptr;
    // If this m= section isn't bundled, it's safe to demux by payload type
    // since other m= sections using the same payload type will also be using
    // different transports.
    if (!bundle_group) {
      continue;
    }
    PayloadTypes* payload_types = &payload_types_by_bundle[bundle_group];
    if (content_info.rejected ||
        (source == ContentSource::CS_LOCAL &&
         !RtpTransceiverDirectionHasRecv(
             content_info.media_description()->direction())) ||
        (source == ContentSource::CS_REMOTE &&
         !RtpTransceiverDirectionHasSend(
             content_info.media_description()->direction()))) {
      // Ignore transceivers that are not receiving.
      continue;
    }
    const MediaType media_type = content_info.media_description()->type();
    if (media_type == MediaType::AUDIO || media_type == MediaType::VIDEO) {
      if (media_type == MediaType::AUDIO &&
          !mid_header_extension_missing_audio) {
        mid_header_extension_missing_audio =
            !ContentHasHeaderExtension(content_info, RtpExtension::kMidUri);
      } else if (media_type == MediaType::VIDEO &&
                 !mid_header_extension_missing_video) {
        mid_header_extension_missing_video =
            !ContentHasHeaderExtension(content_info, RtpExtension::kMidUri);
      }
      const MediaContentDescription* media_desc =
          content_info.media_description();
      for (const Codec& codec : media_desc->codecs()) {
        if (media_type == MediaType::AUDIO) {
          if (payload_types->audio_payload_types.count(codec.id)) {
            // Two m= sections are using the same payload type, thus demuxing
            // by payload type is not possible.
            if (media_type == MediaType::AUDIO) {
              payload_types->pt_demuxing_possible_audio = false;
            }
          }
          payload_types->audio_payload_types.insert(codec.id);
        } else if (media_type == MediaType::VIDEO) {
          if (payload_types->video_payload_types.count(codec.id)) {
            // Two m= sections are using the same payload type, thus demuxing
            // by payload type is not possible.
            payload_types->pt_demuxing_possible_video = false;
          }
          payload_types->video_payload_types.insert(codec.id);
        }
      }
    }
  }

  // In Unified Plan, payload type demuxing is useful for legacy endpoints that
  // don't support the MID header extension, but it can also cause incorrrect
  // forwarding of packets when going from one m= section to multiple m=
  // sections in the same BUNDLE. This only happens if media arrives prior to
  // negotiation, but this can cause missing video and unsignalled ssrc bugs
  // severe enough to warrant disabling PT demuxing in such cases. Therefore, if
  // a MID header extension is present on all m= sections for a given kind
  // (audio/video) then we use that as an OK to disable payload type demuxing in
  // BUNDLEs of that kind. However if PT demuxing was ever turned on (e.g. MID
  // was ever removed on ANY m= section of that kind) then we continue to allow
  // PT demuxing in order to prevent disabling it in follow-up O/A exchanges and
  // allowing early media by PT.
  bool bundled_pt_demux_allowed_audio = !IsUnifiedPlan() ||
                                        mid_header_extension_missing_audio ||
                                        pt_demuxing_has_been_used_audio_;
  bool bundled_pt_demux_allowed_video = !IsUnifiedPlan() ||
                                        mid_header_extension_missing_video ||
                                        pt_demuxing_has_been_used_video_;

  // Gather all updates ahead of time so that all transports can be updated in a
  // single BlockingCall; necessary due to thread guards.
  flat_map<std::string, bool> transports_to_update;
  for (RtpTransceiver* transceiver : transceivers()->ListInternal()) {
    const ContentInfo* content =
        FindMediaSectionForTransceiver(transceiver, sdesc);
    if (!content) {
      continue;
    }

    const MediaType media_type = transceiver->media_type();
    if (media_type != MediaType::AUDIO && media_type != MediaType::VIDEO) {
      continue;
    }

    RtpTransceiverDirection local_direction =
        content->media_description()->direction();
    if (source == CS_REMOTE) {
      local_direction = RtpTransceiverDirectionReversed(local_direction);
    }

    auto bundle_it = bundle_groups_by_mid.find(content->mid());
    const ContentGroup* bundle_group =
        bundle_it != bundle_groups_by_mid.end() ? bundle_it->second : nullptr;
    bool pt_demux_enabled = RtpTransceiverDirectionHasRecv(local_direction);
    if (media_type == MediaType::AUDIO) {
      pt_demux_enabled &=
          !bundle_group ||
          (bundled_pt_demux_allowed_audio &&
           payload_types_by_bundle[bundle_group].pt_demuxing_possible_audio);
      if (pt_demux_enabled) {
        pt_demuxing_has_been_used_audio_ = true;
      }
    } else {
      RTC_DCHECK_EQ(media_type, MediaType::VIDEO);
      pt_demux_enabled &=
          !bundle_group ||
          (bundled_pt_demux_allowed_video &&
           payload_types_by_bundle[bundle_group].pt_demuxing_possible_video);
      if (pt_demux_enabled) {
        pt_demuxing_has_been_used_video_ = true;
      }
    }

    // Accumulate the pt_demux_enabled state per transport (represented by mid).
    // If any transceiver associated with the transport requires PT demuxing,
    // the transport will have it enabled.
    transports_to_update[content->mid()] |= pt_demux_enabled;
  }

  if (transports_to_update.empty()) {
    return;
  }

  context_->network_thread()->BlockingCall([&]() {
    JsepTransportController* transport_controller =
        pc_->transport_controller_n();
    for (const auto& [mid, pt_demux_enabled] : transports_to_update) {
      RtpTransportInternal* rtp_transport =
          transport_controller->GetRtpTransport(mid);
      if (rtp_transport != nullptr) {
        rtp_transport->SetActivePayloadTypeDemuxing(pt_demux_enabled);
      }
    }
  });

  // If payload type demuxing is disabled for a transport, clear out any
  // unsignaled streams that might have been created previously based on
  // payload type matches. This prevents SSRC collisions if those SSRCs
  // are later reused.
  for (RtpTransceiver* transceiver : transceivers()->ListInternal()) {
    const ContentInfo* content =
        FindMediaSectionForTransceiver(transceiver, sdesc);
    if (!content) {
      continue;
    }
    auto it = transports_to_update.find(content->mid());
    if (it != transports_to_update.end() && !it->second) {
      if (MediaReceiveChannelInterface* receive_channel =
              transceiver->media_receive_channel()) {
        // TODO: bugs.webrtc.org/42221580 - This will remove *all* unsignaled
        // streams (those without an explicitly signaled SSRC), which may
        // include streams that were matched to this channel by MID or RID.
        // Ideally we'd remove only the streams that were matched based on
        // payload type alone, but currently there is no straightforward way to
        // identify those streams.
        worker_tasks.Add(receive_channel->GetResetUnsignaledRecvStreamTask());
      }
    }
  }
}

bool SdpOfferAnswerHandler::ConfiguredForMedia() const {
  return context_->is_configured_for_media();
}

}  // namespace webrtc

Messung V0.5 in Prozent
C=86 H=100 G=93

¤ Dauer der Verarbeitung: 0.175 Sekunden  (vorverarbeitet am  2026-08-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=434850
#Domains=655579