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))) {
// 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)) {
continue;
}
if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
JSEP_SET_ERROR("Remote description changes the media type of m-line "
<< i);
return NS_ERROR_INVALID_ARG;
}
bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
if (mIsPendingOfferer.isSome() && *mIsPendingOfferer && differ &&
!IsIceRestarting()) {
JSEP_SET_ERROR(
"Remote description indicates ICE restart but offer did not "
"request ICE restart (new remote description changes either "
"the ice-ufrag or ice-pwd)");
return NS_ERROR_INVALID_ARG;
}
// Detect whether all the creds are the same or all are different
if (!iceCredsDiffer.isSome()) {
// for the first msection capture whether creds are different or same
iceCredsDiffer = mozilla::Some(differ);
} else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
// subsequent msections must match the first sections
JSEP_SET_ERROR(
"Partial ICE restart is unsupported at this time "
"(new remote description changes either the ice-ufrag "
"or ice-pwd on fewer than all msections)");
return NS_ERROR_INVALID_ARG;
}
}
return NS_OK;
}
nsresult JsepSessionImpl::ValidateOffer(const Sdp& offer) {
return mSdpHelper.ValidateTransportAttributes(offer, sdp::kOffer);
}
nsresult JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer) {
if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) {
JSEP_SET_ERROR("Offer and answer have different number of m-lines "
<< "(" << offer.GetMediaSectionCount() << " vs "
<< answer.GetMediaSectionCount() << ")");
return NS_ERROR_INVALID_ARG;
}
nsresult rv = mSdpHelper.ValidateTransportAttributes(answer, sdp::kAnswer);
NS_ENSURE_SUCCESS(rv, rv);
for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) {
JSEP_SET_ERROR("Answer and offer have different media types at m-line "
<< i);
return NS_ERROR_INVALID_ARG;
}
if (mSdpHelper.MsectionIsDisabled(answerMsection)) {
continue;
}
if (mSdpHelper.MsectionIsDisabled(offerMsection)) {
JSEP_SET_ERROR(
"Answer tried to enable an m-section that was disabled in the offer");
return NS_ERROR_INVALID_ARG;
}
if (!offerMsection.IsSending() && answerMsection.IsReceiving()) {
JSEP_SET_ERROR("Answer tried to set recv when offer did not set send");
return NS_ERROR_INVALID_ARG;
}
if (!offerMsection.IsReceiving() && answerMsection.IsSending()) {
JSEP_SET_ERROR("Answer tried to set send when offer did not set recv");
return NS_ERROR_INVALID_ARG;
}
const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList());
const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList());
if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
offerAttrs.GetMid() != answerAttrs.GetMid()) {
JSEP_SET_ERROR("Answer changes mid for level, was \'"
<< offerMsection.GetAttributeList().GetMid()
<< "\', now \'"
<< answerMsection.GetAttributeList().GetMid() << "\'");
return NS_ERROR_INVALID_ARG;
}
// Sanity check extmap
if (answerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
if (!offerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
JSEP_SET_ERROR("Answer adds extmap attributes to level " << i);
return NS_ERROR_INVALID_ARG;
}
for (const auto& ansExt : answerAttrs.GetExtmap().mExtmaps) {
bool found = false;
for (const auto& offExt : offerAttrs.GetExtmap().mExtmaps) {
if (ansExt.extensionname == offExt.extensionname) {
if ((ansExt.direction & reverse(offExt.direction)) !=
ansExt.direction) {
// FIXME we do not return an error here, because Chrome up to
// version 57 is actually tripping over this if they are the
// answerer. See bug 1355010 for details.
MOZ_MTLOG(ML_WARNING,
"[" << mName
<< "]: Answer has inconsistent"
" direction on extmap attribute at level "
<< i << " (" << ansExt.extensionname
<< "). Offer had " << offExt.direction
<< ", answer had " << ansExt.direction << ".");
// return NS_ERROR_INVALID_ARG;
}
if (offExt.entry < 4096 && (offExt.entry != ansExt.entry)) {
JSEP_SET_ERROR("Answer changed id for extmap attribute at level "
<< i << " (" << offExt.extensionname << ") from "
<< offExt.entry << " to " << ansExt.entry << ".");
return NS_ERROR_INVALID_ARG;
}
if (ansExt.entry >= 4096) {
JSEP_SET_ERROR("Answer used an invalid id ("
<< ansExt.entry
<< ") for extmap attribute at level " << i << " ("
<< ansExt.extensionname << ").");
return NS_ERROR_INVALID_ARG;
}
found = true;
break;
}
}
if (!found) {
JSEP_SET_ERROR("Answer has extmap "
<< ansExt.extensionname
<< " at "
"level "
<< i << " that was not present in offer.");
return NS_ERROR_INVALID_ARG;
}
}
}
}
return NS_OK;
}
nsresult JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp) {
// draft-ietf-rtcweb-jsep-08 Section 5.2.1:
// o The second SDP line MUST be an "o=" line, as specified in
// [RFC4566], Section 5.2. The value of the <username> field SHOULD
// be "-". The value of the <sess-id> field SHOULD be a
// cryptographically random number. To ensure uniqueness, this
// number SHOULD be at least 64 bits long. The value of the <sess-
// version> field SHOULD be zero. The value of the <nettype>
// <addrtype> <unicast-address> tuple SHOULD be set to a non-
// meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the
// local address in this field. As mentioned in [RFC4566], the
// entire o= line needs to be unique, but selecting a random number
// for <sess-id> is sufficient to accomplish this.
//
// Historical note: we used to report the actual version number here, after
// "SDPARTA-", but that becomes a problem starting with version 100, since
// some services parse 100 as "10" and give us legacy/broken behavior. So
// we're freezing the version number at 99.0 in this string.
auto origin = SdpOrigin("mozilla...THIS_IS_SDPARTA-99.0", mSessionId,
mSessionVersion, sdp::kIPv4, "0.0.0.0");
UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin);
if (mDtlsFingerprints.empty()) {
JSEP_SET_ERROR("Missing DTLS fingerprint");
return NS_ERROR_FAILURE;
}
UniquePtr<SdpFingerprintAttributeList> fpl =
MakeUnique<SdpFingerprintAttributeList>();
for (auto& dtlsFingerprint : mDtlsFingerprints) {
fpl->PushEntry(dtlsFingerprint.mAlgorithm, dtlsFingerprint.mValue);
}
sdp->GetAttributeList().SetAttribute(fpl.release());
auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
iceOpts->PushEntry("trickle");
sdp->GetAttributeList().SetAttribute(iceOpts);
// This assumes content doesn't add a bunch of msid attributes with a
// different semantic in mind.
std::vector<std::string> msids;
msids.push_back("*");
mSdpHelper.SetupMsidSemantic(msids, sdp.get());
*sdpp = std::move(sdp);
return NS_OK;
}
nsresult JsepSessionImpl::SetupIds() {
SECStatus rv = PK11_GenerateRandom(
reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
// RFC 3264 says that session-ids MUST be representable as a _signed_
// 64 bit number, meaning the MSB cannot be set.
mSessionId = mSessionId >> 1;
if (rv != SECSuccess) {
JSEP_SET_ERROR("Failed to generate session id: " << rv);
return NS_ERROR_FAILURE;
}
if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
JSEP_SET_ERROR("Failed to generate default uuid for streams");
return NS_ERROR_FAILURE;
}
if (!mUuidGen->Generate(&mCNAME)) {
JSEP_SET_ERROR("Failed to generate CNAME");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void JsepSessionImpl::SetDefaultCodecs(
const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) {
mSupportedCodecs.clear();
for (const auto& codec : aPreferredCodecs) {
mSupportedCodecs.emplace_back(codec->Clone());
}
}
void JsepSessionImpl::SetState(JsepSignalingState state) {
if (state == mState) return;
MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> "
<< GetStateStr(state));
mState = state;
}
JsepSession::Result JsepSessionImpl::AddRemoteIceCandidate(
const std::string& candidate, const std::string& mid,
const Maybe<uint16_t>& level, const std::string& ufrag,
std::string* transportId) {
mLastError.clear();
if (!mCurrentRemoteDescription && !mPendingRemoteDescription) {
JSEP_SET_ERROR("Cannot add ICE candidate when there is no remote SDP");
return dom::PCError::InvalidStateError;
}
if (mid.empty() && !level.isSome() && candidate.empty()) {
// Set end-of-candidates on SDP
if (mCurrentRemoteDescription) {
nsresult rv = mSdpHelper.SetIceGatheringComplete(
mCurrentRemoteDescription.get(), ufrag);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
}
if (mPendingRemoteDescription) {
// If we had an error when adding the candidate to the current
// description, we stomp them here. This is deliberate.
nsresult rv = mSdpHelper.SetIceGatheringComplete(
mPendingRemoteDescription.get(), ufrag);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
}
return Result();
}
Maybe<JsepTransceiver> transceiver;
if (!mid.empty()) {
transceiver = GetTransceiverForMid(mid);
} else if (level.isSome()) {
transceiver = GetTransceiverForLevel(level.value());
}
if (!transceiver) {
JSEP_SET_ERROR("Cannot set ICE candidate for level="
<< level << " mid=" << mid << ": No such transceiver.");
return dom::PCError::OperationError;
}
if (level.isSome() && transceiver->GetLevel() != level.value()) {
MOZ_MTLOG(ML_WARNING, "Mismatch between mid and level - \""
<< mid << "\" is not the mid for level "
<< level);
}
*transportId = transceiver->mTransport.mTransportId;
nsresult rv = NS_ERROR_UNEXPECTED;
if (mCurrentRemoteDescription) {
rv =
mSdpHelper.AddCandidateToSdp(mCurrentRemoteDescription.get(), candidate,
transceiver->GetLevel(), ufrag);
}
if (mPendingRemoteDescription) {
// If we had an error when adding the candidate to the current description,
// we stomp them here. This is deliberate.
rv =
mSdpHelper.AddCandidateToSdp(mPendingRemoteDescription.get(), candidate,
transceiver->GetLevel(), ufrag);
}
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
return Result();
}
nsresult JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
const std::string& transportId,
const std::string& ufrag,
uint16_t* level,
std::string* mid,
bool* skipped) {
mLastError.clear();
*skipped = true;
if (!mCurrentLocalDescription && !mPendingLocalDescription) {
JSEP_SET_ERROR("Cannot add ICE candidate when there is no local SDP");
return NS_ERROR_UNEXPECTED;
}
Maybe<const JsepTransceiver> transceiver =
GetTransceiverWithTransport(transportId);
if (!transceiver || !transceiver->IsAssociated()) {
// mainly here to make some testing less complicated, but also just in case
return NS_OK;
}
*level = transceiver->GetLevel();
*mid = transceiver->GetMid();
nsresult rv = NS_ERROR_INVALID_ARG;
if (mCurrentLocalDescription) {
rv = mSdpHelper.AddCandidateToSdp(mCurrentLocalDescription.get(), candidate,
*level, ufrag);
}
if (mPendingLocalDescription) {
// If we had an error when adding the candidate to the current description,
// we stomp them here. This is deliberate.
rv = mSdpHelper.AddCandidateToSdp(mPendingLocalDescription.get(), candidate,
*level, ufrag);
}
*skipped = false;
return rv;
}
nsresult JsepSessionImpl::UpdateDefaultCandidate(
const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
const std::string& defaultRtcpCandidateAddr,
uint16_t defaultRtcpCandidatePort, const std::string& transportId) {
mLastError.clear();
mozilla::Sdp* sdp =
GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent);
if (!sdp) {
JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
return NS_ERROR_UNEXPECTED;
}
for (const auto& transceiver : mTransceivers) {
// We set the default address for bundled m-sections, but not candidate
// attributes. Ugh.
if (transceiver.mTransport.mTransportId == transportId) {
MOZ_ASSERT(transceiver.HasLevel(),
"Transceiver has a transport, but no level! "
"This should never happen.");
std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
if (mState == kJsepStateStable) {
if (transceiver.mTransport.mComponents == 1) {
// We know we're doing rtcp-mux by now. Don't create an rtcp attr.
defaultRtcpCandidateAddrCopy = "";
defaultRtcpCandidatePort = 0;
}
}
size_t level = transceiver.GetLevel();
if (level >= sdp->GetMediaSectionCount()) {
MOZ_ASSERT(false, "Transceiver's level is too large!");
JSEP_SET_ERROR("Transceiver's level is too large!");
return NS_ERROR_FAILURE;
}
auto& msection = sdp->GetMediaSection(level);
// Do not add default candidate to a bundle-only m-section, sinice that
// might confuse endpoints that do not support bundle-only.
if (!msection.GetAttributeList().HasAttribute(
SdpAttribute::kBundleOnlyAttribute)) {
mSdpHelper.SetDefaultAddresses(
defaultCandidateAddr, defaultCandidatePort,
defaultRtcpCandidateAddrCopy, defaultRtcpCandidatePort, &msection);
}
}
}
return NS_OK;
}
nsresult JsepSessionImpl::GetNegotiatedBundledMids(
SdpHelper::BundledMids* bundledMids) {
const Sdp* answerSdp = GetAnswer();
if (!answerSdp) {
return NS_OK;
}
return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
}
mozilla::Sdp* JsepSessionImpl::GetParsedLocalDescription(
JsepDescriptionPendingOrCurrent type) const {
if (type == kJsepDescriptionPending) {
return mPendingLocalDescription.get();
} else if (mPendingLocalDescription &&
type == kJsepDescriptionPendingOrCurrent) {
return mPendingLocalDescription.get();
}
return mCurrentLocalDescription.get();
}
mozilla::Sdp* JsepSessionImpl::GetParsedRemoteDescription(
JsepDescriptionPendingOrCurrent type) const {
if (type == kJsepDescriptionPending) {
return mPendingRemoteDescription.get();
} else if (mPendingRemoteDescription &&
type == kJsepDescriptionPendingOrCurrent) {
return mPendingRemoteDescription.get();
}
return mCurrentRemoteDescription.get();
}
const Sdp* JsepSessionImpl::GetAnswer() const {
return (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer)
? mCurrentRemoteDescription.get()
: mCurrentLocalDescription.get();
}
void JsepSessionImpl::SetIceRestarting(bool restarting) {
if (restarting) {
// not restarting -> restarting
if (!IsIceRestarting()) {
// We don't set this more than once, so the old ufrag/pwd is preserved
// even if we CreateOffer({iceRestart:true}) multiple times in a row.
mOldIceUfrag = mIceUfrag;
mOldIcePwd = mIcePwd;
}
mIceUfrag = GetRandomHex(1);
mIcePwd = GetRandomHex(4);
} else if (IsIceRestarting()) {
// restarting -> not restarting, restore old ufrag/pwd
mIceUfrag = mOldIceUfrag;
mIcePwd = mOldIcePwd;
mOldIceUfrag.clear();
mOldIcePwd.clear();
}
}
nsresult JsepSessionImpl::Close() {
mLastError.clear();
SetState(kJsepStateClosed);
return NS_OK;
}
const std::string JsepSessionImpl::GetLastError() const { return mLastError; }
const std::vector<std::pair<size_t, std::string>>&
JsepSessionImpl::GetLastSdpParsingErrors() const {
return mLastSdpParsingErrors;
}
bool JsepSessionImpl::CheckNegotiationNeeded() const {
MOZ_ASSERT(mState == kJsepStateStable);
for (const auto& transceiver : mTransceivers) {
if (transceiver.IsStopped()) {
// Nothing to do with this
continue;
}
if (transceiver.IsStopping()) {
MOZ_MTLOG(ML_DEBUG, "[" << mName
<< "]: Negotiation needed because of "
"transceiver we need to stop");
return true;
}
if (!transceiver.IsAssociated()) {
MOZ_MTLOG(ML_DEBUG, "[" << mName
<< "]: Negotiation needed because of "
"transceiver we need to associate.");
return true;
}
MOZ_ASSERT(transceiver.IsAssociated() && !transceiver.IsStopping() &&
!transceiver.IsStopped());
if (!mCurrentLocalDescription || !mCurrentRemoteDescription) {
MOZ_CRASH(
"Transceivers should not be associated if we're in stable "
"before the first negotiation.");
continue;
}
if (!transceiver.HasLevel()) {
MOZ_CRASH("Associated transceivers should always have a level.");
continue;
}
if (transceiver.GetMediaType() == SdpMediaSection::kApplication) {
continue;
}
size_t level = transceiver.GetLevel();
if (NS_WARN_IF(mCurrentLocalDescription->GetMediaSectionCount() <= level) ||
NS_WARN_IF(mCurrentRemoteDescription->GetMediaSectionCount() <=
level)) {
MOZ_ASSERT(false);
continue;
}
const SdpMediaSection& local =
mCurrentLocalDescription->GetMediaSection(level);
const SdpMediaSection& remote =
mCurrentRemoteDescription->GetMediaSection(level);
if (transceiver.mJsDirection & sdp::kSend) {
std::vector<std::string> sdpMsids;
if (local.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
for (const auto& msidAttr : local.GetAttributeList().GetMsid().mMsids) {
if (msidAttr.identifier != "-") {
sdpMsids.push_back(msidAttr.identifier);
}
}
}
std::sort(sdpMsids.begin(), sdpMsids.end());
std::vector<std::string> jsepMsids;
for (const auto& jsepMsid : transceiver.mSendTrack.GetStreamIds()) {
jsepMsids.push_back(jsepMsid);
}
std::sort(jsepMsids.begin(), jsepMsids.end());
if (!std::equal(sdpMsids.begin(), sdpMsids.end(), jsepMsids.begin(),
jsepMsids.end())) {
MOZ_MTLOG(ML_DEBUG,
"[" << mName
<< "]: Negotiation needed because transceiver "
"is sending, and the local SDP has different "
"msids than the send track");
MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SDP msids = [");
for (const auto& msid : sdpMsids) {
MOZ_MTLOG(ML_DEBUG, msid << ", ");
}
MOZ_MTLOG(ML_DEBUG, "]");
MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: JSEP msids = [");
for (const auto& msid : jsepMsids) {
MOZ_MTLOG(ML_DEBUG, msid << ", ");
}
MOZ_MTLOG(ML_DEBUG, "]");
return true;
}
}
if (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer) {
if ((local.GetDirection() != transceiver.mJsDirection) &&
reverse(remote.GetDirection()) != transceiver.mJsDirection) {
MOZ_MTLOG(ML_DEBUG, "[" << mName
<< "]: Negotiation needed because "
"the direction on our offer, and the remote "
"answer, does not "
"match the direction on a transceiver.");
return true;
}
} else if (local.GetDirection() !=
(transceiver.mJsDirection & reverse(remote.GetDirection()))) {
MOZ_MTLOG(
ML_DEBUG,
"[" << mName
<< "]: Negotiation needed because "
"the direction on our answer doesn't match the direction on a "
"transceiver, even though the remote offer would have allowed "
"it.");
return true;
}
}
return false;
}
} // namespace mozilla