diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/media/webrtc/jsep | |
parent | Initial commit. (diff) | |
download | firefox-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.h | 1179 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepSession.h | 287 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepSessionImpl.cpp | 2473 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepSessionImpl.h | 284 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepTrack.cpp | 706 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepTrack.h | 323 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepTrackEncoding.h | 62 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepTransceiver.h | 220 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/JsepTransport.h | 102 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/SsrcGenerator.cpp | 22 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/SsrcGenerator.h | 20 | ||||
-rw-r--r-- | dom/media/webrtc/jsep/moz.build | 18 |
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" |