/* 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 #include #include #include #include #include #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(&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>* 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> JsepSessionImpl::GetLocalIceCredentials() const { std::set> 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& 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 mids; std::set 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 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; // 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 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 JsepSessionImpl::GetRtpExtensions( const SdpMediaSection& msection) { std::vector 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; // 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 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 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 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 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 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 offer) { MOZ_ASSERT(mState == kJsepStateStable); mPendingLocalDescription = std::move(offer); mIsPendingOfferer = Some(true); SetState(kJsepStateHaveLocalOffer); return NS_OK; } nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr 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 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 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& local, const UniquePtr& 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 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 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 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 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 ice = MakeUnique(); 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 dtls = MakeUnique(); 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 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* 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 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 offer) { MOZ_ASSERT(mState == kJsepStateStable); mPendingRemoteDescription = std::move(offer); mIsPendingOfferer = Some(false); SetState(kJsepStateHaveRemoteOffer); return NS_OK; } nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr 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 JsepSessionImpl::GetTransceiverForLevel( size_t level) const { return FindTransceiver([level](const JsepTransceiver& transceiver) { return transceiver.HasLevel() && (transceiver.GetLevel() == level); }); } Maybe JsepSessionImpl::GetTransceiverForMid( const std::string& mid) const { return FindTransceiver([mid](const JsepTransceiver& transceiver) { return transceiver.IsAssociated() && (transceiver.GetMid() == mid); }); } Maybe JsepSessionImpl::GetTransceiverForLocal(size_t level) { if (Maybe transceiver = GetTransceiverForLevel(level)) { if (transceiver->CanRecycleMyMsection() && transceiver->GetMediaType() != SdpMediaSection::kApplication) { // Attempt to recycle. If this fails, the old transceiver stays put. transceiver->Disassociate(); Maybe 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 JsepSessionImpl::GetTransceiverForRemote( const SdpMediaSection& msection) { size_t level = msection.GetLevel(); Maybe 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 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 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 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 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* 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 field SHOULD // be "-". The value of the field SHOULD be a // cryptographically random number. To ensure uniqueness, this // number SHOULD be at least 64 bits long. The value of the field SHOULD be zero. The value of the // 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 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 = MakeUnique(origin); if (mDtlsFingerprints.empty()) { JSEP_SET_ERROR("Missing DTLS fingerprint"); return NS_ERROR_FAILURE; } UniquePtr fpl = MakeUnique(); 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 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(&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>& 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& 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 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 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>& 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 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 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