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,
#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
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::InvalidState