Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/webrtc/jsep/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 85 kB image not shown  

Quelle  JsepSessionImpl.cpp   Sprache: C

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "jsep/JsepSessionImpl.h"

#include <stdlib.h>

#include <bitset>
#include <iterator>
#include <set>
#include <string>
#include <utility>

#include "mozilla/StaticPrefs_media.h"
#include "transport/logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/net/DataChannelProtocol.h"
#include "nsDebug.h"
#include "nspr.h"
#include "nss.h"
#include "pk11pub.h"

#include "api/rtp_parameters.h"

#include "jsep/JsepTrack.h"
#include "jsep/JsepTransport.h"
#include "sdp/HybridSdpParser.h"
#include "sdp/SipccSdp.h"

namespace mozilla {

MOZ_MTLOG_MODULE("jsep")

#define JSEP_SET_ERROR(error)                                 \
  do {                                                        \
    std::ostringstream os;                                    \
    os << error;                                              \
    mLastError = os.str();                                    \
    MOZ_MTLOG(ML_ERROR, "[" << mName << "]: " << mLastError); \
  } while (0);

static std::bitset<128> GetForbiddenSdpPayloadTypes() {
  std::bitset<128> forbidden(0);
  forbidden[1] = true;
  forbidden[2] = true;
  forbidden[19] = true;
  for (uint16_t i = 64; i < 96; ++i) {
    forbidden[i] = true;
  }
  return forbidden;
}

static std::string GetRandomHex(size_t words) {
  std::ostringstream os;

  for (size_t i = 0; i < words; ++i) {
    uint32_t rand;
    SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(&rand),
                                       sizeof(rand));
    if (rv != SECSuccess) {
      MOZ_CRASH();
      return "";
    }

    os << std::hex << std::setfill('0') << std::setw(8) << rand;
  }
  return os.str();
}

JsepSessionImpl::JsepSessionImpl(const JsepSessionImpl& aOrig)
    : JsepSession(aOrig),
      JsepSessionCopyableStuff(aOrig),
      mUuidGen(aOrig.mUuidGen->Clone()),
      mGeneratedOffer(aOrig.mGeneratedOffer ? aOrig.mGeneratedOffer->Clone()
                                            : nullptr),
      mGeneratedAnswer(aOrig.mGeneratedAnswer ? aOrig.mGeneratedAnswer->Clone()
                                              : nullptr),
      mCurrentLocalDescription(aOrig.mCurrentLocalDescription
                                   ? aOrig.mCurrentLocalDescription->Clone()
                                   : nullptr),
      mCurrentRemoteDescription(aOrig.mCurrentRemoteDescription
                                    ? aOrig.mCurrentRemoteDescription->Clone()
                                    : nullptr),
      mPendingLocalDescription(aOrig.mPendingLocalDescription
                                   ? aOrig.mPendingLocalDescription->Clone()
                                   : nullptr),
      mPendingRemoteDescription(aOrig.mPendingRemoteDescription
                                    ? aOrig.mPendingRemoteDescription->Clone()
                                    : nullptr),
      mSdpHelper(&mLastError),
      mParser(new HybridSdpParser()) {
  for (const auto& codec : aOrig.mSupportedCodecs) {
    mSupportedCodecs.emplace_back(codec->Clone());
  }
}

nsresult JsepSessionImpl::Init() {
  mLastError.clear();

  MOZ_ASSERT(!mSessionId, "Init called more than once");

  nsresult rv = SetupIds();
  NS_ENSURE_SUCCESS(rv, rv);

  mEncodeTrackId =
      Preferences::GetBool("media.peerconnection.sdp.encode_track_id"true);

  mIceUfrag = GetRandomHex(1);
  mIcePwd = GetRandomHex(4);
  return NS_OK;
}

static void GetIceCredentials(
    const Sdp& aSdp,
    std::set<std::pair<std::string, std::string>>* aCredentials) {
  for (size_t i = 0; i < aSdp.GetMediaSectionCount(); ++i) {
    const SdpAttributeList& attrs = aSdp.GetMediaSection(i).GetAttributeList();
    if (attrs.HasAttribute(SdpAttribute::kIceUfragAttribute) &&
        attrs.HasAttribute(SdpAttribute::kIcePwdAttribute)) {
      aCredentials->insert(
          std::make_pair(attrs.GetIceUfrag(), attrs.GetIcePwd()));
    }
  }
}

std::set<std::pair<std::string, std::string>>
JsepSessionImpl::GetLocalIceCredentials() const {
  std::set<std::pair<std::string, std::string>> result;
  if (mCurrentLocalDescription) {
    GetIceCredentials(*mCurrentLocalDescription, &result);
  }
  if (mPendingLocalDescription) {
    GetIceCredentials(*mPendingLocalDescription, &result);
  }
  return result;
}

void JsepSessionImpl::AddTransceiver(const JsepTransceiver& aTransceiver) {
  mLastError.clear();
  MOZ_MTLOG(ML_DEBUG,
            "[" << mName << "]: Adding transceiver " << aTransceiver.GetUuid());
#ifdef DEBUG
  if (aTransceiver.GetMediaType() == SdpMediaSection::kApplication) {
    // Make sure we don't add more than one DataChannel transceiver
    for (const auto& transceiver : mTransceivers) {
      MOZ_ASSERT(transceiver.GetMediaType() != SdpMediaSection::kApplication);
    }
  }
#endif
  mTransceivers.push_back(aTransceiver);
  InitTransceiver(mTransceivers.back());
}

void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) {
  mLastError.clear();

  if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) {
    // Make sure we have an ssrc. Might already be set.
    aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
    aTransceiver.mSendTrack.SetCNAME(mCNAME);

    // Make sure we have identifiers for send track, just in case.
    // (man I hate this)
    if (mEncodeTrackId) {
      aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid());
    }
  } else {
    // Datachannel transceivers should always be sendrecv. Just set it instead
    // of asserting.
    aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv;
  }

  aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs);
  aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs);
  // We do not set mLevel yet, we do that either on createOffer, or setRemote
}

nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) {
  mLastError.clear();

  if (mBundlePolicy == policy) {
    return NS_OK;
  }

  if (mCurrentLocalDescription) {
    JSEP_SET_ERROR(
        "Changing the bundle policy is only supported before the "
        "first SetLocalDescription.");
    return NS_ERROR_UNEXPECTED;
  }

  mBundlePolicy = policy;
  return NS_OK;
}

nsresult JsepSessionImpl::AddDtlsFingerprint(
    const nsACString& algorithm, const std::vector<uint8_t>& value) {
  mLastError.clear();
  JsepDtlsFingerprint fp;

  fp.mAlgorithm = algorithm;
  fp.mValue = value;

  mDtlsFingerprints.push_back(fp);

  return NS_OK;
}

nsresult JsepSessionImpl::AddRtpExtension(
    JsepMediaType mediaType, const std::string& extensionName,
    SdpDirectionAttribute::Direction direction) {
  mLastError.clear();

  for (auto& ext : mRtpExtensions) {
    if (ext.mExtmap.direction == direction &&
        ext.mExtmap.extensionname == extensionName) {
      if (ext.mMediaType != mediaType) {
        ext.mMediaType = JsepMediaType::kAudioVideo;
      }
      return NS_OK;
    }
  }

  uint16_t freeEntry = GetNeverUsedExtmapEntry();

  if (freeEntry == 0) {
    return NS_ERROR_FAILURE;
  }

  JsepExtmapMediaType extMediaType = {
      mediaType,
      {freeEntry, direction,
       // do we want to specify direction?
       direction != SdpDirectionAttribute::kSendrecv, extensionName, ""}};

  mRtpExtensions.push_back(extMediaType);
  return NS_OK;
}

nsresult JsepSessionImpl::AddAudioRtpExtension(
    const std::string& extensionName,
    SdpDirectionAttribute::Direction direction) {
  return AddRtpExtension(JsepMediaType::kAudio, extensionName, direction);
}

nsresult JsepSessionImpl::AddVideoRtpExtension(
    const std::string& extensionName,
    SdpDirectionAttribute::Direction direction) {
  return AddRtpExtension(JsepMediaType::kVideo, extensionName, direction);
}

nsresult JsepSessionImpl::AddAudioVideoRtpExtension(
    const std::string& extensionName,
    SdpDirectionAttribute::Direction direction) {
  return AddRtpExtension(JsepMediaType::kAudioVideo, extensionName, direction);
}

nsresult JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options,
                                              JsepTransceiver& transceiver,
                                              Sdp* local) {
  SdpMediaSection::Protocol protocol(
      SdpHelper::GetProtocolForMediaType(transceiver.GetMediaType()));

  const Sdp* answer(GetAnswer());
  const SdpMediaSection* lastAnswerMsection = nullptr;

  if (answer &&
      (local->GetMediaSectionCount() < answer->GetMediaSectionCount())) {
    lastAnswerMsection =
        &answer->GetMediaSection(local->GetMediaSectionCount());
    // Use the protocol the answer used, even if it is not what we would have
    // used.
    protocol = lastAnswerMsection->GetProtocol();
  }

  SdpMediaSection* msection = &local->AddMediaSection(
      transceiver.GetMediaType(), transceiver.mJsDirection, 0, protocol,
      sdp::kIPv4, "0.0.0.0");

  // Some of this stuff (eg; mid) sticks around even if disabled
  if (lastAnswerMsection) {
    MOZ_ASSERT(lastAnswerMsection->GetMediaType() ==
               transceiver.GetMediaType());
    nsresult rv = mSdpHelper.CopyStickyParams(*lastAnswerMsection, msection);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (transceiver.IsStopping() || transceiver.IsStopped()) {
    SdpHelper::DisableMsection(local, msection);
    return NS_OK;
  }

  msection->SetPort(9);

  // We don't do this in AddTransportAttributes because that is also used for
  // making answers, and we don't want to unconditionally set rtcp-mux or
  // rtcp-rsize there.
  if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
    // Set RTCP-MUX.
    msection->GetAttributeList().SetAttribute(
        new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
    // Set RTCP-RSIZE
    if (msection->GetMediaType() == SdpMediaSection::MediaType::kVideo &&
        Preferences::GetBool("media.navigator.video.offer_rtcp_rsize"false)) {
      msection->GetAttributeList().SetAttribute(
          new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
    }
  }

  nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
  NS_ENSURE_SUCCESS(rv, rv);

  transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection);
  transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection);

  AddExtmap(msection);

  std::string mid;
  // We do not set the mid on the transceiver, that happens when a description
  // is set.
  if (transceiver.IsAssociated()) {
    mid = transceiver.GetMid();
  } else {
    mid = GetNewMid();
  }

  msection->GetAttributeList().SetAttribute(
      new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));

  return NS_OK;
}

void JsepSessionImpl::SetupBundle(Sdp* sdp) const {
  std::vector<std::string> mids;
  std::set<SdpMediaSection::MediaType> observedTypes;

  // This has the effect of changing the bundle level if the first m-section
  // goes from disabled to enabled. This is kinda inefficient.

  for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
    auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
    if ((sdp->GetMediaSection(i).GetPort() != 0) &&
        attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
      bool useBundleOnly = false;
      switch (mBundlePolicy) {
        case kBundleMaxCompat:
          // We don't use bundle-only for max-compat
          break;
        case kBundleBalanced:
          // balanced means we use bundle-only on everything but the first
          // m-section of a given type
          if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) {
            useBundleOnly = true;
          }
          observedTypes.insert(sdp->GetMediaSection(i).GetMediaType());
          break;
        case kBundleMaxBundle:
          // max-bundle means we use bundle-only on everything but the first
          // m-section
          useBundleOnly = !mids.empty();
          break;
      }

      if (useBundleOnly) {
        attrs.SetAttribute(
            new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
        // Set port to 0 for sections with bundle-only attribute. (mjf)
        sdp->GetMediaSection(i).SetPort(0);
      }

      mids.push_back(attrs.GetMid());
    }
  }

  if (!mids.empty()) {
    UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
    groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
    sdp->GetAttributeList().SetAttribute(groupAttr.release());
  }
}

JsepSession::Result JsepSessionImpl::CreateOffer(
    const JsepOfferOptions& options, std::string* offer) {
  mLastError.clear();

  if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) {
    JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
    // Spec doesn't seem to say this is an error. It probably should.
    return dom::PCError::InvalidStateError;
  }

  // This is one of those places where CreateOffer sets some state.
  SetIceRestarting(options.mIceRestart.isSome() && *(options.mIceRestart));

  UniquePtr<Sdp> sdp;

  // Make the basic SDP that is common to offer/answer.
  nsresult rv = CreateGenericSDP(&sdp);
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  for (size_t level = 0;
       Maybe<JsepTransceiver> transceiver = GetTransceiverForLocal(level);
       ++level) {
    rv = CreateOfferMsection(options, *transceiver, sdp.get());
    NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
    SetTransceiver(*transceiver);
  }

  SetupBundle(sdp.get());

  if (mCurrentLocalDescription && GetAnswer()) {
    rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription,
                                     *sdp, sdp.get());
    NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
  }

  *offer = sdp->ToString();
  mGeneratedOffer = std::move(sdp);
  ++mSessionVersion;
  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateOffer \nSDP=\n" << *offer);

  return Result();
}

std::string JsepSessionImpl::GetLocalDescription(
    JsepDescriptionPendingOrCurrent type) const {
  std::ostringstream os;
  mozilla::Sdp* sdp = GetParsedLocalDescription(type);
  if (sdp) {
    sdp->Serialize(os);
  }
  return os.str();
}

std::string JsepSessionImpl::GetRemoteDescription(
    JsepDescriptionPendingOrCurrent type) const {
  std::ostringstream os;
  mozilla::Sdp* sdp = GetParsedRemoteDescription(type);
  if (sdp) {
    sdp->Serialize(os);
  }
  return os.str();
}

void JsepSessionImpl::AddExtmap(SdpMediaSection* msection) {
  auto extensions = GetRtpExtensions(*msection);

  if (!extensions.empty()) {
    SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
    extmap->mExtmaps = extensions;
    msection->GetAttributeList().SetAttribute(extmap);
  }
}

std::vector<SdpExtmapAttributeList::Extmap> JsepSessionImpl::GetRtpExtensions(
    const SdpMediaSection& msection) {
  std::vector<SdpExtmapAttributeList::Extmap> result;
  JsepMediaType mediaType = JsepMediaType::kNone;
  const auto direction = msection.GetDirection();
  const auto includes_send = direction == SdpDirectionAttribute::kSendrecv ||
                             direction == SdpDirectionAttribute::kSendonly;
  switch (msection.GetMediaType()) {
    case SdpMediaSection::kAudio:
      mediaType = JsepMediaType::kAudio;
      break;
    case SdpMediaSection::kVideo:
      mediaType = JsepMediaType::kVideo;
      // We need to add the dependency descriptor extension for simulcast
      if (includes_send && StaticPrefs::media_peerconnection_video_use_dd() &&
          msection.GetAttributeList().HasAttribute(
              SdpAttribute::kSimulcastAttribute)) {
        AddVideoRtpExtension(webrtc::RtpExtension::kDependencyDescriptorUri,
                             SdpDirectionAttribute::kSendonly);
      }
      if (msection.GetAttributeList().HasAttribute(
              SdpAttribute::kRidAttribute)) {
        // We need RID support
        // TODO: Would it be worth checking that the direction is sane?
        AddVideoRtpExtension(webrtc::RtpExtension::kRidUri,
                             SdpDirectionAttribute::kSendonly);

        if (mRtxIsAllowed &&
            Preferences::GetBool("media.peerconnection.video.use_rtx"false)) {
          AddVideoRtpExtension(webrtc::RtpExtension::kRepairedRidUri,
                               SdpDirectionAttribute::kSendonly);
        }
      }
      break;
    default:;
  }
  if (mediaType != JsepMediaType::kNone) {
    for (auto ext = mRtpExtensions.begin(); ext != mRtpExtensions.end();
         ++ext) {
      if (ext->mMediaType == mediaType ||
          ext->mMediaType == JsepMediaType::kAudioVideo) {
        result.push_back(ext->mExtmap);
      }
    }
  }
  return result;
}

std::string JsepSessionImpl::GetNewMid() {
  std::string mid;

  do {
    std::ostringstream osMid;
    osMid << mMidCounter++;
    mid = osMid.str();
  } while (mUsedMids.count(mid));

  mUsedMids.insert(mid);
  return mid;
}

void JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
                                       SdpMediaSection* msection) {
  auto negotiatedRtpExtensions = GetRtpExtensions(*msection);
  mSdpHelper.NegotiateAndAddExtmaps(remoteMsection, negotiatedRtpExtensions,
                                    msection);
}

uint16_t JsepSessionImpl::GetNeverUsedExtmapEntry() {
  uint16_t result = 1;

  // Walk the set in order, and return the first "hole" we find
  for (const auto used : mExtmapEntriesEverUsed) {
    if (result != used) {
      MOZ_ASSERT(result < used);
      break;
    }

    // RFC 5285 says entries >= 4096 are used in offers to force the answerer
    // to pick, so we do not want to actually use these
    if (used == 4095) {
      JSEP_SET_ERROR(
          "Too many rtp extensions have been added. "
          "That's 4095. Who _does_ that?");
      return 0;
    }

    result = used + 1;
  }

  mExtmapEntriesEverUsed.insert(result);
  return result;
}

JsepSession::Result JsepSessionImpl::CreateAnswer(
    const JsepAnswerOptions& options, std::string* answer) {
  mLastError.clear();

  if (mState != kJsepStateHaveRemoteOffer) {
    JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
    return dom::PCError::InvalidStateError;
  }

  UniquePtr<Sdp> sdp;

  // Make the basic SDP that is common to offer/answer.
  nsresult rv = CreateGenericSDP(&sdp);
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  const Sdp& offer = *mPendingRemoteDescription;

  // Copy the bundle groups into our answer
  UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
  mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
  sdp->GetAttributeList().SetAttribute(groupAttr.release());

  for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
    // The transceivers are already in place, due to setRemote
    Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
    if (!transceiver) {
      JSEP_SET_ERROR("No transceiver for level " << i);
      MOZ_ASSERT(false);
      return dom::PCError::OperationError;
    }
    rv = CreateAnswerMsection(options, *transceiver, offer.GetMediaSection(i),
                              sdp.get());
    NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
    SetTransceiver(*transceiver);
  }

  // Ensure that each bundle-group starts with a mid that has a transport, in
  // case we've disabled what the offerer wanted to use. If the group doesn't
  // contain anything that has a transport, remove it.
  groupAttr.reset(new SdpGroupAttributeList);
  std::vector<SdpGroupAttributeList::Group> bundleGroups;
  mSdpHelper.GetBundleGroups(*sdp, &bundleGroups);
  for (auto& group : bundleGroups) {
    for (auto& mid : group.tags) {
      const SdpMediaSection* msection =
          mSdpHelper.FindMsectionByMid(offer, mid);

      if (msection && !msection->GetAttributeList().HasAttribute(
                          SdpAttribute::kBundleOnlyAttribute)) {
        std::swap(group.tags[0], mid);
        groupAttr->mGroups.push_back(group);
        break;
      }
    }
  }
  sdp->GetAttributeList().SetAttribute(groupAttr.release());

  if (mCurrentLocalDescription) {
    // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
    rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription,
                                     offer, sdp.get());
    NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
  }

  *answer = sdp->ToString();
  mGeneratedAnswer = std::move(sdp);
  ++mSessionVersion;
  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateAnswer \nSDP=\n" << *answer);

  return Result();
}

nsresult JsepSessionImpl::CreateAnswerMsection(
    const JsepAnswerOptions& options, JsepTransceiver& transceiver,
    const SdpMediaSection& remoteMsection, Sdp* sdp) {
  MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType());
  SdpDirectionAttribute::Direction direction =
      reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection;
  SdpMediaSection& msection =
      sdp->AddMediaSection(remoteMsection.GetMediaType(), direction, 9,
                           remoteMsection.GetProtocol(), sdp::kIPv4, "0.0.0.0");

  nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
    SdpHelper::DisableMsection(sdp, &msection);
    return NS_OK;
  }

  MOZ_ASSERT(transceiver.IsAssociated());
  if (msection.GetAttributeList().GetMid().empty()) {
    msection.GetAttributeList().SetAttribute(new SdpStringAttribute(
        SdpAttribute::kMidAttribute, transceiver.GetMid()));
  }

  MOZ_ASSERT(transceiver.GetMid() == msection.GetAttributeList().GetMid());

  SdpSetupAttribute::Role role;
  if (transceiver.mTransport.mDtls && !IsIceRestarting()) {
    role = (transceiver.mTransport.mDtls->mRole ==
            JsepDtlsTransport::kJsepDtlsClient)
               ? SdpSetupAttribute::kActive
               : SdpSetupAttribute::kPassive;
  } else {
    rv = DetermineAnswererSetupRole(remoteMsection, &role);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = AddTransportAttributes(&msection, role);
  NS_ENSURE_SUCCESS(rv, rv);

  transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
  transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);

  // Add extmap attributes. This logic will probably be moved to the track,
  // since it can be specified on a per-sender basis in JS.
  // We will need some validation to ensure that the ids are identical for
  // RTP streams that are bundled together, though (bug 1406529).
  AddCommonExtmaps(remoteMsection, &msection);

  if (msection.GetFormats().empty()) {
    // Could not negotiate anything. Disable m-section.
    SdpHelper::DisableMsection(sdp, &msection);
  }

  return NS_OK;
}

nsresult JsepSessionImpl::DetermineAnswererSetupRole(
    const SdpMediaSection& remoteMsection, SdpSetupAttribute::Role* rolep) {
  // Determine the role.
  // RFC 5763 says:
  //
  //   The endpoint MUST use the setup attribute defined in [RFC4145].
  //   The endpoint that is the offerer MUST use the setup attribute
  //   value of setup:actpass and be prepared to receive a client_hello
  //   before it receives the answer.  The answerer MUST use either a
  //   setup attribute value of setup:active or setup:passive.  Note that
  //   if the answerer uses setup:passive, then the DTLS handshake will
  //   not begin until the answerer is received, which adds additional
  //   latency. setup:active allows the answer and the DTLS handshake to
  //   occur in parallel.  Thus, setup:active is RECOMMENDED.  Whichever
  //   party is active MUST initiate a DTLS handshake by sending a
  //   ClientHello over each flow (host/port quartet).
  //
  //   We default to assuming that the offerer is passive and we are active.
  SdpSetupAttribute::Role role = SdpSetupAttribute::kActive;

  if (remoteMsection.GetAttributeList().HasAttribute(
          SdpAttribute::kSetupAttribute)) {
    switch (remoteMsection.GetAttributeList().GetSetup().mRole) {
      case SdpSetupAttribute::kActive:
        role = SdpSetupAttribute::kPassive;
        break;
      case SdpSetupAttribute::kPassive:
      case SdpSetupAttribute::kActpass:
        role = SdpSetupAttribute::kActive;
        break;
      case SdpSetupAttribute::kHoldconn:
        // This should have been caught by ParseSdp
        MOZ_ASSERT(false);
        JSEP_SET_ERROR(
            "The other side used an illegal setup attribute"
            " (\"holdconn\").");
        return NS_ERROR_INVALID_ARG;
    }
  }

  *rolep = role;
  return NS_OK;
}

JsepSession::Result JsepSessionImpl::SetLocalDescription(
    JsepSdpType type, const std::string& constSdp) {
  mLastError.clear();
  std::string sdp = constSdp;

  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetLocalDescription type=" << type
                          << "\nSDP=\n"
                          << sdp);

  switch (type) {
    case kJsepSdpOffer:
      if (!mGeneratedOffer) {
        JSEP_SET_ERROR(
            "Cannot set local offer when createOffer has not been called.");
        return dom::PCError::InvalidModificationError;
      }
      if (sdp.empty()) {
        sdp = mGeneratedOffer->ToString();
      }
      if (mState == kJsepStateHaveLocalOffer) {
        // Rollback previous offer before applying the new one.
        SetLocalDescription(kJsepSdpRollback, "");
        MOZ_ASSERT(mState == kJsepStateStable);
      }
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      if (!mGeneratedAnswer) {
        JSEP_SET_ERROR(
            "Cannot set local answer when createAnswer has not been called.");
        return dom::PCError::InvalidModificationError;
      }
      if (sdp.empty()) {
        sdp = mGeneratedAnswer->ToString();
      }
      break;
    case kJsepSdpRollback:
      if (mState != kJsepStateHaveLocalOffer) {
        JSEP_SET_ERROR("Cannot rollback local description in "
                       << GetStateStr(mState));
        // Currently, spec allows this in any state except stable, and
        // sRD(rollback) and sLD(rollback) do exactly the same thing.
        return dom::PCError::InvalidStateError;
      }

      mPendingLocalDescription.reset();
      SetState(kJsepStateStable);
      RollbackLocalOffer();
      return Result();
  }

  switch (mState) {
    case kJsepStateStable:
      if (type != kJsepSdpOffer) {
        JSEP_SET_ERROR("Cannot set local answer in state "
                       << GetStateStr(mState));
        return dom::PCError::InvalidStateError;
      }
      break;
    case kJsepStateHaveRemoteOffer:
      if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
        JSEP_SET_ERROR("Cannot set local offer in state "
                       << GetStateStr(mState));
        return dom::PCError::InvalidStateError;
      }
      break;
    default:
      JSEP_SET_ERROR("Cannot set local offer or answer in state "
                     << GetStateStr(mState));
      return dom::PCError::InvalidStateError;
  }

  UniquePtr<Sdp> parsed;
  nsresult rv = ParseSdp(sdp, &parsed);
  // Needs to be RTCError with sdp-syntax-error
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  // Check that content hasn't done anything unsupported with the SDP
  rv = ValidateLocalDescription(*parsed, type);
  NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidModificationError);

  switch (type) {
    case kJsepSdpOffer:
      rv = ValidateOffer(*parsed);
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = ValidateAnswer(*mPendingRemoteDescription, *parsed);
      break;
    case kJsepSdpRollback:
      MOZ_CRASH();  // Handled above
  }
  NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);

  if (type == kJsepSdpOffer) {
    // Save in case we need to rollback
    mOldTransceivers = mTransceivers;
  }

  SdpHelper::BundledMids bundledMids;
  rv = mSdpHelper.GetBundledMids(*parsed, &bundledMids);
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  SdpHelper::BundledMids remoteBundledMids;
  if (type != kJsepSdpOffer) {
    rv = mSdpHelper.GetBundledMids(*mPendingRemoteDescription,
                                   &remoteBundledMids);
    NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
  }

  for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
    Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
    if (!transceiver) {
      MOZ_ASSERT(false);
      JSEP_SET_ERROR("No transceiver for level " << i);
      return dom::PCError::OperationError;
    }

    const auto& msection = parsed->GetMediaSection(i);
    transceiver->Associate(msection.GetAttributeList().GetMid());
    transceiver->mRecvTrack.RecvTrackSetLocal(msection);

    if (mSdpHelper.MsectionIsDisabled(msection)) {
      transceiver->mTransport.Close();
      SetTransceiver(*transceiver);
      continue;
    }

    bool hasOwnTransport = mSdpHelper.OwnsTransport(
        msection, bundledMids,
        (type == kJsepSdpOffer) ? sdp::kOffer : sdp::kAnswer);
    if (type != kJsepSdpOffer) {
      const auto& remoteMsection =
          mPendingRemoteDescription->GetMediaSection(i);
      // Don't allow the answer to override what the offer allowed for
      hasOwnTransport &= mSdpHelper.OwnsTransport(
          remoteMsection, remoteBundledMids, sdp::kOffer);
    }

    if (hasOwnTransport) {
      EnsureHasOwnTransport(parsed->GetMediaSection(i), *transceiver);
    }

    if (type == kJsepSdpOffer) {
      if (!hasOwnTransport) {
        auto it = bundledMids.find(transceiver->GetMid());
        if (it != bundledMids.end()) {
          transceiver->SetBundleLevel(it->second->GetLevel());
        }
      }
    } else {
      auto it = remoteBundledMids.find(transceiver->GetMid());
      if (it != remoteBundledMids.end()) {
        transceiver->SetBundleLevel(it->second->GetLevel());
      }
    }
    SetTransceiver(*transceiver);
  }

  CopyBundleTransports();

  switch (type) {
    case kJsepSdpOffer:
      rv = SetLocalDescriptionOffer(std::move(parsed));
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = SetLocalDescriptionAnswer(type, std::move(parsed));
      break;
    case kJsepSdpRollback:
      MOZ_CRASH();  // Handled above
  }

  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
  return Result();
}

nsresult JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer) {
  MOZ_ASSERT(mState == kJsepStateStable);
  mPendingLocalDescription = std::move(offer);
  mIsPendingOfferer = Some(true);
  SetState(kJsepStateHaveLocalOffer);
  return NS_OK;
}

nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
                                                    UniquePtr<Sdp> answer) {
  MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
  mPendingLocalDescription = std::move(answer);

  nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
                                        mPendingRemoteDescription);
  NS_ENSURE_SUCCESS(rv, rv);

  mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
  mCurrentLocalDescription = std::move(mPendingLocalDescription);
  MOZ_ASSERT(mIsPendingOfferer.isSome() && !*mIsPendingOfferer);
  mIsPendingOfferer.reset();
  mIsCurrentOfferer = Some(false);

  SetState(kJsepStateStable);
  return NS_OK;
}

JsepSession::Result JsepSessionImpl::SetRemoteDescription(
    JsepSdpType type, const std::string& sdp) {
  mLastError.clear();

  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetRemoteDescription type=" << type
                          << "\nSDP=\n"
                          << sdp);

  if (mState == kJsepStateHaveRemoteOffer && type == kJsepSdpOffer) {
    // Rollback previous offer before applying the new one.
    SetRemoteDescription(kJsepSdpRollback, "");
    MOZ_ASSERT(mState == kJsepStateStable);
  }

  if (type == kJsepSdpRollback) {
    if (mState != kJsepStateHaveRemoteOffer) {
      JSEP_SET_ERROR("Cannot rollback remote description in "
                     << GetStateStr(mState));
      return dom::PCError::InvalidStateError;
    }

    mPendingRemoteDescription.reset();
    SetState(kJsepStateStable);
    RollbackRemoteOffer();

    return Result();
  }

  switch (mState) {
    case kJsepStateStable:
      if (type != kJsepSdpOffer) {
        JSEP_SET_ERROR("Cannot set remote answer in state "
                       << GetStateStr(mState));
        return dom::PCError::InvalidStateError;
      }
      break;
    case kJsepStateHaveLocalOffer:
    case kJsepStateHaveRemotePranswer:
      if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
        JSEP_SET_ERROR("Cannot set remote offer in state "
                       << GetStateStr(mState));
        return dom::PCError::InvalidStateError;
      }
      break;
    default:
      JSEP_SET_ERROR("Cannot set remote offer or answer in current state "
                     << GetStateStr(mState));
      return dom::PCError::InvalidStateError;
  }

  // Parse.
  UniquePtr<Sdp> parsed;
  nsresult rv = ParseSdp(sdp, &parsed);
  // Needs to be RTCError with sdp-syntax-error
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  rv = ValidateRemoteDescription(*parsed);
  NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);

  switch (type) {
    case kJsepSdpOffer:
      rv = ValidateOffer(*parsed);
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = ValidateAnswer(*mPendingLocalDescription, *parsed);
      break;
    case kJsepSdpRollback:
      MOZ_CRASH();  // Handled above
  }
  NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);

  bool iceLite =
      parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);

  // check for mismatch ufrag/pwd indicating ice restart
  // can't just check the first one because it might be disabled
  bool iceRestarting = false;
  if (mCurrentRemoteDescription.get()) {
    for (size_t i = 0; !iceRestarting &&
                       i < mCurrentRemoteDescription->GetMediaSectionCount();
         ++i) {
      const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
      const SdpMediaSection& oldMsection =
          mCurrentRemoteDescription->GetMediaSection(i);

      if (mSdpHelper.MsectionIsDisabled(newMsection) ||
          mSdpHelper.MsectionIsDisabled(oldMsection)) {
        continue;
      }

      iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
    }
  }

  std::vector<std::string> iceOptions;
  if (parsed->GetAttributeList().HasAttribute(
          SdpAttribute::kIceOptionsAttribute)) {
    iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
  }

  if (type == kJsepSdpOffer) {
    // Save in case we need to rollback.
    mOldTransceivers = mTransceivers;
    for (auto& transceiver : mTransceivers) {
      if (!transceiver.IsNegotiated()) {
        // We chose a level for this transceiver, but never negotiated it.
        // Discard this state.
        transceiver.ClearLevel();
      }
    }
  }

  // TODO(bug 1095780): Note that we create remote tracks even when
  // They contain only codecs we can't negotiate or other craziness.
  rv = UpdateTransceiversFromRemoteDescription(*parsed);
  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
    MOZ_ASSERT(GetTransceiverForLevel(i));
  }

  switch (type) {
    case kJsepSdpOffer:
      rv = SetRemoteDescriptionOffer(std::move(parsed));
      break;
    case kJsepSdpAnswer:
    case kJsepSdpPranswer:
      rv = SetRemoteDescriptionAnswer(type, std::move(parsed));
      break;
    case kJsepSdpRollback:
      MOZ_CRASH();  // Handled above
  }

  NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);

  mRemoteIsIceLite = iceLite;
  mIceOptions = iceOptions;
  SetIceRestarting(iceRestarting);
  return Result();
}

nsresult JsepSessionImpl::HandleNegotiatedSession(
    const UniquePtr<Sdp>& local, const UniquePtr<Sdp>& remote) {
  // local ufrag/pwd has been negotiated; we will never go back to the old ones
  mOldIceUfrag.clear();
  mOldIcePwd.clear();

  bool remoteIceLite =
      remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);

  mIceControlling = remoteIceLite || *mIsPendingOfferer;

  const Sdp& answer = *mIsPendingOfferer ? *remote : *local;

  SdpHelper::BundledMids bundledMids;
  nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  // First, set the bundle level on the transceivers
  for (auto& [mid, transportOwner] : bundledMids) {
    Maybe<JsepTransceiver> bundledTransceiver = GetTransceiverForMid(mid);
    if (!bundledTransceiver) {
      JSEP_SET_ERROR("No transceiver for bundled mid " << mid);
      return NS_ERROR_INVALID_ARG;
    }
    bundledTransceiver->SetBundleLevel(transportOwner->GetLevel());
    SetTransceiver(*bundledTransceiver);
  }

  // Now walk through the m-sections, perform negotiation, and update the
  // transceivers.
  for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
    Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
    if (!transceiver) {
      MOZ_ASSERT(false);
      JSEP_SET_ERROR("No transceiver for level " << i);
      return NS_ERROR_FAILURE;
    }

    if (mSdpHelper.MsectionIsDisabled(local->GetMediaSection(i))) {
      transceiver->SetRemoved();
    }

    // Skip disabled m-sections.
    if (mSdpHelper.MsectionIsDisabled(answer.GetMediaSection(i))) {
      transceiver->mTransport.Close();
      transceiver->SetStopped();
      transceiver->Disassociate();
      transceiver->ClearBundleLevel();
      transceiver->mSendTrack.SetActive(false);
      transceiver->mRecvTrack.SetActive(false);
      transceiver->SetCanRecycleMyMsection();
      SetTransceiver(*transceiver);
      // Do not clear mLevel yet! That will happen on the next negotiation.
      continue;
    }

    rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i),
                                   local->GetMediaSection(i), *transceiver);
    NS_ENSURE_SUCCESS(rv, rv);
    SetTransceiver(*transceiver);
  }

  CopyBundleTransports();

  std::vector<JsepTrack*> receiveTracks;
  for (auto& transceiver : mTransceivers) {
    // Do not count payload types for non-active recv tracks as duplicates. If
    // we receive an RTP packet with a payload type that is used by both a
    // sendrecv and a sendonly m-section, there is no ambiguity; it is for the
    // sendrecv m-section.
    if (transceiver.mRecvTrack.GetActive()) {
      receiveTracks.push_back(&transceiver.mRecvTrack);
    }
  }
  JsepTrack::SetUniqueReceivePayloadTypes(receiveTracks);

  mNegotiations++;

  mGeneratedAnswer.reset();
  mGeneratedOffer.reset();

  return NS_OK;
}

nsresult JsepSessionImpl::MakeNegotiatedTransceiver(
    const SdpMediaSection& remote, const SdpMediaSection& local,
    JsepTransceiver& transceiver) {
  const SdpMediaSection& answer = *mIsPendingOfferer ? remote : local;

  bool sending = false;
  bool receiving = false;

  // We do not pay any attention to whether the transceiver is stopped here,
  // because that is only a signal to the JSEP engine to _attempt_ to reject
  // the corresponding m-section the next time we're the offerer.
  if (*mIsPendingOfferer) {
    receiving = answer.IsSending();
    sending = answer.IsReceiving();
  } else {
    sending = answer.IsSending();
    receiving = answer.IsReceiving();
  }

  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiated m= line"
                          << " index=" << local.GetLevel() << " type="
                          << local.GetMediaType() << " sending=" << sending
                          << " receiving=" << receiving);

  transceiver.SetNegotiated();

  // Ensure that this is finalized in case we need to copy it below
  nsresult rv =
      FinalizeTransport(remote.GetAttributeList(), answer.GetAttributeList(),
                        &transceiver.mTransport);
  NS_ENSURE_SUCCESS(rv, rv);

  transceiver.mSendTrack.SetActive(sending);
  rv = transceiver.mSendTrack.Negotiate(answer, remote, local);
  if (NS_FAILED(rv)) {
    JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
                   << local.GetLevel());
    return rv;
  }

  JsepTrack& recvTrack = transceiver.mRecvTrack;
  recvTrack.SetActive(receiving);
  rv = recvTrack.Negotiate(answer, remote, local);
  if (NS_FAILED(rv)) {
    JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
                   << local.GetLevel());
    return rv;
  }

  if (transceiver.HasBundleLevel() && recvTrack.GetSsrcs().empty() &&
      recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
    // TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid
    // support, we should only fire this warning if that extension was not
    // negotiated.
    MOZ_MTLOG(ML_ERROR, "[" << mName
                            << "]: Bundled m-section has no ssrc "
                               "attributes. This may cause media packets to be "
                               "dropped.");
  }

  if (transceiver.mTransport.mComponents == 2) {
    // RTCP MUX or not.
    // TODO(bug 1095743): verify that the PTs are consistent with mux.
    MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: RTCP-MUX is off");
  }

  if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
    const auto extmaps = answer.GetAttributeList().GetExtmap().mExtmaps;
    for (const auto& negotiatedExtension : extmaps) {
      if (negotiatedExtension.entry == 0) {
        MOZ_ASSERT(false"This should have been caught sooner");
        continue;
      }

      mExtmapEntriesEverNegotiated[negotiatedExtension.entry] =
          negotiatedExtension.extensionname;

      for (auto& originalExtension : mRtpExtensions) {
        if (negotiatedExtension.extensionname ==
            originalExtension.mExtmap.extensionname) {
          // Update extmap to match what was negotiated
          originalExtension.mExtmap.entry = negotiatedExtension.entry;
          mExtmapEntriesEverUsed.insert(negotiatedExtension.entry);
        } else if (originalExtension.mExtmap.entry ==
                   negotiatedExtension.entry) {
          // If this extmap entry was claimed for a different extension, update
          // it to a new value so we don't end up with a duplicate.
          originalExtension.mExtmap.entry = GetNeverUsedExtmapEntry();
        }
      }
    }
  }

  return NS_OK;
}

void JsepSessionImpl::EnsureHasOwnTransport(const SdpMediaSection& msection,
                                            JsepTransceiver& transceiver) {
  JsepTransport& transport = transceiver.mTransport;

  if (!transceiver.HasOwnTransport()) {
    // Transceiver didn't own this transport last time, it won't now either
    transport.Close();
  }

  transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag();
  transport.mLocalPwd = msection.GetAttributeList().GetIcePwd();

  transceiver.ClearBundleLevel();

  if (!transport.mComponents) {
    if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
      transport.mComponents = 2;
    } else {
      transport.mComponents = 1;
    }
  }

  if (transport.mTransportId.empty()) {
    // TODO: Once we use different ICE ufrag/pass for each m-section, we can
    // use that here.
    std::ostringstream os;
    os << "transport_" << mTransportIdCounter++;
    transport.mTransportId = os.str();
  }
}

void JsepSessionImpl::CopyBundleTransports() {
  for (auto& transceiver : mTransceivers) {
    if (transceiver.HasBundleLevel()) {
      MOZ_MTLOG(ML_DEBUG,
                "[" << mName << "] Transceiver " << transceiver.GetLevel()
                    << " is in a bundle; transceiver "
                    << transceiver.BundleLevel() << " owns the transport.");
      Maybe<const JsepTransceiver> transportOwner =
          GetTransceiverForLevel(transceiver.BundleLevel());
      MOZ_ASSERT(transportOwner);
      if (transportOwner) {
        transceiver.mTransport = transportOwner->mTransport;
      }
    } else if (transceiver.HasLevel()) {
      MOZ_MTLOG(ML_DEBUG, "[" << mName << "] Transceiver "
                              << transceiver.GetLevel()
                              << " is not necessarily in a bundle.");
    }
    if (transceiver.HasLevel()) {
      MOZ_MTLOG(ML_DEBUG,
                "[" << mName << "] Transceiver " << transceiver.GetLevel()
                    << " transport-id: " << transceiver.mTransport.mTransportId
                    << " components: " << transceiver.mTransport.mComponents);
    }
  }
}

nsresult JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote,
                                            const SdpAttributeList& answer,
                                            JsepTransport* transport) const {
  if (!transport->mComponents) {
    return NS_OK;
  }

  if (!transport->mIce || transport->mIce->mUfrag != remote.GetIceUfrag() ||
      transport->mIce->mPwd != remote.GetIcePwd()) {
    UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();
    transport->mDtls = nullptr;

    // We do sanity-checking for these in ParseSdp
    ice->mUfrag = remote.GetIceUfrag();
    ice->mPwd = remote.GetIcePwd();
    transport->mIce = std::move(ice);
  }

  if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
    transport->mIce->mCandidates = remote.GetCandidate();
  }

  if (!transport->mDtls) {
    // RFC 5763 says:
    //
    //   The endpoint MUST use the setup attribute defined in [RFC4145].
    //   The endpoint that is the offerer MUST use the setup attribute
    //   value of setup:actpass and be prepared to receive a client_hello
    //   before it receives the answer.  The answerer MUST use either a
    //   setup attribute value of setup:active or setup:passive.  Note that
    //   if the answerer uses setup:passive, then the DTLS handshake will
    //   not begin until the answerer is received, which adds additional
    //   latency. setup:active allows the answer and the DTLS handshake to
    //   occur in parallel.  Thus, setup:active is RECOMMENDED.  Whichever
    //   party is active MUST initiate a DTLS handshake by sending a
    //   ClientHello over each flow (host/port quartet).
    UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>();
    dtls->mFingerprints = remote.GetFingerprint();
    if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) {
      dtls->mRole = *mIsPendingOfferer ? JsepDtlsTransport::kJsepDtlsServer
                                       : JsepDtlsTransport::kJsepDtlsClient;
    } else {
      if (*mIsPendingOfferer) {
        dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
                          ? JsepDtlsTransport::kJsepDtlsServer
                          : JsepDtlsTransport::kJsepDtlsClient;
      } else {
        dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
                          ? JsepDtlsTransport::kJsepDtlsClient
                          : JsepDtlsTransport::kJsepDtlsServer;
      }
    }

    transport->mDtls = std::move(dtls);
  }

  if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
    transport->mComponents = 1;
  }

  return NS_OK;
}

nsresult JsepSessionImpl::AddTransportAttributes(
    SdpMediaSection* msection, SdpSetupAttribute::Role dtlsRole) {
  if (mIceUfrag.empty() || mIcePwd.empty()) {
    JSEP_SET_ERROR("Missing ICE ufrag or password");
    return NS_ERROR_FAILURE;
  }

  SdpAttributeList& attrList = msection->GetAttributeList();
  attrList.SetAttribute(
      new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag));
  attrList.SetAttribute(
      new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));

  msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));

  return NS_OK;
}

nsresult JsepSessionImpl::CopyPreviousTransportParams(
    const Sdp& oldAnswer, const Sdp& offerersPreviousSdp, const Sdp& newOffer,
    Sdp* newLocal) {
  for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
    if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
        mSdpHelper.AreOldTransportParamsValid(oldAnswer, offerersPreviousSdp,
                                              newOffer, i)) {
      // If newLocal is an offer, this will be the number of components we used
      // last time, and if it is an answer, this will be the number of
      // components we've decided we're using now.
      Maybe<const JsepTransceiver> transceiver(GetTransceiverForLevel(i));
      if (!transceiver) {
        MOZ_ASSERT(false);
        JSEP_SET_ERROR("No transceiver for level " << i);
        return NS_ERROR_FAILURE;
      }
      size_t numComponents = transceiver->mTransport.mComponents;
      nsresult rv = mSdpHelper.CopyTransportParams(
          numComponents, mCurrentLocalDescription->GetMediaSection(i),
          &newLocal->GetMediaSection(i));
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }

  return NS_OK;
}

nsresult JsepSessionImpl::ParseSdp(const std::string& sdp,
                                   UniquePtr<Sdp>* parsedp) {
  auto results = mParser->Parse(sdp);
  auto parsed = std::move(results->Sdp());
  mLastSdpParsingErrors = results->Errors();
  if (!parsed) {
    std::string error = results->ParserName() + " Failed to parse SDP: ";
    mSdpHelper.AppendSdpParseErrors(mLastSdpParsingErrors, &error);
    JSEP_SET_ERROR(error);
    return NS_ERROR_INVALID_ARG;
  }
  // Verify that the JSEP rules for all SDP are followed
  for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
    if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
      // Disabled, let this stuff slide.
      continue;
    }

    const SdpMediaSection& msection(parsed->GetMediaSection(i));
    auto& mediaAttrs = msection.GetAttributeList();

    if (mediaAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
        mediaAttrs.GetMid().length() > 16) {
      JSEP_SET_ERROR(
          "Invalid description, mid length greater than 16 "
          "unsupported until 2-byte rtp header extensions are "
          "supported in webrtc.org");
      return NS_ERROR_INVALID_ARG;
    }

    if (mediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
      std::set<uint16_t> extIds;
      for (const auto& ext : mediaAttrs.GetExtmap().mExtmaps) {
        uint16_t id = ext.entry;

        if (id < 1 || id > 14) {
          JSEP_SET_ERROR("Description contains invalid extension id "
                         << id << " on level " << i
                         << " which is unsupported until 2-byte rtp"
                            " header extensions are supported in webrtc.org");
          return NS_ERROR_INVALID_ARG;
        }

        if (extIds.find(id) != extIds.end()) {
          JSEP_SET_ERROR("Description contains duplicate extension id "
                         << id << " on level " << i);
          return NS_ERROR_INVALID_ARG;
        }
        extIds.insert(id);
      }
    }

    static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes();
    if (msection.GetMediaType() == SdpMediaSection::kAudio ||
        msection.GetMediaType() == SdpMediaSection::kVideo) {
      // Sanity-check that payload type can work with RTP
      for (const std::string& fmt : msection.GetFormats()) {
        uint16_t payloadType;
        if (!SdpHelper::GetPtAsInt(fmt, &payloadType)) {
          JSEP_SET_ERROR("Payload type \""
                         << fmt << "\" is not a 16-bit unsigned int at level "
                         << i);
          return NS_ERROR_INVALID_ARG;
        }
        if (payloadType > 127) {
          JSEP_SET_ERROR("audio/video payload type \""
                         << fmt << "\" is too large at level " << i);
          return NS_ERROR_INVALID_ARG;
        }
        if (forbidden.test(payloadType)) {
          JSEP_SET_ERROR("Illegal audio/video payload type \""
                         << fmt << "\" at level " << i);
          return NS_ERROR_INVALID_ARG;
        }
      }
    }
  }

  *parsedp = std::move(parsed);
  return NS_OK;
}

nsresult JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer) {
  MOZ_ASSERT(mState == kJsepStateStable);

  mPendingRemoteDescription = std::move(offer);
  mIsPendingOfferer = Some(false);

  SetState(kJsepStateHaveRemoteOffer);
  return NS_OK;
}

nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
                                                     UniquePtr<Sdp> answer) {
  MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
             mState == kJsepStateHaveRemotePranswer);

  mPendingRemoteDescription = std::move(answer);

  nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
                                        mPendingRemoteDescription);
  NS_ENSURE_SUCCESS(rv, rv);

  mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
  mCurrentLocalDescription = std::move(mPendingLocalDescription);
  MOZ_ASSERT(mIsPendingOfferer.isSome() && *mIsPendingOfferer);
  mIsPendingOfferer.reset();
  mIsCurrentOfferer = Some(true);

  SetState(kJsepStateStable);
  return NS_OK;
}

Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLevel(
    size_t level) const {
  return FindTransceiver([level](const JsepTransceiver& transceiver) {
    return transceiver.HasLevel() && (transceiver.GetLevel() == level);
  });
}

Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForMid(
    const std::string& mid) const {
  return FindTransceiver([mid](const JsepTransceiver& transceiver) {
    return transceiver.IsAssociated() && (transceiver.GetMid() == mid);
  });
}

Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLocal(size_t level) {
  if (Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level)) {
    if (transceiver->CanRecycleMyMsection() &&
        transceiver->GetMediaType() != SdpMediaSection::kApplication) {
      // Attempt to recycle. If this fails, the old transceiver stays put.
      transceiver->Disassociate();
      Maybe<JsepTransceiver> newTransceiver =
          FindUnassociatedTransceiver(transceiver->GetMediaType(), false);
      if (newTransceiver) {
        newTransceiver->SetLevel(level);
        transceiver->ClearLevel();
        transceiver->mSendTrack.ClearRids();
        SetTransceiver(*newTransceiver);
        SetTransceiver(*transceiver);
        return newTransceiver;
      }
    }

    SetTransceiver(*transceiver);
    return transceiver;
  }

  // There is no transceiver for |level| right now.

  // Look for an RTP transceiver (spec requires us to give the lower levels to
  // new RTP transceivers)
  for (auto& transceiver : mTransceivers) {
    if (transceiver.GetMediaType() != SdpMediaSection::kApplication &&
        transceiver.IsFreeToUse()) {
      transceiver.SetLevel(level);
      return Some(transceiver);
    }
  }

  // Ok, look for a datachannel
  for (auto& transceiver : mTransceivers) {
    if (transceiver.IsFreeToUse()) {
      transceiver.SetLevel(level);
      return Some(transceiver);
    }
  }

  return Nothing();
}

Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForRemote(
    const SdpMediaSection& msection) {
  size_t level = msection.GetLevel();
  Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level);
  if (transceiver) {
    if (!transceiver->CanRecycleMyMsection()) {
      return transceiver;
    }
    transceiver->Disassociate();
    transceiver->ClearLevel();
    transceiver->mSendTrack.ClearRids();
    SetTransceiver(*transceiver);
  }

  // No transceiver for |level|
  transceiver = FindUnassociatedTransceiver(msection.GetMediaType(), true);
  if (transceiver) {
    transceiver->SetLevel(level);
    SetTransceiver(*transceiver);
    return transceiver;
  }

  // Make a new transceiver
  JsepTransceiver newTransceiver(msection.GetMediaType(), *mUuidGen,
                                 SdpDirectionAttribute::kRecvonly);
  newTransceiver.SetLevel(level);
  newTransceiver.SetOnlyExistsBecauseOfSetRemote(true);
  AddTransceiver(newTransceiver);
  return Some(mTransceivers.back());
}

Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverWithTransport(
    const std::string& transportId) const {
  for (const auto& transceiver : mTransceivers) {
    if (transceiver.HasOwnTransport() &&
        (transceiver.mTransport.mTransportId == transportId)) {
      MOZ_ASSERT(transceiver.HasLevel(),
                 "Transceiver has a transport, but no level!");
      return Some(transceiver);
    }
  }

  return Nothing();
}

nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
    const Sdp& remote) {
  // Iterate over the sdp, updating remote tracks as we go
  for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
    const SdpMediaSection& msection = remote.GetMediaSection(i);

    Maybe<JsepTransceiver> transceiver(GetTransceiverForRemote(msection));
    if (!transceiver) {
      return NS_ERROR_FAILURE;
    }

    if (!mSdpHelper.MsectionIsDisabled(msection)) {
      if (msection.GetAttributeList().HasAttribute(
              SdpAttribute::kMidAttribute)) {
        transceiver->Associate(msection.GetAttributeList().GetMid());
      }
      if (!transceiver->IsAssociated()) {
        transceiver->Associate(GetNewMid());
      } else {
        mUsedMids.insert(transceiver->GetMid());
      }
    } else {
      // We do not disassociate here, that happens when negotiation completes
      // These things cannot be rolled back.
      transceiver->mTransport.Close();
      transceiver->SetStopped();
      SetTransceiver(*transceiver);
      continue;
    }

    if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
      SetTransceiver(*transceiver);
      continue;
    }

    transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection);

    // Interop workaround for endpoints that don't support msid.
    // Ensures that there is a default stream id set, provided the remote is
    // sending.
    // TODO(bug 1426005): Remove this, or at least move it to JsepTrack.
    transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId});

    // This will process a=msid if present, or clear the stream ids if the
    // msection is not sending. If the msection is sending, and there are no
    // a=msid, the previously set default will stay.
    transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection);
    SetTransceiver(*transceiver);
  }

  return NS_OK;
}

Maybe<JsepTransceiver> JsepSessionImpl::FindUnassociatedTransceiver(
    SdpMediaSection::MediaType type, bool magic) {
  // Look through transceivers that are not mapped to an m-section
  for (auto& transceiver : mTransceivers) {
    if (type == SdpMediaSection::kApplication &&
        type == transceiver.GetMediaType()) {
      transceiver.RestartDatachannelTransceiver();
      return Some(transceiver);
    }
    if (transceiver.IsFreeToUse() &&
        (!magic || transceiver.HasAddTrackMagic()) &&
        (transceiver.GetMediaType() == type)) {
      return Some(transceiver);
    }
  }

  return Nothing();
}

void JsepSessionImpl::RollbackLocalOffer() {
  for (size_t i = 0; i < mTransceivers.size(); ++i) {
    auto& transceiver = mTransceivers[i];
    if (mOldTransceivers.size() > i) {
      transceiver.Rollback(mOldTransceivers[i], false);
      mOldTransceivers[i] = transceiver;
      continue;
    }

    JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
    InitTransceiver(temp);
    transceiver.Rollback(temp, false);
    mOldTransceivers.push_back(transceiver);
  }

  mTransceivers = std::move(mOldTransceivers);
}

void JsepSessionImpl::RollbackRemoteOffer() {
  for (size_t i = 0; i < mTransceivers.size(); ++i) {
    auto& transceiver = mTransceivers[i];
    if (mOldTransceivers.size() > i) {
      // Some stuff cannot be rolled back. Save this information.
      transceiver.Rollback(mOldTransceivers[i], true);
      mOldTransceivers[i] = transceiver;
      continue;
    }

    if (transceiver.HasLevel()) {
      // New transceiver, that was either created by the remote offer, or
      // attached to the remote offer.
      // We rollback even for transceivers we will remove, just to ensure we end
      // up at the starting state.
      JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
      InitTransceiver(temp);
      transceiver.Rollback(temp, true);

      if (transceiver.OnlyExistsBecauseOfSetRemote()) {
        transceiver.SetStopped();
        transceiver.Disassociate();
        transceiver.SetRemoved();
      } else {
        // Oof. This hangs around because of addTrack. Make it magic!
        transceiver.SetAddTrackMagic();
      }
    }  // else, _we_ added this and it is not attached to the remote offer yet

    mOldTransceivers.push_back(transceiver);
  }

  mTransceivers = std::move(mOldTransceivers);
}

nsresult JsepSessionImpl::ValidateLocalDescription(const Sdp& description,
                                                   JsepSdpType type) {
  Sdp* generated = nullptr;
  // TODO(bug 1095226): Better checking.
  if (type == kJsepSdpOffer) {
    generated = mGeneratedOffer.get();
  } else {
    generated = mGeneratedAnswer.get();
  }

  if (!generated) {
    JSEP_SET_ERROR(
        "Calling SetLocal without first calling CreateOffer/Answer"
        " is not supported.");
    return NS_ERROR_UNEXPECTED;
  }

  if (description.GetMediaSectionCount() != generated->GetMediaSectionCount()) {
    JSEP_SET_ERROR("Changing the number of m-sections is not allowed");
    return NS_ERROR_INVALID_ARG;
  }

  for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
    auto& origMsection = generated->GetMediaSection(i);
    auto& finalMsection = description.GetMediaSection(i);
    if (origMsection.GetMediaType() != finalMsection.GetMediaType()) {
      JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed");
      return NS_ERROR_INVALID_ARG;
    }

    // These will be present in reoffer
    if (!mCurrentLocalDescription) {
      if (finalMsection.GetAttributeList().HasAttribute(
              SdpAttribute::kCandidateAttribute)) {
        JSEP_SET_ERROR("Adding your own candidate attributes is not supported");
        return NS_ERROR_INVALID_ARG;
      }

      if (finalMsection.GetAttributeList().HasAttribute(
              SdpAttribute::kEndOfCandidatesAttribute)) {
        JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?");
        return NS_ERROR_INVALID_ARG;
      }
    }

    if (mSdpHelper.MsectionIsDisabled(finalMsection)) {
      continue;
    }

    if (!finalMsection.GetAttributeList().HasAttribute(
            SdpAttribute::kMidAttribute)) {
      JSEP_SET_ERROR("Local descriptions must have a=mid attributes.");
      return NS_ERROR_INVALID_ARG;
    }

    if (finalMsection.GetAttributeList().GetMid() !=
        origMsection.GetAttributeList().GetMid()) {
      JSEP_SET_ERROR("Changing the mid of m-sections is not allowed.");
      return NS_ERROR_INVALID_ARG;
    }

    // TODO(bug 1095218): Check msid
    // TODO(bug 1095226): Check ice-ufrag and ice-pwd
    // TODO(bug 1095226): Check fingerprints
    // TODO(bug 1095226): Check payload types (at least ensure that payload
    // types we don't actually support weren't added)
    // TODO(bug 1095226): Check ice-options?
  }

  if (description.GetAttributeList().HasAttribute(
          SdpAttribute::kIceLiteAttribute)) {
    JSEP_SET_ERROR("Running ICE in lite mode is unsupported");
    return NS_ERROR_INVALID_ARG;
  }

  return NS_OK;
}

nsresult JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) {
  if (!mCurrentLocalDescription) {
    // Initial offer; nothing to validate besides the stuff in ParseSdp
    return NS_OK;
  }

  if (mCurrentLocalDescription->GetMediaSectionCount() >
      description.GetMediaSectionCount()) {
    JSEP_SET_ERROR(
        "New remote description has fewer m-sections than the "
        "previous remote description.");
    return NS_ERROR_INVALID_ARG;
  }

  for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
    const SdpAttributeList& attrs =
        description.GetMediaSection(i).GetAttributeList();

    if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
      for (const auto& ext : attrs.GetExtmap().mExtmaps) {
        if (mExtmapEntriesEverNegotiated.count(ext.entry) &&
            mExtmapEntriesEverNegotiated[ext.entry] != ext.extensionname) {
          JSEP_SET_ERROR(
              "Remote description attempted to remap RTP extension id "
              << ext.entry << " from "
              << mExtmapEntriesEverNegotiated[ext.entry] << " to "
              << ext.extensionname);
          return NS_ERROR_INVALID_ARG;
        }
      }
    }
  }

  if (!mCurrentRemoteDescription) {
    // No further checking for initial answers
    return NS_OK;
  }

  // These are solely to check that bundle is valid
  SdpHelper::BundledMids bundledMids;
  nsresult rv = GetNegotiatedBundledMids(&bundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  SdpHelper::BundledMids newBundledMids;
  rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
  NS_ENSURE_SUCCESS(rv, rv);

  // check for partial ice restart, which is not supported
  Maybe<bool> iceCredsDiffer;
  for (size_t i = 0; i < mCurrentRemoteDescription->GetMediaSectionCount();
       ++i) {
    const SdpMediaSection& newMsection = description.GetMediaSection(i);
    const SdpMediaSection& oldMsection =
        mCurrentRemoteDescription->GetMediaSection(i);

    if (mSdpHelper.MsectionIsDisabled(newMsection) ||
        mSdpHelper.MsectionIsDisabled(oldMsection)) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=86 G=88

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

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.