summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/src/jsep
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling/src/jsep')
-rw-r--r--media/webrtc/signaling/src/jsep/JsepCodecDescription.h780
-rw-r--r--media/webrtc/signaling/src/jsep/JsepSession.h243
-rw-r--r--media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp2497
-rw-r--r--media/webrtc/signaling/src/jsep/JsepSessionImpl.h352
-rw-r--r--media/webrtc/signaling/src/jsep/JsepTrack.cpp531
-rw-r--r--media/webrtc/signaling/src/jsep/JsepTrack.h292
-rw-r--r--media/webrtc/signaling/src/jsep/JsepTrackEncoding.h60
-rw-r--r--media/webrtc/signaling/src/jsep/JsepTransport.h116
8 files changed, 4871 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
new file mode 100644
index 000000000..6ae5c9380
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -0,0 +1,780 @@
+/* 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/. */
+
+#ifndef _JSEPCODECDESCRIPTION_H_
+#define _JSEPCODECDESCRIPTION_H_
+
+#include <string>
+#include "signaling/src/sdp/SdpMediaSection.h"
+#include "signaling/src/sdp/SdpHelper.h"
+#include "nsCRT.h"
+
+namespace mozilla {
+
+#define JSEP_CODEC_CLONE(T) \
+ virtual JsepCodecDescription* Clone() const override \
+ { \
+ return new T(*this); \
+ }
+
+// A single entry in our list of known codecs.
+class JsepCodecDescription {
+ public:
+ JsepCodecDescription(mozilla::SdpMediaSection::MediaType type,
+ const std::string& defaultPt,
+ const std::string& name,
+ uint32_t clock,
+ uint32_t channels,
+ bool enabled)
+ : mType(type),
+ mDefaultPt(defaultPt),
+ mName(name),
+ mClock(clock),
+ mChannels(channels),
+ mEnabled(enabled),
+ mStronglyPreferred(false),
+ mDirection(sdp::kSend)
+ {
+ }
+ virtual ~JsepCodecDescription() {}
+
+ virtual JsepCodecDescription* Clone() const = 0;
+
+ bool
+ GetPtAsInt(uint16_t* ptOutparam) const
+ {
+ return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
+ }
+
+ virtual bool
+ Matches(const std::string& fmt, const SdpMediaSection& remoteMsection) const
+ {
+ // note: fmt here is remote fmt (to go with remoteMsection)
+ if (mType != remoteMsection.GetMediaType()) {
+ return false;
+ }
+
+ const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt));
+
+ if (entry) {
+ if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())
+ && (mClock == entry->clock)
+ && (mChannels == entry->channels)) {
+ return ParametersMatch(fmt, remoteMsection);
+ }
+ } else if (!fmt.compare("9") && mName == "G722") {
+ return true;
+ } else if (!fmt.compare("0") && mName == "PCMU") {
+ return true;
+ } else if (!fmt.compare("8") && mName == "PCMA") {
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool
+ ParametersMatch(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const
+ {
+ return true;
+ }
+
+ virtual bool
+ Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection)
+ {
+ mDefaultPt = pt;
+ return true;
+ }
+
+ virtual void
+ AddToMediaSection(SdpMediaSection& msection) const
+ {
+ if (mEnabled && msection.GetMediaType() == mType) {
+ // Both send and recv codec will have the same pt, so don't add twice
+ if (!msection.HasFormat(mDefaultPt)) {
+ if (mType == SdpMediaSection::kApplication) {
+ // Hack: using mChannels for number of streams
+ msection.AddDataChannel(mDefaultPt, mName, mChannels);
+ } else {
+ msection.AddCodec(mDefaultPt, mName, mClock, mChannels);
+ }
+ }
+
+ AddParametersToMSection(msection);
+ }
+ }
+
+ virtual void AddParametersToMSection(SdpMediaSection& msection) const {}
+
+ mozilla::SdpMediaSection::MediaType mType;
+ std::string mDefaultPt;
+ std::string mName;
+ uint32_t mClock;
+ uint32_t mChannels;
+ bool mEnabled;
+ bool mStronglyPreferred;
+ sdp::Direction mDirection;
+ // Will hold constraints from both fmtp and rid
+ EncodingConstraints mConstraints;
+};
+
+class JsepAudioCodecDescription : public JsepCodecDescription {
+ public:
+ JsepAudioCodecDescription(const std::string& defaultPt,
+ const std::string& name,
+ uint32_t clock,
+ uint32_t channels,
+ uint32_t packetSize,
+ uint32_t bitRate,
+ bool enabled = true)
+ : JsepCodecDescription(mozilla::SdpMediaSection::kAudio, defaultPt, name,
+ clock, channels, enabled),
+ mPacketSize(packetSize),
+ mBitrate(bitRate),
+ mMaxPlaybackRate(0),
+ mForceMono(false),
+ mFECEnabled(false),
+ mDtmfEnabled(false)
+ {
+ }
+
+ JSEP_CODEC_CLONE(JsepAudioCodecDescription)
+
+ SdpFmtpAttributeList::OpusParameters
+ GetOpusParameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::OpusParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) {
+ result =
+ static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::TelephoneEventParameters
+ GetTelephoneEventParameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::TelephoneEventParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) {
+ result =
+ static_cast<const SdpFmtpAttributeList::TelephoneEventParameters&>
+ (*params);
+ }
+
+ return result;
+ }
+
+ void
+ AddParametersToMSection(SdpMediaSection& msection) const override
+ {
+ if (mDirection == sdp::kSend) {
+ return;
+ }
+
+ if (mName == "opus") {
+ SdpFmtpAttributeList::OpusParameters opusParams(
+ GetOpusParameters(mDefaultPt, msection));
+ if (mMaxPlaybackRate) {
+ opusParams.maxplaybackrate = mMaxPlaybackRate;
+ }
+ if (mChannels == 2 && !mForceMono) {
+ // We prefer to receive stereo, if available.
+ opusParams.stereo = 1;
+ }
+ opusParams.useInBandFec = mFECEnabled ? 1 : 0;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, opusParams));
+ } else if (mName == "telephone-event") {
+ // add the default dtmf tones
+ SdpFmtpAttributeList::TelephoneEventParameters teParams(
+ GetTelephoneEventParameters(mDefaultPt, msection));
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams));
+ }
+ }
+
+ bool
+ Negotiate(const std::string& pt,
+ const SdpMediaSection& remoteMsection) override
+ {
+ JsepCodecDescription::Negotiate(pt, remoteMsection);
+ if (mName == "opus" && mDirection == sdp::kSend) {
+ SdpFmtpAttributeList::OpusParameters opusParams(
+ GetOpusParameters(mDefaultPt, remoteMsection));
+
+ mMaxPlaybackRate = opusParams.maxplaybackrate;
+ mForceMono = !opusParams.stereo;
+ // draft-ietf-rtcweb-fec-03.txt section 4.2 says support for FEC
+ // at the received side is declarative and can be negotiated
+ // separately for either media direction.
+ mFECEnabled = opusParams.useInBandFec;
+ }
+
+ return true;
+ }
+
+ uint32_t mPacketSize;
+ uint32_t mBitrate;
+ uint32_t mMaxPlaybackRate;
+ bool mForceMono;
+ bool mFECEnabled;
+ bool mDtmfEnabled;
+};
+
+class JsepVideoCodecDescription : public JsepCodecDescription {
+ public:
+ JsepVideoCodecDescription(const std::string& defaultPt,
+ const std::string& name,
+ uint32_t clock,
+ bool enabled = true)
+ : JsepCodecDescription(mozilla::SdpMediaSection::kVideo, defaultPt, name,
+ clock, 0, enabled),
+ mTmmbrEnabled(false),
+ mRembEnabled(false),
+ mFECEnabled(false),
+ mPacketizationMode(0)
+ {
+ // Add supported rtcp-fb types
+ mNackFbTypes.push_back("");
+ mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
+ mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
+ }
+
+ virtual void
+ EnableTmmbr() {
+ // EnableTmmbr can be called multiple times due to multiple calls to
+ // PeerConnectionImpl::ConfigureJsepSessionCodecs
+ if (!mTmmbrEnabled) {
+ mTmmbrEnabled = true;
+ mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr);
+ }
+ }
+
+ virtual void
+ EnableRemb() {
+ // EnableRemb can be called multiple times due to multiple calls to
+ // PeerConnectionImpl::ConfigureJsepSessionCodecs
+ if (!mRembEnabled) {
+ mRembEnabled = true;
+ mOtherFbTypes.push_back({ "", SdpRtcpFbAttributeList::kRemb, "", ""});
+ }
+ }
+
+ virtual void
+ EnableFec() {
+ // Enabling FEC for video works a little differently than enabling
+ // REMB or TMMBR. Support for FEC is indicated by the presence of
+ // particular codes (red and ulpfec) instead of using rtcpfb
+ // attributes on a given codec. There is no rtcpfb to push for FEC
+ // as can be seen above when REMB or TMMBR are enabled.
+ mFECEnabled = true;
+ }
+
+ void
+ AddParametersToMSection(SdpMediaSection& msection) const override
+ {
+ AddFmtpsToMSection(msection);
+ AddRtcpFbsToMSection(msection);
+ }
+
+ void
+ AddFmtpsToMSection(SdpMediaSection& msection) const
+ {
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params(
+ GetH264Parameters(mDefaultPt, msection));
+
+ if (mDirection == sdp::kSend) {
+ if (!h264Params.level_asymmetry_allowed) {
+ // First time the fmtp has been set; set just in case this is for a
+ // sendonly m-line, since even though we aren't receiving the level
+ // negotiation still needs to happen (sigh).
+ h264Params.profile_level_id = mProfileLevelId;
+ }
+ } else {
+ // Parameters that only apply to what we receive
+ h264Params.max_mbps = mConstraints.maxMbps;
+ h264Params.max_fs = mConstraints.maxFs;
+ h264Params.max_cpb = mConstraints.maxCpb;
+ h264Params.max_dpb = mConstraints.maxDpb;
+ h264Params.max_br = mConstraints.maxBr;
+ strncpy(h264Params.sprop_parameter_sets,
+ mSpropParameterSets.c_str(),
+ sizeof(h264Params.sprop_parameter_sets) - 1);
+ h264Params.profile_level_id = mProfileLevelId;
+ }
+
+ // Parameters that apply to both the send and recv directions
+ h264Params.packetization_mode = mPacketizationMode;
+ // Hard-coded, may need to change someday?
+ h264Params.level_asymmetry_allowed = true;
+
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params));
+ } else if (mName == "red") {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, msection));
+ redParams.encodings = mRedundantEncodings;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams));
+ } else if (mName == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kRecv) {
+ // VP8 and VP9 share the same SDP parameters thus far
+ SdpFmtpAttributeList::VP8Parameters vp8Params(
+ GetVP8Parameters(mDefaultPt, msection));
+
+ vp8Params.max_fs = mConstraints.maxFs;
+ vp8Params.max_fr = mConstraints.maxFps;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, vp8Params));
+ }
+ }
+ }
+
+ void
+ AddRtcpFbsToMSection(SdpMediaSection& msection) const
+ {
+ SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs());
+ for (const auto& rtcpfb : rtcpfbs.mFeedbacks) {
+ if (rtcpfb.pt == mDefaultPt) {
+ // Already set by the codec for the other direction.
+ return;
+ }
+ }
+
+ for (const std::string& type : mAckFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type);
+ }
+ for (const std::string& type : mNackFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type);
+ }
+ for (const std::string& type : mCcmFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type);
+ }
+ for (const auto& fb : mOtherFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra);
+ }
+
+ msection.SetRtcpFbs(rtcpfbs);
+ }
+
+ SdpFmtpAttributeList::H264Parameters
+ GetH264Parameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::H264Parameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kH264) {
+ result =
+ static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::RedParameters
+ GetRedParameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ SdpFmtpAttributeList::RedParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRed) {
+ result =
+ static_cast<const SdpFmtpAttributeList::RedParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::VP8Parameters
+ GetVP8Parameters(const std::string& pt,
+ const SdpMediaSection& msection) const
+ {
+ SdpRtpmapAttributeList::CodecType expectedType(
+ mName == "VP8" ?
+ SdpRtpmapAttributeList::kVP8 :
+ SdpRtpmapAttributeList::kVP9);
+
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::VP8Parameters result(expectedType);
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == expectedType) {
+ result =
+ static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*params);
+ }
+
+ return result;
+ }
+
+ void
+ NegotiateRtcpFb(const SdpMediaSection& remoteMsection,
+ SdpRtcpFbAttributeList::Type type,
+ std::vector<std::string>* supportedTypes)
+ {
+ std::vector<std::string> temp;
+ for (auto& subType : *supportedTypes) {
+ if (remoteMsection.HasRtcpFb(mDefaultPt, type, subType)) {
+ temp.push_back(subType);
+ }
+ }
+ *supportedTypes = temp;
+ }
+
+ void
+ NegotiateRtcpFb(const SdpMediaSection& remoteMsection,
+ std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) {
+ std::vector<SdpRtcpFbAttributeList::Feedback> temp;
+ for (auto& fb : *supportedFbs) {
+ if (remoteMsection.HasRtcpFb(mDefaultPt, fb.type, fb.parameter)) {
+ temp.push_back(fb);
+ }
+ }
+ *supportedFbs = temp;
+ }
+
+ void
+ NegotiateRtcpFb(const SdpMediaSection& remote)
+ {
+ // Removes rtcp-fb types that the other side doesn't support
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes);
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes);
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes);
+ NegotiateRtcpFb(remote, &mOtherFbTypes);
+ }
+
+ virtual bool
+ Negotiate(const std::string& pt,
+ const SdpMediaSection& remoteMsection) override
+ {
+ JsepCodecDescription::Negotiate(pt, remoteMsection);
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params(
+ GetH264Parameters(mDefaultPt, remoteMsection));
+
+ // Level is negotiated symmetrically if level asymmetry is disallowed
+ if (!h264Params.level_asymmetry_allowed) {
+ SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
+ GetSaneH264Level(mProfileLevelId)),
+ &mProfileLevelId);
+ }
+
+ if (mDirection == sdp::kSend) {
+ // Remote values of these apply only to the send codec.
+ mConstraints.maxFs = h264Params.max_fs;
+ mConstraints.maxMbps = h264Params.max_mbps;
+ mConstraints.maxCpb = h264Params.max_cpb;
+ mConstraints.maxDpb = h264Params.max_dpb;
+ mConstraints.maxBr = h264Params.max_br;
+ mSpropParameterSets = h264Params.sprop_parameter_sets;
+ // Only do this if we didn't symmetrically negotiate above
+ if (h264Params.level_asymmetry_allowed) {
+ SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id),
+ &mProfileLevelId);
+ }
+ } else {
+ // TODO(bug 1143709): max-recv-level support
+ }
+ } else if (mName == "red") {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, remoteMsection));
+ mRedundantEncodings = redParams.encodings;
+ } else if (mName == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kSend) {
+ SdpFmtpAttributeList::VP8Parameters vp8Params(
+ GetVP8Parameters(mDefaultPt, remoteMsection));
+
+ mConstraints.maxFs = vp8Params.max_fs;
+ mConstraints.maxFps = vp8Params.max_fr;
+ }
+ }
+
+ NegotiateRtcpFb(remoteMsection);
+ return true;
+ }
+
+ // Maps the not-so-sane encoding of H264 level into something that is
+ // ordered in the way one would expect
+ // 1b is 0xAB, everything else is the level left-shifted one half-byte
+ // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0)
+ static uint32_t
+ GetSaneH264Level(uint32_t profileLevelId)
+ {
+ uint32_t profileIdc = (profileLevelId >> 16);
+
+ if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) {
+ if ((profileLevelId & 0x10FF) == 0x100B) {
+ // Level 1b
+ return 0xAB;
+ }
+ }
+
+ uint32_t level = profileLevelId & 0xFF;
+
+ if (level == 0x09) {
+ // Another way to encode level 1b
+ return 0xAB;
+ }
+
+ return level << 4;
+ }
+
+ static void
+ SetSaneH264Level(uint32_t level, uint32_t* profileLevelId)
+ {
+ uint32_t profileIdc = (*profileLevelId >> 16);
+ uint32_t levelMask = 0xFF;
+
+ if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) {
+ levelMask = 0x10FF;
+ if (level == 0xAB) {
+ // Level 1b
+ level = 0x100B;
+ } else {
+ // Not 1b, just shift
+ level = level >> 4;
+ }
+ } else if (level == 0xAB) {
+ // Another way to encode 1b
+ level = 0x09;
+ } else {
+ // Not 1b, just shift
+ level = level >> 4;
+ }
+
+ *profileLevelId = (*profileLevelId & ~levelMask) | level;
+ }
+
+ enum Subprofile {
+ kH264ConstrainedBaseline,
+ kH264Baseline,
+ kH264Main,
+ kH264Extended,
+ kH264High,
+ kH264High10,
+ kH264High42,
+ kH264High44,
+ kH264High10I,
+ kH264High42I,
+ kH264High44I,
+ kH264CALVC44,
+ kH264UnknownSubprofile
+ };
+
+ static Subprofile
+ GetSubprofile(uint32_t profileLevelId)
+ {
+ // Based on Table 5 from RFC 6184:
+ // Profile profile_idc profile-iop
+ // (hexadecimal) (binary)
+
+ // CB 42 (B) x1xx0000
+ // same as: 4D (M) 1xxx0000
+ // same as: 58 (E) 11xx0000
+ // B 42 (B) x0xx0000
+ // same as: 58 (E) 10xx0000
+ // M 4D (M) 0x0x0000
+ // E 58 00xx0000
+ // H 64 00000000
+ // H10 6E 00000000
+ // H42 7A 00000000
+ // H44 F4 00000000
+ // H10I 6E 00010000
+ // H42I 7A 00010000
+ // H44I F4 00010000
+ // C44I 2C 00010000
+
+ if ((profileLevelId & 0xFF4F00) == 0x424000) {
+ // 01001111 (mask, 0x4F)
+ // x1xx0000 (from table)
+ // 01000000 (expected value, 0x40)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFF8F00) == 0x4D8000) {
+ // 10001111 (mask, 0x8F)
+ // 1xxx0000 (from table)
+ // 10000000 (expected value, 0x80)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFFCF00) == 0x58C000) {
+ // 11001111 (mask, 0xCF)
+ // 11xx0000 (from table)
+ // 11000000 (expected value, 0xC0)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFF4F00) == 0x420000) {
+ // 01001111 (mask, 0x4F)
+ // x0xx0000 (from table)
+ // 00000000 (expected value)
+ return kH264Baseline;
+ }
+
+ if ((profileLevelId & 0xFFCF00) == 0x588000) {
+ // 11001111 (mask, 0xCF)
+ // 10xx0000 (from table)
+ // 10000000 (expected value, 0x80)
+ return kH264Baseline;
+ }
+
+ if ((profileLevelId & 0xFFAF00) == 0x4D0000) {
+ // 10101111 (mask, 0xAF)
+ // 0x0x0000 (from table)
+ // 00000000 (expected value)
+ return kH264Main;
+ }
+
+ if ((profileLevelId & 0xFF0000) == 0x580000) {
+ // 11001111 (mask, 0xCF)
+ // 00xx0000 (from table)
+ // 00000000 (expected value)
+ return kH264Extended;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x640000) {
+ return kH264High;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x6E0000) {
+ return kH264High10;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x7A0000) {
+ return kH264High42;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0xF40000) {
+ return kH264High44;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x6E1000) {
+ return kH264High10I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x7A1000) {
+ return kH264High42I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0xF41000) {
+ return kH264High44I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x2C1000) {
+ return kH264CALVC44;
+ }
+
+ return kH264UnknownSubprofile;
+ }
+
+ virtual bool
+ ParametersMatch(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const override
+ {
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params(
+ GetH264Parameters(fmt, remoteMsection));
+
+ if (h264Params.packetization_mode != mPacketizationMode) {
+ return false;
+ }
+
+ if (GetSubprofile(h264Params.profile_level_id) !=
+ GetSubprofile(mProfileLevelId)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ virtual bool
+ RtcpFbRembIsSet() const
+ {
+ for (const auto& fb : mOtherFbTypes) {
+ if (fb.type == SdpRtcpFbAttributeList::kRemb) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ virtual void
+ UpdateRedundantEncodings(std::vector<JsepCodecDescription*> codecs)
+ {
+ for (const auto codec : codecs) {
+ if (codec->mType == SdpMediaSection::kVideo &&
+ codec->mEnabled &&
+ codec->mName != "red") {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // returns 0 if failed to convert, and since zero could
+ // be valid, check the defaultPt for 0
+ if (pt == 0 && codec->mDefaultPt != "0") {
+ continue;
+ }
+ mRedundantEncodings.push_back(pt);
+ }
+ }
+ }
+
+ JSEP_CODEC_CLONE(JsepVideoCodecDescription)
+
+ std::vector<std::string> mAckFbTypes;
+ std::vector<std::string> mNackFbTypes;
+ std::vector<std::string> mCcmFbTypes;
+ std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes;
+ bool mTmmbrEnabled;
+ bool mRembEnabled;
+ bool mFECEnabled;
+ std::vector<uint8_t> mRedundantEncodings;
+
+ // H264-specific stuff
+ uint32_t mProfileLevelId;
+ uint32_t mPacketizationMode;
+ std::string mSpropParameterSets;
+};
+
+class JsepApplicationCodecDescription : public JsepCodecDescription {
+ public:
+ JsepApplicationCodecDescription(const std::string& defaultPt,
+ const std::string& name,
+ uint16_t channels,
+ bool enabled = true)
+ : JsepCodecDescription(mozilla::SdpMediaSection::kApplication, defaultPt,
+ name, 0, channels, enabled)
+ {
+ }
+
+ JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
+
+ // Override, uses sctpmap instead of rtpmap
+ virtual bool
+ Matches(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const override
+ {
+ if (mType != remoteMsection.GetMediaType()) {
+ return false;
+ }
+
+ const SdpSctpmapAttributeList::Sctpmap* entry(
+ remoteMsection.FindSctpmap(fmt));
+
+ if (entry && !nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())) {
+ return true;
+ }
+ return false;
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/jsep/JsepSession.h b/media/webrtc/signaling/src/jsep/JsepSession.h
new file mode 100644
index 000000000..29bcbde05
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -0,0 +1,243 @@
+/* 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/. */
+
+#ifndef _JSEPSESSION_H_
+#define _JSEPSESSION_H_
+
+#include <string>
+#include <vector>
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsError.h"
+
+#include "signaling/src/jsep/JsepTransport.h"
+#include "signaling/src/sdp/Sdp.h"
+
+#include "JsepTrack.h"
+
+namespace mozilla {
+
+// Forward declarations
+class JsepCodecDescription;
+class JsepTrack;
+
+enum JsepSignalingState {
+ kJsepStateStable,
+ kJsepStateHaveLocalOffer,
+ kJsepStateHaveRemoteOffer,
+ kJsepStateHaveLocalPranswer,
+ kJsepStateHaveRemotePranswer,
+ kJsepStateClosed
+};
+
+enum JsepSdpType {
+ kJsepSdpOffer,
+ kJsepSdpAnswer,
+ kJsepSdpPranswer,
+ kJsepSdpRollback
+};
+
+struct JsepOAOptions {};
+struct JsepOfferOptions : public JsepOAOptions {
+ Maybe<size_t> mOfferToReceiveAudio;
+ Maybe<size_t> mOfferToReceiveVideo;
+ Maybe<bool> mDontOfferDataChannel;
+ Maybe<bool> mIceRestart; // currently ignored by JsepSession
+};
+struct JsepAnswerOptions : public JsepOAOptions {};
+
+enum JsepBundlePolicy {
+ kBundleBalanced,
+ kBundleMaxCompat,
+ kBundleMaxBundle
+};
+
+class JsepSession
+{
+public:
+ explicit JsepSession(const std::string& name)
+ : mName(name), mState(kJsepStateStable), mNegotiations(0)
+ {
+ }
+ virtual ~JsepSession() {}
+
+ virtual nsresult Init() = 0;
+
+ // Accessors for basic properties.
+ virtual const std::string&
+ GetName() const
+ {
+ return mName;
+ }
+ virtual JsepSignalingState
+ GetState() const
+ {
+ return mState;
+ }
+ virtual uint32_t
+ GetNegotiations() const
+ {
+ return mNegotiations;
+ }
+
+ // Set up the ICE And DTLS data.
+ virtual nsresult SetIceCredentials(const std::string& ufrag,
+ const std::string& pwd) = 0;
+ virtual const std::string& GetUfrag() const = 0;
+ virtual const std::string& GetPwd() const = 0;
+ virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0;
+ virtual bool RemoteIsIceLite() const = 0;
+ virtual bool RemoteIceIsRestarting() const = 0;
+ virtual std::vector<std::string> GetIceOptions() const = 0;
+
+ virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
+ const std::vector<uint8_t>& value) = 0;
+
+ virtual nsresult AddAudioRtpExtension(const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddVideoRtpExtension(const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+
+ // Kinda gross to be locking down the data structure type like this, but
+ // returning by value is problematic due to the lack of stl move semantics in
+ // our build config, since we can't use UniquePtr in the container. The
+ // alternative is writing a raft of accessor functions that allow arbitrary
+ // manipulation (which will be unwieldy), or allowing functors to be injected
+ // that manipulate the data structure (still pretty unwieldy).
+ virtual std::vector<JsepCodecDescription*>& Codecs() = 0;
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction& function)
+ {
+ std::for_each(Codecs().begin(), Codecs().end(), function);
+ for (RefPtr<JsepTrack>& track : GetLocalTracks()) {
+ track->ForEachCodec(function);
+ }
+ for (RefPtr<JsepTrack>& track : GetRemoteTracks()) {
+ track->ForEachCodec(function);
+ }
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate& sorter)
+ {
+ std::stable_sort(Codecs().begin(), Codecs().end(), sorter);
+ for (RefPtr<JsepTrack>& track : GetLocalTracks()) {
+ track->SortCodecs(sorter);
+ }
+ for (RefPtr<JsepTrack>& track : GetRemoteTracks()) {
+ track->SortCodecs(sorter);
+ }
+ }
+
+ // Manage tracks. We take shared ownership of any track.
+ virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) = 0;
+ virtual nsresult RemoveTrack(const std::string& streamId,
+ const std::string& trackId) = 0;
+ virtual nsresult ReplaceTrack(const std::string& oldStreamId,
+ const std::string& oldTrackId,
+ const std::string& newStreamId,
+ const std::string& newTrackId) = 0;
+ virtual nsresult SetParameters(
+ const std::string& streamId,
+ const std::string& trackId,
+ const std::vector<JsepTrack::JsConstraints>& constraints) = 0;
+
+ virtual nsresult GetParameters(
+ const std::string& streamId,
+ const std::string& trackId,
+ std::vector<JsepTrack::JsConstraints>* outConstraints) = 0;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const = 0;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const = 0;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksAdded() const = 0;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksRemoved() const = 0;
+
+ // Access the negotiated track pairs.
+ virtual std::vector<JsepTrackPair> GetNegotiatedTrackPairs() const = 0;
+
+ // Access transports.
+ virtual std::vector<RefPtr<JsepTransport>> GetTransports() const = 0;
+
+ // Basic JSEP operations.
+ virtual nsresult CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) = 0;
+ virtual nsresult CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) = 0;
+ virtual std::string GetLocalDescription() const = 0;
+ virtual std::string GetRemoteDescription() const = 0;
+ virtual nsresult SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual nsresult SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual nsresult AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ uint16_t level) = 0;
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ uint16_t level,
+ std::string* mid,
+ bool* skipped) = 0;
+ virtual nsresult UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr,
+ uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ uint16_t level) = 0;
+ virtual nsresult EndOfLocalCandidates(uint16_t level) = 0;
+ virtual nsresult Close() = 0;
+
+ // ICE controlling or controlled
+ virtual bool IsIceControlling() const = 0;
+
+ virtual const std::string
+ GetLastError() const
+ {
+ return "Error";
+ }
+
+ static const char*
+ GetStateStr(JsepSignalingState state)
+ {
+ static const char* states[] = { "stable", "have-local-offer",
+ "have-remote-offer", "have-local-pranswer",
+ "have-remote-pranswer", "closed" };
+
+ return states[state];
+ }
+
+ virtual bool AllLocalTracksAreAssigned() const = 0;
+
+ void
+ CountTracks(uint16_t (&receiving)[SdpMediaSection::kMediaTypes],
+ uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const
+ {
+ auto trackPairs = GetNegotiatedTrackPairs();
+
+ memset(receiving, 0, sizeof(receiving));
+ memset(sending, 0, sizeof(sending));
+
+ for (auto& pair : trackPairs) {
+ if (pair.mReceiving) {
+ receiving[pair.mReceiving->GetMediaType()]++;
+ }
+
+ if (pair.mSending) {
+ sending[pair.mSending->GetMediaType()]++;
+ }
+ }
+ }
+
+protected:
+ const std::string mName;
+ JsepSignalingState mState;
+ uint32_t mNegotiations;
+};
+
+} // namespace mozilla
+
+#endif
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
diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
new file mode 100644
index 000000000..00c07d25e
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -0,0 +1,352 @@
+/* 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/. */
+
+#ifndef _JSEPSESSIONIMPL_H_
+#define _JSEPSESSIONIMPL_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "signaling/src/jsep/JsepCodecDescription.h"
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepSession.h"
+#include "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/sdp/SipccSdpParser.h"
+#include "signaling/src/sdp/SdpHelper.h"
+#include "signaling/src/common/PtrVector.h"
+
+namespace mozilla {
+
+class JsepUuidGenerator
+{
+public:
+ virtual ~JsepUuidGenerator() {}
+ virtual bool Generate(std::string* id) = 0;
+};
+
+class JsepSessionImpl : public JsepSession
+{
+public:
+ JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
+ : JsepSession(name),
+ mIsOfferer(false),
+ mWasOffererLastTime(false),
+ mIceControlling(false),
+ mRemoteIsIceLite(false),
+ mRemoteIceIsRestarting(false),
+ mBundlePolicy(kBundleBalanced),
+ mSessionId(0),
+ mSessionVersion(0),
+ mUuidGen(Move(uuidgen)),
+ mSdpHelper(&mLastError)
+ {
+ }
+
+ // Implement JsepSession methods.
+ virtual nsresult Init() override;
+
+ virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) override;
+
+ virtual nsresult RemoveTrack(const std::string& streamId,
+ const std::string& trackId) override;
+
+ virtual nsresult SetIceCredentials(const std::string& ufrag,
+ const std::string& pwd) override;
+ virtual const std::string& GetUfrag() const override { return mIceUfrag; }
+ virtual const std::string& GetPwd() const override { return mIcePwd; }
+ nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
+
+ virtual bool
+ RemoteIsIceLite() const override
+ {
+ return mRemoteIsIceLite;
+ }
+
+ virtual bool
+ RemoteIceIsRestarting() const override
+ {
+ return mRemoteIceIsRestarting;
+ }
+
+ virtual std::vector<std::string>
+ GetIceOptions() const override
+ {
+ return mIceOptions;
+ }
+
+ virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
+ const std::vector<uint8_t>& value) override;
+
+ nsresult AddRtpExtension(std::vector<SdpExtmapAttributeList::Extmap>& extensions,
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction);
+ virtual nsresult AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual nsresult AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual std::vector<JsepCodecDescription*>&
+ Codecs() override
+ {
+ return mSupportedCodecs.values;
+ }
+
+ virtual nsresult ReplaceTrack(const std::string& oldStreamId,
+ const std::string& oldTrackId,
+ const std::string& newStreamId,
+ const std::string& newTrackId) override;
+
+ virtual nsresult SetParameters(
+ const std::string& streamId,
+ const std::string& trackId,
+ const std::vector<JsepTrack::JsConstraints>& constraints) override;
+
+ virtual nsresult GetParameters(
+ const std::string& streamId,
+ const std::string& trackId,
+ std::vector<JsepTrack::JsConstraints>* outConstraints) override;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const override;
+
+ virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const override;
+
+ virtual std::vector<RefPtr<JsepTrack>>
+ GetRemoteTracksAdded() const override;
+
+ virtual std::vector<RefPtr<JsepTrack>>
+ GetRemoteTracksRemoved() const override;
+
+ virtual nsresult CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) override;
+
+ virtual nsresult CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) override;
+
+ virtual std::string GetLocalDescription() const override;
+
+ virtual std::string GetRemoteDescription() const override;
+
+ virtual nsresult SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual nsresult SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual nsresult AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ uint16_t level) override;
+
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ uint16_t level,
+ std::string* mid,
+ bool* skipped) override;
+
+ virtual nsresult UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr,
+ uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ uint16_t level) override;
+
+ virtual nsresult EndOfLocalCandidates(uint16_t level) override;
+
+ virtual nsresult Close() override;
+
+ virtual const std::string GetLastError() const override;
+
+ virtual bool
+ IsIceControlling() const override
+ {
+ return mIceControlling;
+ }
+
+ virtual bool
+ IsOfferer() const
+ {
+ return mIsOfferer;
+ }
+
+ // Access transports.
+ virtual std::vector<RefPtr<JsepTransport>>
+ GetTransports() const override
+ {
+ return mTransports;
+ }
+
+ virtual std::vector<JsepTrackPair>
+ GetNegotiatedTrackPairs() const override
+ {
+ return mNegotiatedTrackPairs;
+ }
+
+ virtual bool AllLocalTracksAreAssigned() const override;
+
+private:
+ struct JsepDtlsFingerprint {
+ std::string mAlgorithm;
+ std::vector<uint8_t> mValue;
+ };
+
+ struct JsepSendingTrack {
+ RefPtr<JsepTrack> mTrack;
+ Maybe<size_t> mAssignedMLine;
+ };
+
+ struct JsepReceivingTrack {
+ RefPtr<JsepTrack> mTrack;
+ Maybe<size_t> mAssignedMLine;
+ };
+
+ // Non-const so it can set mLastError
+ nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
+ void AddExtmap(SdpMediaSection* msection) const;
+ void AddMid(const std::string& mid, SdpMediaSection* msection) const;
+ const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions(
+ SdpMediaSection::MediaType type) const;
+
+ void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection);
+ nsresult SetupIds();
+ nsresult CreateSsrc(uint32_t* ssrc);
+ void SetupDefaultCodecs();
+ void SetupDefaultRtpExtensions();
+ void SetState(JsepSignalingState state);
+ // Non-const so it can set mLastError
+ nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp);
+ nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer);
+ nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
+ nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer);
+ nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
+ nsresult ValidateLocalDescription(const Sdp& description);
+ nsresult ValidateRemoteDescription(const Sdp& description);
+ nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer);
+ nsresult SetRemoteTracksFromDescription(const Sdp* remoteDescription);
+ // Non-const because we use our Uuid generator
+ nsresult CreateReceivingTrack(size_t mline,
+ const Sdp& sdp,
+ const SdpMediaSection& msection,
+ RefPtr<JsepTrack>* track);
+ nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
+ const UniquePtr<Sdp>& remote);
+ nsresult AddTransportAttributes(SdpMediaSection* msection,
+ SdpSetupAttribute::Role dtlsRole);
+ nsresult CopyPreviousTransportParams(const Sdp& oldAnswer,
+ const Sdp& offerersPreviousSdp,
+ const Sdp& newOffer,
+ Sdp* newLocal);
+ nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp);
+ // Non-const so it can assign m-line index to tracks
+ nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type,
+ Maybe<size_t> offerToReceive,
+ Sdp* sdp);
+ nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype,
+ Sdp* sdp);
+ nsresult BindRemoteTracks(SdpMediaSection::MediaType mediatype,
+ Sdp* sdp,
+ size_t* offerToReceive);
+ nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype,
+ Sdp* sdp,
+ size_t* offerToRecv);
+ void SetupOfferToReceiveMsection(SdpMediaSection* offer);
+ nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype,
+ size_t count,
+ Sdp* sdp);
+ nsresult AddReofferMsections(const Sdp& oldLocalSdp,
+ const Sdp& oldAnswer,
+ Sdp* newSdp);
+ void SetupBundle(Sdp* sdp) const;
+ nsresult GetRemoteIds(const Sdp& sdp,
+ const SdpMediaSection& msection,
+ std::string* streamId,
+ std::string* trackId);
+ nsresult CreateOfferMSection(SdpMediaSection::MediaType type,
+ SdpMediaSection::Protocol proto,
+ SdpDirectionAttribute::Direction direction,
+ Sdp* sdp);
+ nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type,
+ Sdp* sdp,
+ SdpMediaSection** msection);
+ nsresult CreateAnswerMSection(const JsepAnswerOptions& options,
+ size_t mlineIndex,
+ const SdpMediaSection& remoteMsection,
+ Sdp* sdp);
+ nsresult SetRecvonlySsrc(SdpMediaSection* msection);
+ nsresult BindMatchingLocalTrackToAnswer(SdpMediaSection* msection);
+ nsresult BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection);
+ nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
+ SdpSetupAttribute::Role* rolep);
+ nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ const RefPtr<JsepTransport>& transport,
+ bool usingBundle,
+ size_t transportLevel,
+ JsepTrackPair* trackPairOut);
+ void InitTransport(const SdpMediaSection& msection, JsepTransport* transport);
+
+ nsresult FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ const RefPtr<JsepTransport>& transport);
+
+ nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids);
+
+ nsresult EnableOfferMsection(SdpMediaSection* msection);
+
+ mozilla::Sdp* GetParsedLocalDescription() const;
+ mozilla::Sdp* GetParsedRemoteDescription() const;
+ const Sdp* GetAnswer() const;
+
+ std::vector<JsepSendingTrack> mLocalTracks;
+ std::vector<JsepReceivingTrack> mRemoteTracks;
+ // By the most recent SetRemoteDescription
+ std::vector<JsepReceivingTrack> mRemoteTracksAdded;
+ std::vector<JsepReceivingTrack> mRemoteTracksRemoved;
+ std::vector<RefPtr<JsepTransport> > mTransports;
+ // So we can rollback
+ std::vector<RefPtr<JsepTransport> > mOldTransports;
+ std::vector<JsepTrackPair> mNegotiatedTrackPairs;
+
+ bool mIsOfferer;
+ bool mWasOffererLastTime;
+ bool mIceControlling;
+ std::string mIceUfrag;
+ std::string mIcePwd;
+ bool mRemoteIsIceLite;
+ bool mRemoteIceIsRestarting;
+ std::vector<std::string> mIceOptions;
+ JsepBundlePolicy mBundlePolicy;
+ std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
+ uint64_t mSessionId;
+ uint64_t mSessionVersion;
+ std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions;
+ std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions;
+ UniquePtr<JsepUuidGenerator> mUuidGen;
+ std::string mDefaultRemoteStreamId;
+ std::map<size_t, std::string> mDefaultRemoteTrackIdsByLevel;
+ std::string mCNAME;
+ // Used to prevent duplicate local SSRCs. Not used to prevent local/remote or
+ // remote-only duplication, which will be important for EKT but not now.
+ std::set<uint32_t> mSsrcs;
+ // When an m-section doesn't have a local track, it still needs an ssrc, which
+ // is stored here.
+ std::vector<uint32_t> mRecvonlySsrcs;
+ UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set.
+ UniquePtr<Sdp> mCurrentLocalDescription;
+ UniquePtr<Sdp> mCurrentRemoteDescription;
+ UniquePtr<Sdp> mPendingLocalDescription;
+ UniquePtr<Sdp> mPendingRemoteDescription;
+ PtrVector<JsepCodecDescription> mSupportedCodecs;
+ std::string mLastError;
+ SipccSdpParser mParser;
+ SdpHelper mSdpHelper;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.cpp b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
new file mode 100644
index 000000000..1b045d8ec
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -0,0 +1,531 @@
+/* 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 "signaling/src/jsep/JsepTrack.h"
+#include "signaling/src/jsep/JsepCodecDescription.h"
+#include "signaling/src/jsep/JsepTrackEncoding.h"
+
+#include <algorithm>
+
+namespace mozilla
+{
+void
+JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes)
+{
+ if (!mNegotiatedDetails) {
+ return;
+ }
+
+ for (const auto* encoding : mNegotiatedDetails->mEncodings.values) {
+ GetPayloadTypes(encoding->GetCodecs(), payloadTypes);
+ }
+
+ // Prune out dupes
+ std::sort(payloadTypes->begin(), payloadTypes->end());
+ auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end());
+ payloadTypes->erase(newEnd, payloadTypes->end());
+}
+
+/* static */
+void
+JsepTrack::GetPayloadTypes(
+ const std::vector<JsepCodecDescription*>& codecs,
+ std::vector<uint16_t>* payloadTypes)
+{
+ for (JsepCodecDescription* codec : codecs) {
+ uint16_t pt;
+ if (!codec->GetPtAsInt(&pt)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+ payloadTypes->push_back(pt);
+ }
+}
+
+void
+JsepTrack::EnsureNoDuplicatePayloadTypes(
+ std::vector<JsepCodecDescription*>* codecs)
+{
+ std::set<uint16_t> uniquePayloadTypes;
+
+ for (JsepCodecDescription* codec : *codecs) {
+ // We assume there are no dupes in negotiated codecs; unnegotiated codecs
+ // need to change if there is a clash.
+ if (!codec->mEnabled) {
+ continue;
+ }
+
+ // Disable, and only re-enable if we can ensure it has a unique pt.
+ codec->mEnabled = false;
+
+ uint16_t currentPt;
+ if (!codec->GetPtAsInt(&currentPt)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+
+ if (!uniquePayloadTypes.count(currentPt)) {
+ codec->mEnabled = true;
+ uniquePayloadTypes.insert(currentPt);
+ continue;
+ }
+
+ // |codec| cannot use its current payload type. Try to find another.
+ for (uint16_t freePt = 96; freePt <= 127; ++freePt) {
+ // Not super efficient, but readability is probably more important.
+ if (!uniquePayloadTypes.count(freePt)) {
+ uniquePayloadTypes.insert(freePt);
+ codec->mEnabled = true;
+ std::ostringstream os;
+ os << freePt;
+ codec->mDefaultPt = os.str();
+ break;
+ }
+ }
+ }
+}
+
+void
+JsepTrack::PopulateCodecs(const std::vector<JsepCodecDescription*>& prototype)
+{
+ for (const JsepCodecDescription* prototypeCodec : prototype) {
+ if (prototypeCodec->mType == mType) {
+ mPrototypeCodecs.values.push_back(prototypeCodec->Clone());
+ mPrototypeCodecs.values.back()->mDirection = mDirection;
+ }
+ }
+
+ EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
+}
+
+void
+JsepTrack::AddToOffer(SdpMediaSection* offer) const
+{
+ AddToMsection(mPrototypeCodecs.values, offer);
+ if (mDirection == sdp::kSend) {
+ AddToMsection(mJsEncodeConstraints, sdp::kSend, offer);
+ }
+}
+
+void
+JsepTrack::AddToAnswer(const SdpMediaSection& offer,
+ SdpMediaSection* answer) const
+{
+ // We do not modify mPrototypeCodecs here, since we're only creating an
+ // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
+ PtrVector<JsepCodecDescription> codecs;
+ codecs.values = GetCodecClones();
+ NegotiateCodecs(offer, &codecs.values);
+ if (codecs.values.empty()) {
+ return;
+ }
+
+ AddToMsection(codecs.values, answer);
+
+ if (mDirection == sdp::kSend) {
+ std::vector<JsConstraints> constraints;
+ std::vector<SdpRidAttributeList::Rid> rids;
+ GetRids(offer, sdp::kRecv, &rids);
+ NegotiateRids(rids, &constraints);
+ AddToMsection(constraints, sdp::kSend, answer);
+ }
+}
+
+void
+JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
+ SdpMediaSection* msection) const
+{
+ MOZ_ASSERT(msection->GetMediaType() == mType);
+ MOZ_ASSERT(!codecs.empty());
+
+ for (const JsepCodecDescription* codec : codecs) {
+ codec->AddToMediaSection(*msection);
+ }
+
+ if (mDirection == sdp::kSend) {
+ if (msection->GetMediaType() != SdpMediaSection::kApplication) {
+ msection->SetSsrcs(mSsrcs, mCNAME);
+ msection->AddMsid(mStreamId, mTrackId);
+ }
+ msection->SetSending(true);
+ } else {
+ msection->SetReceiving(true);
+ }
+}
+
+// Updates the |id| values in |constraintsList| with the rid values in |rids|,
+// where necessary.
+void
+JsepTrack::NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
+ std::vector<JsConstraints>* constraintsList) const
+{
+ for (const SdpRidAttributeList::Rid& rid : rids) {
+ if (!FindConstraints(rid.id, *constraintsList)) {
+ // Pair up the first JsConstraints with an empty id, if it exists.
+ JsConstraints* constraints = FindConstraints("", *constraintsList);
+ if (constraints) {
+ constraints->rid = rid.id;
+ }
+ }
+ }
+}
+
+/* static */
+void
+JsepTrack::AddToMsection(const std::vector<JsConstraints>& constraintsList,
+ sdp::Direction direction,
+ SdpMediaSection* msection)
+{
+ UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
+ UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList);
+ for (const JsConstraints& constraints : constraintsList) {
+ if (!constraints.rid.empty()) {
+ SdpRidAttributeList::Rid rid;
+ rid.id = constraints.rid;
+ rid.direction = direction;
+ rids->mRids.push_back(rid);
+
+ SdpSimulcastAttribute::Version version;
+ version.choices.push_back(constraints.rid);
+ if (direction == sdp::kSend) {
+ simulcast->sendVersions.push_back(version);
+ } else {
+ simulcast->recvVersions.push_back(version);
+ }
+ }
+ }
+
+ if (!rids->mRids.empty()) {
+ msection->GetAttributeList().SetAttribute(simulcast.release());
+ msection->GetAttributeList().SetAttribute(rids.release());
+ }
+}
+
+void
+JsepTrack::GetRids(const SdpMediaSection& msection,
+ sdp::Direction direction,
+ std::vector<SdpRidAttributeList::Rid>* rids) const
+{
+ rids->clear();
+ if (!msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute)) {
+ return;
+ }
+
+ const SdpSimulcastAttribute& simulcast(
+ msection.GetAttributeList().GetSimulcast());
+
+ const SdpSimulcastAttribute::Versions* versions = nullptr;
+ switch (direction) {
+ case sdp::kSend:
+ versions = &simulcast.sendVersions;
+ break;
+ case sdp::kRecv:
+ versions = &simulcast.recvVersions;
+ break;
+ }
+
+ if (!versions->IsSet()) {
+ return;
+ }
+
+ if (versions->type != SdpSimulcastAttribute::Versions::kRid) {
+ // No support for PT-based simulcast, yet.
+ return;
+ }
+
+ for (const SdpSimulcastAttribute::Version& version : *versions) {
+ if (!version.choices.empty()) {
+ // We validate that rids are present (and sane) elsewhere.
+ rids->push_back(*msection.FindRid(version.choices[0]));
+ }
+ }
+}
+
+JsepTrack::JsConstraints*
+JsepTrack::FindConstraints(const std::string& id,
+ std::vector<JsConstraints>& constraintsList) const
+{
+ for (JsConstraints& constraints : constraintsList) {
+ if (constraints.rid == id) {
+ return &constraints;
+ }
+ }
+ return nullptr;
+}
+
+void
+JsepTrack::CreateEncodings(
+ const SdpMediaSection& remote,
+ const std::vector<JsepCodecDescription*>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* negotiatedDetails)
+{
+ std::vector<SdpRidAttributeList::Rid> rids;
+ GetRids(remote, sdp::kRecv, &rids); // Get rids we will send
+ NegotiateRids(rids, &mJsEncodeConstraints);
+ if (rids.empty()) {
+ // Add dummy value with an empty id to make sure we get a single unicast
+ // stream.
+ rids.push_back(SdpRidAttributeList::Rid());
+ }
+
+ // For each rid in the remote, make sure we have an encoding, and configure
+ // that encoding appropriately.
+ for (size_t i = 0; i < rids.size(); ++i) {
+ if (i == negotiatedDetails->mEncodings.values.size()) {
+ negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding);
+ }
+
+ JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i];
+
+ for (const JsepCodecDescription* codec : negotiatedCodecs) {
+ if (rids[i].HasFormat(codec->mDefaultPt)) {
+ encoding->AddCodec(*codec);
+ }
+ }
+
+ encoding->mRid = rids[i].id;
+ // If we end up supporting params for rid, we would handle that here.
+
+ // Incorporate the corresponding JS encoding constraints, if they exist
+ for (const JsConstraints& jsConstraints : mJsEncodeConstraints) {
+ if (jsConstraints.rid == rids[i].id) {
+ encoding->mConstraints = jsConstraints.constraints;
+ }
+ }
+
+ encoding->UpdateMaxBitrate(remote);
+ }
+}
+
+std::vector<JsepCodecDescription*>
+JsepTrack::GetCodecClones() const
+{
+ std::vector<JsepCodecDescription*> clones;
+ for (const JsepCodecDescription* codec : mPrototypeCodecs.values) {
+ clones.push_back(codec->Clone());
+ }
+ return clones;
+}
+
+static bool
+CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs)
+{
+ return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
+}
+
+void
+JsepTrack::NegotiateCodecs(
+ const SdpMediaSection& remote,
+ std::vector<JsepCodecDescription*>* codecs,
+ std::map<std::string, std::string>* formatChanges) const
+{
+ PtrVector<JsepCodecDescription> unnegotiatedCodecs;
+ std::swap(unnegotiatedCodecs.values, *codecs);
+
+ // Outer loop establishes the remote side's preference
+ for (const std::string& fmt : remote.GetFormats()) {
+ for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) {
+ JsepCodecDescription* codec = unnegotiatedCodecs.values[i];
+ if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
+ continue;
+ }
+
+ std::string originalFormat = codec->mDefaultPt;
+ if(codec->Negotiate(fmt, remote)) {
+ codecs->push_back(codec);
+ unnegotiatedCodecs.values[i] = nullptr;
+ if (formatChanges) {
+ (*formatChanges)[originalFormat] = codec->mDefaultPt;
+ }
+ break;
+ }
+ }
+ }
+
+ // Find the (potential) red codec and ulpfec codec or telephone-event
+ JsepVideoCodecDescription* red = nullptr;
+ JsepVideoCodecDescription* ulpfec = nullptr;
+ JsepAudioCodecDescription* dtmf = nullptr;
+ // We can safely cast here since JsepTrack has a MediaType and only codecs
+ // that match that MediaType (kAudio or kVideo) are added.
+ for (auto codec : *codecs) {
+ if (codec->mName == "red") {
+ red = static_cast<JsepVideoCodecDescription*>(codec);
+ }
+ else if (codec->mName == "ulpfec") {
+ ulpfec = static_cast<JsepVideoCodecDescription*>(codec);
+ }
+ else if (codec->mName == "telephone-event") {
+ dtmf = static_cast<JsepAudioCodecDescription*>(codec);
+ }
+ }
+ // if we have a red codec remove redundant encodings that don't exist
+ if (red) {
+ // Since we could have an externally specified redundant endcodings
+ // list, we shouldn't simply rebuild the redundant encodings list
+ // based on the current list of codecs.
+ std::vector<uint8_t> unnegotiatedEncodings;
+ std::swap(unnegotiatedEncodings, red->mRedundantEncodings);
+ for (auto redundantPt : unnegotiatedEncodings) {
+ std::string pt = std::to_string(redundantPt);
+ for (auto codec : *codecs) {
+ if (pt == codec->mDefaultPt) {
+ red->mRedundantEncodings.push_back(redundantPt);
+ break;
+ }
+ }
+ }
+ }
+ // Video FEC is indicated by the existence of the red and ulpfec
+ // codecs and not an attribute on the particular video codec (like in
+ // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
+ // on all the other codecs.
+ if (red && ulpfec) {
+ for (auto codec : *codecs) {
+ if (codec->mName != "red" && codec->mName != "ulpfec") {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec);
+ videoCodec->EnableFec();
+ }
+ }
+ }
+
+ // Dtmf support is indicated by the existence of the telephone-event
+ // codec, and not an attribute on the particular audio codec (like in a
+ // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf
+ // support on all the other audio codecs.
+ if (dtmf) {
+ for (auto codec : *codecs) {
+ JsepAudioCodecDescription* audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec);
+ audioCodec->mDtmfEnabled = true;
+ }
+ }
+
+ // Make sure strongly preferred codecs are up front, overriding the remote
+ // side's preference.
+ std::stable_sort(codecs->begin(), codecs->end(), CompareCodec);
+
+ // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an
+ // answer. For now, remove all but the first codec unless the red codec
+ // exists, and then we include the others per RFC 5109, section 14.2.
+ // Note: now allows keeping the telephone-event codec, if it appears, as the
+ // last codec in the list.
+ if (!codecs->empty() && !red) {
+ int newSize = dtmf ? 2 : 1;
+ for (size_t i = 1; i < codecs->size(); ++i) {
+ if (!dtmf || dtmf != (*codecs)[i]) {
+ delete (*codecs)[i];
+ (*codecs)[i] = nullptr;
+ }
+ }
+ if (dtmf) {
+ (*codecs)[newSize-1] = dtmf;
+ }
+ codecs->resize(newSize);
+ }
+}
+
+void
+JsepTrack::Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote)
+{
+ PtrVector<JsepCodecDescription> negotiatedCodecs;
+ negotiatedCodecs.values = GetCodecClones();
+
+ std::map<std::string, std::string> formatChanges;
+ NegotiateCodecs(remote,
+ &negotiatedCodecs.values,
+ &formatChanges);
+
+ // Use |formatChanges| to update mPrototypeCodecs
+ size_t insertPos = 0;
+ for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) {
+ if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) {
+ // Update the payload type to what was negotiated
+ mPrototypeCodecs.values[i]->mDefaultPt =
+ formatChanges[mPrototypeCodecs.values[i]->mDefaultPt];
+ // Move this negotiated codec up front
+ std::swap(mPrototypeCodecs.values[insertPos],
+ mPrototypeCodecs.values[i]);
+ ++insertPos;
+ }
+ }
+
+ EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values);
+
+ UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
+ MakeUnique<JsepTrackNegotiatedDetails>();
+
+ CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get());
+
+ if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
+ negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
+ }
+ }
+
+ if (mDirection == sdp::kRecv) {
+ mSsrcs.clear();
+ if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) {
+ AddSsrc(ssrcAttr.ssrc);
+ }
+ }
+ }
+
+ mNegotiatedDetails = Move(negotiatedDetails);
+}
+
+// When doing bundle, if all else fails we can try to figure out which m-line a
+// given RTP packet belongs to by looking at the payload type field. This only
+// works, however, if that payload type appeared in only one m-section.
+// We figure that out here.
+/* static */
+void
+JsepTrack::SetUniquePayloadTypes(const std::vector<RefPtr<JsepTrack>>& tracks)
+{
+ // Maps to track details if no other track contains the payload type,
+ // otherwise maps to nullptr.
+ std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
+
+ for (const RefPtr<JsepTrack>& track : tracks) {
+ if (track->GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+
+ auto* details = track->GetNegotiatedDetails();
+ if (!details) {
+ // Can happen if negotiation fails on a track
+ continue;
+ }
+
+ std::vector<uint16_t> payloadTypesForTrack;
+ track->GetNegotiatedPayloadTypes(&payloadTypesForTrack);
+
+ for (uint16_t pt : payloadTypesForTrack) {
+ if (payloadTypeToDetailsMap.count(pt)) {
+ // Found in more than one track, not unique
+ payloadTypeToDetailsMap[pt] = nullptr;
+ } else {
+ payloadTypeToDetailsMap[pt] = details;
+ }
+ }
+ }
+
+ for (auto ptAndDetails : payloadTypeToDetailsMap) {
+ uint16_t uniquePt = ptAndDetails.first;
+ MOZ_ASSERT(uniquePt <= UINT8_MAX);
+ auto trackDetails = ptAndDetails.second;
+
+ if (trackDetails) {
+ trackDetails->mUniquePayloadTypes.push_back(
+ static_cast<uint8_t>(uniquePt));
+ }
+ }
+}
+
+} // namespace mozilla
+
diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.h b/media/webrtc/signaling/src/jsep/JsepTrack.h
new file mode 100644
index 000000000..5aa37404f
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -0,0 +1,292 @@
+/* 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/. */
+
+#ifndef _JSEPTRACK_H_
+#define _JSEPTRACK_H_
+
+#include <algorithm>
+#include <string>
+#include <map>
+#include <set>
+
+#include <mozilla/RefPtr.h>
+#include <mozilla/UniquePtr.h>
+#include <mozilla/Maybe.h>
+#include "nsISupportsImpl.h"
+#include "nsError.h"
+
+#include "signaling/src/jsep/JsepTransport.h"
+#include "signaling/src/jsep/JsepTrackEncoding.h"
+#include "signaling/src/sdp/Sdp.h"
+#include "signaling/src/sdp/SdpAttribute.h"
+#include "signaling/src/sdp/SdpMediaSection.h"
+#include "signaling/src/common/PtrVector.h"
+
+namespace mozilla {
+
+class JsepTrackNegotiatedDetails
+{
+public:
+ size_t
+ GetEncodingCount() const
+ {
+ return mEncodings.values.size();
+ }
+
+ const JsepTrackEncoding&
+ GetEncoding(size_t index) const
+ {
+ MOZ_RELEASE_ASSERT(index < mEncodings.values.size());
+ return *mEncodings.values[index];
+ }
+
+ const SdpExtmapAttributeList::Extmap*
+ GetExt(const std::string& ext_name) const
+ {
+ auto it = mExtmap.find(ext_name);
+ if (it != mExtmap.end()) {
+ return &it->second;
+ }
+ return nullptr;
+ }
+
+ std::vector<uint8_t> GetUniquePayloadTypes() const
+ {
+ return mUniquePayloadTypes;
+ }
+
+private:
+ friend class JsepTrack;
+
+ std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
+ std::vector<uint8_t> mUniquePayloadTypes;
+ PtrVector<JsepTrackEncoding> mEncodings;
+};
+
+class JsepTrack
+{
+public:
+ JsepTrack(mozilla::SdpMediaSection::MediaType type,
+ const std::string& streamid,
+ const std::string& trackid,
+ sdp::Direction direction = sdp::kSend)
+ : mType(type),
+ mStreamId(streamid),
+ mTrackId(trackid),
+ mDirection(direction),
+ mActive(false)
+ {}
+
+ virtual mozilla::SdpMediaSection::MediaType
+ GetMediaType() const
+ {
+ return mType;
+ }
+
+ virtual const std::string&
+ GetStreamId() const
+ {
+ return mStreamId;
+ }
+
+ virtual void
+ SetStreamId(const std::string& id)
+ {
+ mStreamId = id;
+ }
+
+ virtual const std::string&
+ GetTrackId() const
+ {
+ return mTrackId;
+ }
+
+ virtual void
+ SetTrackId(const std::string& id)
+ {
+ mTrackId = id;
+ }
+
+ virtual const std::string&
+ GetCNAME() const
+ {
+ return mCNAME;
+ }
+
+ virtual void
+ SetCNAME(const std::string& cname)
+ {
+ mCNAME = cname;
+ }
+
+ virtual sdp::Direction
+ GetDirection() const
+ {
+ return mDirection;
+ }
+
+ virtual const std::vector<uint32_t>&
+ GetSsrcs() const
+ {
+ return mSsrcs;
+ }
+
+ virtual void
+ AddSsrc(uint32_t ssrc)
+ {
+ mSsrcs.push_back(ssrc);
+ }
+
+ bool
+ GetActive() const
+ {
+ return mActive;
+ }
+
+ void
+ SetActive(bool active)
+ {
+ mActive = active;
+ }
+
+ virtual void PopulateCodecs(
+ const std::vector<JsepCodecDescription*>& prototype);
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction func)
+ {
+ std::for_each(mPrototypeCodecs.values.begin(),
+ mPrototypeCodecs.values.end(), func);
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate sorter)
+ {
+ std::stable_sort(mPrototypeCodecs.values.begin(),
+ mPrototypeCodecs.values.end(), sorter);
+ }
+
+ virtual void AddToOffer(SdpMediaSection* offer) const;
+ virtual void AddToAnswer(const SdpMediaSection& offer,
+ SdpMediaSection* answer) const;
+ virtual void Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote);
+ static void SetUniquePayloadTypes(
+ const std::vector<RefPtr<JsepTrack>>& tracks);
+ virtual void GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes);
+
+ // This will be set when negotiation is carried out.
+ virtual const JsepTrackNegotiatedDetails*
+ GetNegotiatedDetails() const
+ {
+ if (mNegotiatedDetails) {
+ return mNegotiatedDetails.get();
+ }
+ return nullptr;
+ }
+
+ virtual JsepTrackNegotiatedDetails*
+ GetNegotiatedDetails()
+ {
+ if (mNegotiatedDetails) {
+ return mNegotiatedDetails.get();
+ }
+ return nullptr;
+ }
+
+ virtual void
+ ClearNegotiatedDetails()
+ {
+ mNegotiatedDetails.reset();
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack);
+
+ struct JsConstraints
+ {
+ std::string rid;
+ EncodingConstraints constraints;
+ };
+
+ void SetJsConstraints(const std::vector<JsConstraints>& constraintsList)
+ {
+ mJsEncodeConstraints = constraintsList;
+ }
+
+ void GetJsConstraints(std::vector<JsConstraints>* outConstraintsList) const
+ {
+ MOZ_ASSERT(outConstraintsList);
+ *outConstraintsList = mJsEncodeConstraints;
+ }
+
+ static void AddToMsection(const std::vector<JsConstraints>& constraintsList,
+ sdp::Direction direction,
+ SdpMediaSection* msection);
+
+protected:
+ virtual ~JsepTrack() {}
+
+private:
+ std::vector<JsepCodecDescription*> GetCodecClones() const;
+ static void EnsureNoDuplicatePayloadTypes(
+ std::vector<JsepCodecDescription*>* codecs);
+ static void GetPayloadTypes(
+ const std::vector<JsepCodecDescription*>& codecs,
+ std::vector<uint16_t>* pts);
+ static void EnsurePayloadTypeIsUnique(std::set<uint16_t>* uniquePayloadTypes,
+ JsepCodecDescription* codec);
+ void AddToMsection(const std::vector<JsepCodecDescription*>& codecs,
+ SdpMediaSection* msection) const;
+ void GetRids(const SdpMediaSection& msection,
+ sdp::Direction direction,
+ std::vector<SdpRidAttributeList::Rid>* rids) const;
+ void CreateEncodings(
+ const SdpMediaSection& remote,
+ const std::vector<JsepCodecDescription*>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* details);
+
+ // |formatChanges| is set on completion of offer/answer, and records how the
+ // formats in |codecs| were changed, which is used by |Negotiate| to update
+ // |mPrototypeCodecs|.
+ virtual void NegotiateCodecs(
+ const SdpMediaSection& remote,
+ std::vector<JsepCodecDescription*>* codecs,
+ std::map<std::string, std::string>* formatChanges = nullptr) const;
+
+ JsConstraints* FindConstraints(
+ const std::string& rid,
+ std::vector<JsConstraints>& constraintsList) const;
+ void NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids,
+ std::vector<JsConstraints>* constraints) const;
+
+ const mozilla::SdpMediaSection::MediaType mType;
+ std::string mStreamId;
+ std::string mTrackId;
+ std::string mCNAME;
+ const sdp::Direction mDirection;
+ PtrVector<JsepCodecDescription> mPrototypeCodecs;
+ // Holds encoding params/constraints from JS. Simulcast happens when there are
+ // multiple of these. If there are none, we assume unconstrained unicast with
+ // no rid.
+ std::vector<JsConstraints> mJsEncodeConstraints;
+ UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
+ std::vector<uint32_t> mSsrcs;
+ bool mActive;
+};
+
+// Need a better name for this.
+struct JsepTrackPair {
+ size_t mLevel;
+ // Is this track pair sharing a transport with another?
+ Maybe<size_t> mBundleLevel;
+ uint32_t mRecvonlySsrc;
+ RefPtr<JsepTrack> mSending;
+ RefPtr<JsepTrack> mReceiving;
+ RefPtr<JsepTransport> mRtpTransport;
+ RefPtr<JsepTransport> mRtcpTransport;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h
new file mode 100644
index 000000000..b59b672e4
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef _JESPTRACKENCODING_H_
+#define _JESPTRACKENCODING_H_
+
+#include "signaling/src/jsep/JsepCodecDescription.h"
+#include "signaling/src/common/EncodingConstraints.h"
+#include "signaling/src/common/PtrVector.h"
+
+namespace mozilla {
+// Represents a single encoding of a media track. When simulcast is used, there
+// may be multiple. Each encoding may have some constraints (imposed by JS), and
+// may be able to use any one of multiple codecs (JsepCodecDescription) at any
+// given time.
+class JsepTrackEncoding
+{
+public:
+ const std::vector<JsepCodecDescription*>& GetCodecs() const
+ {
+ return mCodecs.values;
+ }
+
+ void AddCodec(const JsepCodecDescription& codec)
+ {
+ mCodecs.values.push_back(codec.Clone());
+ }
+
+ bool HasFormat(const std::string& format) const
+ {
+ for (const JsepCodecDescription* codec : mCodecs.values) {
+ if (codec->mDefaultPt == format) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void UpdateMaxBitrate(const SdpMediaSection& remote)
+ {
+ uint32_t tias = remote.GetBandwidth("TIAS");
+ // select minimum of the two which is not zero
+ mConstraints.maxBr = std::min(tias ? tias : mConstraints.maxBr,
+ mConstraints.maxBr ? mConstraints.maxBr :
+ tias);
+ // TODO add support for b=AS if TIAS is not set (bug 976521)
+ }
+
+ EncodingConstraints mConstraints;
+ std::string mRid;
+
+private:
+ PtrVector<JsepCodecDescription> mCodecs;
+};
+}
+
+#endif // _JESPTRACKENCODING_H_
diff --git a/media/webrtc/signaling/src/jsep/JsepTransport.h b/media/webrtc/signaling/src/jsep/JsepTransport.h
new file mode 100644
index 000000000..3b0d38ad6
--- /dev/null
+++ b/media/webrtc/signaling/src/jsep/JsepTransport.h
@@ -0,0 +1,116 @@
+/* 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/. */
+
+#ifndef _JSEPTRANSPORT_H_
+#define _JSEPTRANSPORT_H_
+
+#include <string>
+#include <vector>
+
+#include <mozilla/RefPtr.h>
+#include <mozilla/UniquePtr.h>
+#include "nsISupportsImpl.h"
+
+#include "signaling/src/sdp/SdpAttribute.h"
+
+namespace mozilla {
+
+class JsepDtlsTransport
+{
+public:
+ JsepDtlsTransport() : mRole(kJsepDtlsInvalidRole) {}
+
+ virtual ~JsepDtlsTransport() {}
+
+ enum Role {
+ kJsepDtlsClient,
+ kJsepDtlsServer,
+ kJsepDtlsInvalidRole
+ };
+
+ virtual const SdpFingerprintAttributeList&
+ GetFingerprints() const
+ {
+ return mFingerprints;
+ }
+
+ virtual Role
+ GetRole() const
+ {
+ return mRole;
+ }
+
+private:
+ friend class JsepSessionImpl;
+
+ SdpFingerprintAttributeList mFingerprints;
+ Role mRole;
+};
+
+class JsepIceTransport
+{
+public:
+ JsepIceTransport() {}
+
+ virtual ~JsepIceTransport() {}
+
+ const std::string&
+ GetUfrag() const
+ {
+ return mUfrag;
+ }
+ const std::string&
+ GetPassword() const
+ {
+ return mPwd;
+ }
+ const std::vector<std::string>&
+ GetCandidates() const
+ {
+ return mCandidates;
+ }
+
+private:
+ friend class JsepSessionImpl;
+
+ std::string mUfrag;
+ std::string mPwd;
+ std::vector<std::string> mCandidates;
+};
+
+class JsepTransport
+{
+public:
+ JsepTransport()
+ : mComponents(0)
+ {
+ }
+
+ void Close()
+ {
+ mComponents = 0;
+ mTransportId.clear();
+ mIce.reset();
+ mDtls.reset();
+ }
+
+ // Unique identifier for this transport within this call. Group?
+ std::string mTransportId;
+
+ // ICE stuff.
+ UniquePtr<JsepIceTransport> mIce;
+ UniquePtr<JsepDtlsTransport> mDtls;
+
+ // Number of required components.
+ size_t mComponents;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTransport);
+
+protected:
+ ~JsepTransport() {}
+};
+
+} // namespace mozilla
+
+#endif