summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsep
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/media/webrtc/jsep
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/jsep')
-rw-r--r--dom/media/webrtc/jsep/JsepCodecDescription.h1179
-rw-r--r--dom/media/webrtc/jsep/JsepSession.h287
-rw-r--r--dom/media/webrtc/jsep/JsepSessionImpl.cpp2473
-rw-r--r--dom/media/webrtc/jsep/JsepSessionImpl.h284
-rw-r--r--dom/media/webrtc/jsep/JsepTrack.cpp706
-rw-r--r--dom/media/webrtc/jsep/JsepTrack.h323
-rw-r--r--dom/media/webrtc/jsep/JsepTrackEncoding.h62
-rw-r--r--dom/media/webrtc/jsep/JsepTransceiver.h220
-rw-r--r--dom/media/webrtc/jsep/JsepTransport.h102
-rw-r--r--dom/media/webrtc/jsep/SsrcGenerator.cpp22
-rw-r--r--dom/media/webrtc/jsep/SsrcGenerator.h20
-rw-r--r--dom/media/webrtc/jsep/moz.build18
12 files changed, 5696 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsep/JsepCodecDescription.h b/dom/media/webrtc/jsep/JsepCodecDescription.h
new file mode 100644
index 0000000000..f10ffd183e
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepCodecDescription.h
@@ -0,0 +1,1179 @@
+/* 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 <cmath>
+#include <string>
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SdpHelper.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "mozilla/Preferences.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(const std::string& defaultPt, const std::string& name,
+ uint32_t clock, uint32_t channels, bool enabled)
+ : mDefaultPt(defaultPt),
+ mName(name),
+ mClock(clock),
+ mChannels(channels),
+ mEnabled(enabled),
+ mStronglyPreferred(false),
+ mDirection(sdp::kSend) {}
+ virtual ~JsepCodecDescription() {}
+
+ virtual SdpMediaSection::MediaType Type() const = 0;
+
+ virtual JsepCodecDescription* Clone() const = 0;
+
+ bool GetPtAsInt(uint16_t* ptOutparam) const {
+ return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
+ }
+
+ // The id used for codec stats, to uniquely identify this codec configuration
+ // within a transport.
+ const nsString& StatsId() const {
+ if (!mStatsId) {
+ mStatsId.emplace();
+ mStatsId->AppendPrintf(
+ "_%s_%s/%s_%u_%u_%s", mDefaultPt.c_str(),
+ Type() == SdpMediaSection::kVideo ? "video" : "audio", mName.c_str(),
+ mClock, mChannels, mSdpFmtpLine ? mSdpFmtpLine->c_str() : "nothing");
+ }
+ return *mStatsId;
+ }
+
+ virtual bool Matches(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const {
+ // note: fmt here is remote fmt (to go with remoteMsection)
+ if (Type() != 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;
+ }
+
+ Maybe<std::string> GetMatchingFormat(
+ const SdpMediaSection& remoteMsection) const {
+ for (const auto& fmt : remoteMsection.GetFormats()) {
+ if (Matches(fmt, remoteMsection)) {
+ return Some(fmt);
+ }
+ }
+ return Nothing();
+ }
+
+ virtual bool Negotiate(const std::string& pt,
+ const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) {
+ // Configuration might change. Invalidate the stats id.
+ mStatsId = Nothing();
+ if (mDirection == sdp::kSend || remoteIsOffer) {
+ mDefaultPt = pt;
+ }
+ if (localMsection) {
+ // Offer/answer is concluding. Update the sdpFmtpLine.
+ MOZ_ASSERT(mDirection == sdp::kSend || mDirection == sdp::kRecv);
+ const SdpMediaSection& msection =
+ mDirection == sdp::kSend ? remoteMsection : *localMsection;
+ UpdateSdpFmtpLine(ToMaybeRef(msection.FindFmtp(mDefaultPt)));
+ }
+ return true;
+ }
+
+ virtual void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const = 0;
+
+ virtual void AddToMediaSection(SdpMediaSection& msection) const {
+ if (mEnabled && msection.GetMediaType() == Type()) {
+ if (mDirection == sdp::kRecv) {
+ msection.AddCodec(mDefaultPt, mName, mClock, mChannels);
+ }
+
+ AddParametersToMSection(msection);
+ }
+ }
+
+ virtual void AddParametersToMSection(SdpMediaSection& msection) const {}
+
+ virtual void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) {
+ mEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mDefaultPt);
+ }
+
+ bool EnsurePayloadTypeNotDuplicate(std::set<std::string>& aUsedPts,
+ std::string& aPtToCheck) {
+ if (!mEnabled) {
+ return false;
+ }
+
+ if (!aUsedPts.count(aPtToCheck)) {
+ aUsedPts.insert(aPtToCheck);
+ return true;
+ }
+
+ // |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.
+ std::ostringstream os;
+ os << freePt;
+ std::string freePtAsString = os.str();
+
+ if (!aUsedPts.count(freePtAsString)) {
+ aUsedPts.insert(freePtAsString);
+ aPtToCheck = freePtAsString;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // TODO Bug 1751671: Take a verbatim fmtp line (std::string or eq.) instead
+ // of fmtp parameters that have to be (re-)serialized.
+ void UpdateSdpFmtpLine(
+ const Maybe<const SdpFmtpAttributeList::Parameters&> aParams) {
+ mSdpFmtpLine = aParams.map([](const auto& aFmtp) {
+ std::stringstream ss;
+ aFmtp.Serialize(ss);
+ return ss.str();
+ });
+ }
+
+ std::string mDefaultPt;
+ std::string mName;
+ Maybe<std::string> mSdpFmtpLine;
+ mutable Maybe<nsString> mStatsId;
+ 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, bool enabled = true)
+ : JsepCodecDescription(defaultPt, name, clock, channels, enabled),
+ mMaxPlaybackRate(0),
+ mForceMono(false),
+ mFECEnabled(false),
+ mDtmfEnabled(false),
+ mMaxAverageBitrate(0),
+ mDTXEnabled(false),
+ mFrameSizeMs(0),
+ mMinFrameSizeMs(0),
+ mMaxFrameSizeMs(0),
+ mCbrEnabled(false) {}
+
+ static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kAudio;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ JSEP_CODEC_CLONE(JsepAudioCodecDescription)
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultOpus() {
+ // 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.
+ return MakeUnique<JsepAudioCodecDescription>("109", "opus", 48000, 2);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultG722() {
+ return MakeUnique<JsepAudioCodecDescription>("9", "G722", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMU() {
+ return MakeUnique<JsepAudioCodecDescription>("0", "PCMU", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMA() {
+ return MakeUnique<JsepAudioCodecDescription>("8", "PCMA", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultTelephoneEvent() {
+ return MakeUnique<JsepAudioCodecDescription>("101", "telephone-event", 8000,
+ 1);
+ }
+
+ 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") {
+ UniquePtr<SdpFmtpAttributeList::Parameters> opusParams =
+ MakeUnique<SdpFmtpAttributeList::OpusParameters>(
+ GetOpusParameters(mDefaultPt, msection));
+
+ ApplyConfigToFmtp(opusParams);
+
+ 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,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+ 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;
+ if ((opusParams.maxAverageBitrate >= 6000) &&
+ (opusParams.maxAverageBitrate <= 510000)) {
+ mMaxAverageBitrate = opusParams.maxAverageBitrate;
+ }
+ mDTXEnabled = opusParams.useDTX;
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute)) {
+ mFrameSizeMs = remoteMsection.GetAttributeList().GetPtime();
+ } else {
+ mFrameSizeMs = opusParams.frameSizeMs;
+ }
+ mMinFrameSizeMs = opusParams.minFrameSizeMs;
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute)) {
+ mMaxFrameSizeMs = remoteMsection.GetAttributeList().GetMaxptime();
+ } else {
+ mMaxFrameSizeMs = opusParams.maxFrameSizeMs;
+ }
+ mCbrEnabled = opusParams.useCbr;
+ }
+
+ return true;
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override {
+ if (mName == "opus") {
+ SdpFmtpAttributeList::OpusParameters opusParams;
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kOpus);
+ opusParams =
+ static_cast<const SdpFmtpAttributeList::OpusParameters&>(*aFmtp);
+ opusParams.useInBandFec = mFECEnabled ? 1 : 0;
+ } else {
+ // If we weren't passed a fmtp to use then show we can do in band FEC
+ // for getCapabilities queries.
+ opusParams.useInBandFec = 1;
+ }
+ if (mMaxPlaybackRate) {
+ opusParams.maxplaybackrate = mMaxPlaybackRate;
+ }
+ opusParams.maxAverageBitrate = mMaxAverageBitrate;
+
+ if (mChannels == 2 &&
+ !Preferences::GetBool("media.peerconnection.sdp.disable_stereo_fmtp",
+ false) &&
+ !mForceMono) {
+ // We prefer to receive stereo, if available.
+ opusParams.stereo = 1;
+ }
+ opusParams.useDTX = mDTXEnabled;
+ opusParams.frameSizeMs = mFrameSizeMs;
+ opusParams.minFrameSizeMs = mMinFrameSizeMs;
+ opusParams.maxFrameSizeMs = mMaxFrameSizeMs;
+ opusParams.useCbr = mCbrEnabled;
+ aFmtp.reset(opusParams.Clone());
+ }
+ };
+
+ uint32_t mMaxPlaybackRate;
+ bool mForceMono;
+ bool mFECEnabled;
+ bool mDtmfEnabled;
+ uint32_t mMaxAverageBitrate;
+ bool mDTXEnabled;
+ uint32_t mFrameSizeMs;
+ uint32_t mMinFrameSizeMs;
+ uint32_t mMaxFrameSizeMs;
+ bool mCbrEnabled;
+};
+
+class JsepVideoCodecDescription : public JsepCodecDescription {
+ public:
+ JsepVideoCodecDescription(const std::string& defaultPt,
+ const std::string& name, uint32_t clock,
+ bool enabled = true)
+ : JsepCodecDescription(defaultPt, name, clock, 0, enabled),
+ mTmmbrEnabled(false),
+ mRembEnabled(false),
+ mFECEnabled(false),
+ mTransportCCEnabled(false),
+ mRtxEnabled(false),
+ mProfileLevelId(0),
+ mPacketizationMode(0) {
+ // Add supported rtcp-fb types
+ mNackFbTypes.push_back("");
+ mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
+ mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
+ }
+
+ static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kVideo;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP8(bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("120", "VP8", 90000);
+ // Defaults for mandatory params
+ codec->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ codec->mConstraints.maxFps = Some(60);
+ if (aUseRtx) {
+ codec->EnableRtx("124");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP9(bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("121", "VP9", 90000);
+ // Defaults for mandatory params
+ codec->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ codec->mConstraints.maxFps = Some(60);
+ if (aUseRtx) {
+ codec->EnableRtx("125");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_0(
+ bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("97", "H264", 90000);
+ codec->mPacketizationMode = 0;
+ // Defaults for mandatory params
+ codec->mProfileLevelId = 0x42E01F;
+ if (aUseRtx) {
+ codec->EnableRtx("98");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_1(
+ bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("126", "H264", 90000);
+ codec->mPacketizationMode = 1;
+ // Defaults for mandatory params
+ codec->mProfileLevelId = 0x42E01F;
+ if (aUseRtx) {
+ codec->EnableRtx("127");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultUlpFec() {
+ return MakeUnique<JsepVideoCodecDescription>(
+ "123", // payload type
+ "ulpfec", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultRed() {
+ auto codec = MakeUnique<JsepVideoCodecDescription>(
+ "122", // payload type
+ "red", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ codec->EnableRtx("119");
+ return codec;
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override {
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params;
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kH264);
+ h264Params =
+ static_cast<const SdpFmtpAttributeList::H264Parameters&>(*aFmtp);
+ }
+
+ 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;
+
+ // 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;
+ aFmtp.reset(h264Params.Clone());
+ } else if (mName == "VP8" || mName == "VP9") {
+ SdpRtpmapAttributeList::CodecType type =
+ mName == "VP8" ? SdpRtpmapAttributeList::CodecType::kVP8
+ : SdpRtpmapAttributeList::CodecType::kVP9;
+ auto vp8Params = SdpFmtpAttributeList::VP8Parameters(type);
+
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == type);
+ vp8Params =
+ static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*aFmtp);
+ }
+ // VP8 and VP9 share the same SDP parameters thus far
+ vp8Params.max_fs = mConstraints.maxFs;
+ if (mConstraints.maxFps.isSome()) {
+ vp8Params.max_fr =
+ static_cast<unsigned int>(std::round(*mConstraints.maxFps));
+ } else {
+ vp8Params.max_fr = 60;
+ }
+ aFmtp.reset(vp8Params.Clone());
+ }
+ }
+
+ 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(std::string redPayloadType,
+ std::string ulpfecPayloadType,
+ std::string redRtxPayloadType) {
+ // 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.
+
+ // Ensure we have valid payload types. This returns zero on failure, which
+ // is a valid payload type.
+ uint16_t redPt, ulpfecPt, redRtxPt;
+ if (!SdpHelper::GetPtAsInt(redPayloadType, &redPt) ||
+ !SdpHelper::GetPtAsInt(ulpfecPayloadType, &ulpfecPt) ||
+ !SdpHelper::GetPtAsInt(redRtxPayloadType, &redRtxPt)) {
+ return;
+ }
+
+ mFECEnabled = true;
+ mREDPayloadType = redPayloadType;
+ mULPFECPayloadType = ulpfecPayloadType;
+ mREDRTXPayloadType = redRtxPayloadType;
+ }
+
+ virtual void EnableTransportCC() {
+ if (!mTransportCCEnabled) {
+ mTransportCCEnabled = true;
+ mOtherFbTypes.push_back(
+ {"", SdpRtcpFbAttributeList::kTransportCC, "", ""});
+ }
+ }
+
+ void EnableRtx(const std::string& rtxPayloadType) {
+ mRtxEnabled = true;
+ mRtxPayloadType = rtxPayloadType;
+ }
+
+ void AddParametersToMSection(SdpMediaSection& msection) const override {
+ AddFmtpsToMSection(msection);
+ AddRtcpFbsToMSection(msection);
+ }
+
+ void AddFmtpsToMSection(SdpMediaSection& msection) const {
+ if (mName == "H264") {
+ UniquePtr<SdpFmtpAttributeList::Parameters> h264Params =
+ MakeUnique<SdpFmtpAttributeList::H264Parameters>(
+ GetH264Parameters(mDefaultPt, msection));
+
+ ApplyConfigToFmtp(h264Params);
+
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *h264Params));
+ } else if (mName == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kRecv) {
+ // VP8 and VP9 share the same SDP parameters thus far
+ UniquePtr<SdpFmtpAttributeList::Parameters> vp8Params =
+ MakeUnique<SdpFmtpAttributeList::VP8Parameters>(
+ GetVP8Parameters(mDefaultPt, msection));
+ ApplyConfigToFmtp(vp8Params);
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *vp8Params));
+ }
+ }
+
+ if (mRtxEnabled && mDirection == sdp::kRecv) {
+ SdpFmtpAttributeList::RtxParameters params(
+ GetRtxParameters(mDefaultPt, msection));
+ uint16_t apt;
+ if (SdpHelper::GetPtAsInt(mDefaultPt, &apt)) {
+ if (apt <= 127) {
+ msection.AddCodec(mRtxPayloadType, "rtx", mClock, mChannels);
+
+ params.apt = apt;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mRtxPayloadType, params));
+ }
+ }
+ }
+ }
+
+ 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::RtxParameters GetRtxParameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ SdpFmtpAttributeList::RtxParameters result;
+ const auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) {
+ result = static_cast<const SdpFmtpAttributeList::RtxParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ Maybe<std::string> GetRtxPtByApt(const std::string& apt,
+ const SdpMediaSection& msection) const {
+ Maybe<std::string> result;
+ uint16_t aptAsInt;
+ if (!SdpHelper::GetPtAsInt(apt, &aptAsInt)) {
+ return result;
+ }
+
+ const SdpAttributeList& attrs = msection.GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ for (const auto& fmtpAttr : attrs.GetFmtp().mFmtps) {
+ if (fmtpAttr.parameters) {
+ auto* params = fmtpAttr.parameters.get();
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) {
+ const SdpFmtpAttributeList::RtxParameters* rtxParams =
+ static_cast<const SdpFmtpAttributeList::RtxParameters*>(params);
+ if (rtxParams->apt == aptAsInt) {
+ result = Some(fmtpAttr.format);
+ break;
+ }
+ }
+ }
+ }
+ }
+ 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) {
+ Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection);
+ if (!remoteFmt) {
+ return;
+ }
+ std::vector<std::string> temp;
+ for (auto& subType : *supportedTypes) {
+ if (remoteMsection.HasRtcpFb(*remoteFmt, type, subType)) {
+ temp.push_back(subType);
+ }
+ }
+ *supportedTypes = temp;
+ }
+
+ void NegotiateRtcpFb(
+ const SdpMediaSection& remoteMsection,
+ std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) {
+ Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection);
+ if (!remoteFmt) {
+ return;
+ }
+ std::vector<SdpRtcpFbAttributeList::Feedback> temp;
+ for (auto& fb : *supportedFbs) {
+ if (remoteMsection.HasRtcpFb(*remoteFmt, 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,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+ 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 == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kSend) {
+ SdpFmtpAttributeList::VP8Parameters vp8Params(
+ GetVP8Parameters(mDefaultPt, remoteMsection));
+
+ mConstraints.maxFs = vp8Params.max_fs;
+ // Right now, we treat max-fr=0 (or the absence of max-fr) as no limit.
+ // We will eventually want to stop doing this (bug 1762600).
+ if (vp8Params.max_fr) {
+ mConstraints.maxFps = Some(vp8Params.max_fr);
+ }
+ }
+ }
+
+ if (mRtxEnabled && (mDirection == sdp::kSend || remoteIsOffer)) {
+ Maybe<std::string> rtxPt = GetRtxPtByApt(mDefaultPt, remoteMsection);
+ if (rtxPt.isSome()) {
+ EnableRtx(*rtxPt);
+ } else {
+ mRtxEnabled = false;
+ mRtxPayloadType = "";
+ }
+ }
+
+ 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 bool RtcpFbTransportCCIsSet() const {
+ for (const auto& fb : mOtherFbTypes) {
+ if (fb.type == SdpRtcpFbAttributeList::kTransportCC) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override {
+ JsepCodecDescription::EnsureNoDuplicatePayloadTypes(aUsedPts);
+ if (mFECEnabled) {
+ mFECEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mREDPayloadType) &&
+ EnsurePayloadTypeNotDuplicate(aUsedPts, mULPFECPayloadType);
+ }
+ if (mRtxEnabled) {
+ mRtxEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mRtxPayloadType);
+ }
+ }
+
+ 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;
+ bool mTransportCCEnabled;
+ bool mRtxEnabled;
+ std::string mREDPayloadType;
+ std::string mREDRTXPayloadType;
+ std::string mULPFECPayloadType;
+ std::string mRtxPayloadType;
+
+ // H264-specific stuff
+ uint32_t mProfileLevelId;
+ uint32_t mPacketizationMode;
+ std::string mSpropParameterSets;
+};
+
+class JsepApplicationCodecDescription : public JsepCodecDescription {
+ // This is the new draft-21 implementation
+ public:
+ JsepApplicationCodecDescription(const std::string& name, uint16_t channels,
+ uint16_t localPort,
+ uint32_t localMaxMessageSize,
+ bool enabled = true)
+ : JsepCodecDescription("", name, 0, channels, enabled),
+ mLocalPort(localPort),
+ mLocalMaxMessageSize(localMaxMessageSize),
+ mRemotePort(0),
+ mRemoteMaxMessageSize(0),
+ mRemoteMMSSet(false) {}
+
+ static constexpr SdpMediaSection::MediaType type =
+ SdpMediaSection::kApplication;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
+
+ static UniquePtr<JsepApplicationCodecDescription> CreateDefault() {
+ return MakeUnique<JsepApplicationCodecDescription>(
+ "webrtc-datachannel", WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
+ WEBRTC_DATACHANNEL_PORT_DEFAULT,
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL);
+ }
+
+ // Override, uses sctpport or sctpmap instead of rtpmap
+ virtual bool Matches(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const override {
+ if (type != remoteMsection.GetMediaType()) {
+ return false;
+ }
+
+ int sctp_port = remoteMsection.GetSctpPort();
+ bool fmt_matches =
+ nsCRT::strcasecmp(mName.c_str(),
+ remoteMsection.GetFormats()[0].c_str()) == 0;
+ if (sctp_port && fmt_matches) {
+ // New sctp draft 21 format
+ return true;
+ }
+
+ const SdpSctpmapAttributeList::Sctpmap* sctp_map(
+ remoteMsection.GetSctpmap());
+ if (sctp_map) {
+ // Old sctp draft 05 format
+ return nsCRT::strcasecmp(mName.c_str(), sctp_map->name.c_str()) == 0;
+ }
+
+ return false;
+ }
+
+ virtual void AddToMediaSection(SdpMediaSection& msection) const override {
+ if (mEnabled && msection.GetMediaType() == type) {
+ if (mDirection == sdp::kRecv) {
+ msection.AddDataChannel(mName, mLocalPort, mChannels,
+ mLocalMaxMessageSize);
+ }
+
+ AddParametersToMSection(msection);
+ }
+ }
+
+ bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+
+ uint32_t message_size;
+ mRemoteMMSSet = remoteMsection.GetMaxMessageSize(&message_size);
+ if (mRemoteMMSSet) {
+ mRemoteMaxMessageSize = message_size;
+ } else {
+ mRemoteMaxMessageSize =
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT;
+ }
+
+ int sctp_port = remoteMsection.GetSctpPort();
+ if (sctp_port) {
+ mRemotePort = sctp_port;
+ return true;
+ }
+
+ const SdpSctpmapAttributeList::Sctpmap* sctp_map(
+ remoteMsection.GetSctpmap());
+ if (sctp_map) {
+ mRemotePort = std::stoi(sctp_map->pt);
+ return true;
+ }
+
+ return false;
+ }
+
+ // We only support one datachannel per m-section
+ void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override {
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override{};
+
+ uint16_t mLocalPort;
+ uint32_t mLocalMaxMessageSize;
+ uint16_t mRemotePort;
+ uint32_t mRemoteMaxMessageSize;
+ bool mRemoteMMSSet;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepSession.h b/dom/media/webrtc/jsep/JsepSession.h
new file mode 100644
index 0000000000..be86a8a427
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSession.h
@@ -0,0 +1,287 @@
+/* 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 <map>
+#include <string>
+#include <vector>
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsError.h"
+
+#include "jsep/JsepTransport.h"
+#include "sdp/Sdp.h"
+
+#include "jsep/JsepTransceiver.h"
+
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+
+namespace mozilla {
+
+// Forward declarations
+class JsepCodecDescription;
+
+enum JsepSignalingState {
+ kJsepStateStable,
+ kJsepStateHaveLocalOffer,
+ kJsepStateHaveRemoteOffer,
+ kJsepStateHaveLocalPranswer,
+ kJsepStateHaveRemotePranswer,
+ kJsepStateClosed
+};
+
+enum JsepSdpType {
+ kJsepSdpOffer,
+ kJsepSdpAnswer,
+ kJsepSdpPranswer,
+ kJsepSdpRollback
+};
+
+enum JsepDescriptionPendingOrCurrent {
+ kJsepDescriptionCurrent,
+ kJsepDescriptionPending,
+ kJsepDescriptionPendingOrCurrent
+};
+
+struct JsepOAOptions {};
+struct JsepOfferOptions : public JsepOAOptions {
+ Maybe<size_t> mOfferToReceiveAudio;
+ Maybe<size_t> mOfferToReceiveVideo;
+ Maybe<bool> mIceRestart; // currently ignored by JsepSession
+};
+struct JsepAnswerOptions : public JsepOAOptions {};
+
+enum JsepBundlePolicy { kBundleBalanced, kBundleMaxCompat, kBundleMaxBundle };
+
+enum JsepMediaType { kNone = 0, kAudio, kVideo, kAudioVideo };
+
+struct JsepExtmapMediaType {
+ JsepMediaType mMediaType;
+ SdpExtmapAttributeList::Extmap mExtmap;
+};
+
+class JsepSession {
+ public:
+ explicit JsepSession(const std::string& name)
+ : mName(name), mState(kJsepStateStable), mNegotiations(0) {}
+ virtual ~JsepSession() {}
+
+ virtual JsepSession* Clone() const = 0;
+
+ 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 SetBundlePolicy(JsepBundlePolicy policy) = 0;
+ virtual bool RemoteIsIceLite() const = 0;
+ virtual std::vector<std::string> GetIceOptions() const = 0;
+
+ virtual nsresult AddDtlsFingerprint(const nsACString& algorithm,
+ const std::vector<uint8_t>& value) = 0;
+
+ virtual nsresult AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddAudioVideoRtpExtension(
+ 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<UniquePtr<JsepCodecDescription>>& Codecs() = 0;
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction& function) {
+ std::for_each(Codecs().begin(), Codecs().end(), function);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver.mSendTrack.ForEachCodec(function);
+ transceiver.mRecvTrack.ForEachCodec(function);
+ }
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate& sorter) {
+ std::stable_sort(Codecs().begin(), Codecs().end(), sorter);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver.mSendTrack.SortCodecs(sorter);
+ transceiver.mRecvTrack.SortCodecs(sorter);
+ }
+ }
+
+ // Would be nice to have this return a Maybe containing the return of
+ // |aFunction|, but Maybe cannot contain a void.
+ template <typename UnaryFunction>
+ bool ApplyToTransceiver(const std::string& aId, UnaryFunction&& aFunction) {
+ for (auto& transceiver : GetTransceivers()) {
+ if (transceiver.GetUuid() == aId) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <typename UnaryFunction>
+ void ForEachTransceiver(UnaryFunction&& aFunction) {
+ for (auto& transceiver : GetTransceivers()) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ }
+ }
+
+ template <typename UnaryFunction>
+ void ForEachTransceiver(UnaryFunction&& aFunction) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ }
+ }
+
+ Maybe<const JsepTransceiver> GetTransceiver(const std::string& aId) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ if (transceiver.GetUuid() == aId) {
+ return Some(transceiver);
+ }
+ }
+ return Nothing();
+ }
+
+ template <typename MatchFunction>
+ Maybe<const JsepTransceiver> FindTransceiver(MatchFunction&& aFunc) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ if (std::forward<MatchFunction>(aFunc)(transceiver)) {
+ return Some(transceiver);
+ }
+ }
+ return Nothing();
+ }
+
+ bool SetTransceiver(const JsepTransceiver& aNew) {
+ return ApplyToTransceiver(aNew.GetUuid(),
+ [aNew](JsepTransceiver& aOld) { aOld = aNew; });
+ }
+
+ virtual void AddTransceiver(const JsepTransceiver& transceiver) = 0;
+
+ class Result {
+ public:
+ Result() = default;
+ MOZ_IMPLICIT Result(dom::PCError aError) : mError(Some(aError)) {}
+ // TODO(bug 1527916): Need c'tor and members for handling RTCError.
+ Maybe<dom::PCError> mError;
+ };
+
+ // Basic JSEP operations.
+ virtual Result CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) = 0;
+ virtual Result CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) = 0;
+ virtual std::string GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const = 0;
+ virtual std::string GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const = 0;
+ virtual Result SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual Result SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual Result AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ const Maybe<uint16_t>& level,
+ const std::string& ufrag,
+ std::string* transportId) = 0;
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ 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, const std::string& transportId) = 0;
+ virtual nsresult Close() = 0;
+
+ // ICE controlling or controlled
+ virtual bool IsIceControlling() const = 0;
+ virtual Maybe<bool> IsPendingOfferer() const = 0;
+ virtual Maybe<bool> IsCurrentOfferer() const = 0;
+ virtual bool IsIceRestarting() const = 0;
+ virtual std::set<std::pair<std::string, std::string>> GetLocalIceCredentials()
+ const = 0;
+
+ virtual const std::string GetLastError() const { return "Error"; }
+
+ virtual const std::vector<std::pair<size_t, std::string>>&
+ GetLastSdpParsingErrors() const = 0;
+
+ 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 CheckNegotiationNeeded() const = 0;
+
+ void CountTracksAndDatachannels(
+ uint16_t (&receiving)[SdpMediaSection::kMediaTypes],
+ uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const {
+ memset(receiving, 0, sizeof(receiving));
+ memset(sending, 0, sizeof(sending));
+
+ for (const auto& transceiver : GetTransceivers()) {
+ if (transceiver.mRecvTrack.GetActive() ||
+ transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ receiving[transceiver.mRecvTrack.GetMediaType()]++;
+ }
+
+ if (transceiver.mSendTrack.GetActive() ||
+ transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ sending[transceiver.mSendTrack.GetMediaType()]++;
+ }
+ }
+ }
+
+ virtual void SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) = 0;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; }
+
+ protected:
+ friend class JsepSessionTest;
+ // Returns transceivers in the order they were added.
+ virtual std::vector<JsepTransceiver>& GetTransceivers() = 0;
+ virtual const std::vector<JsepTransceiver>& GetTransceivers() const = 0;
+
+ const std::string mName;
+ JsepSignalingState mState;
+ uint32_t mNegotiations;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.cpp b/dom/media/webrtc/jsep/JsepSessionImpl.cpp
new file mode 100644
index 0000000000..50def6eb6c
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSessionImpl.cpp
@@ -0,0 +1,2473 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/JsepSessionImpl.h"
+
+#include <stdlib.h>
+
+#include <bitset>
+#include <iterator>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "transport/logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "nsDebug.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "api/rtp_parameters.h"
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepTransport.h"
+#include "sdp/HybridSdpParser.h"
+#include "sdp/SipccSdp.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("jsep")
+
+#define JSEP_SET_ERROR(error) \
+ do { \
+ std::ostringstream os; \
+ os << error; \
+ mLastError = os.str(); \
+ MOZ_MTLOG(ML_ERROR, "[" << mName << "]: " << mLastError); \
+ } while (0);
+
+static std::bitset<128> GetForbiddenSdpPayloadTypes() {
+ std::bitset<128> forbidden(0);
+ forbidden[1] = true;
+ forbidden[2] = true;
+ forbidden[19] = true;
+ for (uint16_t i = 64; i < 96; ++i) {
+ forbidden[i] = true;
+ }
+ return forbidden;
+}
+
+static std::string GetRandomHex(size_t words) {
+ std::ostringstream os;
+
+ for (size_t i = 0; i < words; ++i) {
+ uint32_t rand;
+ SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(&rand),
+ sizeof(rand));
+ if (rv != SECSuccess) {
+ MOZ_CRASH();
+ return "";
+ }
+
+ os << std::hex << std::setfill('0') << std::setw(8) << rand;
+ }
+ return os.str();
+}
+
+JsepSessionImpl::JsepSessionImpl(const JsepSessionImpl& aOrig)
+ : JsepSession(aOrig),
+ JsepSessionCopyableStuff(aOrig),
+ mUuidGen(aOrig.mUuidGen->Clone()),
+ mGeneratedOffer(aOrig.mGeneratedOffer ? aOrig.mGeneratedOffer->Clone()
+ : nullptr),
+ mGeneratedAnswer(aOrig.mGeneratedAnswer ? aOrig.mGeneratedAnswer->Clone()
+ : nullptr),
+ mCurrentLocalDescription(aOrig.mCurrentLocalDescription
+ ? aOrig.mCurrentLocalDescription->Clone()
+ : nullptr),
+ mCurrentRemoteDescription(aOrig.mCurrentRemoteDescription
+ ? aOrig.mCurrentRemoteDescription->Clone()
+ : nullptr),
+ mPendingLocalDescription(aOrig.mPendingLocalDescription
+ ? aOrig.mPendingLocalDescription->Clone()
+ : nullptr),
+ mPendingRemoteDescription(aOrig.mPendingRemoteDescription
+ ? aOrig.mPendingRemoteDescription->Clone()
+ : nullptr),
+ mSdpHelper(&mLastError),
+ mParser(new HybridSdpParser()) {
+ for (const auto& codec : aOrig.mSupportedCodecs) {
+ mSupportedCodecs.emplace_back(codec->Clone());
+ }
+}
+
+nsresult JsepSessionImpl::Init() {
+ mLastError.clear();
+
+ MOZ_ASSERT(!mSessionId, "Init called more than once");
+
+ nsresult rv = SetupIds();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEncodeTrackId =
+ Preferences::GetBool("media.peerconnection.sdp.encode_track_id", true);
+
+ mIceUfrag = GetRandomHex(1);
+ mIcePwd = GetRandomHex(4);
+ return NS_OK;
+}
+
+static void GetIceCredentials(
+ const Sdp& aSdp,
+ std::set<std::pair<std::string, std::string>>* aCredentials) {
+ for (size_t i = 0; i < aSdp.GetMediaSectionCount(); ++i) {
+ const SdpAttributeList& attrs = aSdp.GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kIceUfragAttribute) &&
+ attrs.HasAttribute(SdpAttribute::kIcePwdAttribute)) {
+ aCredentials->insert(
+ std::make_pair(attrs.GetIceUfrag(), attrs.GetIcePwd()));
+ }
+ }
+}
+
+std::set<std::pair<std::string, std::string>>
+JsepSessionImpl::GetLocalIceCredentials() const {
+ std::set<std::pair<std::string, std::string>> result;
+ if (mCurrentLocalDescription) {
+ GetIceCredentials(*mCurrentLocalDescription, &result);
+ }
+ if (mPendingLocalDescription) {
+ GetIceCredentials(*mPendingLocalDescription, &result);
+ }
+ return result;
+}
+
+void JsepSessionImpl::AddTransceiver(const JsepTransceiver& aTransceiver) {
+ mLastError.clear();
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "]: Adding transceiver " << aTransceiver.GetUuid());
+#ifdef DEBUG
+ if (aTransceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ // Make sure we don't add more than one DataChannel transceiver
+ for (const auto& transceiver : mTransceivers) {
+ MOZ_ASSERT(transceiver.GetMediaType() != SdpMediaSection::kApplication);
+ }
+ }
+#endif
+ mTransceivers.push_back(aTransceiver);
+ InitTransceiver(mTransceivers.back());
+}
+
+void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) {
+ mLastError.clear();
+
+ if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) {
+ // Make sure we have an ssrc. Might already be set.
+ aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
+ aTransceiver.mSendTrack.SetCNAME(mCNAME);
+
+ // Make sure we have identifiers for send track, just in case.
+ // (man I hate this)
+ if (mEncodeTrackId) {
+ aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid());
+ }
+ } else {
+ // Datachannel transceivers should always be sendrecv. Just set it instead
+ // of asserting.
+ aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv;
+ }
+
+ aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs);
+ aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs);
+ // We do not set mLevel yet, we do that either on createOffer, or setRemote
+}
+
+nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) {
+ mLastError.clear();
+
+ if (mBundlePolicy == policy) {
+ return NS_OK;
+ }
+
+ if (mCurrentLocalDescription) {
+ JSEP_SET_ERROR(
+ "Changing the bundle policy is only supported before the "
+ "first SetLocalDescription.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mBundlePolicy = policy;
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddDtlsFingerprint(
+ const nsACString& algorithm, const std::vector<uint8_t>& value) {
+ mLastError.clear();
+ JsepDtlsFingerprint fp;
+
+ fp.mAlgorithm = algorithm;
+ fp.mValue = value;
+
+ mDtlsFingerprints.push_back(fp);
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ mLastError.clear();
+
+ for (auto& ext : mRtpExtensions) {
+ if (ext.mExtmap.direction == direction &&
+ ext.mExtmap.extensionname == extensionName) {
+ if (ext.mMediaType != mediaType) {
+ ext.mMediaType = JsepMediaType::kAudioVideo;
+ }
+ return NS_OK;
+ }
+ }
+
+ uint16_t freeEntry = GetNeverUsedExtmapEntry();
+
+ if (freeEntry == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JsepExtmapMediaType extMediaType = {
+ mediaType,
+ {freeEntry, direction,
+ // do we want to specify direction?
+ direction != SdpDirectionAttribute::kSendrecv, extensionName, ""}};
+
+ mRtpExtensions.push_back(extMediaType);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kAudio, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kVideo, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::AddAudioVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kAudioVideo, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver,
+ Sdp* local) {
+ SdpMediaSection::Protocol protocol(
+ SdpHelper::GetProtocolForMediaType(transceiver.GetMediaType()));
+
+ const Sdp* answer(GetAnswer());
+ const SdpMediaSection* lastAnswerMsection = nullptr;
+
+ if (answer &&
+ (local->GetMediaSectionCount() < answer->GetMediaSectionCount())) {
+ lastAnswerMsection =
+ &answer->GetMediaSection(local->GetMediaSectionCount());
+ // Use the protocol the answer used, even if it is not what we would have
+ // used.
+ protocol = lastAnswerMsection->GetProtocol();
+ }
+
+ SdpMediaSection* msection = &local->AddMediaSection(
+ transceiver.GetMediaType(), transceiver.mJsDirection, 0, protocol,
+ sdp::kIPv4, "0.0.0.0");
+
+ // Some of this stuff (eg; mid) sticks around even if disabled
+ if (lastAnswerMsection) {
+ MOZ_ASSERT(lastAnswerMsection->GetMediaType() ==
+ transceiver.GetMediaType());
+ nsresult rv = mSdpHelper.CopyStickyParams(*lastAnswerMsection, msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (transceiver.IsStopping() || transceiver.IsStopped()) {
+ SdpHelper::DisableMsection(local, msection);
+ return NS_OK;
+ }
+
+ msection->SetPort(9);
+
+ // We don't do this in AddTransportAttributes because that is also used for
+ // making answers, and we don't want to unconditionally set rtcp-mux or
+ // rtcp-rsize there.
+ if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
+ // Set RTCP-MUX.
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ // Set RTCP-RSIZE
+ if (msection->GetMediaType() == SdpMediaSection::MediaType::kVideo &&
+ Preferences::GetBool("media.navigator.video.offer_rtcp_rsize", false)) {
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
+ }
+ }
+
+ nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection);
+ transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection);
+
+ AddExtmap(msection);
+
+ std::string mid;
+ // We do not set the mid on the transceiver, that happens when a description
+ // is set.
+ if (transceiver.IsAssociated()) {
+ mid = transceiver.GetMid();
+ } else {
+ mid = GetNewMid();
+ }
+
+ msection->GetAttributeList().SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::SetupBundle(Sdp* sdp) const {
+ std::vector<std::string> mids;
+ std::set<SdpMediaSection::MediaType> observedTypes;
+
+ // This has the effect of changing the bundle level if the first m-section
+ // goes from disabled to enabled. This is kinda inefficient.
+
+ for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+ auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
+ if ((sdp->GetMediaSection(i).GetPort() != 0) &&
+ attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+ bool useBundleOnly = false;
+ switch (mBundlePolicy) {
+ case kBundleMaxCompat:
+ // We don't use bundle-only for max-compat
+ break;
+ case kBundleBalanced:
+ // balanced means we use bundle-only on everything but the first
+ // m-section of a given type
+ if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) {
+ useBundleOnly = true;
+ }
+ observedTypes.insert(sdp->GetMediaSection(i).GetMediaType());
+ break;
+ case kBundleMaxBundle:
+ // max-bundle means we use bundle-only on everything but the first
+ // m-section
+ useBundleOnly = !mids.empty();
+ break;
+ }
+
+ if (useBundleOnly) {
+ attrs.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ // Set port to 0 for sections with bundle-only attribute. (mjf)
+ sdp->GetMediaSection(i).SetPort(0);
+ }
+
+ mids.push_back(attrs.GetMid());
+ }
+ }
+
+ if (!mids.empty()) {
+ UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+ groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+ }
+}
+
+JsepSession::Result JsepSessionImpl::CreateOffer(
+ const JsepOfferOptions& options, std::string* offer) {
+ mLastError.clear();
+
+ if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) {
+ JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
+ // Spec doesn't seem to say this is an error. It probably should.
+ return dom::PCError::InvalidStateError;
+ }
+
+ // This is one of those places where CreateOffer sets some state.
+ SetIceRestarting(options.mIceRestart.isSome() && *(options.mIceRestart));
+
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ for (size_t level = 0;
+ Maybe<JsepTransceiver> transceiver = GetTransceiverForLocal(level);
+ ++level) {
+ rv = CreateOfferMsection(options, *transceiver, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ SetTransceiver(*transceiver);
+ }
+
+ SetupBundle(sdp.get());
+
+ if (mCurrentLocalDescription && GetAnswer()) {
+ rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription,
+ *sdp, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ *offer = sdp->ToString();
+ mGeneratedOffer = std::move(sdp);
+ ++mSessionVersion;
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateOffer \nSDP=\n" << *offer);
+
+ return Result();
+}
+
+std::string JsepSessionImpl::GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedLocalDescription(type);
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+std::string JsepSessionImpl::GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedRemoteDescription(type);
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+void JsepSessionImpl::AddExtmap(SdpMediaSection* msection) {
+ auto extensions = GetRtpExtensions(*msection);
+
+ if (!extensions.empty()) {
+ SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
+ extmap->mExtmaps = extensions;
+ msection->GetAttributeList().SetAttribute(extmap);
+ }
+}
+
+std::vector<SdpExtmapAttributeList::Extmap> JsepSessionImpl::GetRtpExtensions(
+ const SdpMediaSection& msection) {
+ std::vector<SdpExtmapAttributeList::Extmap> result;
+ JsepMediaType mediaType = JsepMediaType::kNone;
+ switch (msection.GetMediaType()) {
+ case SdpMediaSection::kAudio:
+ mediaType = JsepMediaType::kAudio;
+ break;
+ case SdpMediaSection::kVideo:
+ mediaType = JsepMediaType::kVideo;
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute)) {
+ // We need RID support
+ // TODO: Would it be worth checking that the direction is sane?
+ AddVideoRtpExtension(webrtc::RtpExtension::kRidUri,
+ SdpDirectionAttribute::kSendonly);
+
+ if (mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false)) {
+ AddVideoRtpExtension(webrtc::RtpExtension::kRepairedRidUri,
+ SdpDirectionAttribute::kSendonly);
+ }
+ }
+ break;
+ default:;
+ }
+ if (mediaType != JsepMediaType::kNone) {
+ for (auto ext = mRtpExtensions.begin(); ext != mRtpExtensions.end();
+ ++ext) {
+ if (ext->mMediaType == mediaType ||
+ ext->mMediaType == JsepMediaType::kAudioVideo) {
+ result.push_back(ext->mExtmap);
+ }
+ }
+ }
+ return result;
+}
+
+std::string JsepSessionImpl::GetNewMid() {
+ std::string mid;
+
+ do {
+ std::ostringstream osMid;
+ osMid << mMidCounter++;
+ mid = osMid.str();
+ } while (mUsedMids.count(mid));
+
+ mUsedMids.insert(mid);
+ return mid;
+}
+
+void JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection) {
+ auto negotiatedRtpExtensions = GetRtpExtensions(*msection);
+ mSdpHelper.NegotiateAndAddExtmaps(remoteMsection, negotiatedRtpExtensions,
+ msection);
+}
+
+uint16_t JsepSessionImpl::GetNeverUsedExtmapEntry() {
+ uint16_t result = 1;
+
+ // Walk the set in order, and return the first "hole" we find
+ for (const auto used : mExtmapEntriesEverUsed) {
+ if (result != used) {
+ MOZ_ASSERT(result < used);
+ break;
+ }
+
+ // RFC 5285 says entries >= 4096 are used in offers to force the answerer
+ // to pick, so we do not want to actually use these
+ if (used == 4095) {
+ JSEP_SET_ERROR(
+ "Too many rtp extensions have been added. "
+ "That's 4095. Who _does_ that?");
+ return 0;
+ }
+
+ result = used + 1;
+ }
+
+ mExtmapEntriesEverUsed.insert(result);
+ return result;
+}
+
+JsepSession::Result JsepSessionImpl::CreateAnswer(
+ const JsepAnswerOptions& options, std::string* answer) {
+ mLastError.clear();
+
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ const Sdp& offer = *mPendingRemoteDescription;
+
+ // Copy the bundle groups into our answer
+ UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+ mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+
+ for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+ // The transceivers are already in place, due to setRemote
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ MOZ_ASSERT(false);
+ return dom::PCError::OperationError;
+ }
+ rv = CreateAnswerMsection(options, *transceiver, offer.GetMediaSection(i),
+ sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ SetTransceiver(*transceiver);
+ }
+
+ // Ensure that each bundle-group starts with a mid that has a transport, in
+ // case we've disabled what the offerer wanted to use. If the group doesn't
+ // contain anything that has a transport, remove it.
+ groupAttr.reset(new SdpGroupAttributeList);
+ std::vector<SdpGroupAttributeList::Group> bundleGroups;
+ mSdpHelper.GetBundleGroups(*sdp, &bundleGroups);
+ for (auto& group : bundleGroups) {
+ for (auto& mid : group.tags) {
+ const SdpMediaSection* msection =
+ mSdpHelper.FindMsectionByMid(offer, mid);
+
+ if (msection && !msection->GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute)) {
+ std::swap(group.tags[0], mid);
+ groupAttr->mGroups.push_back(group);
+ break;
+ }
+ }
+ }
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+
+ if (mCurrentLocalDescription) {
+ // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
+ rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription,
+ offer, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ *answer = sdp->ToString();
+ mGeneratedAnswer = std::move(sdp);
+ ++mSessionVersion;
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateAnswer \nSDP=\n" << *answer);
+
+ return Result();
+}
+
+nsresult JsepSessionImpl::CreateAnswerMsection(
+ const JsepAnswerOptions& options, JsepTransceiver& transceiver,
+ const SdpMediaSection& remoteMsection, Sdp* sdp) {
+ MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType());
+ SdpDirectionAttribute::Direction direction =
+ reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection;
+ SdpMediaSection& msection =
+ sdp->AddMediaSection(remoteMsection.GetMediaType(), direction, 9,
+ remoteMsection.GetProtocol(), sdp::kIPv4, "0.0.0.0");
+
+ nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSdpHelper.MsectionIsDisabled(remoteMsection)) {
+ SdpHelper::DisableMsection(sdp, &msection);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(transceiver.IsAssociated());
+ if (msection.GetAttributeList().GetMid().empty()) {
+ msection.GetAttributeList().SetAttribute(new SdpStringAttribute(
+ SdpAttribute::kMidAttribute, transceiver.GetMid()));
+ }
+
+ MOZ_ASSERT(transceiver.GetMid() == msection.GetAttributeList().GetMid());
+
+ SdpSetupAttribute::Role role;
+ if (transceiver.mTransport.mDtls && !IsIceRestarting()) {
+ role = (transceiver.mTransport.mDtls->mRole ==
+ JsepDtlsTransport::kJsepDtlsClient)
+ ? SdpSetupAttribute::kActive
+ : SdpSetupAttribute::kPassive;
+ } else {
+ rv = DetermineAnswererSetupRole(remoteMsection, &role);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = AddTransportAttributes(&msection, role);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+ transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+
+ // Add extmap attributes. This logic will probably be moved to the track,
+ // since it can be specified on a per-sender basis in JS.
+ // We will need some validation to ensure that the ids are identical for
+ // RTP streams that are bundled together, though (bug 1406529).
+ AddCommonExtmaps(remoteMsection, &msection);
+
+ if (msection.GetFormats().empty()) {
+ // Could not negotiate anything. Disable m-section.
+ SdpHelper::DisableMsection(sdp, &msection);
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::DetermineAnswererSetupRole(
+ const SdpMediaSection& remoteMsection, SdpSetupAttribute::Role* rolep) {
+ // Determine the role.
+ // RFC 5763 says:
+ //
+ // The endpoint MUST use the setup attribute defined in [RFC4145].
+ // The endpoint that is the offerer MUST use the setup attribute
+ // value of setup:actpass and be prepared to receive a client_hello
+ // before it receives the answer. The answerer MUST use either a
+ // setup attribute value of setup:active or setup:passive. Note that
+ // if the answerer uses setup:passive, then the DTLS handshake will
+ // not begin until the answerer is received, which adds additional
+ // latency. setup:active allows the answer and the DTLS handshake to
+ // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
+ // party is active MUST initiate a DTLS handshake by sending a
+ // ClientHello over each flow (host/port quartet).
+ //
+ // We default to assuming that the offerer is passive and we are active.
+ SdpSetupAttribute::Role role = SdpSetupAttribute::kActive;
+
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute)) {
+ switch (remoteMsection.GetAttributeList().GetSetup().mRole) {
+ case SdpSetupAttribute::kActive:
+ role = SdpSetupAttribute::kPassive;
+ break;
+ case SdpSetupAttribute::kPassive:
+ case SdpSetupAttribute::kActpass:
+ role = SdpSetupAttribute::kActive;
+ break;
+ case SdpSetupAttribute::kHoldconn:
+ // This should have been caught by ParseSdp
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR(
+ "The other side used an illegal setup attribute"
+ " (\"holdconn\").");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ *rolep = role;
+ return NS_OK;
+}
+
+JsepSession::Result JsepSessionImpl::SetLocalDescription(
+ JsepSdpType type, const std::string& constSdp) {
+ mLastError.clear();
+ std::string sdp = constSdp;
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetLocalDescription type=" << type
+ << "\nSDP=\n"
+ << sdp);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ if (!mGeneratedOffer) {
+ JSEP_SET_ERROR(
+ "Cannot set local offer when createOffer has not been called.");
+ return dom::PCError::InvalidModificationError;
+ }
+ if (sdp.empty()) {
+ sdp = mGeneratedOffer->ToString();
+ }
+ if (mState == kJsepStateHaveLocalOffer) {
+ // Rollback previous offer before applying the new one.
+ SetLocalDescription(kJsepSdpRollback, "");
+ MOZ_ASSERT(mState == kJsepStateStable);
+ }
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ if (!mGeneratedAnswer) {
+ JSEP_SET_ERROR(
+ "Cannot set local answer when createAnswer has not been called.");
+ return dom::PCError::InvalidModificationError;
+ }
+ if (sdp.empty()) {
+ sdp = mGeneratedAnswer->ToString();
+ }
+ break;
+ case kJsepSdpRollback:
+ if (mState != kJsepStateHaveLocalOffer) {
+ JSEP_SET_ERROR("Cannot rollback local description in "
+ << GetStateStr(mState));
+ // Currently, spec allows this in any state except stable, and
+ // sRD(rollback) and sLD(rollback) do exactly the same thing.
+ return dom::PCError::InvalidStateError;
+ }
+
+ mPendingLocalDescription.reset();
+ SetState(kJsepStateStable);
+ RollbackLocalOffer();
+ return Result();
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set local answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ case kJsepStateHaveRemoteOffer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set local offer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set local offer or answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ // Needs to be RTCError with sdp-syntax-error
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ // Check that content hasn't done anything unsupported with the SDP
+ rv = ValidateLocalDescription(*parsed, type);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidModificationError);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = ValidateOffer(*parsed);
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = ValidateAnswer(*mPendingRemoteDescription, *parsed);
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ if (type == kJsepSdpOffer) {
+ // Save in case we need to rollback
+ mOldTransceivers = mTransceivers;
+ }
+
+ SdpHelper::BundledMids bundledMids;
+ rv = mSdpHelper.GetBundledMids(*parsed, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ SdpHelper::BundledMids remoteBundledMids;
+ if (type != kJsepSdpOffer) {
+ rv = mSdpHelper.GetBundledMids(*mPendingRemoteDescription,
+ &remoteBundledMids);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return dom::PCError::OperationError;
+ }
+
+ const auto& msection = parsed->GetMediaSection(i);
+ transceiver->Associate(msection.GetAttributeList().GetMid());
+ transceiver->mRecvTrack.RecvTrackSetLocal(msection);
+
+ if (mSdpHelper.MsectionIsDisabled(msection)) {
+ transceiver->mTransport.Close();
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ bool hasOwnTransport = mSdpHelper.OwnsTransport(
+ msection, bundledMids,
+ (type == kJsepSdpOffer) ? sdp::kOffer : sdp::kAnswer);
+ if (type != kJsepSdpOffer) {
+ const auto& remoteMsection =
+ mPendingRemoteDescription->GetMediaSection(i);
+ // Don't allow the answer to override what the offer allowed for
+ hasOwnTransport &= mSdpHelper.OwnsTransport(
+ remoteMsection, remoteBundledMids, sdp::kOffer);
+ }
+
+ if (hasOwnTransport) {
+ EnsureHasOwnTransport(parsed->GetMediaSection(i), *transceiver);
+ }
+
+ if (type == kJsepSdpOffer) {
+ if (!hasOwnTransport) {
+ auto it = bundledMids.find(transceiver->GetMid());
+ if (it != bundledMids.end()) {
+ transceiver->SetBundleLevel(it->second->GetLevel());
+ }
+ }
+ } else {
+ auto it = remoteBundledMids.find(transceiver->GetMid());
+ if (it != remoteBundledMids.end()) {
+ transceiver->SetBundleLevel(it->second->GetLevel());
+ }
+ }
+ SetTransceiver(*transceiver);
+ }
+
+ CopyBundleTransports();
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetLocalDescriptionOffer(std::move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetLocalDescriptionAnswer(type, std::move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ return Result();
+}
+
+nsresult JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer) {
+ MOZ_ASSERT(mState == kJsepStateStable);
+ mPendingLocalDescription = std::move(offer);
+ mIsPendingOfferer = Some(true);
+ SetState(kJsepStateHaveLocalOffer);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer) {
+ MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
+ mPendingLocalDescription = std::move(answer);
+
+ nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
+ mCurrentLocalDescription = std::move(mPendingLocalDescription);
+ MOZ_ASSERT(mIsPendingOfferer.isSome() && !*mIsPendingOfferer);
+ mIsPendingOfferer.reset();
+ mIsCurrentOfferer = Some(false);
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+JsepSession::Result JsepSessionImpl::SetRemoteDescription(
+ JsepSdpType type, const std::string& sdp) {
+ mLastError.clear();
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetRemoteDescription type=" << type
+ << "\nSDP=\n"
+ << sdp);
+
+ if (mState == kJsepStateHaveRemoteOffer && type == kJsepSdpOffer) {
+ // Rollback previous offer before applying the new one.
+ SetRemoteDescription(kJsepSdpRollback, "");
+ MOZ_ASSERT(mState == kJsepStateStable);
+ }
+
+ if (type == kJsepSdpRollback) {
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot rollback remote description in "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ mPendingRemoteDescription.reset();
+ SetState(kJsepStateStable);
+ RollbackRemoteOffer();
+
+ return Result();
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set remote answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ case kJsepStateHaveLocalOffer:
+ case kJsepStateHaveRemotePranswer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set remote offer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set remote offer or answer in current state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ // Parse.
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ // Needs to be RTCError with sdp-syntax-error
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ rv = ValidateRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = ValidateOffer(*parsed);
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = ValidateAnswer(*mPendingLocalDescription, *parsed);
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ bool iceLite =
+ parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+
+ // check for mismatch ufrag/pwd indicating ice restart
+ // can't just check the first one because it might be disabled
+ bool iceRestarting = false;
+ if (mCurrentRemoteDescription.get()) {
+ for (size_t i = 0; !iceRestarting &&
+ i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+ const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
+ const SdpMediaSection& oldMsection =
+ mCurrentRemoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+ mSdpHelper.MsectionIsDisabled(oldMsection)) {
+ continue;
+ }
+
+ iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+ }
+ }
+
+ std::vector<std::string> iceOptions;
+ if (parsed->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute)) {
+ iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
+ }
+
+ if (type == kJsepSdpOffer) {
+ // Save in case we need to rollback.
+ mOldTransceivers = mTransceivers;
+ for (auto& transceiver : mTransceivers) {
+ if (!transceiver.IsNegotiated()) {
+ // We chose a level for this transceiver, but never negotiated it.
+ // Discard this state.
+ transceiver.ClearLevel();
+ }
+ }
+ }
+
+ // TODO(bug 1095780): Note that we create remote tracks even when
+ // They contain only codecs we can't negotiate or other craziness.
+ rv = UpdateTransceiversFromRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ MOZ_ASSERT(GetTransceiverForLevel(i));
+ }
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetRemoteDescriptionOffer(std::move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetRemoteDescriptionAnswer(type, std::move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ mRemoteIsIceLite = iceLite;
+ mIceOptions = iceOptions;
+ SetIceRestarting(iceRestarting);
+ return Result();
+}
+
+nsresult JsepSessionImpl::HandleNegotiatedSession(
+ const UniquePtr<Sdp>& local, const UniquePtr<Sdp>& remote) {
+ // local ufrag/pwd has been negotiated; we will never go back to the old ones
+ mOldIceUfrag.clear();
+ mOldIcePwd.clear();
+
+ bool remoteIceLite =
+ remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+
+ mIceControlling = remoteIceLite || *mIsPendingOfferer;
+
+ const Sdp& answer = *mIsPendingOfferer ? *remote : *local;
+
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // First, set the bundle level on the transceivers
+ for (auto& [mid, transportOwner] : bundledMids) {
+ Maybe<JsepTransceiver> bundledTransceiver = GetTransceiverForMid(mid);
+ if (!bundledTransceiver) {
+ JSEP_SET_ERROR("No transceiver for bundled mid " << mid);
+ return NS_ERROR_INVALID_ARG;
+ }
+ bundledTransceiver->SetBundleLevel(transportOwner->GetLevel());
+ SetTransceiver(*bundledTransceiver);
+ }
+
+ // Now walk through the m-sections, perform negotiation, and update the
+ // transceivers.
+ for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(local->GetMediaSection(i))) {
+ transceiver->SetRemoved();
+ }
+
+ // Skip disabled m-sections.
+ if (answer.GetMediaSection(i).GetPort() == 0) {
+ transceiver->mTransport.Close();
+ transceiver->SetStopped();
+ transceiver->Disassociate();
+ transceiver->ClearBundleLevel();
+ transceiver->mSendTrack.SetActive(false);
+ transceiver->mRecvTrack.SetActive(false);
+ transceiver->SetCanRecycleMyMsection();
+ SetTransceiver(*transceiver);
+ // Do not clear mLevel yet! That will happen on the next negotiation.
+ continue;
+ }
+
+ rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i),
+ local->GetMediaSection(i), *transceiver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetTransceiver(*transceiver);
+ }
+
+ CopyBundleTransports();
+
+ std::vector<JsepTrack*> remoteTracks;
+ for (auto& transceiver : mTransceivers) {
+ remoteTracks.push_back(&transceiver.mRecvTrack);
+ }
+ JsepTrack::SetUniquePayloadTypes(remoteTracks);
+
+ mNegotiations++;
+
+ mGeneratedAnswer.reset();
+ mGeneratedOffer.reset();
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::MakeNegotiatedTransceiver(
+ const SdpMediaSection& remote, const SdpMediaSection& local,
+ JsepTransceiver& transceiver) {
+ const SdpMediaSection& answer = *mIsPendingOfferer ? remote : local;
+
+ bool sending = false;
+ bool receiving = false;
+
+ // We do not pay any attention to whether the transceiver is stopped here,
+ // because that is only a signal to the JSEP engine to _attempt_ to reject
+ // the corresponding m-section the next time we're the offerer.
+ if (*mIsPendingOfferer) {
+ receiving = answer.IsSending();
+ sending = answer.IsReceiving();
+ } else {
+ sending = answer.IsSending();
+ receiving = answer.IsReceiving();
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiated m= line"
+ << " index=" << local.GetLevel() << " type="
+ << local.GetMediaType() << " sending=" << sending
+ << " receiving=" << receiving);
+
+ transceiver.SetNegotiated();
+
+ // Ensure that this is finalized in case we need to copy it below
+ nsresult rv =
+ FinalizeTransport(remote.GetAttributeList(), answer.GetAttributeList(),
+ &transceiver.mTransport);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.SetActive(sending);
+ rv = transceiver.mSendTrack.Negotiate(answer, remote, local);
+ if (NS_FAILED(rv)) {
+ JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
+ << local.GetLevel());
+ return rv;
+ }
+
+ JsepTrack& recvTrack = transceiver.mRecvTrack;
+ recvTrack.SetActive(receiving);
+ rv = recvTrack.Negotiate(answer, remote, local);
+ if (NS_FAILED(rv)) {
+ JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
+ << local.GetLevel());
+ return rv;
+ }
+
+ if (transceiver.HasBundleLevel() && recvTrack.GetSsrcs().empty() &&
+ recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
+ // TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid
+ // support, we should only fire this warning if that extension was not
+ // negotiated.
+ MOZ_MTLOG(ML_ERROR, "[" << mName
+ << "]: Bundled m-section has no ssrc "
+ "attributes. This may cause media packets to be "
+ "dropped.");
+ }
+
+ if (transceiver.mTransport.mComponents == 2) {
+ // RTCP MUX or not.
+ // TODO(bug 1095743): verify that the PTs are consistent with mux.
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: RTCP-MUX is off");
+ }
+
+ if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ const auto extmaps = answer.GetAttributeList().GetExtmap().mExtmaps;
+ for (const auto& negotiatedExtension : extmaps) {
+ if (negotiatedExtension.entry == 0) {
+ MOZ_ASSERT(false, "This should have been caught sooner");
+ continue;
+ }
+
+ mExtmapEntriesEverNegotiated[negotiatedExtension.entry] =
+ negotiatedExtension.extensionname;
+
+ for (auto& originalExtension : mRtpExtensions) {
+ if (negotiatedExtension.extensionname ==
+ originalExtension.mExtmap.extensionname) {
+ // Update extmap to match what was negotiated
+ originalExtension.mExtmap.entry = negotiatedExtension.entry;
+ mExtmapEntriesEverUsed.insert(negotiatedExtension.entry);
+ } else if (originalExtension.mExtmap.entry ==
+ negotiatedExtension.entry) {
+ // If this extmap entry was claimed for a different extension, update
+ // it to a new value so we don't end up with a duplicate.
+ originalExtension.mExtmap.entry = GetNeverUsedExtmapEntry();
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::EnsureHasOwnTransport(const SdpMediaSection& msection,
+ JsepTransceiver& transceiver) {
+ JsepTransport& transport = transceiver.mTransport;
+
+ if (!transceiver.HasOwnTransport()) {
+ // Transceiver didn't own this transport last time, it won't now either
+ transport.Close();
+ }
+
+ transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag();
+ transport.mLocalPwd = msection.GetAttributeList().GetIcePwd();
+
+ transceiver.ClearBundleLevel();
+
+ if (!transport.mComponents) {
+ if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
+ transport.mComponents = 2;
+ } else {
+ transport.mComponents = 1;
+ }
+ }
+
+ if (transport.mTransportId.empty()) {
+ // TODO: Once we use different ICE ufrag/pass for each m-section, we can
+ // use that here.
+ std::ostringstream os;
+ os << "transport_" << mTransportIdCounter++;
+ transport.mTransportId = os.str();
+ }
+}
+
+void JsepSessionImpl::CopyBundleTransports() {
+ for (auto& transceiver : mTransceivers) {
+ if (transceiver.HasBundleLevel()) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "] Transceiver " << transceiver.GetLevel()
+ << " is in a bundle; transceiver "
+ << transceiver.BundleLevel() << " owns the transport.");
+ Maybe<const JsepTransceiver> transportOwner =
+ GetTransceiverForLevel(transceiver.BundleLevel());
+ MOZ_ASSERT(transportOwner);
+ if (transportOwner) {
+ transceiver.mTransport = transportOwner->mTransport;
+ }
+ } else if (transceiver.HasLevel()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "] Transceiver "
+ << transceiver.GetLevel()
+ << " is not necessarily in a bundle.");
+ }
+ if (transceiver.HasLevel()) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "] Transceiver " << transceiver.GetLevel()
+ << " transport-id: " << transceiver.mTransport.mTransportId
+ << " components: " << transceiver.mTransport.mComponents);
+ }
+ }
+}
+
+nsresult JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ JsepTransport* transport) const {
+ if (!transport->mComponents) {
+ return NS_OK;
+ }
+
+ if (!transport->mIce || transport->mIce->mUfrag != remote.GetIceUfrag() ||
+ transport->mIce->mPwd != remote.GetIcePwd()) {
+ UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();
+ transport->mDtls = nullptr;
+
+ // We do sanity-checking for these in ParseSdp
+ ice->mUfrag = remote.GetIceUfrag();
+ ice->mPwd = remote.GetIcePwd();
+ transport->mIce = std::move(ice);
+ }
+
+ if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ transport->mIce->mCandidates = remote.GetCandidate();
+ }
+
+ if (!transport->mDtls) {
+ // RFC 5763 says:
+ //
+ // The endpoint MUST use the setup attribute defined in [RFC4145].
+ // The endpoint that is the offerer MUST use the setup attribute
+ // value of setup:actpass and be prepared to receive a client_hello
+ // before it receives the answer. The answerer MUST use either a
+ // setup attribute value of setup:active or setup:passive. Note that
+ // if the answerer uses setup:passive, then the DTLS handshake will
+ // not begin until the answerer is received, which adds additional
+ // latency. setup:active allows the answer and the DTLS handshake to
+ // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
+ // party is active MUST initiate a DTLS handshake by sending a
+ // ClientHello over each flow (host/port quartet).
+ UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>();
+ dtls->mFingerprints = remote.GetFingerprint();
+ if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) {
+ dtls->mRole = *mIsPendingOfferer ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ if (*mIsPendingOfferer) {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsClient
+ : JsepDtlsTransport::kJsepDtlsServer;
+ }
+ }
+
+ transport->mDtls = std::move(dtls);
+ }
+
+ if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
+ transport->mComponents = 1;
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddTransportAttributes(
+ SdpMediaSection* msection, SdpSetupAttribute::Role dtlsRole) {
+ if (mIceUfrag.empty() || mIcePwd.empty()) {
+ JSEP_SET_ERROR("Missing ICE ufrag or password");
+ return NS_ERROR_FAILURE;
+ }
+
+ SdpAttributeList& attrList = msection->GetAttributeList();
+ attrList.SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag));
+ attrList.SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));
+
+ msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::CopyPreviousTransportParams(
+ const Sdp& oldAnswer, const Sdp& offerersPreviousSdp, const Sdp& newOffer,
+ Sdp* newLocal) {
+ for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
+ if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
+ mSdpHelper.AreOldTransportParamsValid(oldAnswer, offerersPreviousSdp,
+ newOffer, i)) {
+ // If newLocal is an offer, this will be the number of components we used
+ // last time, and if it is an answer, this will be the number of
+ // components we've decided we're using now.
+ Maybe<const JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+ size_t numComponents = transceiver->mTransport.mComponents;
+ nsresult rv = mSdpHelper.CopyTransportParams(
+ numComponents, mCurrentLocalDescription->GetMediaSection(i),
+ &newLocal->GetMediaSection(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ParseSdp(const std::string& sdp,
+ UniquePtr<Sdp>* parsedp) {
+ auto results = mParser->Parse(sdp);
+ auto parsed = std::move(results->Sdp());
+ mLastSdpParsingErrors = results->Errors();
+ if (!parsed) {
+ std::string error = results->ParserName() + " Failed to parse SDP: ";
+ mSdpHelper.AppendSdpParseErrors(mLastSdpParsingErrors, &error);
+ JSEP_SET_ERROR(error);
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Verify that the JSEP rules for all SDP are followed
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
+ // Disabled, let this stuff slide.
+ continue;
+ }
+
+ const SdpMediaSection& msection(parsed->GetMediaSection(i));
+ auto& mediaAttrs = msection.GetAttributeList();
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ mediaAttrs.GetMid().length() > 16) {
+ JSEP_SET_ERROR(
+ "Invalid description, mid length greater than 16 "
+ "unsupported until 2-byte rtp header extensions are "
+ "supported in webrtc.org");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ std::set<uint16_t> extIds;
+ for (const auto& ext : mediaAttrs.GetExtmap().mExtmaps) {
+ uint16_t id = ext.entry;
+
+ if (id < 1 || id > 14) {
+ JSEP_SET_ERROR("Description contains invalid extension id "
+ << id << " on level " << i
+ << " which is unsupported until 2-byte rtp"
+ " header extensions are supported in webrtc.org");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (extIds.find(id) != extIds.end()) {
+ JSEP_SET_ERROR("Description contains duplicate extension id "
+ << id << " on level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ extIds.insert(id);
+ }
+ }
+
+ static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes();
+ if (msection.GetMediaType() == SdpMediaSection::kAudio ||
+ msection.GetMediaType() == SdpMediaSection::kVideo) {
+ // Sanity-check that payload type can work with RTP
+ for (const std::string& fmt : msection.GetFormats()) {
+ uint16_t payloadType;
+ if (!SdpHelper::GetPtAsInt(fmt, &payloadType)) {
+ JSEP_SET_ERROR("Payload type \""
+ << fmt << "\" is not a 16-bit unsigned int at level "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (payloadType > 127) {
+ JSEP_SET_ERROR("audio/video payload type \""
+ << fmt << "\" is too large at level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (forbidden.test(payloadType)) {
+ JSEP_SET_ERROR("Illegal audio/video payload type \""
+ << fmt << "\" at level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ *parsedp = std::move(parsed);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer) {
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ mPendingRemoteDescription = std::move(offer);
+ mIsPendingOfferer = Some(false);
+
+ SetState(kJsepStateHaveRemoteOffer);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer) {
+ MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
+ mState == kJsepStateHaveRemotePranswer);
+
+ mPendingRemoteDescription = std::move(answer);
+
+ nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
+ mCurrentLocalDescription = std::move(mPendingLocalDescription);
+ MOZ_ASSERT(mIsPendingOfferer.isSome() && *mIsPendingOfferer);
+ mIsPendingOfferer.reset();
+ mIsCurrentOfferer = Some(true);
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLevel(
+ size_t level) const {
+ return FindTransceiver([level](const JsepTransceiver& transceiver) {
+ return transceiver.HasLevel() && (transceiver.GetLevel() == level);
+ });
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForMid(
+ const std::string& mid) const {
+ return FindTransceiver([mid](const JsepTransceiver& transceiver) {
+ return transceiver.IsAssociated() && (transceiver.GetMid() == mid);
+ });
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLocal(size_t level) {
+ if (Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level)) {
+ if (transceiver->CanRecycleMyMsection() &&
+ transceiver->GetMediaType() != SdpMediaSection::kApplication) {
+ // Attempt to recycle. If this fails, the old transceiver stays put.
+ transceiver->Disassociate();
+ Maybe<JsepTransceiver> newTransceiver =
+ FindUnassociatedTransceiver(transceiver->GetMediaType(), false);
+ if (newTransceiver) {
+ newTransceiver->SetLevel(level);
+ transceiver->ClearLevel();
+ transceiver->mSendTrack.ClearRids();
+ SetTransceiver(*newTransceiver);
+ SetTransceiver(*transceiver);
+ return newTransceiver;
+ }
+ }
+
+ SetTransceiver(*transceiver);
+ return transceiver;
+ }
+
+ // There is no transceiver for |level| right now.
+
+ // Look for an RTP transceiver (spec requires us to give the lower levels to
+ // new RTP transceivers)
+ for (auto& transceiver : mTransceivers) {
+ if (transceiver.GetMediaType() != SdpMediaSection::kApplication &&
+ transceiver.IsFreeToUse()) {
+ transceiver.SetLevel(level);
+ return Some(transceiver);
+ }
+ }
+
+ // Ok, look for a datachannel
+ for (auto& transceiver : mTransceivers) {
+ if (transceiver.IsFreeToUse()) {
+ transceiver.SetLevel(level);
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForRemote(
+ const SdpMediaSection& msection) {
+ size_t level = msection.GetLevel();
+ Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level);
+ if (transceiver) {
+ if (!transceiver->CanRecycleMyMsection()) {
+ return transceiver;
+ }
+ transceiver->Disassociate();
+ transceiver->ClearLevel();
+ transceiver->mSendTrack.ClearRids();
+ SetTransceiver(*transceiver);
+ }
+
+ // No transceiver for |level|
+ transceiver = FindUnassociatedTransceiver(msection.GetMediaType(), true);
+ if (transceiver) {
+ transceiver->SetLevel(level);
+ SetTransceiver(*transceiver);
+ return transceiver;
+ }
+
+ // Make a new transceiver
+ JsepTransceiver newTransceiver(msection.GetMediaType(), *mUuidGen,
+ SdpDirectionAttribute::kRecvonly);
+ newTransceiver.SetLevel(level);
+ newTransceiver.SetOnlyExistsBecauseOfSetRemote(true);
+ AddTransceiver(newTransceiver);
+ return Some(mTransceivers.back());
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverWithTransport(
+ const std::string& transportId) const {
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver.HasOwnTransport() &&
+ (transceiver.mTransport.mTransportId == transportId)) {
+ MOZ_ASSERT(transceiver.HasLevel(),
+ "Transceiver has a transport, but no level!");
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
+ const Sdp& remote) {
+ // Iterate over the sdp, updating remote tracks as we go
+ for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& msection = remote.GetMediaSection(i);
+
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForRemote(msection));
+ if (!transceiver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mSdpHelper.MsectionIsDisabled(msection)) {
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute)) {
+ transceiver->Associate(msection.GetAttributeList().GetMid());
+ }
+ if (!transceiver->IsAssociated()) {
+ transceiver->Associate(GetNewMid());
+ } else {
+ mUsedMids.insert(transceiver->GetMid());
+ }
+ } else {
+ // We do not disassociate here, that happens when negotiation completes
+ // These things cannot be rolled back.
+ transceiver->mTransport.Close();
+ transceiver->SetStopped();
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection);
+
+ // Interop workaround for endpoints that don't support msid.
+ // Ensures that there is a default stream id set, provided the remote is
+ // sending.
+ // TODO(bug 1426005): Remove this, or at least move it to JsepTrack.
+ transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId});
+
+ // This will process a=msid if present, or clear the stream ids if the
+ // msection is not sending. If the msection is sending, and there are no
+ // a=msid, the previously set default will stay.
+ transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection);
+ SetTransceiver(*transceiver);
+ }
+
+ return NS_OK;
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic) {
+ // Look through transceivers that are not mapped to an m-section
+ for (auto& transceiver : mTransceivers) {
+ if (type == SdpMediaSection::kApplication &&
+ type == transceiver.GetMediaType()) {
+ transceiver.RestartDatachannelTransceiver();
+ return Some(transceiver);
+ }
+ if (transceiver.IsFreeToUse() &&
+ (!magic || transceiver.HasAddTrackMagic()) &&
+ (transceiver.GetMediaType() == type)) {
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+void JsepSessionImpl::RollbackLocalOffer() {
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ auto& transceiver = mTransceivers[i];
+ if (mOldTransceivers.size() > i) {
+ transceiver.Rollback(mOldTransceivers[i], false);
+ mOldTransceivers[i] = transceiver;
+ continue;
+ }
+
+ JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
+ InitTransceiver(temp);
+ transceiver.Rollback(temp, false);
+ mOldTransceivers.push_back(transceiver);
+ }
+
+ mTransceivers = std::move(mOldTransceivers);
+}
+
+void JsepSessionImpl::RollbackRemoteOffer() {
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ auto& transceiver = mTransceivers[i];
+ if (mOldTransceivers.size() > i) {
+ // Some stuff cannot be rolled back. Save this information.
+ transceiver.Rollback(mOldTransceivers[i], true);
+ mOldTransceivers[i] = transceiver;
+ continue;
+ }
+
+ if (transceiver.HasLevel()) {
+ // New transceiver, that was either created by the remote offer, or
+ // attached to the remote offer.
+ // We rollback even for transceivers we will remove, just to ensure we end
+ // up at the starting state.
+ JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
+ InitTransceiver(temp);
+ transceiver.Rollback(temp, true);
+
+ if (transceiver.OnlyExistsBecauseOfSetRemote()) {
+ transceiver.SetStopped();
+ transceiver.Disassociate();
+ transceiver.SetRemoved();
+ } else {
+ // Oof. This hangs around because of addTrack. Make it magic!
+ transceiver.SetAddTrackMagic();
+ }
+ } // else, _we_ added this and it is not attached to the remote offer yet
+
+ mOldTransceivers.push_back(transceiver);
+ }
+
+ mTransceivers = std::move(mOldTransceivers);
+}
+
+nsresult JsepSessionImpl::ValidateLocalDescription(const Sdp& description,
+ JsepSdpType type) {
+ Sdp* generated = nullptr;
+ // TODO(bug 1095226): Better checking.
+ if (type == kJsepSdpOffer) {
+ generated = mGeneratedOffer.get();
+ } else {
+ generated = mGeneratedAnswer.get();
+ }
+
+ if (!generated) {
+ JSEP_SET_ERROR(
+ "Calling SetLocal without first calling CreateOffer/Answer"
+ " is not supported.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (description.GetMediaSectionCount() != generated->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Changing the number of m-sections is not allowed");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
+ auto& origMsection = generated->GetMediaSection(i);
+ auto& finalMsection = description.GetMediaSection(i);
+ if (origMsection.GetMediaType() != finalMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // These will be present in reoffer
+ if (!mCurrentLocalDescription) {
+ if (finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute)) {
+ JSEP_SET_ERROR("Adding your own candidate attributes is not supported");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute)) {
+ JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(finalMsection)) {
+ continue;
+ }
+
+ if (!finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute)) {
+ JSEP_SET_ERROR("Local descriptions must have a=mid attributes.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (finalMsection.GetAttributeList().GetMid() !=
+ origMsection.GetAttributeList().GetMid()) {
+ JSEP_SET_ERROR("Changing the mid of m-sections is not allowed.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // TODO(bug 1095218): Check msid
+ // TODO(bug 1095226): Check ice-ufrag and ice-pwd
+ // TODO(bug 1095226): Check fingerprints
+ // TODO(bug 1095226): Check payload types (at least ensure that payload
+ // types we don't actually support weren't added)
+ // TODO(bug 1095226): Check ice-options?
+ }
+
+ if (description.GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute)) {
+ JSEP_SET_ERROR("Running ICE in lite mode is unsupported");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) {
+ if (!mCurrentLocalDescription) {
+ // Initial offer; nothing to validate besides the stuff in ParseSdp
+ return NS_OK;
+ }
+
+ if (mCurrentLocalDescription->GetMediaSectionCount() >
+ description.GetMediaSectionCount()) {
+ JSEP_SET_ERROR(
+ "New remote description has fewer m-sections than the "
+ "previous remote description.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
+ const SdpAttributeList& attrs =
+ description.GetMediaSection(i).GetAttributeList();
+
+ if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ for (const auto& ext : attrs.GetExtmap().mExtmaps) {
+ if (mExtmapEntriesEverNegotiated.count(ext.entry) &&
+ mExtmapEntriesEverNegotiated[ext.entry] != ext.extensionname) {
+ JSEP_SET_ERROR(
+ "Remote description attempted to remap RTP extension id "
+ << ext.entry << " from "
+ << mExtmapEntriesEverNegotiated[ext.entry] << " to "
+ << ext.extensionname);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ if (!mCurrentRemoteDescription) {
+ // No further checking for initial answers
+ return NS_OK;
+ }
+
+ // These are solely to check that bundle is valid
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = GetNegotiatedBundledMids(&bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SdpHelper::BundledMids newBundledMids;
+ rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for partial ice restart, which is not supported
+ Maybe<bool> iceCredsDiffer;
+ for (size_t i = 0; i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+ const SdpMediaSection& newMsection = description.GetMediaSection(i);
+ const SdpMediaSection& oldMsection =
+ mCurrentRemoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+ mSdpHelper.MsectionIsDisabled(oldMsection)) {
+ continue;
+ }
+
+ if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Remote description changes the media type of m-line "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+
+ if (mIsPendingOfferer.isSome() && *mIsPendingOfferer && differ &&
+ !IsIceRestarting()) {
+ JSEP_SET_ERROR(
+ "Remote description indicates ICE restart but offer did not "
+ "request ICE restart (new remote description changes either "
+ "the ice-ufrag or ice-pwd)");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Detect whether all the creds are the same or all are different
+ if (!iceCredsDiffer.isSome()) {
+ // for the first msection capture whether creds are different or same
+ iceCredsDiffer = mozilla::Some(differ);
+ } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
+ // subsequent msections must match the first sections
+ JSEP_SET_ERROR(
+ "Partial ICE restart is unsupported at this time "
+ "(new remote description changes either the ice-ufrag "
+ "or ice-pwd on fewer than all msections)");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ValidateOffer(const Sdp& offer) {
+ return mSdpHelper.ValidateTransportAttributes(offer, sdp::kOffer);
+}
+
+nsresult JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer) {
+ if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Offer and answer have different number of m-lines "
+ << "(" << offer.GetMediaSectionCount() << " vs "
+ << answer.GetMediaSectionCount() << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = mSdpHelper.ValidateTransportAttributes(answer, sdp::kAnswer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
+ const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
+
+ if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Answer and offer have different media types at m-line "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(answerMsection)) {
+ continue;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(offerMsection)) {
+ JSEP_SET_ERROR(
+ "Answer tried to enable an m-section that was disabled in the offer");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!offerMsection.IsSending() && answerMsection.IsReceiving()) {
+ JSEP_SET_ERROR("Answer tried to set recv when offer did not set send");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!offerMsection.IsReceiving() && answerMsection.IsSending()) {
+ JSEP_SET_ERROR("Answer tried to set send when offer did not set recv");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList());
+ const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList());
+ if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ offerAttrs.GetMid() != answerAttrs.GetMid()) {
+ JSEP_SET_ERROR("Answer changes mid for level, was \'"
+ << offerMsection.GetAttributeList().GetMid()
+ << "\', now \'"
+ << answerMsection.GetAttributeList().GetMid() << "\'");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Sanity check extmap
+ if (answerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ if (!offerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ JSEP_SET_ERROR("Answer adds extmap attributes to level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (const auto& ansExt : answerAttrs.GetExtmap().mExtmaps) {
+ bool found = false;
+ for (const auto& offExt : offerAttrs.GetExtmap().mExtmaps) {
+ if (ansExt.extensionname == offExt.extensionname) {
+ if ((ansExt.direction & reverse(offExt.direction)) !=
+ ansExt.direction) {
+ // FIXME we do not return an error here, because Chrome up to
+ // version 57 is actually tripping over this if they are the
+ // answerer. See bug 1355010 for details.
+ MOZ_MTLOG(ML_WARNING,
+ "[" << mName
+ << "]: Answer has inconsistent"
+ " direction on extmap attribute at level "
+ << i << " (" << ansExt.extensionname
+ << "). Offer had " << offExt.direction
+ << ", answer had " << ansExt.direction << ".");
+ // return NS_ERROR_INVALID_ARG;
+ }
+
+ if (offExt.entry < 4096 && (offExt.entry != ansExt.entry)) {
+ JSEP_SET_ERROR("Answer changed id for extmap attribute at level "
+ << i << " (" << offExt.extensionname << ") from "
+ << offExt.entry << " to " << ansExt.entry << ".");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (ansExt.entry >= 4096) {
+ JSEP_SET_ERROR("Answer used an invalid id ("
+ << ansExt.entry
+ << ") for extmap attribute at level " << i << " ("
+ << ansExt.extensionname << ").");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ JSEP_SET_ERROR("Answer has extmap "
+ << ansExt.extensionname
+ << " at "
+ "level "
+ << i << " that was not present in offer.");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp) {
+ // draft-ietf-rtcweb-jsep-08 Section 5.2.1:
+ // o The second SDP line MUST be an "o=" line, as specified in
+ // [RFC4566], Section 5.2. The value of the <username> field SHOULD
+ // be "-". The value of the <sess-id> field SHOULD be a
+ // cryptographically random number. To ensure uniqueness, this
+ // number SHOULD be at least 64 bits long. The value of the <sess-
+ // version> field SHOULD be zero. The value of the <nettype>
+ // <addrtype> <unicast-address> tuple SHOULD be set to a non-
+ // meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the
+ // local address in this field. As mentioned in [RFC4566], the
+ // entire o= line needs to be unique, but selecting a random number
+ // for <sess-id> is sufficient to accomplish this.
+ //
+ // Historical note: we used to report the actual version number here, after
+ // "SDPARTA-", but that becomes a problem starting with version 100, since
+ // some services parse 100 as "10" and give us legacy/broken behavior. So
+ // we're freezing the version number at 99.0 in this string.
+ auto origin = SdpOrigin("mozilla...THIS_IS_SDPARTA-99.0", mSessionId,
+ mSessionVersion, sdp::kIPv4, "0.0.0.0");
+
+ UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin);
+
+ if (mDtlsFingerprints.empty()) {
+ JSEP_SET_ERROR("Missing DTLS fingerprint");
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<SdpFingerprintAttributeList> fpl =
+ MakeUnique<SdpFingerprintAttributeList>();
+ for (auto& dtlsFingerprint : mDtlsFingerprints) {
+ fpl->PushEntry(dtlsFingerprint.mAlgorithm, dtlsFingerprint.mValue);
+ }
+ sdp->GetAttributeList().SetAttribute(fpl.release());
+
+ auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
+ iceOpts->PushEntry("trickle");
+ sdp->GetAttributeList().SetAttribute(iceOpts);
+
+ // This assumes content doesn't add a bunch of msid attributes with a
+ // different semantic in mind.
+ std::vector<std::string> msids;
+ msids.push_back("*");
+ mSdpHelper.SetupMsidSemantic(msids, sdp.get());
+
+ *sdpp = std::move(sdp);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetupIds() {
+ SECStatus rv = PK11_GenerateRandom(
+ reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
+ // RFC 3264 says that session-ids MUST be representable as a _signed_
+ // 64 bit number, meaning the MSB cannot be set.
+ mSessionId = mSessionId >> 1;
+ if (rv != SECSuccess) {
+ JSEP_SET_ERROR("Failed to generate session id: " << rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
+ JSEP_SET_ERROR("Failed to generate default uuid for streams");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mCNAME)) {
+ JSEP_SET_ERROR("Failed to generate CNAME");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) {
+ mSupportedCodecs.clear();
+
+ for (const auto& codec : aPreferredCodecs) {
+ mSupportedCodecs.emplace_back(codec->Clone());
+ }
+}
+
+void JsepSessionImpl::SetState(JsepSignalingState state) {
+ if (state == mState) return;
+
+ MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> "
+ << GetStateStr(state));
+ mState = state;
+}
+
+JsepSession::Result JsepSessionImpl::AddRemoteIceCandidate(
+ const std::string& candidate, const std::string& mid,
+ const Maybe<uint16_t>& level, const std::string& ufrag,
+ std::string* transportId) {
+ mLastError.clear();
+ if (!mCurrentRemoteDescription && !mPendingRemoteDescription) {
+ JSEP_SET_ERROR("Cannot add ICE candidate when there is no remote SDP");
+ return dom::PCError::InvalidStateError;
+ }
+
+ if (mid.empty() && !level.isSome() && candidate.empty()) {
+ // Set end-of-candidates on SDP
+ if (mCurrentRemoteDescription) {
+ nsresult rv = mSdpHelper.SetIceGatheringComplete(
+ mCurrentRemoteDescription.get(), ufrag);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ if (mPendingRemoteDescription) {
+ // If we had an error when adding the candidate to the current
+ // description, we stomp them here. This is deliberate.
+ nsresult rv = mSdpHelper.SetIceGatheringComplete(
+ mPendingRemoteDescription.get(), ufrag);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+ return Result();
+ }
+
+ Maybe<JsepTransceiver> transceiver;
+ if (!mid.empty()) {
+ transceiver = GetTransceiverForMid(mid);
+ } else if (level.isSome()) {
+ transceiver = GetTransceiverForLevel(level.value());
+ }
+
+ if (!transceiver) {
+ JSEP_SET_ERROR("Cannot set ICE candidate for level="
+ << level << " mid=" << mid << ": No such transceiver.");
+ return dom::PCError::OperationError;
+ }
+
+ if (level.isSome() && transceiver->GetLevel() != level.value()) {
+ MOZ_MTLOG(ML_WARNING, "Mismatch between mid and level - \""
+ << mid << "\" is not the mid for level "
+ << level);
+ }
+
+ *transportId = transceiver->mTransport.mTransportId;
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mCurrentRemoteDescription) {
+ rv =
+ mSdpHelper.AddCandidateToSdp(mCurrentRemoteDescription.get(), candidate,
+ transceiver->GetLevel(), ufrag);
+ }
+
+ if (mPendingRemoteDescription) {
+ // If we had an error when adding the candidate to the current description,
+ // we stomp them here. This is deliberate.
+ rv =
+ mSdpHelper.AddCandidateToSdp(mPendingRemoteDescription.get(), candidate,
+ transceiver->GetLevel(), ufrag);
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ return Result();
+}
+
+nsresult JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ uint16_t* level,
+ std::string* mid,
+ bool* skipped) {
+ mLastError.clear();
+ *skipped = true;
+ if (!mCurrentLocalDescription && !mPendingLocalDescription) {
+ JSEP_SET_ERROR("Cannot add ICE candidate when there is no local SDP");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Maybe<const JsepTransceiver> transceiver =
+ GetTransceiverWithTransport(transportId);
+ if (!transceiver || !transceiver->IsAssociated()) {
+ // mainly here to make some testing less complicated, but also just in case
+ return NS_OK;
+ }
+
+ *level = transceiver->GetLevel();
+ *mid = transceiver->GetMid();
+
+ nsresult rv = NS_ERROR_INVALID_ARG;
+ if (mCurrentLocalDescription) {
+ rv = mSdpHelper.AddCandidateToSdp(mCurrentLocalDescription.get(), candidate,
+ *level, ufrag);
+ }
+
+ if (mPendingLocalDescription) {
+ // If we had an error when adding the candidate to the current description,
+ // we stomp them here. This is deliberate.
+ rv = mSdpHelper.AddCandidateToSdp(mPendingLocalDescription.get(), candidate,
+ *level, ufrag);
+ }
+
+ *skipped = false;
+ return rv;
+}
+
+nsresult JsepSessionImpl::UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort, const std::string& transportId) {
+ mLastError.clear();
+
+ mozilla::Sdp* sdp =
+ GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent);
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (const auto& transceiver : mTransceivers) {
+ // We set the default address for bundled m-sections, but not candidate
+ // attributes. Ugh.
+ if (transceiver.mTransport.mTransportId == transportId) {
+ MOZ_ASSERT(transceiver.HasLevel(),
+ "Transceiver has a transport, but no level! "
+ "This should never happen.");
+ std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
+ if (mState == kJsepStateStable) {
+ if (transceiver.mTransport.mComponents == 1) {
+ // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
+ defaultRtcpCandidateAddrCopy = "";
+ defaultRtcpCandidatePort = 0;
+ }
+ }
+
+ size_t level = transceiver.GetLevel();
+ if (level >= sdp->GetMediaSectionCount()) {
+ MOZ_ASSERT(false, "Transceiver's level is too large!");
+ JSEP_SET_ERROR("Transceiver's level is too large!");
+ return NS_ERROR_FAILURE;
+ }
+
+ auto& msection = sdp->GetMediaSection(level);
+
+ // Do not add default candidate to a bundle-only m-section, sinice that
+ // might confuse endpoints that do not support bundle-only.
+ if (!msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute)) {
+ mSdpHelper.SetDefaultAddresses(
+ defaultCandidateAddr, defaultCandidatePort,
+ defaultRtcpCandidateAddrCopy, defaultRtcpCandidatePort, &msection);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::GetNegotiatedBundledMids(
+ SdpHelper::BundledMids* bundledMids) {
+ const Sdp* answerSdp = GetAnswer();
+
+ if (!answerSdp) {
+ return NS_OK;
+ }
+
+ return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
+}
+
+mozilla::Sdp* JsepSessionImpl::GetParsedLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ if (type == kJsepDescriptionPending) {
+ return mPendingLocalDescription.get();
+ } else if (mPendingLocalDescription &&
+ type == kJsepDescriptionPendingOrCurrent) {
+ return mPendingLocalDescription.get();
+ }
+ return mCurrentLocalDescription.get();
+}
+
+mozilla::Sdp* JsepSessionImpl::GetParsedRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ if (type == kJsepDescriptionPending) {
+ return mPendingRemoteDescription.get();
+ } else if (mPendingRemoteDescription &&
+ type == kJsepDescriptionPendingOrCurrent) {
+ return mPendingRemoteDescription.get();
+ }
+ return mCurrentRemoteDescription.get();
+}
+
+const Sdp* JsepSessionImpl::GetAnswer() const {
+ return (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer)
+ ? mCurrentRemoteDescription.get()
+ : mCurrentLocalDescription.get();
+}
+
+void JsepSessionImpl::SetIceRestarting(bool restarting) {
+ if (restarting) {
+ // not restarting -> restarting
+ if (!IsIceRestarting()) {
+ // We don't set this more than once, so the old ufrag/pwd is preserved
+ // even if we CreateOffer({iceRestart:true}) multiple times in a row.
+ mOldIceUfrag = mIceUfrag;
+ mOldIcePwd = mIcePwd;
+ }
+ mIceUfrag = GetRandomHex(1);
+ mIcePwd = GetRandomHex(4);
+ } else if (IsIceRestarting()) {
+ // restarting -> not restarting, restore old ufrag/pwd
+ mIceUfrag = mOldIceUfrag;
+ mIcePwd = mOldIcePwd;
+ mOldIceUfrag.clear();
+ mOldIcePwd.clear();
+ }
+}
+
+nsresult JsepSessionImpl::Close() {
+ mLastError.clear();
+ SetState(kJsepStateClosed);
+ return NS_OK;
+}
+
+const std::string JsepSessionImpl::GetLastError() const { return mLastError; }
+
+const std::vector<std::pair<size_t, std::string>>&
+JsepSessionImpl::GetLastSdpParsingErrors() const {
+ return mLastSdpParsingErrors;
+}
+
+bool JsepSessionImpl::CheckNegotiationNeeded() const {
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver.IsStopped()) {
+ // Nothing to do with this
+ continue;
+ }
+
+ if (transceiver.IsStopping()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because of "
+ "transceiver we need to stop");
+ return true;
+ }
+
+ if (!transceiver.IsAssociated()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because of "
+ "transceiver we need to associate.");
+ return true;
+ }
+
+ MOZ_ASSERT(transceiver.IsAssociated() && !transceiver.IsStopping() &&
+ !transceiver.IsStopped());
+
+ if (!mCurrentLocalDescription || !mCurrentRemoteDescription) {
+ MOZ_CRASH(
+ "Transceivers should not be associated if we're in stable "
+ "before the first negotiation.");
+ continue;
+ }
+
+ if (!transceiver.HasLevel()) {
+ MOZ_CRASH("Associated transceivers should always have a level.");
+ continue;
+ }
+
+ if (transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+
+ size_t level = transceiver.GetLevel();
+ if (NS_WARN_IF(mCurrentLocalDescription->GetMediaSectionCount() <= level) ||
+ NS_WARN_IF(mCurrentRemoteDescription->GetMediaSectionCount() <=
+ level)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+
+ const SdpMediaSection& local =
+ mCurrentLocalDescription->GetMediaSection(level);
+ const SdpMediaSection& remote =
+ mCurrentRemoteDescription->GetMediaSection(level);
+
+ if (transceiver.mJsDirection & sdp::kSend) {
+ std::vector<std::string> sdpMsids;
+ if (local.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
+ for (const auto& msidAttr : local.GetAttributeList().GetMsid().mMsids) {
+ if (msidAttr.identifier != "-") {
+ sdpMsids.push_back(msidAttr.identifier);
+ }
+ }
+ }
+ std::sort(sdpMsids.begin(), sdpMsids.end());
+
+ std::vector<std::string> jsepMsids;
+ for (const auto& jsepMsid : transceiver.mSendTrack.GetStreamIds()) {
+ jsepMsids.push_back(jsepMsid);
+ }
+ std::sort(jsepMsids.begin(), jsepMsids.end());
+
+ if (!std::equal(sdpMsids.begin(), sdpMsids.end(), jsepMsids.begin(),
+ jsepMsids.end())) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName
+ << "]: Negotiation needed because transceiver "
+ "is sending, and the local SDP has different "
+ "msids than the send track");
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SDP msids = [");
+ for (const auto& msid : sdpMsids) {
+ MOZ_MTLOG(ML_DEBUG, msid << ", ");
+ }
+ MOZ_MTLOG(ML_DEBUG, "]");
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: JSEP msids = [");
+ for (const auto& msid : jsepMsids) {
+ MOZ_MTLOG(ML_DEBUG, msid << ", ");
+ }
+ MOZ_MTLOG(ML_DEBUG, "]");
+ return true;
+ }
+ }
+
+ if (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer) {
+ if ((local.GetDirection() != transceiver.mJsDirection) &&
+ reverse(remote.GetDirection()) != transceiver.mJsDirection) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because "
+ "the direction on our offer, and the remote "
+ "answer, does not "
+ "match the direction on a transceiver.");
+ return true;
+ }
+ } else if (local.GetDirection() !=
+ (transceiver.mJsDirection & reverse(remote.GetDirection()))) {
+ MOZ_MTLOG(
+ ML_DEBUG,
+ "[" << mName
+ << "]: Negotiation needed because "
+ "the direction on our answer doesn't match the direction on a "
+ "transceiver, even though the remote offer would have allowed "
+ "it.");
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.h b/dom/media/webrtc/jsep/JsepSessionImpl.h
new file mode 100644
index 0000000000..5ca5165a50
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSessionImpl.h
@@ -0,0 +1,284 @@
+/* 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 <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepTransceiver.h"
+#include "jsep/SsrcGenerator.h"
+#include "sdp/HybridSdpParser.h"
+#include "sdp/SdpHelper.h"
+
+namespace mozilla {
+
+// JsepSessionImpl members that have default copy c'tors, to simplify the
+// implementation of the copy c'tor for JsepSessionImpl
+class JsepSessionCopyableStuff {
+ protected:
+ struct JsepDtlsFingerprint {
+ std::string mAlgorithm;
+ std::vector<uint8_t> mValue;
+ };
+
+ Maybe<bool> mIsPendingOfferer;
+ Maybe<bool> mIsCurrentOfferer;
+ bool mIceControlling = false;
+ std::string mIceUfrag;
+ std::string mIcePwd;
+ std::string mOldIceUfrag;
+ std::string mOldIcePwd;
+ bool mRemoteIsIceLite = false;
+ std::vector<std::string> mIceOptions;
+ JsepBundlePolicy mBundlePolicy = kBundleBalanced;
+ std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
+ uint64_t mSessionId = 0;
+ uint64_t mSessionVersion = 0;
+ size_t mMidCounter = 0;
+ std::set<std::string> mUsedMids;
+ size_t mTransportIdCounter = 0;
+ std::vector<JsepExtmapMediaType> mRtpExtensions;
+ std::set<uint16_t> mExtmapEntriesEverUsed;
+ std::map<uint16_t, std::string> mExtmapEntriesEverNegotiated;
+ std::string mDefaultRemoteStreamId;
+ 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;
+ std::string mLastError;
+ std::vector<std::pair<size_t, std::string>> mLastSdpParsingErrors;
+ bool mEncodeTrackId = true;
+ SsrcGenerator mSsrcGenerator;
+ // !!!NOT INDEXED BY LEVEL!!! The level mapping is done with
+ // JsepTransceiver::mLevel. The keys are UUIDs.
+ std::vector<JsepTransceiver> mTransceivers;
+ // So we can rollback. Not as simple as just going back to the old, though...
+ std::vector<JsepTransceiver> mOldTransceivers;
+};
+
+class JsepSessionImpl : public JsepSession, public JsepSessionCopyableStuff {
+ public:
+ JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
+ : JsepSession(name),
+ mUuidGen(std::move(uuidgen)),
+ mSdpHelper(&mLastError),
+ mParser(new HybridSdpParser()) {}
+
+ JsepSessionImpl(const JsepSessionImpl& aOrig);
+
+ JsepSession* Clone() const override { return new JsepSessionImpl(*this); }
+
+ // Implement JsepSession methods.
+ virtual nsresult Init() override;
+
+ nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
+
+ virtual bool RemoteIsIceLite() const override { return mRemoteIsIceLite; }
+
+ virtual std::vector<std::string> GetIceOptions() const override {
+ return mIceOptions;
+ }
+
+ virtual nsresult AddDtlsFingerprint(
+ const nsACString& algorithm, const std::vector<uint8_t>& value) override;
+
+ virtual nsresult AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) override;
+ 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 nsresult AddAudioVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual std::vector<UniquePtr<JsepCodecDescription>>& Codecs() override {
+ return mSupportedCodecs;
+ }
+
+ virtual Result CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) override;
+
+ virtual Result CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) override;
+
+ virtual std::string GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const override;
+
+ virtual std::string GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const override;
+
+ virtual Result SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual Result SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual Result AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ const Maybe<uint16_t>& level,
+ const std::string& ufrag,
+ std::string* transportId) override;
+
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ 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,
+ const std::string& transportId) override;
+
+ virtual nsresult Close() override;
+
+ virtual const std::string GetLastError() const override;
+
+ virtual const std::vector<std::pair<size_t, std::string>>&
+ GetLastSdpParsingErrors() const override;
+
+ virtual bool IsIceControlling() const override { return mIceControlling; }
+
+ virtual Maybe<bool> IsPendingOfferer() const override {
+ return mIsPendingOfferer;
+ }
+
+ virtual Maybe<bool> IsCurrentOfferer() const override {
+ return mIsCurrentOfferer;
+ }
+
+ virtual bool IsIceRestarting() const override {
+ return !mOldIceUfrag.empty();
+ }
+
+ virtual std::set<std::pair<std::string, std::string>> GetLocalIceCredentials()
+ const override;
+
+ virtual void AddTransceiver(const JsepTransceiver& transceiver) override;
+
+ virtual bool CheckNegotiationNeeded() const override;
+
+ virtual void SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs)
+ override;
+
+ private:
+ friend class JsepSessionTest;
+ virtual const std::vector<JsepTransceiver>& GetTransceivers() const override {
+ return mTransceivers;
+ }
+
+ virtual std::vector<JsepTransceiver>& GetTransceivers() override {
+ return mTransceivers;
+ }
+
+ // Non-const so it can set mLastError
+ nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
+ void AddExtmap(SdpMediaSection* msection);
+ std::vector<SdpExtmapAttributeList::Extmap> GetRtpExtensions(
+ const SdpMediaSection& msection);
+ std::string GetNewMid();
+
+ void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection);
+ uint16_t GetNeverUsedExtmapEntry();
+ nsresult SetupIds();
+ 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, JsepSdpType type);
+ nsresult ValidateRemoteDescription(const Sdp& description);
+ nsresult ValidateOffer(const Sdp& offer);
+ nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer);
+ nsresult UpdateTransceiversFromRemoteDescription(const Sdp& remote);
+ Maybe<JsepTransceiver> GetTransceiverForLevel(size_t level) const;
+ Maybe<JsepTransceiver> GetTransceiverForMid(const std::string& mid) const;
+ Maybe<JsepTransceiver> GetTransceiverForLocal(size_t level);
+ Maybe<JsepTransceiver> GetTransceiverForRemote(
+ const SdpMediaSection& msection);
+ Maybe<JsepTransceiver> GetTransceiverWithTransport(
+ const std::string& transportId) const;
+ // The w3c and IETF specs have a lot of "magical" behavior that happens when
+ // addTrack is used. This was a deliberate design choice. Sadface.
+ Maybe<JsepTransceiver> FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic);
+ // Called for rollback of local description
+ void RollbackLocalOffer();
+ // Called for rollback of remote description
+ void RollbackRemoteOffer();
+ 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);
+ void EnsureMsid(Sdp* remote);
+ void SetupBundle(Sdp* sdp) const;
+ nsresult CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver, Sdp* local);
+ nsresult CreateAnswerMsection(const JsepAnswerOptions& options,
+ JsepTransceiver& transceiver,
+ const SdpMediaSection& remoteMsection,
+ Sdp* sdp);
+ nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
+ SdpSetupAttribute::Role* rolep);
+ nsresult MakeNegotiatedTransceiver(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ JsepTransceiver& transceiverOut);
+ void EnsureHasOwnTransport(const SdpMediaSection& msection,
+ JsepTransceiver& transceiver);
+ void CopyBundleTransports();
+
+ nsresult FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ JsepTransport* transport) const;
+
+ nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids);
+
+ mozilla::Sdp* GetParsedLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const;
+ mozilla::Sdp* GetParsedRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const;
+ const Sdp* GetAnswer() const;
+ void SetIceRestarting(bool restarting);
+
+ void InitTransceiver(JsepTransceiver& aTransceiver);
+
+ UniquePtr<JsepUuidGenerator> mUuidGen;
+ UniquePtr<Sdp> mGeneratedOffer; // Created but not set.
+ UniquePtr<Sdp> mGeneratedAnswer; // Created but not set.
+ UniquePtr<Sdp> mCurrentLocalDescription;
+ UniquePtr<Sdp> mCurrentRemoteDescription;
+ UniquePtr<Sdp> mPendingLocalDescription;
+ UniquePtr<Sdp> mPendingRemoteDescription;
+ std::vector<UniquePtr<JsepCodecDescription>> mSupportedCodecs;
+ SdpHelper mSdpHelper;
+ UniquePtr<SdpParser> mParser;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepTrack.cpp b/dom/media/webrtc/jsep/JsepTrack.cpp
new file mode 100644
index 0000000000..59c038cfc0
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrack.cpp
@@ -0,0 +1,706 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepTrackEncoding.h"
+
+#include <algorithm>
+
+namespace mozilla {
+void JsepTrack::GetNegotiatedPayloadTypes(
+ std::vector<uint16_t>* payloadTypes) const {
+ if (!mNegotiatedDetails) {
+ return;
+ }
+
+ for (const auto& encoding : mNegotiatedDetails->mEncodings) {
+ 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<UniquePtr<JsepCodecDescription>>& codecs,
+ std::vector<uint16_t>* payloadTypes) {
+ for (const auto& codec : codecs) {
+ uint16_t pt;
+ if (!codec->GetPtAsInt(&pt)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+ payloadTypes->push_back(pt);
+ }
+}
+
+void JsepTrack::EnsureNoDuplicatePayloadTypes(
+ std::vector<UniquePtr<JsepCodecDescription>>* codecs) {
+ std::set<std::string> uniquePayloadTypes;
+ for (auto& codec : *codecs) {
+ codec->EnsureNoDuplicatePayloadTypes(uniquePayloadTypes);
+ }
+}
+
+void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) {
+ while (mSsrcs.size() < aNumber) {
+ uint32_t ssrc, rtxSsrc;
+ if (!ssrcGenerator.GenerateSsrc(&ssrc) ||
+ !ssrcGenerator.GenerateSsrc(&rtxSsrc)) {
+ return;
+ }
+ mSsrcs.push_back(ssrc);
+ mSsrcToRtxSsrc[ssrc] = rtxSsrc;
+ MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
+ }
+}
+
+void JsepTrack::PopulateCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& prototype) {
+ mPrototypeCodecs.clear();
+ for (const auto& prototypeCodec : prototype) {
+ if (prototypeCodec->Type() == mType) {
+ mPrototypeCodecs.emplace_back(prototypeCodec->Clone());
+ mPrototypeCodecs.back()->mDirection = mDirection;
+ }
+ }
+
+ EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs);
+}
+
+void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* offer) {
+ AddToMsection(mPrototypeCodecs, offer);
+
+ if (mDirection == sdp::kSend) {
+ std::vector<std::string> rids;
+ if (offer->IsSending()) {
+ rids = mRids;
+ }
+
+ AddToMsection(rids, sdp::kSend, ssrcGenerator,
+ IsRtxEnabled(mPrototypeCodecs), offer);
+ }
+}
+
+void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer) {
+ // We do not modify mPrototypeCodecs here, since we're only creating an
+ // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
+ std::vector<UniquePtr<JsepCodecDescription>> codecs =
+ NegotiateCodecs(offer, true, Nothing());
+ if (codecs.empty()) {
+ return;
+ }
+
+ AddToMsection(codecs, answer);
+
+ if (mDirection == sdp::kSend) {
+ AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs),
+ answer);
+ }
+}
+
+void JsepTrack::SetRids(const std::vector<std::string>& aRids) {
+ MOZ_ASSERT(!aRids.empty());
+ if (!mRids.empty()) {
+ return;
+ }
+ mRids = aRids;
+}
+
+void JsepTrack::SetMaxEncodings(size_t aMax) {
+ mMaxEncodings = aMax;
+ if (mRids.size() > mMaxEncodings) {
+ mRids.resize(mMaxEncodings);
+ }
+}
+
+void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
+ const SdpMediaSection& aMsection) {
+ mInHaveRemote = true;
+ MOZ_ASSERT(mDirection == sdp::kRecv);
+ MOZ_ASSERT(aMsection.GetMediaType() !=
+ SdpMediaSection::MediaType::kApplication);
+ std::string error;
+ SdpHelper helper(&error);
+
+ mRemoteSetSendBit = aMsection.IsSending();
+ if (!mRemoteSetSendBit) {
+ mReceptive = false;
+ }
+
+ if (aMsection.IsSending()) {
+ (void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds);
+ } else {
+ mStreamIds.clear();
+ }
+
+ // We do this whether or not the track is active
+ SetCNAME(helper.GetCNAME(aMsection));
+ mSsrcs.clear();
+ if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ for (const auto& ssrcAttr : aMsection.GetAttributeList().GetSsrc().mSsrcs) {
+ mSsrcs.push_back(ssrcAttr.ssrc);
+ }
+ }
+
+ // Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite
+ // not being part of RFC 4588, this is how rtx is negotiated by libwebrtc
+ // and jitsi.
+ mSsrcToRtxSsrc.clear();
+ if (aMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcGroupAttribute)) {
+ for (const auto& group :
+ aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) {
+ if (group.semantics == SdpSsrcGroupAttributeList::kFid &&
+ group.ssrcs.size() == 2) {
+ // Ensure we have a "regular" ssrc for each rtx ssrc.
+ if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) !=
+ mSsrcs.end()) {
+ mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1];
+
+ // Remove rtx ssrcs from mSsrcs
+ auto res = std::remove_if(
+ mSsrcs.begin(), mSsrcs.end(),
+ [group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; });
+ mSsrcs.erase(res, mSsrcs.end());
+ }
+ }
+ }
+ }
+}
+
+void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) {
+ MOZ_ASSERT(mDirection == sdp::kRecv);
+
+ // TODO: Should more stuff live in here? Anything that needs to happen when we
+ // decide we're ready to receive packets should probably go in here.
+ mReceptive = aMsection.IsReceiving();
+}
+
+void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
+ const SdpMediaSection& aRemoteMsection) {
+ mInHaveRemote = true;
+ if (mType == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ std::vector<SdpRidAttributeList::Rid> rids;
+
+ // TODO: Current language in webrtc-pc is completely broken, and so I will
+ // not be quoting it here.
+ if ((mType == SdpMediaSection::kVideo) &&
+ aRemoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute)) {
+ // Note: webrtc-pc does not appear to support the full IETF simulcast
+ // spec. In particular, the IETF simulcast spec supports requesting
+ // multiple different sets of encodings. For example, "a=simulcast:send
+ // 1,2;3,4;5,6" means that there are three simulcast streams, the first of
+ // which can use either rid 1 or 2 (but not both), the second of which can
+ // use rid 3 or 4 (but not both), and the third of which can use rid 5 or
+ // 6 (but not both). webrtc-pc does not support this either/or stuff for
+ // rid; each simulcast stream gets exactly one rid.
+ // Also, webrtc-pc does not support the '~' pause syntax at all
+ // See https://github.com/w3c/webrtc-pc/issues/2769
+ GetRids(aRemoteMsection, sdp::kRecv, &rids);
+ }
+
+ if (mRids.empty()) {
+ // Initial configuration
+ for (const auto& ridAttr : rids) {
+ // TODO: Spec might change, making a length > 16 invalid SDP.
+ std::string dummy;
+ if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy) &&
+ ridAttr.id.size() <= SdpRidAttributeList::kMaxRidLength) {
+ mRids.push_back(ridAttr.id);
+ }
+ }
+ if (mRids.size() > mMaxEncodings) {
+ mRids.resize(mMaxEncodings);
+ }
+ } else {
+ // JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay
+ // attention to reordering.
+ std::vector<std::string> newRids;
+ for (const auto& ridAttr : rids) {
+ for (const auto& oldRid : mRids) {
+ if (oldRid == ridAttr.id) {
+ newRids.push_back(oldRid);
+ break;
+ }
+ }
+ }
+ mRids = std::move(newRids);
+ }
+
+ if (mRids.empty()) {
+ mRids.push_back("");
+ }
+
+ UpdateSsrcs(aSsrcGenerator, mRids.size());
+}
+
+void JsepTrack::AddToMsection(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ SdpMediaSection* msection) const {
+ MOZ_ASSERT(msection->GetMediaType() == mType);
+ MOZ_ASSERT(!codecs.empty());
+
+ for (const auto& codec : codecs) {
+ codec->AddToMediaSection(*msection);
+ }
+
+ if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) &&
+ msection->IsSending()) {
+ if (mStreamIds.empty()) {
+ msection->AddMsid("-", mTrackId);
+ } else {
+ for (const std::string& streamId : mStreamIds) {
+ msection->AddMsid(streamId, mTrackId);
+ }
+ }
+ }
+}
+
+void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
+ MOZ_ASSERT(mDirection == sdp::kSend);
+ MOZ_ASSERT(mType != SdpMediaSection::kApplication);
+ size_t numSsrcs = std::max<size_t>(encodings, 1U);
+
+ EnsureSsrcs(ssrcGenerator, numSsrcs);
+ PruneSsrcs(numSsrcs);
+ if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) {
+ mNegotiatedDetails->TruncateEncodings(numSsrcs);
+ }
+
+ MOZ_ASSERT(!mSsrcs.empty());
+}
+
+void JsepTrack::PruneSsrcs(size_t aNumSsrcs) {
+ mSsrcs.resize(aNumSsrcs);
+
+ // We might have duplicate entries in mSsrcs, so we need to resize first and
+ // then remove ummatched rtx ssrcs.
+ auto itor = mSsrcToRtxSsrc.begin();
+ while (itor != mSsrcToRtxSsrc.end()) {
+ if (std::find(mSsrcs.begin(), mSsrcs.end(), itor->first) == mSsrcs.end()) {
+ itor = mSsrcToRtxSsrc.erase(itor);
+ } else {
+ ++itor;
+ }
+ }
+}
+
+bool JsepTrack::IsRtxEnabled(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const {
+ for (const auto& codec : codecs) {
+ if (codec->Type() == SdpMediaSection::kVideo &&
+ static_cast<const JsepVideoCodecDescription*>(codec.get())
+ ->mRtxEnabled) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
+ sdp::Direction direction,
+ SsrcGenerator& ssrcGenerator, bool rtxEnabled,
+ SdpMediaSection* msection) {
+ if (aRids.size() > 1) {
+ UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
+ UniquePtr<SdpRidAttributeList> ridAttrs(new SdpRidAttributeList);
+ for (const std::string& rid : aRids) {
+ SdpRidAttributeList::Rid ridAttr;
+ ridAttr.id = rid;
+ ridAttr.direction = direction;
+ ridAttrs->mRids.push_back(ridAttr);
+
+ SdpSimulcastAttribute::Version version;
+ version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false));
+ if (direction == sdp::kSend) {
+ simulcast->sendVersions.push_back(version);
+ } else {
+ simulcast->recvVersions.push_back(version);
+ }
+ }
+
+ msection->GetAttributeList().SetAttribute(simulcast.release());
+ msection->GetAttributeList().SetAttribute(ridAttrs.release());
+ }
+
+ bool requireRtxSsrcs = rtxEnabled && msection->IsSending();
+
+ if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) {
+ UpdateSsrcs(ssrcGenerator, aRids.size());
+
+ if (requireRtxSsrcs) {
+ MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
+ std::vector<uint32_t> allSsrcs;
+ UniquePtr<SdpSsrcGroupAttributeList> group(new SdpSsrcGroupAttributeList);
+ for (const auto& ssrc : mSsrcs) {
+ const auto rtxSsrc = mSsrcToRtxSsrc[ssrc];
+ allSsrcs.push_back(ssrc);
+ allSsrcs.push_back(rtxSsrc);
+ group->PushEntry(SdpSsrcGroupAttributeList::kFid, {ssrc, rtxSsrc});
+ }
+ msection->SetSsrcs(allSsrcs, mCNAME);
+ msection->GetAttributeList().SetAttribute(group.release());
+ } else {
+ msection->SetSsrcs(mSsrcs, mCNAME);
+ }
+ }
+}
+
+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;
+ }
+
+ // RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute.
+ // So, while this is obviously silly, we should be prepared for it and
+ // ignore those duplicate rids.
+ std::set<std::string> uniqueRids;
+ for (const SdpSimulcastAttribute::Version& version : *versions) {
+ if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) {
+ // We validate that rids are present (and sane) elsewhere.
+ rids->push_back(*msection.FindRid(version.choices[0].rid));
+ uniqueRids.insert(version.choices[0].rid);
+ }
+ }
+}
+
+void JsepTrack::CreateEncodings(
+ const SdpMediaSection& remote,
+ const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* negotiatedDetails) {
+ negotiatedDetails->mTias = remote.GetBandwidth("TIAS");
+
+ webrtc::RtcpMode rtcpMode = webrtc::RtcpMode::kCompound;
+ // rtcp-rsize (video only)
+ if (remote.GetMediaType() == SdpMediaSection::kVideo &&
+ remote.GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute)) {
+ rtcpMode = webrtc::RtcpMode::kReducedSize;
+ }
+ negotiatedDetails->mRtpRtcpConf = RtpRtcpConfig(rtcpMode);
+
+ // TODO add support for b=AS if TIAS is not set (bug 976521)
+
+ if (mRids.empty()) {
+ mRids.push_back("");
+ }
+
+ size_t numEncodings = mRids.size();
+
+ // Drop SSRCs if fewer RIDs were offered than we have encodings
+ if (mSsrcs.size() > numEncodings) {
+ PruneSsrcs(numEncodings);
+ }
+
+ // For each stream make sure we have an encoding, and configure
+ // that encoding appropriately.
+ for (size_t i = 0; i < numEncodings; ++i) {
+ UniquePtr<JsepTrackEncoding> encoding(new JsepTrackEncoding);
+ if (mRids.size() > i) {
+ encoding->mRid = mRids[i];
+ }
+ for (const auto& codec : negotiatedCodecs) {
+ encoding->AddCodec(*codec);
+ }
+ negotiatedDetails->mEncodings.push_back(std::move(encoding));
+ }
+}
+
+std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::GetCodecClones() const {
+ std::vector<UniquePtr<JsepCodecDescription>> clones;
+ for (const auto& codec : mPrototypeCodecs) {
+ clones.emplace_back(codec->Clone());
+ }
+ return clones;
+}
+
+static bool CompareCodec(const UniquePtr<JsepCodecDescription>& lhs,
+ const UniquePtr<JsepCodecDescription>& rhs) {
+ return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
+}
+
+void JsepTrack::MaybeStoreCodecToLog(const std::string& codec,
+ SdpMediaSection::MediaType type) {
+ // We are logging ulpfec and red elsewhere and will not log rtx.
+ if (!nsCRT::strcasecmp(codec.c_str(), "ulpfec") ||
+ !nsCRT::strcasecmp(codec.c_str(), "red") ||
+ !nsCRT::strcasecmp(codec.c_str(), "rtx")) {
+ return;
+ }
+
+ if (type == SdpMediaSection::kVideo) {
+ if (nsCRT::strcasestr(codec.c_str(), "fec") && mFecCodec.empty()) {
+ mFecCodec = codec;
+ } else if (!nsCRT::strcasestr(codec.c_str(), "fec") &&
+ mVideoPreferredCodec.empty()) {
+ mVideoPreferredCodec = codec;
+ }
+ } else if (type == SdpMediaSection::kAudio && mAudioPreferredCodec.empty()) {
+ mAudioPreferredCodec = codec;
+ }
+}
+
+std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::NegotiateCodecs(
+ const SdpMediaSection& remote, bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> local) {
+ std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs;
+ std::vector<UniquePtr<JsepCodecDescription>> newPrototypeCodecs;
+ // "Pseudo codecs" include things that aren't actually stand-alone codecs (ie;
+ // ulpfec, red, rtx).
+ std::vector<UniquePtr<JsepCodecDescription>> negotiatedPseudoCodecs;
+ std::vector<UniquePtr<JsepCodecDescription>> newPrototypePseudoCodecs;
+
+ // Outer loop establishes the remote side's preference
+ for (const std::string& fmt : remote.GetFormats()) {
+ // Decide if we want to store this codec for logging.
+ const auto* entry = remote.FindRtpmap(fmt);
+ if (entry) {
+ MaybeStoreCodecToLog(entry->name, remote.GetMediaType());
+ }
+
+ for (auto& codec : mPrototypeCodecs) {
+ if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
+ continue;
+ }
+
+ // First codec of ours that matches. See if we can negotiate it.
+ UniquePtr<JsepCodecDescription> clone(codec->Clone());
+ if (clone->Negotiate(fmt, remote, remoteIsOffer, local)) {
+ // If negotiation succeeded, remember the payload type the other side
+ // used for reoffers.
+ codec->mDefaultPt = fmt;
+
+ // Remember whether we negotiated rtx and the associated pt for later.
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ JsepVideoCodecDescription* cloneVideoCodec =
+ static_cast<JsepVideoCodecDescription*>(clone.get());
+ bool useRtx =
+ mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false);
+ videoCodec->mRtxEnabled = useRtx && cloneVideoCodec->mRtxEnabled;
+ videoCodec->mRtxPayloadType = cloneVideoCodec->mRtxPayloadType;
+ }
+
+ // Moves the codec out of mPrototypeCodecs, leaving an empty
+ // UniquePtr, so we don't use it again. Also causes successfully
+ // negotiated codecs to be placed up front in the future.
+ if (codec->mName == "red" || codec->mName == "ulpfec" ||
+ codec->mName == "rtx") {
+ newPrototypePseudoCodecs.emplace_back(std::move(codec));
+ negotiatedPseudoCodecs.emplace_back(std::move(clone));
+ } else {
+ newPrototypeCodecs.emplace_back(std::move(codec));
+ negotiatedCodecs.emplace_back(std::move(clone));
+ }
+ break;
+ }
+ }
+ }
+
+ if (negotiatedCodecs.empty()) {
+ // We don't have any real codecs. Clearing so we don't attempt to create a
+ // connection signaling only RED and/or ULPFEC.
+ negotiatedPseudoCodecs.clear();
+ }
+
+ // Put the pseudo codecs at the back
+ for (auto& pseudoCodec : negotiatedPseudoCodecs) {
+ negotiatedCodecs.emplace_back(std::move(pseudoCodec));
+ }
+
+ for (auto& pseudoCodec : newPrototypePseudoCodecs) {
+ newPrototypeCodecs.emplace_back(std::move(pseudoCodec));
+ }
+
+ // newPrototypeCodecs contains just the negotiated stuff so far. Add the rest.
+ for (auto& codec : mPrototypeCodecs) {
+ if (codec) {
+ newPrototypeCodecs.emplace_back(std::move(codec));
+ }
+ }
+
+ // Negotiated stuff is up front, so it will take precedence when ensuring
+ // there are no duplicate payload types.
+ EnsureNoDuplicatePayloadTypes(&newPrototypeCodecs);
+ std::swap(newPrototypeCodecs, mPrototypeCodecs);
+
+ // 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 : negotiatedCodecs) {
+ if (codec->mName == "red") {
+ red = static_cast<JsepVideoCodecDescription*>(codec.get());
+ } else if (codec->mName == "ulpfec") {
+ ulpfec = static_cast<JsepVideoCodecDescription*>(codec.get());
+ } else if (codec->mName == "telephone-event") {
+ dtmf = static_cast<JsepAudioCodecDescription*>(codec.get());
+ }
+ }
+ // 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 : negotiatedCodecs) {
+ if (codec->mName != "red" && codec->mName != "ulpfec") {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt,
+ red->mRtxPayloadType);
+ }
+ }
+ }
+
+ // 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 : negotiatedCodecs) {
+ JsepAudioCodecDescription* audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec.get());
+ audioCodec->mDtmfEnabled = true;
+ }
+ }
+
+ // Make sure strongly preferred codecs are up front, overriding the remote
+ // side's preference.
+ std::stable_sort(negotiatedCodecs.begin(), negotiatedCodecs.end(),
+ CompareCodec);
+
+ if (!red) {
+ // No red, remove ulpfec
+ negotiatedCodecs.erase(
+ std::remove_if(negotiatedCodecs.begin(), negotiatedCodecs.end(),
+ [ulpfec](const UniquePtr<JsepCodecDescription>& codec) {
+ return codec.get() == ulpfec;
+ }),
+ negotiatedCodecs.end());
+ // Make sure there's no dangling ptr here
+ ulpfec = nullptr;
+ }
+
+ return negotiatedCodecs;
+}
+
+nsresult JsepTrack::Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote,
+ const SdpMediaSection& local) {
+ std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs =
+ NegotiateCodecs(remote, &answer != &remote, SomeRef(local));
+
+ if (negotiatedCodecs.empty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
+ MakeUnique<JsepTrackNegotiatedDetails>();
+
+ CreateEncodings(remote, negotiatedCodecs, negotiatedDetails.get());
+
+ if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
+ SdpDirectionAttribute::Direction direction = extmapAttr.direction;
+ if (&remote == &answer) {
+ // Answer is remote, we need to flip this.
+ direction = reverse(direction);
+ }
+
+ if (direction & mDirection) {
+ negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
+ }
+ }
+ }
+
+ mInHaveRemote = false;
+ mNegotiatedDetails = std::move(negotiatedDetails);
+ return NS_OK;
+}
+
+// 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(std::vector<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 (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/dom/media/webrtc/jsep/JsepTrack.h b/dom/media/webrtc/jsep/JsepTrack.h
new file mode 100644
index 0000000000..d11735f43a
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrack.h
@@ -0,0 +1,323 @@
+/* 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 <functional>
+#include <algorithm>
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <mozilla/UniquePtr.h>
+#include "mozilla/Preferences.h"
+#include "nsError.h"
+
+#include "jsep/JsepTrackEncoding.h"
+#include "jsep/SsrcGenerator.h"
+#include "sdp/Sdp.h"
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpMediaSection.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+namespace mozilla {
+
+class JsepTrackNegotiatedDetails {
+ public:
+ JsepTrackNegotiatedDetails()
+ : mTias(0), mRtpRtcpConf(webrtc::RtcpMode::kCompound) {}
+
+ JsepTrackNegotiatedDetails(const JsepTrackNegotiatedDetails& orig)
+ : mExtmap(orig.mExtmap),
+ mUniquePayloadTypes(orig.mUniquePayloadTypes),
+ mTias(orig.mTias),
+ mRtpRtcpConf(orig.mRtpRtcpConf) {
+ for (const auto& encoding : orig.mEncodings) {
+ mEncodings.emplace_back(new JsepTrackEncoding(*encoding));
+ }
+ }
+
+ size_t GetEncodingCount() const { return mEncodings.size(); }
+
+ const JsepTrackEncoding& GetEncoding(size_t index) const {
+ MOZ_RELEASE_ASSERT(index < mEncodings.size());
+ return *mEncodings[index];
+ }
+
+ void TruncateEncodings(size_t aSize) {
+ if (mEncodings.size() < aSize) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ mEncodings.resize(aSize);
+ }
+
+ 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;
+ }
+
+ void ForEachRTPHeaderExtension(
+ const std::function<void(const SdpExtmapAttributeList::Extmap& extmap)>&
+ fn) const {
+ for (auto entry : mExtmap) {
+ fn(entry.second);
+ }
+ }
+
+ std::vector<uint8_t> GetUniquePayloadTypes() const {
+ return mUniquePayloadTypes;
+ }
+
+ uint32_t GetTias() const { return mTias; }
+
+ RtpRtcpConfig GetRtpRtcpConfig() const { return mRtpRtcpConf; }
+
+ private:
+ friend class JsepTrack;
+
+ std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
+ std::vector<uint8_t> mUniquePayloadTypes;
+ std::vector<UniquePtr<JsepTrackEncoding>> mEncodings;
+ uint32_t mTias; // bits per second
+ RtpRtcpConfig mRtpRtcpConf;
+};
+
+class JsepTrack {
+ public:
+ JsepTrack(mozilla::SdpMediaSection::MediaType type, sdp::Direction direction)
+ : mType(type),
+ mDirection(direction),
+ mActive(false),
+ mRemoteSetSendBit(false) {}
+
+ virtual ~JsepTrack() {}
+
+ void UpdateStreamIds(const std::vector<std::string>& streamIds) {
+ mStreamIds = streamIds;
+ }
+
+ void SetTrackId(const std::string& aTrackId) { mTrackId = aTrackId; }
+
+ void ClearStreamIds() { mStreamIds.clear(); }
+
+ void RecvTrackSetRemote(const Sdp& aSdp, const SdpMediaSection& aMsection);
+ void RecvTrackSetLocal(const SdpMediaSection& aMsection);
+
+ // This is called whenever a remote description is set; we do not wait for
+ // offer/answer to complete, since there's nothing to actually negotiate here.
+ void SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
+ const SdpMediaSection& aRemoteMsection);
+
+ JsepTrack(const JsepTrack& orig) { *this = orig; }
+
+ JsepTrack(JsepTrack&& orig) = default;
+ JsepTrack& operator=(JsepTrack&& rhs) = default;
+
+ JsepTrack& operator=(const JsepTrack& rhs) {
+ if (this != &rhs) {
+ mType = rhs.mType;
+ mStreamIds = rhs.mStreamIds;
+ mTrackId = rhs.mTrackId;
+ mCNAME = rhs.mCNAME;
+ mDirection = rhs.mDirection;
+ mRids = rhs.mRids;
+ mSsrcs = rhs.mSsrcs;
+ mSsrcToRtxSsrc = rhs.mSsrcToRtxSsrc;
+ mActive = rhs.mActive;
+ mRemoteSetSendBit = rhs.mRemoteSetSendBit;
+ mReceptive = rhs.mReceptive;
+ mMaxEncodings = rhs.mMaxEncodings;
+ mInHaveRemote = rhs.mInHaveRemote;
+ mRtxIsAllowed = rhs.mRtxIsAllowed;
+ mFecCodec = rhs.mFecCodec;
+ mAudioPreferredCodec = rhs.mAudioPreferredCodec;
+ mVideoPreferredCodec = rhs.mVideoPreferredCodec;
+
+ mPrototypeCodecs.clear();
+ for (const auto& codec : rhs.mPrototypeCodecs) {
+ mPrototypeCodecs.emplace_back(codec->Clone());
+ }
+ if (rhs.mNegotiatedDetails) {
+ mNegotiatedDetails.reset(
+ new JsepTrackNegotiatedDetails(*rhs.mNegotiatedDetails));
+ }
+ }
+ return *this;
+ }
+
+ virtual mozilla::SdpMediaSection::MediaType GetMediaType() const {
+ return mType;
+ }
+
+ virtual const std::vector<std::string>& GetStreamIds() const {
+ return mStreamIds;
+ }
+
+ 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 std::vector<uint32_t> GetRtxSsrcs() const {
+ std::vector<uint32_t> result;
+ if (mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false) &&
+ !mSsrcToRtxSsrc.empty()) {
+ MOZ_ASSERT(mSsrcToRtxSsrc.size() == mSsrcs.size());
+ for (const auto ssrc : mSsrcs) {
+ auto it = mSsrcToRtxSsrc.find(ssrc);
+ MOZ_ASSERT(it != mSsrcToRtxSsrc.end());
+ result.push_back(it->second);
+ }
+ }
+ return result;
+ }
+
+ virtual void EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber);
+
+ bool GetActive() const { return mActive; }
+
+ void SetActive(bool active) { mActive = active; }
+
+ bool GetRemoteSetSendBit() const { return mRemoteSetSendBit; }
+ bool GetReceptive() const { return mReceptive; }
+
+ virtual void PopulateCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& prototype);
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction func) {
+ std::for_each(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), func);
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate sorter) {
+ std::stable_sort(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), sorter);
+ }
+
+ // These two are non-const because this is where ssrcs are chosen.
+ virtual void AddToOffer(SsrcGenerator& ssrcGenerator, SdpMediaSection* offer);
+ virtual void AddToAnswer(const SdpMediaSection& offer,
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer);
+
+ virtual nsresult Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote,
+ const SdpMediaSection& local);
+ static void SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks);
+ virtual void GetNegotiatedPayloadTypes(
+ std::vector<uint16_t>* payloadTypes) const;
+
+ // 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(); }
+
+ void SetRids(const std::vector<std::string>& aRids);
+ void ClearRids() { mRids.clear(); }
+ const std::vector<std::string>& GetRids() const { return mRids; }
+
+ void AddToMsection(const std::vector<std::string>& aRids,
+ sdp::Direction direction, SsrcGenerator& ssrcGenerator,
+ bool rtxEnabled, SdpMediaSection* msection);
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; }
+
+ void SetMaxEncodings(size_t aMax);
+ bool IsInHaveRemote() const { return mInHaveRemote; }
+
+ const std::string& GetFecCodecName() { return mFecCodec; }
+ const std::string& GetAudioPreferredCodec() { return mAudioPreferredCodec; }
+ const std::string& GetVideoPreferredCodec() { return mVideoPreferredCodec; }
+
+ private:
+ std::vector<UniquePtr<JsepCodecDescription>> GetCodecClones() const;
+ static void EnsureNoDuplicatePayloadTypes(
+ std::vector<UniquePtr<JsepCodecDescription>>* codecs);
+ static void GetPayloadTypes(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ std::vector<uint16_t>* pts);
+ void AddToMsection(const std::vector<UniquePtr<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<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* details);
+ // Identifies codecs we want to store for logging purposes.
+ void MaybeStoreCodecToLog(const std::string& codec,
+ SdpMediaSection::MediaType type);
+ virtual std::vector<UniquePtr<JsepCodecDescription>> NegotiateCodecs(
+ const SdpMediaSection& remote, bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> local);
+
+ void UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings);
+ void PruneSsrcs(size_t aNumSsrcs);
+ bool IsRtxEnabled(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const;
+
+ mozilla::SdpMediaSection::MediaType mType;
+ // These are the ids that everyone outside of JsepSession care about
+ std::vector<std::string> mStreamIds;
+ std::string mTrackId;
+ std::string mCNAME;
+ sdp::Direction mDirection;
+ std::vector<UniquePtr<JsepCodecDescription>> mPrototypeCodecs;
+ // List of rids. May be initially populated from JS, or from a remote SDP.
+ // Can be updated by remote SDP. If no negotiation has taken place at all,
+ // this will be empty. If negotiation has taken place, but no simulcast
+ // attr was negotiated, this will contain the empty string as a single
+ // element. If a simulcast attribute was negotiated, this will contain the
+ // negotiated rids.
+ std::vector<std::string> mRids;
+ UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
+ std::vector<uint32_t> mSsrcs;
+ std::map<uint32_t, uint32_t> mSsrcToRtxSsrc;
+ bool mActive;
+ bool mRemoteSetSendBit;
+ // This is used to drive RTCRtpTransceiver.[[Receptive]]. Basically, this
+ // denotes whether we are prepared to receive RTP. When we apply a local
+ // description with the recv bit set, this is set to true, even if we have
+ // not seen the remote description yet. If we apply either a local or remote
+ // description without the recv bit set (from our perspective), this is set
+ // to false.
+ bool mReceptive = false;
+ size_t mMaxEncodings = 3;
+ bool mInHaveRemote = false;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+
+ // Codec names for logging
+ std::string mFecCodec;
+ std::string mAudioPreferredCodec;
+ std::string mVideoPreferredCodec;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepTrackEncoding.h b/dom/media/webrtc/jsep/JsepTrackEncoding.h
new file mode 100644
index 0000000000..c17dd8534e
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrackEncoding.h
@@ -0,0 +1,62 @@
+/* -*- 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 "jsep/JsepCodecDescription.h"
+#include "common/EncodingConstraints.h"
+
+#include <vector>
+
+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:
+ JsepTrackEncoding() = default;
+ JsepTrackEncoding(const JsepTrackEncoding& orig) { *this = orig; }
+
+ JsepTrackEncoding(JsepTrackEncoding&& aOrig) = default;
+
+ JsepTrackEncoding& operator=(const JsepTrackEncoding& aRhs) {
+ if (this != &aRhs) {
+ mRid = aRhs.mRid;
+ mCodecs.clear();
+ for (const auto& codec : aRhs.mCodecs) {
+ mCodecs.emplace_back(codec->Clone());
+ }
+ }
+ return *this;
+ }
+
+ const std::vector<UniquePtr<JsepCodecDescription>>& GetCodecs() const {
+ return mCodecs;
+ }
+
+ void AddCodec(const JsepCodecDescription& codec) {
+ mCodecs.emplace_back(codec.Clone());
+ }
+
+ bool HasFormat(const std::string& format) const {
+ for (const auto& codec : mCodecs) {
+ if (codec->mDefaultPt == format) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::string mRid;
+
+ private:
+ std::vector<UniquePtr<JsepCodecDescription>> mCodecs;
+};
+} // namespace mozilla
+
+#endif // _JESPTRACKENCODING_H_
diff --git a/dom/media/webrtc/jsep/JsepTransceiver.h b/dom/media/webrtc/jsep/JsepTransceiver.h
new file mode 100644
index 0000000000..45afcee04f
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTransceiver.h
@@ -0,0 +1,220 @@
+/* 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 _JSEPTRANSCEIVER_H_
+#define _JSEPTRANSCEIVER_H_
+
+#include <string>
+
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/Sdp.h"
+#include "jsep/JsepTransport.h"
+#include "jsep/JsepTrack.h"
+
+#include "nsError.h"
+
+namespace mozilla {
+
+class JsepUuidGenerator {
+ public:
+ virtual ~JsepUuidGenerator() = default;
+ virtual bool Generate(std::string* id) = 0;
+ virtual JsepUuidGenerator* Clone() const = 0;
+};
+
+class JsepTransceiver {
+ public:
+ explicit JsepTransceiver(SdpMediaSection::MediaType type,
+ JsepUuidGenerator& aUuidGen,
+ SdpDirectionAttribute::Direction jsDirection =
+ SdpDirectionAttribute::kSendrecv)
+ : mJsDirection(jsDirection),
+ mSendTrack(type, sdp::kSend),
+ mRecvTrack(type, sdp::kRecv) {
+ if (!aUuidGen.Generate(&mUuid)) {
+ MOZ_CRASH();
+ }
+ }
+
+ JsepTransceiver(const JsepTransceiver& orig) = default;
+ JsepTransceiver(JsepTransceiver&& orig) = default;
+ JsepTransceiver& operator=(const JsepTransceiver& aRhs) = default;
+ JsepTransceiver& operator=(JsepTransceiver&& aRhs) = default;
+
+ ~JsepTransceiver() = default;
+
+ void Rollback(JsepTransceiver& oldTransceiver, bool aRemote) {
+ MOZ_ASSERT(oldTransceiver.GetMediaType() == GetMediaType());
+ MOZ_ASSERT(!oldTransceiver.IsNegotiated() || !oldTransceiver.HasLevel() ||
+ !HasLevel() || oldTransceiver.GetLevel() == GetLevel());
+ mTransport = oldTransceiver.mTransport;
+ if (aRemote) {
+ mLevel = oldTransceiver.mLevel;
+ mBundleLevel = oldTransceiver.mBundleLevel;
+ mSendTrack = oldTransceiver.mSendTrack;
+ }
+ mRecvTrack = oldTransceiver.mRecvTrack;
+
+ // Don't allow rollback to re-associate a transceiver.
+ if (!oldTransceiver.IsAssociated()) {
+ Disassociate();
+ }
+ }
+
+ bool IsAssociated() const { return !mMid.empty(); }
+
+ const std::string& GetMid() const {
+ MOZ_ASSERT(IsAssociated());
+ return mMid;
+ }
+
+ void Associate(const std::string& mid) {
+ MOZ_ASSERT(HasLevel());
+ mMid = mid;
+ }
+
+ void Disassociate() { mMid.clear(); }
+
+ bool HasLevel() const { return mLevel != SIZE_MAX; }
+
+ void SetLevel(size_t level) {
+ MOZ_ASSERT(level != SIZE_MAX);
+ MOZ_ASSERT(!HasLevel());
+ MOZ_ASSERT(!IsStopped());
+
+ mLevel = level;
+ }
+
+ void ClearLevel() {
+ MOZ_ASSERT(!IsAssociated());
+ mLevel = SIZE_MAX;
+ mBundleLevel = SIZE_MAX;
+ }
+
+ size_t GetLevel() const {
+ MOZ_ASSERT(HasLevel());
+ return mLevel;
+ }
+
+ void Stop() { mStopping = true; }
+
+ bool IsStopping() const { return mStopping; }
+
+ bool IsStopped() const { return mStopped; }
+
+ void SetStopped() { mStopped = true; }
+
+ bool IsFreeToUse() const { return !mStopping && !mStopped && !HasLevel(); }
+
+ void RestartDatachannelTransceiver() {
+ MOZ_RELEASE_ASSERT(GetMediaType() == SdpMediaSection::kApplication);
+ mStopping = false;
+ mStopped = false;
+ mCanRecycleMyMsection = false;
+ }
+
+ void SetRemoved() { mRemoved = true; }
+
+ bool IsRemoved() const { return mRemoved; }
+
+ bool HasBundleLevel() const { return mBundleLevel != SIZE_MAX; }
+
+ size_t BundleLevel() const {
+ MOZ_ASSERT(HasBundleLevel());
+ return mBundleLevel;
+ }
+
+ void SetBundleLevel(size_t aBundleLevel) {
+ MOZ_ASSERT(aBundleLevel != SIZE_MAX);
+ mBundleLevel = aBundleLevel;
+ }
+
+ void ClearBundleLevel() { mBundleLevel = SIZE_MAX; }
+
+ size_t GetTransportLevel() const {
+ MOZ_ASSERT(HasLevel());
+ if (HasBundleLevel()) {
+ return BundleLevel();
+ }
+ return GetLevel();
+ }
+
+ void SetAddTrackMagic() { mAddTrackMagic = true; }
+
+ bool HasAddTrackMagic() const { return mAddTrackMagic; }
+
+ void SetOnlyExistsBecauseOfSetRemote(bool aValue) {
+ mOnlyExistsBecauseOfSetRemote = aValue;
+ }
+
+ bool OnlyExistsBecauseOfSetRemote() const {
+ return mOnlyExistsBecauseOfSetRemote;
+ }
+
+ void SetNegotiated() {
+ MOZ_ASSERT(IsAssociated());
+ MOZ_ASSERT(HasLevel());
+ mNegotiated = true;
+ }
+
+ bool IsNegotiated() const { return mNegotiated; }
+
+ void SetCanRecycleMyMsection() { mCanRecycleMyMsection = true; }
+
+ bool CanRecycleMyMsection() const { return mCanRecycleMyMsection; }
+
+ const std::string& GetUuid() const { return mUuid; }
+
+ // Convenience function
+ SdpMediaSection::MediaType GetMediaType() const {
+ MOZ_ASSERT(mRecvTrack.GetMediaType() == mSendTrack.GetMediaType());
+ return mRecvTrack.GetMediaType();
+ }
+
+ bool HasTransport() const { return mTransport.mComponents != 0; }
+
+ bool HasOwnTransport() const {
+ if (HasTransport() &&
+ (!HasBundleLevel() || (GetLevel() == BundleLevel()))) {
+ return true;
+ }
+ return false;
+ }
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) {
+ mSendTrack.SetRtxIsAllowed(aRtxIsAllowed);
+ mRecvTrack.SetRtxIsAllowed(aRtxIsAllowed);
+ }
+
+ // This is the direction JS wants. It might not actually happen.
+ SdpDirectionAttribute::Direction mJsDirection;
+
+ JsepTrack mSendTrack;
+ JsepTrack mRecvTrack;
+ JsepTransport mTransport;
+
+ private:
+ std::string mUuid;
+ // Stuff that is not negotiated
+ std::string mMid;
+ size_t mLevel = SIZE_MAX; // SIZE_MAX if no level
+ // Is this track pair sharing a transport with another?
+ size_t mBundleLevel = SIZE_MAX; // SIZE_MAX if no bundle level
+ // The w3c and IETF specs have a lot of "magical" behavior that happens
+ // when addTrack is used to create a transceiver. This was a deliberate
+ // design choice. Sadface.
+ bool mAddTrackMagic = false;
+ bool mOnlyExistsBecauseOfSetRemote = false;
+ bool mStopping = false;
+ bool mStopped = false;
+ bool mRemoved = false;
+ bool mNegotiated = false;
+ bool mCanRecycleMyMsection = false;
+};
+
+} // namespace mozilla
+
+#endif // _JSEPTRANSCEIVER_H_
diff --git a/dom/media/webrtc/jsep/JsepTransport.h b/dom/media/webrtc/jsep/JsepTransport.h
new file mode 100644
index 0000000000..67690b1fd4
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTransport.h
@@ -0,0 +1,102 @@
+/* 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 "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) {}
+
+ JsepTransport(const JsepTransport& orig) { *this = orig; }
+
+ ~JsepTransport() {}
+
+ JsepTransport& operator=(const JsepTransport& orig) {
+ if (this != &orig) {
+ mIce.reset(orig.mIce ? new JsepIceTransport(*orig.mIce) : nullptr);
+ mDtls.reset(orig.mDtls ? new JsepDtlsTransport(*orig.mDtls) : nullptr);
+ mTransportId = orig.mTransportId;
+ mComponents = orig.mComponents;
+ mLocalUfrag = orig.mLocalUfrag;
+ mLocalPwd = orig.mLocalPwd;
+ }
+ return *this;
+ }
+
+ void Close() {
+ mComponents = 0;
+ mTransportId.clear();
+ mIce.reset();
+ mDtls.reset();
+ mLocalUfrag.clear();
+ mLocalPwd.clear();
+ }
+
+ // 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;
+ std::string mLocalUfrag;
+ std::string mLocalPwd;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/SsrcGenerator.cpp b/dom/media/webrtc/jsep/SsrcGenerator.cpp
new file mode 100644
index 0000000000..50025007d1
--- /dev/null
+++ b/dom/media/webrtc/jsep/SsrcGenerator.cpp
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/SsrcGenerator.h"
+#include "pk11pub.h"
+
+namespace mozilla {
+
+bool SsrcGenerator::GenerateSsrc(uint32_t* ssrc) {
+ do {
+ SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(ssrc),
+ sizeof(uint32_t));
+ if (rv != SECSuccess) {
+ return false;
+ }
+ } while (mSsrcs.count(*ssrc));
+ mSsrcs.insert(*ssrc);
+
+ return true;
+}
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsep/SsrcGenerator.h b/dom/media/webrtc/jsep/SsrcGenerator.h
new file mode 100644
index 0000000000..b106358f32
--- /dev/null
+++ b/dom/media/webrtc/jsep/SsrcGenerator.h
@@ -0,0 +1,20 @@
+/* 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 _SSRCGENERATOR_H_
+#define _SSRCGENERATOR_H_
+
+#include <set>
+
+namespace mozilla {
+class SsrcGenerator {
+ public:
+ bool GenerateSsrc(uint32_t* ssrc);
+
+ private:
+ std::set<uint32_t> mSsrcs;
+};
+} // namespace mozilla
+
+#endif // _SSRCGENERATOR_H_
diff --git a/dom/media/webrtc/jsep/moz.build b/dom/media/webrtc/jsep/moz.build
new file mode 100644
index 0000000000..750ac76a30
--- /dev/null
+++ b/dom/media/webrtc/jsep/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc",
+ "/media/webrtc",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+ "/third_party/sipcc",
+]
+
+UNIFIED_SOURCES += ["JsepSessionImpl.cpp", "JsepTrack.cpp", "SsrcGenerator.cpp"]
+
+FINAL_LIBRARY = "xul"