/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsep/JsepSessionImpl.h"
#include <stdlib.h>
#include <bitset>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include "mozilla/StaticPrefs_media.h"
#include "transport/logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/net/DataChannelProtocol.h"
#include "nsDebug.h"
#include "nspr.h"
#include "nss.h"
#include "pk11pub.h"
#include "api/rtp_parameters.h"
#include "jsep/JsepTrack.h"
#include "jsep/JsepTransport.h"
#include "sdp/HybridSdpParser.h"
#include "sdp/SipccSdp.h"
namespace mozilla {
MOZ_MTLOG_MODULE(
"jsep")
#define JSEP_SET_ERROR(error) \
do { \
std::ostringstream os; \
os << error; \
mLastError = os.str(); \
MOZ_MTLOG(ML_ERROR,
"[" << mName <<
"]: " << mLastError); \
}
while (0);
static std::bitset<128> GetForbiddenSdpPayloadTypes() {
std::bitset<128> forbidden(0);
forbidden[1] =
true;
forbidden[2] =
true;
forbidden[19] =
true;
for (uint16_t i = 64; i < 96; ++i) {
forbidden[i] =
true;
}
return forbidden;
}
static std::string GetRandomHex(size_t words) {
std::ostringstream os;
for (size_t i = 0; i < words; ++i) {
uint32_t rand;
SECStatus rv = PK11_GenerateRandom(
reinterpret_cast<
unsigned char*>(&rand),
sizeof(rand));
if (rv != SECSuccess) {
MOZ_CRASH();
return "";
}
os << std::hex << std::setfill(
'0') << std::setw(8) << rand;
}
return os.str();
}
JsepSessionImpl::JsepSessionImpl(
const JsepSessionImpl& aOrig)
: JsepSession(aOrig),
JsepSessionCopyableStuff(aOrig),
mUuidGen(aOrig.mUuidGen->Clone()),
mGeneratedOffer(aOrig.mGeneratedOffer ? aOrig.mGeneratedOffer->Clone()
: nullptr),
mGeneratedAnswer(aOrig.mGeneratedAnswer ? aOrig.mGeneratedAnswer->Clone()
: nullptr),
mCurrentLocalDescription(aOrig.mCurrentLocalDescription
? aOrig.mCurrentLocalDescription->Clone()
: nullptr),
mCurrentRemoteDescription(aOrig.mCurrentRemoteDescription
? aOrig.mCurrentRemoteDescription->Clone()
: nullptr),
mPendingLocalDescription(aOrig.mPendingLocalDescription
? aOrig.mPendingLocalDescription->Clone()
: nullptr),
mPendingRemoteDescription(aOrig.mPendingRemoteDescription
? aOrig.mPendingRemoteDescription->Clone()
: nullptr),
mSdpHelper(&mLastError),
mParser(
new HybridSdpParser()) {
for (
const auto& codec : aOrig.mSupportedCodecs) {
mSupportedCodecs.emplace_back(codec->Clone());
}
}
nsresult JsepSessionImpl::Init() {
mLastError.clear();
MOZ_ASSERT(!mSessionId,
"Init called more than once");
nsresult rv = SetupIds();
NS_ENSURE_SUCCESS(rv, rv);
mEncodeTrackId =
Preferences::GetBool(
"media.peerconnection.sdp.encode_track_id",
true);
mIceUfrag = GetRandomHex(1);
mIcePwd = GetRandomHex(4);
return NS_OK;
}
static void GetIceCredentials(
const Sdp& aSdp,
std::set<std::pair<std::string, std::string>>* aCredentials) {
for (size_t i = 0; i < aSdp.GetMediaSectionCount(); ++i) {
const SdpAttributeList& attrs = aSdp.GetMediaSection(i).GetAttributeList();
if (attrs.HasAttribute(SdpAttribute::kIceUfragAttribute) &&
attrs.HasAttribute(SdpAttribute::kIcePwdAttribute)) {
aCredentials->insert(
std::make_pair(attrs.GetIceUfrag(), attrs.GetIcePwd()));
}
}
}
std::set<std::pair<std::string, std::string>>
JsepSessionImpl::GetLocalIceCredentials()
const {
std::set<std::pair<std::string, std::string>> result;
if (mCurrentLocalDescription) {
GetIceCredentials(*mCurrentLocalDescription, &result);
}
if (mPendingLocalDescription) {
GetIceCredentials(*mPendingLocalDescription, &result);
}
return result;
}
void JsepSessionImpl::AddTransceiver(
const JsepTransceiver& aTransceiver) {
mLastError.clear();
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: Adding transceiver " << aTransceiver.GetUuid());
#ifdef DEBUG
if (aTransceiver.GetMediaType() == SdpMediaSection::kApplication) {
// Make sure we don't add more than one DataChannel transceiver
for (
const auto& transceiver : mTransceivers) {
MOZ_ASSERT(transceiver.GetMediaType() != SdpMediaSection::kApplication);
}
}
#endif
mTransceivers.push_back(aTransceiver);
InitTransceiver(mTransceivers.back());
}
void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) {
mLastError.clear();
if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) {
// Make sure we have an ssrc. Might already be set.
aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
aTransceiver.mSendTrack.SetCNAME(mCNAME);
// Make sure we have identifiers for send track, just in case.
// (man I hate this)
if (mEncodeTrackId) {
aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid());
}
}
else {
// Datachannel transceivers should always be sendrecv. Just set it instead
// of asserting.
aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv;
}
aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs);
aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs);
// We do not set mLevel yet, we do that either on createOffer, or setRemote
}
nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) {
mLastError.clear();
if (mBundlePolicy == policy) {
return NS_OK;
}
if (mCurrentLocalDescription) {
JSEP_SET_ERROR(
"Changing the bundle policy is only supported before the "
"first SetLocalDescription.");
return NS_ERROR_UNEXPECTED;
}
mBundlePolicy = policy;
return NS_OK;
}
nsresult JsepSessionImpl::AddDtlsFingerprint(
const nsACString& algorithm,
const std::vector<uint8_t>& value) {
mLastError.clear();
JsepDtlsFingerprint fp;
fp.mAlgorithm = algorithm;
fp.mValue = value;
mDtlsFingerprints.push_back(fp);
return NS_OK;
}
nsresult JsepSessionImpl::AddRtpExtension(
JsepMediaType mediaType,
const std::string& extensionName,
SdpDirectionAttribute::Direction direction) {
mLastError.clear();
for (
auto& ext : mRtpExtensions) {
if (ext.mExtmap.direction == direction &&
ext.mExtmap.extensionname == extensionName) {
if (ext.mMediaType != mediaType) {
ext.mMediaType = JsepMediaType::kAudioVideo;
}
return NS_OK;
}
}
uint16_t freeEntry = GetNeverUsedExtmapEntry();
if (freeEntry == 0) {
return NS_ERROR_FAILURE;
}
JsepExtmapMediaType extMediaType = {
mediaType,
{freeEntry, direction,
// do we want to specify direction?
direction != SdpDirectionAttribute::kSendrecv, extensionName,
""}};
mRtpExtensions.push_back(extMediaType);
return NS_OK;
}
nsresult JsepSessionImpl::AddAudioRtpExtension(
const std::string& extensionName,
SdpDirectionAttribute::Direction direction) {
return AddRtpExtension(JsepMediaType::kAudio, extensionName, direction);
}
nsresult JsepSessionImpl::AddVideoRtpExtension(
const std::string& extensionName,
SdpDirectionAttribute::Direction direction) {
return AddRtpExtension(JsepMediaType::kVideo, extensionName, direction);
}
nsresult JsepSessionImpl::AddAudioVideoRtpExtension(
const std::string& extensionName,
SdpDirectionAttribute::Direction direction) {
return AddRtpExtension(JsepMediaType::kAudioVideo, extensionName, direction);
}
nsresult JsepSessionImpl::CreateOfferMsection(
const JsepOfferOptions& options,
JsepTransceiver& transceiver,
Sdp* local) {
SdpMediaSection::Protocol protocol(
SdpHelper::GetProtocolForMediaType(transceiver.GetMediaType()));
const Sdp* answer(GetAnswer());
const SdpMediaSection* lastAnswerMsection = nullptr;
if (answer &&
(local->GetMediaSectionCount() < answer->GetMediaSectionCount())) {
lastAnswerMsection =
&answer->GetMediaSection(local->GetMediaSectionCount());
// Use the protocol the answer used, even if it is not what we would have
// used.
protocol = lastAnswerMsection->GetProtocol();
}
SdpMediaSection* msection = &local->AddMediaSection(
transceiver.GetMediaType(), transceiver.mJsDirection, 0, protocol,
sdp::kIPv4,
"0.0.0.0");
// Some of this stuff (eg; mid) sticks around even if disabled
if (lastAnswerMsection) {
MOZ_ASSERT(lastAnswerMsection->GetMediaType() ==
transceiver.GetMediaType());
nsresult rv = mSdpHelper.CopyStickyParams(*lastAnswerMsection, msection);
NS_ENSURE_SUCCESS(rv, rv);
}
if (transceiver.IsStopping() || transceiver.IsStopped()) {
SdpHelper::DisableMsection(local, msection);
return NS_OK;
}
msection->SetPort(9);
// We don't do this in AddTransportAttributes because that is also used for
// making answers, and we don't want to unconditionally set rtcp-mux or
// rtcp-rsize there.
if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
// Set RTCP-MUX.
msection->GetAttributeList().SetAttribute(
new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
// Set RTCP-RSIZE
if (msection->GetMediaType() == SdpMediaSection::MediaType::kVideo &&
Preferences::GetBool(
"media.navigator.video.offer_rtcp_rsize",
false)) {
msection->GetAttributeList().SetAttribute(
new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
}
}
nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
NS_ENSURE_SUCCESS(rv, rv);
transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection);
transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection);
AddExtmap(msection);
std::string mid;
// We do not set the mid on the transceiver, that happens when a description
// is set.
if (transceiver.IsAssociated()) {
mid = transceiver.GetMid();
}
else {
mid = GetNewMid();
}
msection->GetAttributeList().SetAttribute(
new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
return NS_OK;
}
void JsepSessionImpl::SetupBundle(Sdp* sdp)
const {
std::vector<std::string> mids;
std::set<SdpMediaSection::MediaType> observedTypes;
// This has the effect of changing the bundle level if the first m-section
// goes from disabled to enabled. This is kinda inefficient.
for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
if ((sdp->GetMediaSection(i).GetPort() != 0) &&
attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
bool useBundleOnly =
false;
switch (mBundlePolicy) {
case kBundleMaxCompat:
// We don't use bundle-only for max-compat
break;
case kBundleBalanced:
// balanced means we use bundle-only on everything but the first
// m-section of a given type
if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) {
useBundleOnly =
true;
}
observedTypes.insert(sdp->GetMediaSection(i).GetMediaType());
break;
case kBundleMaxBundle:
// max-bundle means we use bundle-only on everything but the first
// m-section
useBundleOnly = !mids.empty();
break;
}
if (useBundleOnly) {
attrs.SetAttribute(
new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
// Set port to 0 for sections with bundle-only attribute. (mjf)
sdp->GetMediaSection(i).SetPort(0);
}
mids.push_back(attrs.GetMid());
}
}
if (!mids.empty()) {
UniquePtr<SdpGroupAttributeList> groupAttr(
new SdpGroupAttributeList);
groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
sdp->GetAttributeList().SetAttribute(groupAttr.release());
}
}
JsepSession::Result JsepSessionImpl::CreateOffer(
const JsepOfferOptions& options, std::string* offer) {
mLastError.clear();
if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) {
JSEP_SET_ERROR(
"Cannot create offer in state " << GetStateStr(mState));
// Spec doesn't seem to say this is an error. It probably should.
return dom::PCError::InvalidStateError;
}
// This is one of those places where CreateOffer sets some state.
SetIceRestarting(options.mIceRestart.isSome() && *(options.mIceRestart));
UniquePtr<Sdp> sdp;
// Make the basic SDP that is common to offer/answer.
nsresult rv = CreateGenericSDP(&sdp);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
for (size_t level = 0;
Maybe<JsepTransceiver> transceiver = GetTransceiverForLocal(level);
++level) {
rv = CreateOfferMsection(options, *transceiver, sdp.get());
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
SetTransceiver(*transceiver);
}
SetupBundle(sdp.get());
if (mCurrentLocalDescription && GetAnswer()) {
rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription,
*sdp, sdp.get());
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
}
*offer = sdp->ToString();
mGeneratedOffer = std::move(sdp);
++mSessionVersion;
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: CreateOffer \nSDP=\n" << *offer);
return Result();
}
std::string JsepSessionImpl::GetLocalDescription(
JsepDescriptionPendingOrCurrent type)
const {
std::ostringstream os;
mozilla::Sdp* sdp = GetParsedLocalDescription(type);
if (sdp) {
sdp->Serialize(os);
}
return os.str();
}
std::string JsepSessionImpl::GetRemoteDescription(
JsepDescriptionPendingOrCurrent type)
const {
std::ostringstream os;
mozilla::Sdp* sdp = GetParsedRemoteDescription(type);
if (sdp) {
sdp->Serialize(os);
}
return os.str();
}
void JsepSessionImpl::AddExtmap(SdpMediaSection* msection) {
auto extensions = GetRtpExtensions(*msection);
if (!extensions.empty()) {
SdpExtmapAttributeList* extmap =
new SdpExtmapAttributeList;
extmap->mExtmaps = extensions;
msection->GetAttributeList().SetAttribute(extmap);
}
}
std::vector<SdpExtmapAttributeList::Extmap> JsepSessionImpl::GetRtpExtensions(
const SdpMediaSection& msection) {
std::vector<SdpExtmapAttributeList::Extmap> result;
JsepMediaType mediaType = JsepMediaType::kNone;
const auto direction = msection.GetDirection();
const auto includes_send = direction == SdpDirectionAttribute::kSendrecv ||
direction == SdpDirectionAttribute::kSendonly;
switch (msection.GetMediaType()) {
case SdpMediaSection::kAudio:
mediaType = JsepMediaType::kAudio;
break;
case SdpMediaSection::kVideo:
mediaType = JsepMediaType::kVideo;
// We need to add the dependency descriptor extension for simulcast
if (includes_send && StaticPrefs::media_peerconnection_video_use_dd() &&
msection.GetAttributeList().HasAttribute(
SdpAttribute::kSimulcastAttribute)) {
AddVideoRtpExtension(webrtc::RtpExtension::kDependencyDescriptorUri,
SdpDirectionAttribute::kSendonly);
}
if (msection.GetAttributeList().HasAttribute(
SdpAttribute::kRidAttribute)) {
// We need RID support
// TODO: Would it be worth checking that the direction is sane?
AddVideoRtpExtension(webrtc::RtpExtension::kRidUri,
SdpDirectionAttribute::kSendonly);
if (mRtxIsAllowed &&
Preferences::GetBool(
"media.peerconnection.video.use_rtx",
false)) {
AddVideoRtpExtension(webrtc::RtpExtension::kRepairedRidUri,
SdpDirectionAttribute::kSendonly);
}
}
break;
default:;
}
if (mediaType != JsepMediaType::kNone) {
for (
auto ext = mRtpExtensions.begin(); ext != mRtpExtensions.end();
++ext) {
if (ext->mMediaType == mediaType ||
ext->mMediaType == JsepMediaType::kAudioVideo) {
result.push_back(ext->mExtmap);
}
}
}
return result;
}
std::string JsepSessionImpl::GetNewMid() {
std::string mid;
do {
std::ostringstream osMid;
osMid << mMidCounter++;
mid = osMid.str();
}
while (mUsedMids.count(mid));
mUsedMids.insert(mid);
return mid;
}
void JsepSessionImpl::AddCommonExtmaps(
const SdpMediaSection& remoteMsection,
SdpMediaSection* msection) {
auto negotiatedRtpExtensions = GetRtpExtensions(*msection);
mSdpHelper.NegotiateAndAddExtmaps(remoteMsection, negotiatedRtpExtensions,
msection);
}
uint16_t JsepSessionImpl::GetNeverUsedExtmapEntry() {
uint16_t result = 1;
// Walk the set in order, and return the first "hole" we find
for (
const auto used : mExtmapEntriesEverUsed) {
if (result != used) {
MOZ_ASSERT(result < used);
break;
}
// RFC 5285 says entries >= 4096 are used in offers to force the answerer
// to pick, so we do not want to actually use these
if (used == 4095) {
JSEP_SET_ERROR(
"Too many rtp extensions have been added. "
"That's 4095. Who _does_ that?");
return 0;
}
result = used + 1;
}
mExtmapEntriesEverUsed.insert(result);
return result;
}
JsepSession::Result JsepSessionImpl::CreateAnswer(
const JsepAnswerOptions& options, std::string* answer) {
mLastError.clear();
if (mState != kJsepStateHaveRemoteOffer) {
JSEP_SET_ERROR(
"Cannot create answer in state " << GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
UniquePtr<Sdp> sdp;
// Make the basic SDP that is common to offer/answer.
nsresult rv = CreateGenericSDP(&sdp);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
const Sdp& offer = *mPendingRemoteDescription;
// Copy the bundle groups into our answer
UniquePtr<SdpGroupAttributeList> groupAttr(
new SdpGroupAttributeList);
mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
sdp->GetAttributeList().SetAttribute(groupAttr.release());
for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
// The transceivers are already in place, due to setRemote
Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
if (!transceiver) {
JSEP_SET_ERROR(
"No transceiver for level " << i);
MOZ_ASSERT(
false);
return dom::PCError::OperationError;
}
rv = CreateAnswerMsection(options, *transceiver, offer.GetMediaSection(i),
sdp.get());
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
SetTransceiver(*transceiver);
}
// Ensure that each bundle-group starts with a mid that has a transport, in
// case we've disabled what the offerer wanted to use. If the group doesn't
// contain anything that has a transport, remove it.
groupAttr.reset(
new SdpGroupAttributeList);
std::vector<SdpGroupAttributeList::Group> bundleGroups;
mSdpHelper.GetBundleGroups(*sdp, &bundleGroups);
for (
auto& group : bundleGroups) {
for (
auto& mid : group.tags) {
const SdpMediaSection* msection =
mSdpHelper.FindMsectionByMid(offer, mid);
if (msection && !msection->GetAttributeList().HasAttribute(
SdpAttribute::kBundleOnlyAttribute)) {
std::swap(group.tags[0], mid);
groupAttr->mGroups.push_back(group);
break;
}
}
}
sdp->GetAttributeList().SetAttribute(groupAttr.release());
if (mCurrentLocalDescription) {
// per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription,
offer, sdp.get());
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
}
*answer = sdp->ToString();
mGeneratedAnswer = std::move(sdp);
++mSessionVersion;
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: CreateAnswer \nSDP=\n" << *answer);
return Result();
}
nsresult JsepSessionImpl::CreateAnswerMsection(
const JsepAnswerOptions& options, JsepTransceiver& transceiver,
const SdpMediaSection& remoteMsection, Sdp* sdp) {
MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType());
SdpDirectionAttribute::Direction direction =
reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection;
SdpMediaSection& msection =
sdp->AddMediaSection(remoteMsection.GetMediaType(), direction, 9,
remoteMsection.GetProtocol(), sdp::kIPv4,
"0.0.0.0");
nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
NS_ENSURE_SUCCESS(rv, rv);
if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
SdpHelper::DisableMsection(sdp, &msection);
return NS_OK;
}
MOZ_ASSERT(transceiver.IsAssociated());
if (msection.GetAttributeList().GetMid().empty()) {
msection.GetAttributeList().SetAttribute(
new SdpStringAttribute(
SdpAttribute::kMidAttribute, transceiver.GetMid()));
}
MOZ_ASSERT(transceiver.GetMid() == msection.GetAttributeList().GetMid());
SdpSetupAttribute::Role role;
if (transceiver.mTransport.mDtls && !IsIceRestarting()) {
role = (transceiver.mTransport.mDtls->mRole ==
JsepDtlsTransport::kJsepDtlsClient)
? SdpSetupAttribute::kActive
: SdpSetupAttribute::kPassive;
}
else {
rv = DetermineAnswererSetupRole(remoteMsection, &role);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = AddTransportAttributes(&msection, role);
NS_ENSURE_SUCCESS(rv, rv);
transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
// Add extmap attributes. This logic will probably be moved to the track,
// since it can be specified on a per-sender basis in JS.
// We will need some validation to ensure that the ids are identical for
// RTP streams that are bundled together, though (bug 1406529).
AddCommonExtmaps(remoteMsection, &msection);
if (msection.GetFormats().empty()) {
// Could not negotiate anything. Disable m-section.
SdpHelper::DisableMsection(sdp, &msection);
}
return NS_OK;
}
nsresult JsepSessionImpl::DetermineAnswererSetupRole(
const SdpMediaSection& remoteMsection, SdpSetupAttribute::Role* rolep) {
// Determine the role.
// RFC 5763 says:
//
// The endpoint MUST use the setup attribute defined in [RFC4145].
// The endpoint that is the offerer MUST use the setup attribute
// value of setup:actpass and be prepared to receive a client_hello
// before it receives the answer. The answerer MUST use either a
// setup attribute value of setup:active or setup:passive. Note that
// if the answerer uses setup:passive, then the DTLS handshake will
// not begin until the answerer is received, which adds additional
// latency. setup:active allows the answer and the DTLS handshake to
// occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
// party is active MUST initiate a DTLS handshake by sending a
// ClientHello over each flow (host/port quartet).
//
// We default to assuming that the offerer is passive and we are active.
SdpSetupAttribute::Role role = SdpSetupAttribute::kActive;
if (remoteMsection.GetAttributeList().HasAttribute(
SdpAttribute::kSetupAttribute)) {
switch (remoteMsection.GetAttributeList().GetSetup().mRole) {
case SdpSetupAttribute::kActive:
role = SdpSetupAttribute::kPassive;
break;
case SdpSetupAttribute::kPassive:
case SdpSetupAttribute::kActpass:
role = SdpSetupAttribute::kActive;
break;
case SdpSetupAttribute::kHoldconn:
// This should have been caught by ParseSdp
MOZ_ASSERT(
false);
JSEP_SET_ERROR(
"The other side used an illegal setup attribute"
" (\"holdconn\
").");
return NS_ERROR_INVALID_ARG;
}
}
*rolep = role;
return NS_OK;
}
JsepSession::Result JsepSessionImpl::SetLocalDescription(
JsepSdpType type,
const std::string& constSdp) {
mLastError.clear();
std::string sdp = constSdp;
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: SetLocalDescription type=" << type
<<
"\nSDP=\n"
<< sdp);
switch (type) {
case kJsepSdpOffer:
if (!mGeneratedOffer) {
JSEP_SET_ERROR(
"Cannot set local offer when createOffer has not been called.");
return dom::PCError::InvalidModificationError;
}
if (sdp.empty()) {
sdp = mGeneratedOffer->ToString();
}
if (mState == kJsepStateHaveLocalOffer) {
// Rollback previous offer before applying the new one.
SetLocalDescription(kJsepSdpRollback,
"");
MOZ_ASSERT(mState == kJsepStateStable);
}
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
if (!mGeneratedAnswer) {
JSEP_SET_ERROR(
"Cannot set local answer when createAnswer has not been called.");
return dom::PCError::InvalidModificationError;
}
if (sdp.empty()) {
sdp = mGeneratedAnswer->ToString();
}
break;
case kJsepSdpRollback:
if (mState != kJsepStateHaveLocalOffer) {
JSEP_SET_ERROR(
"Cannot rollback local description in "
<< GetStateStr(mState));
// Currently, spec allows this in any state except stable, and
// sRD(rollback) and sLD(rollback) do exactly the same thing.
return dom::PCError::InvalidStateError;
}
mPendingLocalDescription.reset();
SetState(kJsepStateStable);
RollbackLocalOffer();
return Result();
}
switch (mState) {
case kJsepStateStable:
if (type != kJsepSdpOffer) {
JSEP_SET_ERROR(
"Cannot set local answer in state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
break;
case kJsepStateHaveRemoteOffer:
if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
JSEP_SET_ERROR(
"Cannot set local offer in state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
break;
default:
JSEP_SET_ERROR(
"Cannot set local offer or answer in state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
UniquePtr<Sdp> parsed;
nsresult rv = ParseSdp(sdp, &parsed);
// Needs to be RTCError with sdp-syntax-error
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
// Check that content hasn't done anything unsupported with the SDP
rv = ValidateLocalDescription(*parsed, type);
NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidModificationError);
switch (type) {
case kJsepSdpOffer:
rv = ValidateOffer(*parsed);
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
rv = ValidateAnswer(*mPendingRemoteDescription, *parsed);
break;
case kJsepSdpRollback:
MOZ_CRASH();
// Handled above
}
NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
if (type == kJsepSdpOffer) {
// Save in case we need to rollback
mOldTransceivers = mTransceivers;
}
SdpHelper::BundledMids bundledMids;
rv = mSdpHelper.GetBundledMids(*parsed, &bundledMids);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
SdpHelper::BundledMids remoteBundledMids;
if (type != kJsepSdpOffer) {
rv = mSdpHelper.GetBundledMids(*mPendingRemoteDescription,
&remoteBundledMids);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
}
for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
if (!transceiver) {
MOZ_ASSERT(
false);
JSEP_SET_ERROR(
"No transceiver for level " << i);
return dom::PCError::OperationError;
}
const auto& msection = parsed->GetMediaSection(i);
transceiver->Associate(msection.GetAttributeList().GetMid());
transceiver->mRecvTrack.RecvTrackSetLocal(msection);
if (mSdpHelper.MsectionIsDisabled(msection)) {
transceiver->mTransport.Close();
SetTransceiver(*transceiver);
continue;
}
bool hasOwnTransport = mSdpHelper.OwnsTransport(
msection, bundledMids,
(type == kJsepSdpOffer) ? sdp::kOffer : sdp::kAnswer);
if (type != kJsepSdpOffer) {
const auto& remoteMsection =
mPendingRemoteDescription->GetMediaSection(i);
// Don't allow the answer to override what the offer allowed for
hasOwnTransport &= mSdpHelper.OwnsTransport(
remoteMsection, remoteBundledMids, sdp::kOffer);
}
if (hasOwnTransport) {
EnsureHasOwnTransport(parsed->GetMediaSection(i), *transceiver);
}
if (type == kJsepSdpOffer) {
if (!hasOwnTransport) {
auto it = bundledMids.find(transceiver->GetMid());
if (it != bundledMids.end()) {
transceiver->SetBundleLevel(it->second->GetLevel());
}
}
}
else {
auto it = remoteBundledMids.find(transceiver->GetMid());
if (it != remoteBundledMids.end()) {
transceiver->SetBundleLevel(it->second->GetLevel());
}
}
SetTransceiver(*transceiver);
}
CopyBundleTransports();
switch (type) {
case kJsepSdpOffer:
rv = SetLocalDescriptionOffer(std::move(parsed));
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
rv = SetLocalDescriptionAnswer(type, std::move(parsed));
break;
case kJsepSdpRollback:
MOZ_CRASH();
// Handled above
}
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
return Result();
}
nsresult JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer) {
MOZ_ASSERT(mState == kJsepStateStable);
mPendingLocalDescription = std::move(offer);
mIsPendingOfferer = Some(
true);
SetState(kJsepStateHaveLocalOffer);
return NS_OK;
}
nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
UniquePtr<Sdp> answer) {
MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
mPendingLocalDescription = std::move(answer);
nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
mPendingRemoteDescription);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
mCurrentLocalDescription = std::move(mPendingLocalDescription);
MOZ_ASSERT(mIsPendingOfferer.isSome() && !*mIsPendingOfferer);
mIsPendingOfferer.reset();
mIsCurrentOfferer = Some(
false);
SetState(kJsepStateStable);
return NS_OK;
}
JsepSession::Result JsepSessionImpl::SetRemoteDescription(
JsepSdpType type,
const std::string& sdp) {
mLastError.clear();
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: SetRemoteDescription type=" << type
<<
"\nSDP=\n"
<< sdp);
if (mState == kJsepStateHaveRemoteOffer && type == kJsepSdpOffer) {
// Rollback previous offer before applying the new one.
SetRemoteDescription(kJsepSdpRollback,
"");
MOZ_ASSERT(mState == kJsepStateStable);
}
if (type == kJsepSdpRollback) {
if (mState != kJsepStateHaveRemoteOffer) {
JSEP_SET_ERROR(
"Cannot rollback remote description in "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
mPendingRemoteDescription.reset();
SetState(kJsepStateStable);
RollbackRemoteOffer();
return Result();
}
switch (mState) {
case kJsepStateStable:
if (type != kJsepSdpOffer) {
JSEP_SET_ERROR(
"Cannot set remote answer in state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
break;
case kJsepStateHaveLocalOffer:
case kJsepStateHaveRemotePranswer:
if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
JSEP_SET_ERROR(
"Cannot set remote offer in state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
break;
default:
JSEP_SET_ERROR(
"Cannot set remote offer or answer in current state "
<< GetStateStr(mState));
return dom::PCError::InvalidStateError;
}
// Parse.
UniquePtr<Sdp> parsed;
nsresult rv = ParseSdp(sdp, &parsed);
// Needs to be RTCError with sdp-syntax-error
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
rv = ValidateRemoteDescription(*parsed);
NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
switch (type) {
case kJsepSdpOffer:
rv = ValidateOffer(*parsed);
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
rv = ValidateAnswer(*mPendingLocalDescription, *parsed);
break;
case kJsepSdpRollback:
MOZ_CRASH();
// Handled above
}
NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
bool iceLite =
parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
// check for mismatch ufrag/pwd indicating ice restart
// can't just check the first one because it might be disabled
bool iceRestarting =
false;
if (mCurrentRemoteDescription.get()) {
for (size_t i = 0; !iceRestarting &&
i < mCurrentRemoteDescription->GetMediaSectionCount();
++i) {
const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
const SdpMediaSection& oldMsection =
mCurrentRemoteDescription->GetMediaSection(i);
if (mSdpHelper.MsectionIsDisabled(newMsection) ||
mSdpHelper.MsectionIsDisabled(oldMsection)) {
continue;
}
iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
}
}
std::vector<std::string> iceOptions;
if (parsed->GetAttributeList().HasAttribute(
SdpAttribute::kIceOptionsAttribute)) {
iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
}
if (type == kJsepSdpOffer) {
// Save in case we need to rollback.
mOldTransceivers = mTransceivers;
for (
auto& transceiver : mTransceivers) {
if (!transceiver.IsNegotiated()) {
// We chose a level for this transceiver, but never negotiated it.
// Discard this state.
transceiver.ClearLevel();
}
}
}
// TODO(bug 1095780): Note that we create remote tracks even when
// They contain only codecs we can't negotiate or other craziness.
rv = UpdateTransceiversFromRemoteDescription(*parsed);
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
MOZ_ASSERT(GetTransceiverForLevel(i));
}
switch (type) {
case kJsepSdpOffer:
rv = SetRemoteDescriptionOffer(std::move(parsed));
break;
case kJsepSdpAnswer:
case kJsepSdpPranswer:
rv = SetRemoteDescriptionAnswer(type, std::move(parsed));
break;
case kJsepSdpRollback:
MOZ_CRASH();
// Handled above
}
NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
mRemoteIsIceLite = iceLite;
mIceOptions = iceOptions;
SetIceRestarting(iceRestarting);
return Result();
}
nsresult JsepSessionImpl::HandleNegotiatedSession(
const UniquePtr<Sdp>& local,
const UniquePtr<Sdp>& remote) {
// local ufrag/pwd has been negotiated; we will never go back to the old ones
mOldIceUfrag.clear();
mOldIcePwd.clear();
bool remoteIceLite =
remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
mIceControlling = remoteIceLite || *mIsPendingOfferer;
const Sdp& answer = *mIsPendingOfferer ? *remote : *local;
SdpHelper::BundledMids bundledMids;
nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
NS_ENSURE_SUCCESS(rv, rv);
// First, set the bundle level on the transceivers
for (
auto& [mid, transportOwner] : bundledMids) {
Maybe<JsepTransceiver> bundledTransceiver = GetTransceiverForMid(mid);
if (!bundledTransceiver) {
JSEP_SET_ERROR(
"No transceiver for bundled mid " << mid);
return NS_ERROR_INVALID_ARG;
}
bundledTransceiver->SetBundleLevel(transportOwner->GetLevel());
SetTransceiver(*bundledTransceiver);
}
// Now walk through the m-sections, perform negotiation, and update the
// transceivers.
for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
if (!transceiver) {
MOZ_ASSERT(
false);
JSEP_SET_ERROR(
"No transceiver for level " << i);
return NS_ERROR_FAILURE;
}
if (mSdpHelper.MsectionIsDisabled(local->GetMediaSection(i))) {
transceiver->SetRemoved();
}
// Skip disabled m-sections.
if (mSdpHelper.MsectionIsDisabled(answer.GetMediaSection(i))) {
transceiver->mTransport.Close();
transceiver->SetStopped();
transceiver->Disassociate();
transceiver->ClearBundleLevel();
transceiver->mSendTrack.SetActive(
false);
transceiver->mRecvTrack.SetActive(
false);
transceiver->SetCanRecycleMyMsection();
SetTransceiver(*transceiver);
// Do not clear mLevel yet! That will happen on the next negotiation.
continue;
}
rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i),
local->GetMediaSection(i), *transceiver);
NS_ENSURE_SUCCESS(rv, rv);
SetTransceiver(*transceiver);
}
CopyBundleTransports();
std::vector<JsepTrack*> receiveTracks;
for (
auto& transceiver : mTransceivers) {
// Do not count payload types for non-active recv tracks as duplicates. If
// we receive an RTP packet with a payload type that is used by both a
// sendrecv and a sendonly m-section, there is no ambiguity; it is for the
// sendrecv m-section.
if (transceiver.mRecvTrack.GetActive()) {
receiveTracks.push_back(&transceiver.mRecvTrack);
}
}
JsepTrack::SetUniqueReceivePayloadTypes(receiveTracks);
mNegotiations++;
mGeneratedAnswer.reset();
mGeneratedOffer.reset();
return NS_OK;
}
nsresult JsepSessionImpl::MakeNegotiatedTransceiver(
const SdpMediaSection& remote,
const SdpMediaSection& local,
JsepTransceiver& transceiver) {
const SdpMediaSection& answer = *mIsPendingOfferer ? remote : local;
bool sending =
false;
bool receiving =
false;
// We do not pay any attention to whether the transceiver is stopped here,
// because that is only a signal to the JSEP engine to _attempt_ to reject
// the corresponding m-section the next time we're the offerer.
if (*mIsPendingOfferer) {
receiving = answer.IsSending();
sending = answer.IsReceiving();
}
else {
sending = answer.IsSending();
receiving = answer.IsReceiving();
}
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: Negotiated m= line"
<<
" index=" << local.GetLevel() <<
" type="
<< local.GetMediaType() <<
" sending=" << sending
<<
" receiving=" << receiving);
transceiver.SetNegotiated();
// Ensure that this is finalized in case we need to copy it below
nsresult rv =
FinalizeTransport(remote.GetAttributeList(), answer.GetAttributeList(),
&transceiver.mTransport);
NS_ENSURE_SUCCESS(rv, rv);
transceiver.mSendTrack.SetActive(sending);
rv = transceiver.mSendTrack.Negotiate(answer, remote, local);
if (NS_FAILED(rv)) {
JSEP_SET_ERROR(
"Answer had no codecs in common with offer in m-section "
<< local.GetLevel());
return rv;
}
JsepTrack& recvTrack = transceiver.mRecvTrack;
recvTrack.SetActive(receiving);
rv = recvTrack.Negotiate(answer, remote, local);
if (NS_FAILED(rv)) {
JSEP_SET_ERROR(
"Answer had no codecs in common with offer in m-section "
<< local.GetLevel());
return rv;
}
if (transceiver.HasBundleLevel() && recvTrack.GetSsrcs().empty() &&
recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
// TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid
// support, we should only fire this warning if that extension was not
// negotiated.
MOZ_MTLOG(ML_ERROR,
"[" << mName
<<
"]: Bundled m-section has no ssrc "
"attributes. This may cause media packets to be "
"dropped.");
}
if (transceiver.mTransport.mComponents == 2) {
// RTCP MUX or not.
// TODO(bug 1095743): verify that the PTs are consistent with mux.
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"]: RTCP-MUX is off");
}
if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
const auto extmaps = answer.GetAttributeList().GetExtmap().mExtmaps;
for (
const auto& negotiatedExtension : extmaps) {
if (negotiatedExtension.entry == 0) {
MOZ_ASSERT(
false,
"This should have been caught sooner");
continue;
}
mExtmapEntriesEverNegotiated[negotiatedExtension.entry] =
negotiatedExtension.extensionname;
for (
auto& originalExtension : mRtpExtensions) {
if (negotiatedExtension.extensionname ==
originalExtension.mExtmap.extensionname) {
// Update extmap to match what was negotiated
originalExtension.mExtmap.entry = negotiatedExtension.entry;
mExtmapEntriesEverUsed.insert(negotiatedExtension.entry);
}
else if (originalExtension.mExtmap.entry ==
negotiatedExtension.entry) {
// If this extmap entry was claimed for a different extension, update
// it to a new value so we don't end up with a duplicate.
originalExtension.mExtmap.entry = GetNeverUsedExtmapEntry();
}
}
}
}
return NS_OK;
}
void JsepSessionImpl::EnsureHasOwnTransport(
const SdpMediaSection& msection,
JsepTransceiver& transceiver) {
JsepTransport& transport = transceiver.mTransport;
if (!transceiver.HasOwnTransport()) {
// Transceiver didn't own this transport last time, it won't now either
transport.Close();
}
transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag();
transport.mLocalPwd = msection.GetAttributeList().GetIcePwd();
transceiver.ClearBundleLevel();
if (!transport.mComponents) {
if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
transport.mComponents = 2;
}
else {
transport.mComponents = 1;
}
}
if (transport.mTransportId.empty()) {
// TODO: Once we use different ICE ufrag/pass for each m-section, we can
// use that here.
std::ostringstream os;
os <<
"transport_" << mTransportIdCounter++;
transport.mTransportId = os.str();
}
}
void JsepSessionImpl::CopyBundleTransports() {
for (
auto& transceiver : mTransceivers) {
if (transceiver.HasBundleLevel()) {
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"] Transceiver " << transceiver.GetLevel()
<<
" is in a bundle; transceiver "
<< transceiver.BundleLevel() <<
" owns the transport.");
Maybe<
const JsepTransceiver> transportOwner =
GetTransceiverForLevel(transceiver.BundleLevel());
MOZ_ASSERT(transportOwner);
if (transportOwner) {
transceiver.mTransport = transportOwner->mTransport;
}
}
else if (transceiver.HasLevel()) {
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"] Transceiver "
<< transceiver.GetLevel()
<<
" is not necessarily in a bundle.");
}
if (transceiver.HasLevel()) {
MOZ_MTLOG(ML_DEBUG,
"[" << mName <<
"] Transceiver " << transceiver.GetLevel()
<<
" transport-id: " << transceiver.mTransport.mTransportId
<<
" components: " << transceiver.mTransport.mComponents);
}
}
}
nsresult JsepSessionImpl::FinalizeTransport(
const SdpAttributeList& remote,
const SdpAttributeList& answer,
JsepTransport* transport)
const {
if (!transport->mComponents) {
return NS_OK;
}
if (!transport->mIce || transport->mIce->mUfrag != remote.GetIceUfrag() ||
transport->mIce->mPwd != remote.GetIcePwd()) {
UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();
transport->mDtls = nullptr;
// We do sanity-checking for these in ParseSdp
ice->mUfrag = remote.GetIceUfrag();
ice->mPwd = remote.GetIcePwd();
transport->mIce = std::move(ice);
}
if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
transport->mIce->mCandidates = remote.GetCandidate();
}
if (!transport->mDtls) {
// RFC 5763 says:
//
// The endpoint MUST use the setup attribute defined in [RFC4145].
// The endpoint that is the offerer MUST use the setup attribute
// value of setup:actpass and be prepared to receive a client_hello
// before it receives the answer. The answerer MUST use either a
// setup attribute value of setup:active or setup:passive. Note that
// if the answerer uses setup:passive, then the DTLS handshake will
// not begin until the answerer is received, which adds additional
// latency. setup:active allows the answer and the DTLS handshake to
// occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
// party is active MUST initiate a DTLS handshake by sending a
// ClientHello over each flow (host/port quartet).
UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>();
dtls->mFingerprints = remote.GetFingerprint();
if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) {
dtls->mRole = *mIsPendingOfferer ? JsepDtlsTransport::kJsepDtlsServer
: JsepDtlsTransport::kJsepDtlsClient;
}
else {
if (*mIsPendingOfferer) {
dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
? JsepDtlsTransport::kJsepDtlsServer
: JsepDtlsTransport::kJsepDtlsClient;
}
else {
dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
? JsepDtlsTransport::kJsepDtlsClient
: JsepDtlsTransport::kJsepDtlsServer;
}
}
transport->mDtls = std::move(dtls);
}
if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
transport->mComponents = 1;
}
return NS_OK;
}
nsresult JsepSessionImpl::AddTransportAttributes(
SdpMediaSection* msection, SdpSetupAttribute::Role dtlsRole) {
if (mIceUfrag.empty() || mIcePwd.empty()) {
JSEP_SET_ERROR(
"Missing ICE ufrag or password");
return NS_ERROR_FAILURE;
}
SdpAttributeList& attrList = msection->GetAttributeList();
attrList.SetAttribute(
new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag));
attrList.SetAttribute(
new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));
msection->GetAttributeList().SetAttribute(
new SdpSetupAttribute(dtlsRole));
return NS_OK;
}
nsresult JsepSessionImpl::CopyPreviousTransportParams(
const Sdp& oldAnswer,
const Sdp& offerersPreviousSdp,
const Sdp& newOffer,
Sdp* newLocal) {
for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
mSdpHelper.AreOldTransportParamsValid(oldAnswer, offerersPreviousSdp,
newOffer, i)) {
// If newLocal is an offer, this will be the number of components we used
// last time, and if it is an answer, this will be the number of
// components we've decided we're using now.
Maybe<
const JsepTransceiver> transceiver(GetTransceiverForLevel(i));
if (!transceiver) {
MOZ_ASSERT(
false);
JSEP_SET_ERROR(
"No transceiver for level " << i);
return NS_ERROR_FAILURE;
}
size_t numComponents = transceiver->mTransport.mComponents;
nsresult rv = mSdpHelper.CopyTransportParams(
numComponents, mCurrentLocalDescription->GetMediaSection(i),
&newLocal->GetMediaSection(i));
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
nsresult JsepSessionImpl::ParseSdp(
const std::string& sdp,
UniquePtr<Sdp>* parsedp) {
auto results = mParser->Parse(sdp);
auto parsed = std::move(results->Sdp());
mLastSdpParsingErrors = results->Errors();
if (!parsed) {
std::string error = results->ParserName() +
" Failed to parse SDP: ";
mSdpHelper.AppendSdpParseErrors(mLastSdpParsingErrors, &error);
JSEP_SET_ERROR(error);
return NS_ERROR_INVALID_ARG;
}
// Verify that the JSEP rules for all SDP are followed
for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
// Disabled, let this stuff slide.
continue;
}
const SdpMediaSection& msection(parsed->GetMediaSection(i));
auto& mediaAttrs = msection.GetAttributeList();
if (mediaAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
mediaAttrs.GetMid().length() > 16) {
JSEP_SET_ERROR(
"Invalid description, mid length greater than 16 "
"unsupported until 2-byte rtp header extensions are "
"supported in webrtc.org");
return NS_ERROR_INVALID_ARG;
}
if (mediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
std::set<uint16_t> extIds;
for (
const auto& ext : mediaAttrs.GetExtmap().mExtmaps) {
uint16_t id = ext.entry;
if (id < 1 || id > 14) {
JSEP_SET_ERROR(
"Description contains invalid extension id "
<< id <<
" on level " << i
<<
" which is unsupported until 2-byte rtp"
" header extensions are supported in webrtc.org");
return NS_ERROR_INVALID_ARG;
}
if (extIds.find(id) != extIds.end()) {
JSEP_SET_ERROR(
"Description contains duplicate extension id "
<< id <<
" on level " << i);
return NS_ERROR_INVALID_ARG;
}
extIds.insert(id);
}
}
static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes();
if (msection.GetMediaType() == SdpMediaSection::kAudio ||
msection.GetMediaType() == SdpMediaSection::kVideo) {
// Sanity-check that payload type can work with RTP
for (
const std::string& fmt : msection.GetFormats()) {
uint16_t payloadType;
if (!SdpHelper::GetPtAsInt(fmt, &payloadType)) {
JSEP_SET_ERROR(
"Payload type \""
<< fmt <<
"\" is
not a 16-bit
unsigned int at level
"
<< i);
return NS_ERROR_INVALID_ARG;
}
if (payloadType > 127) {
JSEP_SET_ERROR(
"audio/video payload type \""
<< fmt <<
"\" is too large at level
" << i);
return NS_ERROR_INVALID_ARG;
}
if (forbidden.test(payloadType)) {
JSEP_SET_ERROR(
"Illegal audio/video payload type \""
<< fmt <<
"\" at level
" << i);
return NS_ERROR_INVALID_ARG;
}
}
}
}
*parsedp = std::move(parsed);
return NS_OK;
}
nsresult JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer) {
MOZ_ASSERT(mState == kJsepStateStable);
mPendingRemoteDescription = std::move(offer);
mIsPendingOfferer = Some(
false);
SetState(kJsepStateHaveRemoteOffer);
return NS_OK;
}
nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
UniquePtr<Sdp> answer) {
MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
mState == kJsepStateHaveRemotePranswer);
mPendingRemoteDescription = std::move(answer);
nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
mPendingRemoteDescription);
NS_ENSURE_SUCCESS(rv, rv);
mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
mCurrentLocalDescription = std::move(mPendingLocalDescription);
MOZ_ASSERT(mIsPendingOfferer.isSome() && *mIsPendingOfferer);
mIsPendingOfferer.reset();
mIsCurrentOfferer = Some(
true);
SetState(kJsepStateStable);
return NS_OK;
}
Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLevel(
size_t level)
const {
return FindTransceiver([level](
const JsepTransceiver& transceiver) {
return transceiver.HasLevel() && (transceiver.GetLevel() == level);
});
}
Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForMid(
const std::string& mid)
const {
return FindTransceiver([mid](
const JsepTransceiver& transceiver) {
return transceiver.IsAssociated() && (transceiver.GetMid() == mid);
});
}
Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLocal(size_t level) {
if (Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level)) {
if (transceiver->CanRecycleMyMsection() &&
transceiver->GetMediaType() != SdpMediaSection::kApplication) {
// Attempt to recycle. If this fails, the old transceiver stays put.
transceiver->Disassociate();
Maybe<JsepTransceiver> newTransceiver =
FindUnassociatedTransceiver(transceiver->GetMediaType(),
false);
if (newTransceiver) {
newTransceiver->SetLevel(level);
transceiver->ClearLevel();
transceiver->mSendTrack.ClearRids();
SetTransceiver(*newTransceiver);
SetTransceiver(*transceiver);
return newTransceiver;
}
}
SetTransceiver(*transceiver);
return transceiver;
}
// There is no transceiver for |level| right now.
// Look for an RTP transceiver (spec requires us to give the lower levels to
// new RTP transceivers)
for (
auto& transceiver : mTransceivers) {
if (transceiver.GetMediaType() != SdpMediaSection::kApplication &&
transceiver.IsFreeToUse()) {
transceiver.SetLevel(level);
return Some(transceiver);
}
}
// Ok, look for a datachannel
for (
auto& transceiver : mTransceivers) {
if (transceiver.IsFreeToUse()) {
transceiver.SetLevel(level);
return Some(transceiver);
}
}
return Nothing();
}
Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForRemote(
const SdpMediaSection& msection) {
size_t level = msection.GetLevel();
Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level);
if (transceiver) {
if (!transceiver->CanRecycleMyMsection()) {
return transceiver;
}
transceiver->Disassociate();
transceiver->ClearLevel();
transceiver->mSendTrack.ClearRids();
SetTransceiver(*transceiver);
}
// No transceiver for |level|
transceiver = FindUnassociatedTransceiver(msection.GetMediaType(),
true);
if (transceiver) {
transceiver->SetLevel(level);
SetTransceiver(*transceiver);
return transceiver;
}
// Make a new transceiver
JsepTransceiver newTransceiver(msection.GetMediaType(), *mUuidGen,
SdpDirectionAttribute::kRecvonly);
newTransceiver.SetLevel(level);
newTransceiver.SetOnlyExistsBecauseOfSetRemote(
true);
AddTransceiver(newTransceiver);
return Some(mTransceivers.back());
}
Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverWithTransport(
const std::string& transportId)
const {
for (
const auto& transceiver : mTransceivers) {
if (transceiver.HasOwnTransport() &&
(transceiver.mTransport.mTransportId == transportId)) {
MOZ_ASSERT(transceiver.HasLevel(),
"Transceiver has a transport, but no level!");
return Some(transceiver);
}
}
return Nothing();
}
nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
const Sdp& remote) {
// Iterate over the sdp, updating remote tracks as we go
for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
const SdpMediaSection& msection = remote.GetMediaSection(i);
Maybe<JsepTransceiver> transceiver(GetTransceiverForRemote(msection));
if (!transceiver) {
return NS_ERROR_FAILURE;
}
if (!mSdpHelper.MsectionIsDisabled(msection)) {
if (msection.GetAttributeList().HasAttribute(
SdpAttribute::kMidAttribute)) {
transceiver->Associate(msection.GetAttributeList().GetMid());
}
if (!transceiver->IsAssociated()) {
transceiver->Associate(GetNewMid());
}
else {
mUsedMids.insert(transceiver->GetMid());
}
}
else {
// We do not disassociate here, that happens when negotiation completes
// These things cannot be rolled back.
transceiver->mTransport.Close();
transceiver->SetStopped();
SetTransceiver(*transceiver);
continue;
}
if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
SetTransceiver(*transceiver);
continue;
}
transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection);
// Interop workaround for endpoints that don't support msid.
// Ensures that there is a default stream id set, provided the remote is
// sending.
// TODO(bug 1426005): Remove this, or at least move it to JsepTrack.
transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId});
// This will process a=msid if present, or clear the stream ids if the
// msection is not sending. If the msection is sending, and there are no
// a=msid, the previously set default will stay.
transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection);
SetTransceiver(*transceiver);
}
return NS_OK;
}
Maybe<JsepTransceiver> JsepSessionImpl::FindUnassociatedTransceiver(
SdpMediaSection::MediaType type,
bool magic) {
// Look through transceivers that are not mapped to an m-section
for (
auto& transceiver : mTransceivers) {
if (type == SdpMediaSection::kApplication &&
type == transceiver.GetMediaType()) {
transceiver.RestartDatachannelTransceiver();
return Some(transceiver);
}
if (transceiver.IsFreeToUse() &&
(!magic || transceiver.HasAddTrackMagic()) &&
(transceiver.GetMediaType() == type)) {
return Some(transceiver);
}
}
return Nothing();
}
void JsepSessionImpl::RollbackLocalOffer() {
for (size_t i = 0; i < mTransceivers.size(); ++i) {
auto& transceiver = mTransceivers[i];
if (mOldTransceivers.size() > i) {
transceiver.Rollback(mOldTransceivers[i],
false);
mOldTransceivers[i] = transceiver;
continue;
}
JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
InitTransceiver(temp);
transceiver.Rollback(temp,
false);
mOldTransceivers.push_back(transceiver);
}
mTransceivers = std::move(mOldTransceivers);
}
void JsepSessionImpl::RollbackRemoteOffer() {
for (size_t i = 0; i < mTransceivers.size(); ++i) {
auto& transceiver = mTransceivers[i];
if (mOldTransceivers.size() > i) {
// Some stuff cannot be rolled back. Save this information.
transceiver.Rollback(mOldTransceivers[i],
true);
mOldTransceivers[i] = transceiver;
continue;
}
if (transceiver.HasLevel()) {
// New transceiver, that was either created by the remote offer, or
// attached to the remote offer.
// We rollback even for transceivers we will remove, just to ensure we end
// up at the starting state.
JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
InitTransceiver(temp);
transceiver.Rollback(temp,
true);
if (transceiver.OnlyExistsBecauseOfSetRemote()) {
transceiver.SetStopped();
transceiver.Disassociate();
transceiver.SetRemoved();
}
else {
// Oof. This hangs around because of addTrack. Make it magic!
transceiver.SetAddTrackMagic();
}
}
// else, _we_ added this and it is not attached to the remote offer yet
mOldTransceivers.push_back(transceiver);
}
mTransceivers = std::move(mOldTransceivers);
}
nsresult JsepSessionImpl::ValidateLocalDescription(
const Sdp& description,
JsepSdpType type) {
Sdp* generated = nullptr;
// TODO(bug 1095226): Better checking.
if (type == kJsepSdpOffer) {
generated = mGeneratedOffer.get();
}
else {
generated = mGeneratedAnswer.get();
}
if (!generated) {
JSEP_SET_ERROR(
"Calling SetLocal without first calling CreateOffer/Answer"
" is not supported.");
return NS_ERROR_UNEXPECTED;
}
if (description.GetMediaSectionCount() != generated->GetMediaSectionCount()) {
JSEP_SET_ERROR(
"Changing the number of m-sections is not allowed");
return NS_ERROR_INVALID_ARG;
}
for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
auto& origMsection = generated->GetMediaSection(i);
auto& finalMsection = description.GetMediaSection(i);
if (origMsection.GetMediaType() != finalMsection.GetMediaType()) {
JSEP_SET_ERROR(
"Changing the media-type of m-sections is not allowed");
return NS_ERROR_INVALID_ARG;
}
// These will be present in reoffer
if (!mCurrentLocalDescription) {
if (finalMsection.GetAttributeList().HasAttribute(
SdpAttribute::kCandidateAttribute)) {
JSEP_SET_ERROR(
"Adding your own candidate attributes is not supported");
return NS_ERROR_INVALID_ARG;
}
if (finalMsection.GetAttributeList().HasAttribute(
SdpAttribute::kEndOfCandidatesAttribute)) {
JSEP_SET_ERROR(
"Why are you trying to set a=end-of-candidates?");
return NS_ERROR_INVALID_ARG;
}
}
if (mSdpHelper.MsectionIsDisabled(finalMsection)) {
continue;
}
if (!finalMsection.GetAttributeList().HasAttribute(
SdpAttribute::kMidAttribute)) {
JSEP_SET_ERROR(
"Local descriptions must have a=mid attributes.");
return NS_ERROR_INVALID_ARG;
}
if (finalMsection.GetAttributeList().GetMid() !=
origMsection.GetAttributeList().GetMid()) {
JSEP_SET_ERROR(
"Changing the mid of m-sections is not allowed.");
return NS_ERROR_INVALID_ARG;
}
// TODO(bug 1095218): Check msid
// TODO(bug 1095226): Check ice-ufrag and ice-pwd
// TODO(bug 1095226): Check fingerprints
// TODO(bug 1095226): Check payload types (at least ensure that payload
// types we don't actually support weren't added)
// TODO(bug 1095226): Check ice-options?
}
if (description.GetAttributeList().HasAttribute(
SdpAttribute::kIceLiteAttribute)) {
JSEP_SET_ERROR(
"Running ICE in lite mode is unsupported");
return NS_ERROR_INVALID_ARG;
}
return NS_OK;
}
nsresult JsepSessionImpl::ValidateRemoteDescription(
const Sdp& description) {
if (!mCurrentLocalDescription) {
// Initial offer; nothing to validate besides the stuff in ParseSdp
return NS_OK;
}
if (mCurrentLocalDescription->GetMediaSectionCount() >
description.GetMediaSectionCount()) {
JSEP_SET_ERROR(
"New remote description has fewer m-sections than the "
"previous remote description.");
return NS_ERROR_INVALID_ARG;
}
for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
const SdpAttributeList& attrs =
description.GetMediaSection(i).GetAttributeList();
if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
for (
const auto& ext : attrs.GetExtmap().mExtmaps) {
if (mExtmapEntriesEverNegotiated.count(ext.entry) &&
mExtmapEntriesEverNegotiated[ext.entry] != ext.extensionname) {
JSEP_SET_ERROR(
"Remote description attempted to remap RTP extension id "
<< ext.entry <<
" from "
<< mExtmapEntriesEverNegotiated[ext.entry] <<
" to "
<< ext.extensionname);
return NS_ERROR_INVALID_ARG;
}
}
}
}
if (!mCurrentRemoteDescription) {
// No further checking for initial answers
return NS_OK;
}
// These are solely to check that bundle is valid
SdpHelper::BundledMids bundledMids;
nsresult rv = GetNegotiatedBundledMids(&bundledMids);
NS_ENSURE_SUCCESS(rv, rv);
SdpHelper::BundledMids newBundledMids;
rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
NS_ENSURE_SUCCESS(rv, rv);
// check for partial ice restart, which is not supported
Maybe<
bool> iceCredsDiffer;
for (size_t i = 0; i < mCurrentRemoteDescription->GetMediaSectionCount();
++i) {
const SdpMediaSection& newMsection = description.GetMediaSection(i);
const SdpMediaSection& oldMsection =
mCurrentRemoteDescription->GetMediaSection(i);
if (mSdpHelper.MsectionIsDisabled(newMsection) ||
mSdpHelper.MsectionIsDisabled(oldMsection)) {
--> --------------------
--> maximum size reached
--> --------------------