Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "transport/logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.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;
switch (msection.GetMediaType()) {
case SdpMediaSection::kAudio:
mediaType = JsepMediaType::kAudio;
break;
case SdpMediaSection::kVideo:
mediaType = JsepMediaType::kVideo;
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 (answer.GetMediaSection(i).GetPort() == 0) {
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))) {