summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp')
-rw-r--r--media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp2497
1 files changed, 2497 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
new file mode 100644
index 000000000..f5015dda2
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -0,0 +1,2497 @@
+/* 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 "logging.h"
+
+#include "signaling/src/jsep/JsepSessionImpl.h"
+#include <string>
+#include <set>
+#include <bitset>
+#include <stdlib.h>
+
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+#include "nsDebug.h"
+
+#include <mozilla/Move.h>
+#include <mozilla/UniquePtr.h>
+
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepTransport.h"
+#include "signaling/src/sdp/Sdp.h"
+#include "signaling/src/sdp/SipccSdp.h"
+#include "signaling/src/sdp/SipccSdpParser.h"
+#include "mozilla/net/DataChannelProtocol.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, 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;
+}
+
+nsresult
+JsepSessionImpl::Init()
+{
+ mLastError.clear();
+
+ MOZ_ASSERT(!mSessionId, "Init called more than once");
+
+ nsresult rv = SetupIds();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetupDefaultCodecs();
+ SetupDefaultRtpExtensions();
+
+ return NS_OK;
+}
+
+// Helper function to find the track for a given m= section.
+template <class T>
+typename std::vector<T>::iterator
+FindTrackByLevel(std::vector<T>& tracks, size_t level)
+{
+ for (auto t = tracks.begin(); t != tracks.end(); ++t) {
+ if (t->mAssignedMLine.isSome() &&
+ (*t->mAssignedMLine == level)) {
+ return t;
+ }
+ }
+
+ return tracks.end();
+}
+
+template <class T>
+typename std::vector<T>::iterator
+FindTrackByIds(std::vector<T>& tracks,
+ const std::string& streamId,
+ const std::string& trackId)
+{
+ for (auto t = tracks.begin(); t != tracks.end(); ++t) {
+ if (t->mTrack->GetStreamId() == streamId &&
+ (t->mTrack->GetTrackId() == trackId)) {
+ return t;
+ }
+ }
+
+ return tracks.end();
+}
+
+template <class T>
+typename std::vector<T>::iterator
+FindUnassignedTrackByType(std::vector<T>& tracks,
+ SdpMediaSection::MediaType type)
+{
+ for (auto t = tracks.begin(); t != tracks.end(); ++t) {
+ if (!t->mAssignedMLine.isSome() &&
+ (t->mTrack->GetMediaType() == type)) {
+ return t;
+ }
+ }
+
+ return tracks.end();
+}
+
+nsresult
+JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track)
+{
+ mLastError.clear();
+ MOZ_ASSERT(track->GetDirection() == sdp::kSend);
+
+ if (track->GetMediaType() != SdpMediaSection::kApplication) {
+ track->SetCNAME(mCNAME);
+
+ if (track->GetSsrcs().empty()) {
+ uint32_t ssrc;
+ nsresult rv = CreateSsrc(&ssrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ track->AddSsrc(ssrc);
+ }
+ }
+
+ track->PopulateCodecs(mSupportedCodecs.values);
+
+ JsepSendingTrack strack;
+ strack.mTrack = track;
+
+ mLocalTracks.push_back(strack);
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::RemoveTrack(const std::string& streamId,
+ const std::string& trackId)
+{
+ if (mState != kJsepStateStable) {
+ JSEP_SET_ERROR("Removing tracks outside of stable is unsupported.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ auto track = FindTrackByIds(mLocalTracks, streamId, trackId);
+
+ if (track == mLocalTracks.end()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mLocalTracks.erase(track);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetIceCredentials(const std::string& ufrag,
+ const std::string& pwd)
+{
+ mLastError.clear();
+ mIceUfrag = ufrag;
+ mIcePwd = pwd;
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy)
+{
+ mLastError.clear();
+ 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 std::string& 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(std::vector<SdpExtmapAttributeList::Extmap>& extensions,
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction)
+{
+ mLastError.clear();
+
+ if (extensions.size() + 1 > UINT16_MAX) {
+ JSEP_SET_ERROR("Too many rtp extensions have been added");
+ return NS_ERROR_FAILURE;
+ }
+
+ SdpExtmapAttributeList::Extmap extmap =
+ { static_cast<uint16_t>(extensions.size() + 1),
+ direction,
+ direction != SdpDirectionAttribute::kSendrecv, // do we want to specify direction?
+ extensionName,
+ "" };
+
+ extensions.push_back(extmap);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::AddAudioRtpExtension(const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction)
+{
+ return AddRtpExtension(mAudioRtpExtensions, extensionName, direction);
+}
+
+nsresult
+JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction)
+{
+ return AddRtpExtension(mVideoRtpExtensions, extensionName, direction);
+}
+
+template<class T>
+std::vector<RefPtr<JsepTrack>>
+GetTracks(const std::vector<T>& wrappedTracks)
+{
+ std::vector<RefPtr<JsepTrack>> result;
+ for (auto i = wrappedTracks.begin(); i != wrappedTracks.end(); ++i) {
+ result.push_back(i->mTrack);
+ }
+ return result;
+}
+
+nsresult
+JsepSessionImpl::ReplaceTrack(const std::string& oldStreamId,
+ const std::string& oldTrackId,
+ const std::string& newStreamId,
+ const std::string& newTrackId)
+{
+ auto it = FindTrackByIds(mLocalTracks, oldStreamId, oldTrackId);
+
+ if (it == mLocalTracks.end()) {
+ JSEP_SET_ERROR("Track " << oldStreamId << "/" << oldTrackId
+ << " was never added.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (FindTrackByIds(mLocalTracks, newStreamId, newTrackId) !=
+ mLocalTracks.end()) {
+ JSEP_SET_ERROR("Track " << newStreamId << "/" << newTrackId
+ << " was already added.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ it->mTrack->SetStreamId(newStreamId);
+ it->mTrack->SetTrackId(newTrackId);
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetParameters(const std::string& streamId,
+ const std::string& trackId,
+ const std::vector<JsepTrack::JsConstraints>& constraints)
+{
+ auto it = FindTrackByIds(mLocalTracks, streamId, trackId);
+
+ if (it == mLocalTracks.end()) {
+ JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Add RtpStreamId Extmap
+ // SdpDirectionAttribute::Direction is a bitmask
+ SdpDirectionAttribute::Direction addVideoExt = SdpDirectionAttribute::kInactive;
+ for (auto constraintEntry: constraints) {
+ if (constraintEntry.rid != "") {
+ if (it->mTrack->GetMediaType() == SdpMediaSection::kVideo) {
+ addVideoExt = static_cast<SdpDirectionAttribute::Direction>(addVideoExt
+ | it->mTrack->GetDirection());
+ }
+ }
+ }
+ if (addVideoExt != SdpDirectionAttribute::kInactive) {
+ AddVideoRtpExtension("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", addVideoExt);
+ }
+
+ it->mTrack->SetJsConstraints(constraints);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::GetParameters(const std::string& streamId,
+ const std::string& trackId,
+ std::vector<JsepTrack::JsConstraints>* outConstraints)
+{
+ auto it = FindTrackByIds(mLocalTracks, streamId, trackId);
+
+ if (it == mLocalTracks.end()) {
+ JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ it->mTrack->GetJsConstraints(outConstraints);
+ return NS_OK;
+}
+
+std::vector<RefPtr<JsepTrack>>
+JsepSessionImpl::GetLocalTracks() const
+{
+ return GetTracks(mLocalTracks);
+}
+
+std::vector<RefPtr<JsepTrack>>
+JsepSessionImpl::GetRemoteTracks() const
+{
+ return GetTracks(mRemoteTracks);
+}
+
+std::vector<RefPtr<JsepTrack>>
+JsepSessionImpl::GetRemoteTracksAdded() const
+{
+ return GetTracks(mRemoteTracksAdded);
+}
+
+std::vector<RefPtr<JsepTrack>>
+JsepSessionImpl::GetRemoteTracksRemoved() const
+{
+ return GetTracks(mRemoteTracksRemoved);
+}
+
+nsresult
+JsepSessionImpl::SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp)
+{
+ // First audio, then video, then datachannel, for interop
+ // TODO(bug 1121756): We need to group these by stream-id, _then_ by media
+ // type, according to the spec. However, this is not going to interop with
+ // older versions of Firefox if a video-only stream is added before an
+ // audio-only stream.
+ // We should probably wait until 38 is ESR before trying to do this.
+ nsresult rv = SetupOfferMSectionsByType(
+ SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupOfferMSectionsByType(
+ SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(options.mDontOfferDataChannel.isSome() &&
+ *options.mDontOfferDataChannel)) {
+ rv = SetupOfferMSectionsByType(
+ SdpMediaSection::kApplication, Maybe<size_t>(), sdp);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!sdp->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Cannot create an offer with no local tracks, "
+ "no offerToReceiveAudio/Video, and no DataChannel.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetupOfferMSectionsByType(SdpMediaSection::MediaType mediatype,
+ Maybe<size_t> offerToReceiveMaybe,
+ Sdp* sdp)
+{
+ // Convert the Maybe into a size_t*, since that is more readable, especially
+ // when using it as an in/out param.
+ size_t offerToReceiveCount;
+ size_t* offerToReceiveCountPtr = nullptr;
+
+ if (offerToReceiveMaybe) {
+ offerToReceiveCount = *offerToReceiveMaybe;
+ offerToReceiveCountPtr = &offerToReceiveCount;
+ }
+
+ // Make sure every local track has an m-section
+ nsresult rv = BindLocalTracks(mediatype, sdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure that m-sections that previously had a remote track have the
+ // recv bit set. Only matters for renegotiation.
+ rv = BindRemoteTracks(mediatype, sdp, offerToReceiveCountPtr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we need more recv sections, start setting the recv bit on other
+ // msections. If not, disable msections that have no tracks.
+ rv = SetRecvAsNeededOrDisable(mediatype,
+ sdp,
+ offerToReceiveCountPtr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we still don't have enough recv m-sections, add some.
+ if (offerToReceiveCountPtr && *offerToReceiveCountPtr) {
+ rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype, Sdp* sdp)
+{
+ for (JsepSendingTrack& track : mLocalTracks) {
+ if (mediatype != track.mTrack->GetMediaType()) {
+ continue;
+ }
+
+ SdpMediaSection* msection;
+ if (track.mAssignedMLine.isSome()) {
+ msection = &sdp->GetMediaSection(*track.mAssignedMLine);
+ } else {
+ nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(),
+ sdp,
+ &msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ track.mAssignedMLine = Some(msection->GetLevel());
+ }
+
+ track.mTrack->AddToOffer(msection);
+ }
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype,
+ Sdp* sdp,
+ size_t* offerToReceive)
+{
+ for (JsepReceivingTrack& track : mRemoteTracks) {
+ if (mediatype != track.mTrack->GetMediaType()) {
+ continue;
+ }
+
+ if (!track.mAssignedMLine.isSome()) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+
+ auto& msection = sdp->GetMediaSection(*track.mAssignedMLine);
+
+ if (mSdpHelper.MsectionIsDisabled(msection)) {
+ // TODO(bug 1095226) Content probably disabled this? Should we allow
+ // content to do this?
+ continue;
+ }
+
+ track.mTrack->AddToOffer(&msection);
+
+ if (offerToReceive && *offerToReceive) {
+ --(*offerToReceive);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
+ Sdp* sdp,
+ size_t* offerToRecv)
+{
+ for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+ auto& msection = sdp->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(msection) ||
+ msection.GetMediaType() != mediatype ||
+ msection.IsReceiving()) {
+ continue;
+ }
+
+ if (offerToRecv) {
+ if (*offerToRecv) {
+ SetupOfferToReceiveMsection(&msection);
+ --(*offerToRecv);
+ continue;
+ }
+ } else if (msection.IsSending()) {
+ SetupOfferToReceiveMsection(&msection);
+ continue;
+ }
+
+ if (!msection.IsSending()) {
+ // Unused m-section, and no reason to offer to recv on it
+ mSdpHelper.DisableMsection(sdp, &msection);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer)
+{
+ // Create a dummy recv track, and have it add codecs, set direction, etc.
+ RefPtr<JsepTrack> dummy = new JsepTrack(offer->GetMediaType(),
+ "",
+ "",
+ sdp::kRecv);
+ dummy->PopulateCodecs(mSupportedCodecs.values);
+ dummy->AddToOffer(offer);
+}
+
+nsresult
+JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
+ size_t count,
+ Sdp* sdp)
+{
+ while (count--) {
+ nsresult rv = CreateOfferMSection(
+ mediatype,
+ mSdpHelper.GetProtocolForMediaType(mediatype),
+ SdpDirectionAttribute::kRecvonly,
+ sdp);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetupOfferToReceiveMsection(
+ &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1));
+ }
+ return NS_OK;
+}
+
+// This function creates a skeleton SDP based on the old descriptions
+// (ie; all m-sections are inactive).
+nsresult
+JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp,
+ const Sdp& oldAnswer,
+ Sdp* newSdp)
+{
+ nsresult rv;
+
+ for (size_t i = 0; i < oldLocalSdp.GetMediaSectionCount(); ++i) {
+ // We do not set the direction in this function (or disable when previously
+ // disabled), that happens in |SetupOfferMSectionsByType|
+ rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(),
+ oldLocalSdp.GetMediaSection(i).GetProtocol(),
+ SdpDirectionAttribute::kInactive,
+ newSdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSdpHelper.CopyStickyParams(oldAnswer.GetMediaSection(i),
+ &newSdp->GetMediaSection(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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 (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));
+ }
+
+ mids.push_back(attrs.GetMid());
+ }
+ }
+
+ if (mids.size() > 1) {
+ UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+ groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+ }
+}
+
+nsresult
+JsepSessionImpl::GetRemoteIds(const Sdp& sdp,
+ const SdpMediaSection& msection,
+ std::string* streamId,
+ std::string* trackId)
+{
+ nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamId, trackId);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ *streamId = mDefaultRemoteStreamId;
+
+ if (!mDefaultRemoteTrackIdsByLevel.count(msection.GetLevel())) {
+ // Generate random track ids.
+ if (!mUuidGen->Generate(trackId)) {
+ JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
+ return NS_ERROR_FAILURE;
+ }
+
+ mDefaultRemoteTrackIdsByLevel[msection.GetLevel()] = *trackId;
+ } else {
+ *trackId = mDefaultRemoteTrackIdsByLevel[msection.GetLevel()];
+ }
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // If, for whatever reason, the other end renegotiates with an msid where
+ // there wasn't one before, don't allow the old default to pop up again
+ // later.
+ mDefaultRemoteTrackIdsByLevel.erase(msection.GetLevel());
+ }
+
+ return rv;
+}
+
+nsresult
+JsepSessionImpl::CreateOffer(const JsepOfferOptions& options,
+ std::string* offer)
+{
+ mLastError.clear();
+
+ if (mState != kJsepStateStable) {
+ JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Undo track assignments from a previous call to CreateOffer
+ // (ie; if the track has not been negotiated yet, it doesn't necessarily need
+ // to stay in the same m-section that it was in)
+ for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+ if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
+ trackWrapper.mAssignedMLine.reset();
+ }
+ }
+
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCurrentLocalDescription) {
+ rv = AddReofferMsections(*mCurrentLocalDescription,
+ *GetAnswer(),
+ sdp.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Ensure that we have all the m-sections we need, and disable extras
+ rv = SetupOfferMSections(options, sdp.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetupBundle(sdp.get());
+
+ if (mCurrentLocalDescription) {
+ rv = CopyPreviousTransportParams(*GetAnswer(),
+ *mCurrentLocalDescription,
+ *sdp,
+ sdp.get());
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ *offer = sdp->ToString();
+ mGeneratedLocalDescription = Move(sdp);
+ ++mSessionVersion;
+
+ return NS_OK;
+}
+
+std::string
+JsepSessionImpl::GetLocalDescription() const
+{
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedLocalDescription();
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+std::string
+JsepSessionImpl::GetRemoteDescription() const
+{
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedRemoteDescription();
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+void
+JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const
+{
+ const auto* extensions = GetRtpExtensions(msection->GetMediaType());
+
+ if (extensions && !extensions->empty()) {
+ SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
+ extmap->mExtmaps = *extensions;
+ msection->GetAttributeList().SetAttribute(extmap);
+ }
+}
+
+void
+JsepSessionImpl::AddMid(const std::string& mid,
+ SdpMediaSection* msection) const
+{
+ msection->GetAttributeList().SetAttribute(new SdpStringAttribute(
+ SdpAttribute::kMidAttribute, mid));
+}
+
+const std::vector<SdpExtmapAttributeList::Extmap>*
+JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const
+{
+ switch (type) {
+ case SdpMediaSection::kAudio:
+ return &mAudioRtpExtensions;
+ case SdpMediaSection::kVideo:
+ return &mVideoRtpExtensions;
+ default:
+ return nullptr;
+ }
+}
+
+void
+JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection)
+{
+ auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType());
+
+ if (ourExtensions) {
+ mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection);
+ }
+}
+
+nsresult
+JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer)
+{
+ mLastError.clear();
+
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // This is the heart of the negotiation code. Depressing that it's
+ // so bad.
+ //
+ // Here's the current algorithm:
+ // 1. Walk through all the m-lines on the other side.
+ // 2. For each m-line, walk through all of our local tracks
+ // in sequence and see if any are unassigned. If so, assign
+ // them and mark it sendrecv, otherwise it's recvonly.
+ // 3. Just replicate their media attributes.
+ // 4. Profit.
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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());
+
+ // Disable send for local tracks if the offer no longer allows it
+ // (i.e., the m-section is recvonly, inactive or disabled)
+ for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+ if (!trackWrapper.mAssignedMLine.isSome()) {
+ continue;
+ }
+
+ // Get rid of all m-line assignments that have not been negotiated
+ if (!trackWrapper.mTrack->GetNegotiatedDetails()) {
+ trackWrapper.mAssignedMLine.reset();
+ continue;
+ }
+
+ if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) {
+ trackWrapper.mAssignedMLine.reset();
+ }
+ }
+
+ size_t numMsections = offer.GetMediaSectionCount();
+
+ for (size_t i = 0; i < numMsections; ++i) {
+ const SdpMediaSection& remoteMsection = offer.GetMediaSection(i);
+ rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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,rv);
+ }
+
+ *answer = sdp->ToString();
+ mGeneratedLocalDescription = Move(sdp);
+ ++mSessionVersion;
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CreateOfferMSection(SdpMediaSection::MediaType mediatype,
+ SdpMediaSection::Protocol proto,
+ SdpDirectionAttribute::Direction dir,
+ Sdp* sdp)
+{
+ SdpMediaSection* msection =
+ &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0");
+
+ return EnableOfferMsection(msection);
+}
+
+nsresult
+JsepSessionImpl::GetFreeMsectionForSend(
+ SdpMediaSection::MediaType type,
+ Sdp* sdp,
+ SdpMediaSection** msectionOut)
+{
+ for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+ SdpMediaSection& msection = sdp->GetMediaSection(i);
+ // draft-ietf-rtcweb-jsep-08 says we should reclaim disabled m-sections
+ // regardless of media type. This breaks some pretty fundamental rules of
+ // SDP offer/answer, so we probably should not do it.
+ if (msection.GetMediaType() != type) {
+ continue;
+ }
+
+ if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) {
+ // Not free
+ continue;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(msection)) {
+ // Was disabled; revive
+ nsresult rv = EnableOfferMsection(&msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *msectionOut = &msection;
+ return NS_OK;
+ }
+
+ // Ok, no pre-existing m-section. Make a new one.
+ nsresult rv = CreateOfferMSection(type,
+ mSdpHelper.GetProtocolForMediaType(type),
+ SdpDirectionAttribute::kInactive,
+ sdp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options,
+ size_t mlineIndex,
+ const SdpMediaSection& remoteMsection,
+ Sdp* sdp)
+{
+ SdpMediaSection& msection =
+ sdp->AddMediaSection(remoteMsection.GetMediaType(),
+ SdpDirectionAttribute::kInactive,
+ 9,
+ remoteMsection.GetProtocol(),
+ sdp::kIPv4,
+ "0.0.0.0");
+
+ nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
+ mSdpHelper.DisableMsection(sdp, &msection);
+ return NS_OK;
+ }
+
+ SdpSetupAttribute::Role role;
+ rv = DetermineAnswererSetupRole(remoteMsection, &role);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddTransportAttributes(&msection, role);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetRecvonlySsrc(&msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only attempt to match up local tracks if the offerer has elected to
+ // receive traffic.
+ if (remoteMsection.IsReceiving()) {
+ rv = BindMatchingLocalTrackToAnswer(&msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (remoteMsection.IsSending()) {
+ BindMatchingRemoteTrackToAnswer(&msection);
+ }
+
+ if (!msection.IsReceiving() && !msection.IsSending()) {
+ mSdpHelper.DisableMsection(sdp, &msection);
+ return NS_OK;
+ }
+
+ // Add extmap attributes.
+ AddCommonExtmaps(remoteMsection, &msection);
+
+ if (msection.GetFormats().empty()) {
+ // Could not negotiate anything. Disable m-section.
+ mSdpHelper.DisableMsection(sdp, &msection);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection)
+{
+ // If previous m-sections are disabled, we do not call this function for them
+ while (mRecvonlySsrcs.size() <= msection->GetLevel()) {
+ uint32_t ssrc;
+ nsresult rv = CreateSsrc(&ssrc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mRecvonlySsrcs.push_back(ssrc);
+ }
+
+ std::vector<uint32_t> ssrcs;
+ ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]);
+ msection->SetSsrcs(ssrcs, mCNAME);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindMatchingLocalTrackToAnswer(SdpMediaSection* msection)
+{
+ auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel());
+
+ if (track == mLocalTracks.end()) {
+ track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
+ }
+
+ if (track == mLocalTracks.end() &&
+ msection->GetMediaType() == SdpMediaSection::kApplication) {
+ // If we are offered datachannel, we need to play along even if no track
+ // for it has been added yet.
+ std::string streamId;
+ std::string trackId;
+
+ if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) {
+ JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track");
+ return NS_ERROR_FAILURE;
+ }
+
+ AddTrack(RefPtr<JsepTrack>(
+ new JsepTrack(SdpMediaSection::kApplication, streamId, trackId)));
+ track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType());
+ MOZ_ASSERT(track != mLocalTracks.end());
+ }
+
+ if (track != mLocalTracks.end()) {
+ track->mAssignedMLine = Some(msection->GetLevel());
+ track->mTrack->AddToAnswer(
+ mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
+ msection);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection)
+{
+ auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel());
+ if (it == mRemoteTracks.end()) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("Failed to find remote track for local answer m-section");
+ return NS_ERROR_FAILURE;
+ }
+
+ it->mTrack->AddToAnswer(
+ mPendingRemoteDescription->GetMediaSection(msection->GetLevel()),
+ 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;
+}
+
+nsresult
+JsepSessionImpl::SetLocalDescription(JsepSdpType type, const std::string& sdp)
+{
+ mLastError.clear();
+
+ MOZ_MTLOG(ML_DEBUG, "SetLocalDescription type=" << type << "\nSDP=\n"
+ << sdp);
+
+ if (type == kJsepSdpRollback) {
+ if (mState != kJsepStateHaveLocalOffer) {
+ JSEP_SET_ERROR("Cannot rollback local description in "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mPendingLocalDescription.reset();
+ SetState(kJsepStateStable);
+ mTransports = mOldTransports;
+ mOldTransports.clear();
+ return NS_OK;
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set local answer in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+ mIsOfferer = true;
+ break;
+ case kJsepStateHaveRemoteOffer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set local offer in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set local offer or answer in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check that content hasn't done anything unsupported with the SDP
+ rv = ValidateLocalDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create transport objects.
+ mOldTransports = mTransports; // Save in case we need to rollback
+ mTransports.clear();
+ for (size_t t = 0; t < parsed->GetMediaSectionCount(); ++t) {
+ mTransports.push_back(RefPtr<JsepTransport>(new JsepTransport));
+ InitTransport(parsed->GetMediaSection(t), mTransports[t].get());
+ }
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetLocalDescriptionOffer(Move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetLocalDescriptionAnswer(type, Move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ return rv;
+}
+
+nsresult
+JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer)
+{
+ MOZ_ASSERT(mState == kJsepStateStable);
+ mPendingLocalDescription = Move(offer);
+ SetState(kJsepStateHaveLocalOffer);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer)
+{
+ MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
+ mPendingLocalDescription = Move(answer);
+
+ nsresult rv = ValidateAnswer(*mPendingRemoteDescription,
+ *mPendingLocalDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = Move(mPendingRemoteDescription);
+ mCurrentLocalDescription = Move(mPendingLocalDescription);
+ mWasOffererLastTime = mIsOfferer;
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRemoteDescription(JsepSdpType type, const std::string& sdp)
+{
+ mLastError.clear();
+ mRemoteTracksAdded.clear();
+ mRemoteTracksRemoved.clear();
+
+ MOZ_MTLOG(ML_DEBUG, "SetRemoteDescription type=" << type << "\nSDP=\n"
+ << sdp);
+
+ if (type == kJsepSdpRollback) {
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot rollback remote description in "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mPendingRemoteDescription.reset();
+ SetState(kJsepStateStable);
+
+ // Update the remote tracks to what they were before the SetRemote
+ return SetRemoteTracksFromDescription(mCurrentRemoteDescription.get());
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set remote answer in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+ mIsOfferer = false;
+ break;
+ case kJsepStateHaveLocalOffer:
+ case kJsepStateHaveRemotePranswer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set remote offer in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set remote offer or answer in current state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Parse.
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ValidateRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+ }
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetRemoteDescriptionOffer(Move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetRemoteDescriptionAnswer(type, Move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ mRemoteIsIceLite = iceLite;
+ mIceOptions = iceOptions;
+ mRemoteIceIsRestarting = iceRestarting;
+ }
+
+ return rv;
+}
+
+nsresult
+JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local,
+ const UniquePtr<Sdp>& remote)
+{
+ bool remoteIceLite =
+ remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+
+ mIceControlling = remoteIceLite || mIsOfferer;
+
+ const Sdp& answer = mIsOfferer ? *remote : *local;
+
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mTransports.size() < local->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Fewer transports set up than m-lines");
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ for (JsepSendingTrack& trackWrapper : mLocalTracks) {
+ trackWrapper.mTrack->ClearNegotiatedDetails();
+ }
+
+ for (JsepReceivingTrack& trackWrapper : mRemoteTracks) {
+ trackWrapper.mTrack->ClearNegotiatedDetails();
+ }
+
+ std::vector<JsepTrackPair> trackPairs;
+
+ // Now walk through the m-sections, make sure they match, and create
+ // track pairs that describe the media to be set up.
+ for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
+ // Skip disabled m-sections.
+ if (answer.GetMediaSection(i).GetPort() == 0) {
+ mTransports[i]->Close();
+ continue;
+ }
+
+ // The transport details are not necessarily on the m-section we're
+ // currently processing.
+ size_t transportLevel = i;
+ bool usingBundle = false;
+ {
+ const SdpMediaSection& answerMsection(answer.GetMediaSection(i));
+ if (answerMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute)) {
+ if (bundledMids.count(answerMsection.GetAttributeList().GetMid())) {
+ const SdpMediaSection* masterBundleMsection =
+ bundledMids[answerMsection.GetAttributeList().GetMid()];
+ transportLevel = masterBundleMsection->GetLevel();
+ usingBundle = true;
+ if (i != transportLevel) {
+ mTransports[i]->Close();
+ }
+ }
+ }
+ }
+
+ RefPtr<JsepTransport> transport = mTransports[transportLevel];
+
+ rv = FinalizeTransport(
+ remote->GetMediaSection(transportLevel).GetAttributeList(),
+ answer.GetMediaSection(transportLevel).GetAttributeList(),
+ transport);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JsepTrackPair trackPair;
+ rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i),
+ local->GetMediaSection(i),
+ transport,
+ usingBundle,
+ transportLevel,
+ &trackPair);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ trackPairs.push_back(trackPair);
+ }
+
+ JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks));
+
+ // Ouch, this probably needs some dirty bit instead of just clearing
+ // stuff for renegotiation.
+ mNegotiatedTrackPairs = trackPairs;
+
+ mGeneratedLocalDescription.reset();
+
+ mNegotiations++;
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ const RefPtr<JsepTransport>& transport,
+ bool usingBundle,
+ size_t transportLevel,
+ JsepTrackPair* trackPairOut)
+{
+ MOZ_ASSERT(transport->mComponents);
+ const SdpMediaSection& answer = mIsOfferer ? remote : local;
+
+ bool sending;
+ bool receiving;
+
+ if (mIsOfferer) {
+ receiving = answer.IsSending();
+ sending = answer.IsReceiving();
+ } else {
+ sending = answer.IsSending();
+ receiving = answer.IsReceiving();
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "Negotiated m= line"
+ << " index=" << local.GetLevel()
+ << " type=" << local.GetMediaType()
+ << " sending=" << sending
+ << " receiving=" << receiving);
+
+ trackPairOut->mLevel = local.GetLevel();
+
+ MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(),
+ "Failed to set the default ssrc for an active m-section");
+ trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()];
+
+ if (usingBundle) {
+ trackPairOut->mBundleLevel = Some(transportLevel);
+ }
+
+ auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel());
+ if (sendTrack != mLocalTracks.end()) {
+ sendTrack->mTrack->Negotiate(answer, remote);
+ sendTrack->mTrack->SetActive(sending);
+ trackPairOut->mSending = sendTrack->mTrack;
+ } else if (sending) {
+ JSEP_SET_ERROR("Failed to find local track for level " <<
+ local.GetLevel()
+ << " in local SDP. This should never happen.");
+ NS_ASSERTION(false, "Failed to find local track for level");
+ return NS_ERROR_FAILURE;
+ }
+
+ auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel());
+ if (recvTrack != mRemoteTracks.end()) {
+ recvTrack->mTrack->Negotiate(answer, remote);
+ recvTrack->mTrack->SetActive(receiving);
+ trackPairOut->mReceiving = recvTrack->mTrack;
+
+ if (receiving &&
+ trackPairOut->mBundleLevel.isSome() &&
+ recvTrack->mTrack->GetSsrcs().empty() &&
+ recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) {
+ MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. "
+ "This may cause media packets to be dropped.");
+ }
+ } else if (receiving) {
+ JSEP_SET_ERROR("Failed to find remote track for level "
+ << local.GetLevel()
+ << " in remote SDP. This should never happen.");
+ NS_ASSERTION(false, "Failed to find remote track for level");
+ return NS_ERROR_FAILURE;
+ }
+
+ trackPairOut->mRtpTransport = transport;
+
+ if (transport->mComponents == 2) {
+ // RTCP MUX or not.
+ // TODO(bug 1095743): verify that the PTs are consistent with mux.
+ MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off");
+ trackPairOut->mRtcpTransport = transport;
+ }
+
+ return NS_OK;
+}
+
+void
+JsepSessionImpl::InitTransport(const SdpMediaSection& msection,
+ JsepTransport* transport)
+{
+ if (mSdpHelper.MsectionIsDisabled(msection)) {
+ transport->Close();
+ return;
+ }
+
+ if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
+ transport->mComponents = 2;
+ } else {
+ transport->mComponents = 1;
+ }
+
+ if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
+ transport->mTransportId = msection.GetAttributeList().GetMid();
+ } else {
+ std::ostringstream os;
+ os << "level_" << msection.GetLevel() << "(no mid)";
+ transport->mTransportId = os.str();
+ }
+}
+
+nsresult
+JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ const RefPtr<JsepTransport>& transport)
+{
+ UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();
+
+ // We do sanity-checking for these in ParseSdp
+ ice->mUfrag = remote.GetIceUfrag();
+ ice->mPwd = remote.GetIcePwd();
+ if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ ice->mCandidates = remote.GetCandidate();
+ }
+
+ // 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 = mIsOfferer ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ if (mIsOfferer) {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsClient
+ : JsepDtlsTransport::kJsepDtlsServer;
+ }
+ }
+
+ transport->mIce = Move(ice);
+ transport->mDtls = 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) &&
+ !mRemoteIceIsRestarting
+ ) {
+ // 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.
+ size_t numComponents = mTransports[i]->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)
+{
+ UniquePtr<Sdp> parsed = mParser.Parse(sdp);
+ if (!parsed) {
+ std::string error = "Failed to parse SDP: ";
+ mSdpHelper.appendSdpParseErrors(mParser.GetParseErrors(), &error);
+ JSEP_SET_ERROR(error);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Verify that the JSEP rules for all SDP are followed
+ if (!parsed->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Description has no media sections");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ std::set<std::string> trackIds;
+
+ 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.GetIceUfrag().empty()) {
+ JSEP_SET_ERROR("Invalid description, no ice-ufrag attribute");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.GetIcePwd().empty()) {
+ JSEP_SET_ERROR("Invalid description, no ice-pwd attribute");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mediaAttrs.HasAttribute(SdpAttribute::kFingerprintAttribute)) {
+ JSEP_SET_ERROR("Invalid description, no fingerprint attribute");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SdpFingerprintAttributeList& fingerprints(
+ mediaAttrs.GetFingerprint());
+ if (fingerprints.mFingerprints.empty()) {
+ JSEP_SET_ERROR("Invalid description, no supported fingerprint algorithms "
+ "present");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute) &&
+ mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) {
+ JSEP_SET_ERROR("Description has illegal setup attribute "
+ "\"holdconn\" at level "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ auto& formats = parsed->GetMediaSection(i).GetFormats();
+ for (auto f = formats.begin(); f != formats.end(); ++f) {
+ uint16_t pt;
+ if (!SdpHelper::GetPtAsInt(*f, &pt)) {
+ JSEP_SET_ERROR("Payload type \""
+ << *f << "\" is not a 16-bit unsigned int at level "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ std::string streamId;
+ std::string trackId;
+ nsresult rv = mSdpHelper.GetIdsFromMsid(*parsed,
+ parsed->GetMediaSection(i),
+ &streamId,
+ &trackId);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (trackIds.count(trackId)) {
+ JSEP_SET_ERROR("track id:" << trackId
+ << " appears in more than one m-section at level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ trackIds.insert(trackId);
+ } else if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // Error has already been set
+ return rv;
+ }
+
+ 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;
+ // TODO (bug 1204099): Make this check for reserved ranges.
+ if (!SdpHelper::GetPtAsInt(fmt, &payloadType) || payloadType > 127) {
+ JSEP_SET_ERROR("audio/video payload type is too large: " << fmt);
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (forbidden.test(payloadType)) {
+ JSEP_SET_ERROR("Illegal audio/video payload type: " << fmt);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ *parsedp = Move(parsed);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer)
+{
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ // TODO(bug 1095780): Note that we create remote tracks even when
+ // They contain only codecs we can't negotiate or other craziness.
+ nsresult rv = SetRemoteTracksFromDescription(offer.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPendingRemoteDescription = Move(offer);
+
+ SetState(kJsepStateHaveRemoteOffer);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer)
+{
+ MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
+ mState == kJsepStateHaveRemotePranswer);
+
+ mPendingRemoteDescription = Move(answer);
+
+ nsresult rv = ValidateAnswer(*mPendingLocalDescription,
+ *mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO(bug 1095780): Note that this creates remote tracks even if
+ // we offered sendonly and other side offered sendrecv or recvonly.
+ rv = SetRemoteTracksFromDescription(mPendingRemoteDescription.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = Move(mPendingRemoteDescription);
+ mCurrentLocalDescription = Move(mPendingLocalDescription);
+ mWasOffererLastTime = mIsOfferer;
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetRemoteTracksFromDescription(const Sdp* remoteDescription)
+{
+ // Unassign all remote tracks
+ for (auto i = mRemoteTracks.begin(); i != mRemoteTracks.end(); ++i) {
+ i->mAssignedMLine.reset();
+ }
+
+ // This will not exist if we're rolling back the first remote description
+ if (remoteDescription) {
+ size_t numMlines = remoteDescription->GetMediaSectionCount();
+ nsresult rv;
+
+ // Iterate over the sdp, re-assigning or creating remote tracks as we go
+ for (size_t i = 0; i < numMlines; ++i) {
+ const SdpMediaSection& msection = remoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(msection) || !msection.IsSending()) {
+ continue;
+ }
+
+ std::vector<JsepReceivingTrack>::iterator track;
+
+ if (msection.GetMediaType() == SdpMediaSection::kApplication) {
+ // Datachannel doesn't have msid, just search by type
+ track = FindUnassignedTrackByType(mRemoteTracks,
+ msection.GetMediaType());
+ } else {
+ std::string streamId;
+ std::string trackId;
+ rv = GetRemoteIds(*remoteDescription, msection, &streamId, &trackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ track = FindTrackByIds(mRemoteTracks, streamId, trackId);
+ }
+
+ if (track == mRemoteTracks.end()) {
+ RefPtr<JsepTrack> track;
+ rv = CreateReceivingTrack(i, *remoteDescription, msection, &track);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JsepReceivingTrack rtrack;
+ rtrack.mTrack = track;
+ rtrack.mAssignedMLine = Some(i);
+ mRemoteTracks.push_back(rtrack);
+ mRemoteTracksAdded.push_back(rtrack);
+ } else {
+ track->mAssignedMLine = Some(i);
+ }
+ }
+ }
+
+ // Remove any unassigned remote track ids
+ for (size_t i = 0; i < mRemoteTracks.size();) {
+ if (!mRemoteTracks[i].mAssignedMLine.isSome()) {
+ mRemoteTracksRemoved.push_back(mRemoteTracks[i]);
+ mRemoteTracks.erase(mRemoteTracks.begin() + i);
+ } else {
+ ++i;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::ValidateLocalDescription(const Sdp& description)
+{
+ // TODO(bug 1095226): Better checking.
+ if (!mGeneratedLocalDescription) {
+ JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer"
+ " is not supported.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (description.GetMediaSectionCount() !=
+ mGeneratedLocalDescription->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 = mGeneratedLocalDescription->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;
+ }
+ }
+
+ // 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 (!mCurrentRemoteDescription || !mCurrentLocalDescription) {
+ // Not renegotiation; checks for whether a remote answer are consistent
+ // with our offer are handled in ValidateAnswer()
+ return NS_OK;
+ }
+
+ if (mCurrentRemoteDescription->GetMediaSectionCount() >
+ description.GetMediaSectionCount()) {
+ JSEP_SET_ERROR("New remote description has fewer m-sections than the "
+ "previous remote description.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // These are solely to check that bundle is valid
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = GetNegotiatedBundledMids(&bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SdpHelper::BundledMids newBundledMids;
+ rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for partial ice restart, which is not supported
+ Maybe<bool> iceCredsDiffer;
+ for (size_t i = 0;
+ i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+
+ const SdpMediaSection& newMsection = description.GetMediaSection(i);
+ const SdpMediaSection& oldMsection =
+ mCurrentRemoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+ mSdpHelper.MsectionIsDisabled(oldMsection)) {
+ continue;
+ }
+
+ if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Remote description changes the media type of m-line "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+ // 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::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;
+ }
+
+ 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 (!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;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CreateReceivingTrack(size_t mline,
+ const Sdp& sdp,
+ const SdpMediaSection& msection,
+ RefPtr<JsepTrack>* track)
+{
+ std::string streamId;
+ std::string trackId;
+
+ nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *track = new JsepTrack(msection.GetMediaType(),
+ streamId,
+ trackId,
+ sdp::kRecv);
+
+ (*track)->SetCNAME(mSdpHelper.GetCNAME(msection));
+ (*track)->PopulateCodecs(mSupportedCodecs.values);
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp)
+{
+ // draft-ietf-rtcweb-jsep-08 Section 5.2.1:
+ // o The second SDP line MUST be an "o=" line, as specified in
+ // [RFC4566], Section 5.2. The value of the <username> field SHOULD
+ // be "-". The value of the <sess-id> field SHOULD be a
+ // cryptographically random number. To ensure uniqueness, this
+ // number SHOULD be at least 64 bits long. The value of the <sess-
+ // version> field SHOULD be zero. The value of the <nettype>
+ // <addrtype> <unicast-address> tuple SHOULD be set to a non-
+ // meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the
+ // local address in this field. As mentioned in [RFC4566], the
+ // entire o= line needs to be unique, but selecting a random number
+ // for <sess-id> is sufficient to accomplish this.
+
+ auto origin =
+ SdpOrigin("mozilla...THIS_IS_SDPARTA-" MOZ_APP_UA_VERSION,
+ mSessionId,
+ mSessionVersion,
+ sdp::kIPv4,
+ "0.0.0.0");
+
+ UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin);
+
+ if (mDtlsFingerprints.empty()) {
+ JSEP_SET_ERROR("Missing DTLS fingerprint");
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<SdpFingerprintAttributeList> fpl =
+ MakeUnique<SdpFingerprintAttributeList>();
+ for (auto fp = mDtlsFingerprints.begin(); fp != mDtlsFingerprints.end();
+ ++fp) {
+ fpl->PushEntry(fp->mAlgorithm, fp->mValue);
+ }
+ sdp->GetAttributeList().SetAttribute(fpl.release());
+
+ auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
+ iceOpts->PushEntry("trickle");
+ sdp->GetAttributeList().SetAttribute(iceOpts);
+
+ // This assumes content doesn't add a bunch of msid attributes with a
+ // different semantic in mind.
+ std::vector<std::string> msids;
+ msids.push_back("*");
+ mSdpHelper.SetupMsidSemantic(msids, sdp.get());
+
+ *sdpp = Move(sdp);
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::SetupIds()
+{
+ SECStatus rv = PK11_GenerateRandom(
+ reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
+ // RFC 3264 says that session-ids MUST be representable as a _signed_
+ // 64 bit number, meaning the MSB cannot be set.
+ mSessionId = mSessionId >> 1;
+ if (rv != SECSuccess) {
+ JSEP_SET_ERROR("Failed to generate session id: " << rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
+ JSEP_SET_ERROR("Failed to generate default uuid for streams");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mCNAME)) {
+ JSEP_SET_ERROR("Failed to generate CNAME");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::CreateSsrc(uint32_t* ssrc)
+{
+ do {
+ SECStatus rv = PK11_GenerateRandom(
+ reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t));
+ if (rv != SECSuccess) {
+ JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv);
+ return NS_ERROR_FAILURE;
+ }
+ } while (mSsrcs.count(*ssrc));
+ mSsrcs.insert(*ssrc);
+
+ return NS_OK;
+}
+
+void
+JsepSessionImpl::SetupDefaultCodecs()
+{
+ // Supported audio codecs.
+ // Per jmspeex on IRC:
+ // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good
+ // quality. Note that 1-2Kbps will be wasted on a stereo Opus channel
+ // with mono input compared to configuring it for mono.
+ // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a
+ // 9KHz tone. This should be adaptive when we're at the low-end of video
+ // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or
+ // 12Kbps.
+ mSupportedCodecs.values.push_back(new JsepAudioCodecDescription(
+ "109",
+ "opus",
+ 48000,
+ 2,
+ 960,
+#ifdef WEBRTC_GONK
+ // TODO Move this elsewhere to be adaptive to rate - Bug 1207925
+ 16000 // B2G uses lower capture sampling rate
+#else
+ 40000
+#endif
+ ));
+
+ mSupportedCodecs.values.push_back(new JsepAudioCodecDescription(
+ "9",
+ "G722",
+ 8000,
+ 1,
+ 320,
+ 64000));
+
+ // packet size and bitrate values below copied from sipcc.
+ // May need reevaluation from a media expert.
+ mSupportedCodecs.values.push_back(
+ new JsepAudioCodecDescription("0",
+ "PCMU",
+ 8000,
+ 1,
+ 8000 / 50, // frequency / 50
+ 8 * 8000 * 1 // 8 * frequency * channels
+ ));
+
+ mSupportedCodecs.values.push_back(
+ new JsepAudioCodecDescription("8",
+ "PCMA",
+ 8000,
+ 1,
+ 8000 / 50, // frequency / 50
+ 8 * 8000 * 1 // 8 * frequency * channels
+ ));
+
+ // note: because telephone-event is effectively a marker codec that indicates
+ // that dtmf rtp packets may be passed, the packetSize and bitRate fields
+ // don't make sense here. For now, use zero. (mjf)
+ mSupportedCodecs.values.push_back(
+ new JsepAudioCodecDescription("101",
+ "telephone-event",
+ 8000,
+ 1,
+ 0, // packetSize doesn't make sense here
+ 0 // bitRate doesn't make sense here
+ ));
+
+ // Supported video codecs.
+ // Note: order here implies priority for building offers!
+ JsepVideoCodecDescription* vp8 = new JsepVideoCodecDescription(
+ "120",
+ "VP8",
+ 90000
+ );
+ // Defaults for mandatory params
+ vp8->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ vp8->mConstraints.maxFps = 60;
+ mSupportedCodecs.values.push_back(vp8);
+
+ JsepVideoCodecDescription* vp9 = new JsepVideoCodecDescription(
+ "121",
+ "VP9",
+ 90000
+ );
+ // Defaults for mandatory params
+ vp9->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ vp9->mConstraints.maxFps = 60;
+ mSupportedCodecs.values.push_back(vp9);
+
+ JsepVideoCodecDescription* h264_1 = new JsepVideoCodecDescription(
+ "126",
+ "H264",
+ 90000
+ );
+ h264_1->mPacketizationMode = 1;
+ // Defaults for mandatory params
+ h264_1->mProfileLevelId = 0x42E00D;
+ mSupportedCodecs.values.push_back(h264_1);
+
+ JsepVideoCodecDescription* h264_0 = new JsepVideoCodecDescription(
+ "97",
+ "H264",
+ 90000
+ );
+ h264_0->mPacketizationMode = 0;
+ // Defaults for mandatory params
+ h264_0->mProfileLevelId = 0x42E00D;
+ mSupportedCodecs.values.push_back(h264_0);
+
+ JsepVideoCodecDescription* red = new JsepVideoCodecDescription(
+ "122", // payload type
+ "red", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ mSupportedCodecs.values.push_back(red);
+
+ JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription(
+ "123", // payload type
+ "ulpfec", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ mSupportedCodecs.values.push_back(ulpfec);
+
+ mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription(
+ "5000",
+ "webrtc-datachannel",
+ WEBRTC_DATACHANNEL_STREAMS_DEFAULT
+ ));
+
+ // Update the redundant encodings for the RED codec with the supported
+ // codecs. Note: only uses the video codecs.
+ red->UpdateRedundantEncodings(mSupportedCodecs.values);
+}
+
+void
+JsepSessionImpl::SetupDefaultRtpExtensions()
+{
+ AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level",
+ SdpDirectionAttribute::Direction::kSendonly);
+}
+
+void
+JsepSessionImpl::SetState(JsepSignalingState state)
+{
+ if (state == mState)
+ return;
+
+ MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " <<
+ GetStateStr(mState) << " -> " << GetStateStr(state));
+ mState = state;
+}
+
+nsresult
+JsepSessionImpl::AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ uint16_t level)
+{
+ mLastError.clear();
+
+ mozilla::Sdp* sdp = GetParsedRemoteDescription();
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mSdpHelper.AddCandidateToSdp(sdp, candidate, mid, level);
+}
+
+nsresult
+JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
+ uint16_t level,
+ std::string* mid,
+ bool* skipped)
+{
+ mLastError.clear();
+
+ mozilla::Sdp* sdp = GetParsedLocalDescription();
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (sdp->GetMediaSectionCount() <= level) {
+ // mainly here to make some testing less complicated, but also just in case
+ *skipped = true;
+ return NS_OK;
+ }
+
+ if (mState == kJsepStateStable) {
+ const Sdp* answer(GetAnswer());
+ if (mSdpHelper.IsBundleSlave(*answer, level)) {
+ // We do not add candidate attributes to bundled m-sections unless they
+ // are the "master" bundle m-section.
+ *skipped = true;
+ return NS_OK;
+ }
+ }
+
+ nsresult rv = mSdpHelper.GetMidFromLevel(*sdp, level, mid);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *skipped = false;
+
+ return mSdpHelper.AddCandidateToSdp(sdp, candidate, *mid, level);
+}
+
+nsresult
+JsepSessionImpl::UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr,
+ uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ uint16_t level)
+{
+ mLastError.clear();
+
+ mozilla::Sdp* sdp = GetParsedLocalDescription();
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (level >= sdp->GetMediaSectionCount()) {
+ return NS_OK;
+ }
+
+ std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
+ if (mState == kJsepStateStable && mTransports[level]->mComponents == 1) {
+ // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
+ defaultRtcpCandidateAddrCopy = "";
+ defaultRtcpCandidatePort = 0;
+ }
+
+ // If offer/answer isn't done, it is too early to tell whether these defaults
+ // need to be applied to other m-sections.
+ SdpHelper::BundledMids bundledMids;
+ if (mState == kJsepStateStable) {
+ nsresult rv = GetNegotiatedBundledMids(&bundledMids);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ mLastError += " (This should have been caught sooner!)";
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mSdpHelper.SetDefaultAddresses(
+ defaultCandidateAddr,
+ defaultCandidatePort,
+ defaultRtcpCandidateAddrCopy,
+ defaultRtcpCandidatePort,
+ sdp,
+ level,
+ bundledMids);
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::EndOfLocalCandidates(uint16_t level)
+{
+ mLastError.clear();
+
+ mozilla::Sdp* sdp = GetParsedLocalDescription();
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot mark end of local ICE candidates in state "
+ << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (level >= sdp->GetMediaSectionCount()) {
+ return NS_OK;
+ }
+
+ // If offer/answer isn't done, it is too early to tell whether this update
+ // needs to be applied to other m-sections.
+ SdpHelper::BundledMids bundledMids;
+ if (mState == kJsepStateStable) {
+ nsresult rv = GetNegotiatedBundledMids(&bundledMids);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ mLastError += " (This should have been caught sooner!)";
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mSdpHelper.SetIceGatheringComplete(sdp,
+ level,
+ bundledMids);
+
+ return NS_OK;
+}
+
+nsresult
+JsepSessionImpl::GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids)
+{
+ const Sdp* answerSdp = GetAnswer();
+
+ if (!answerSdp) {
+ return NS_OK;
+ }
+
+ return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
+}
+
+nsresult
+JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection)
+{
+ // We assert here because adding rtcp-mux to a non-disabled m-section that
+ // did not already have rtcp-mux can cause problems.
+ MOZ_ASSERT(mSdpHelper.MsectionIsDisabled(*msection));
+
+ 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 there.
+ if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
+ // Set RTCP-MUX.
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ }
+
+ nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetRecvonlySsrc(msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddExtmap(msection);
+
+ std::ostringstream osMid;
+ osMid << "sdparta_" << msection->GetLevel();
+ AddMid(osMid.str(), msection);
+
+ return NS_OK;
+}
+
+mozilla::Sdp*
+JsepSessionImpl::GetParsedLocalDescription() const
+{
+ if (mPendingLocalDescription) {
+ return mPendingLocalDescription.get();
+ } else if (mCurrentLocalDescription) {
+ return mCurrentLocalDescription.get();
+ }
+
+ return nullptr;
+}
+
+mozilla::Sdp*
+JsepSessionImpl::GetParsedRemoteDescription() const
+{
+ if (mPendingRemoteDescription) {
+ return mPendingRemoteDescription.get();
+ } else if (mCurrentRemoteDescription) {
+ return mCurrentRemoteDescription.get();
+ }
+
+ return nullptr;
+}
+
+const Sdp*
+JsepSessionImpl::GetAnswer() const
+{
+ return mWasOffererLastTime ? mCurrentRemoteDescription.get()
+ : mCurrentLocalDescription.get();
+}
+
+nsresult
+JsepSessionImpl::Close()
+{
+ mLastError.clear();
+ SetState(kJsepStateClosed);
+ return NS_OK;
+}
+
+const std::string
+JsepSessionImpl::GetLastError() const
+{
+ return mLastError;
+}
+
+bool
+JsepSessionImpl::AllLocalTracksAreAssigned() const
+{
+ for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) {
+ if (!i->mAssignedMLine.isSome()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace mozilla