diff options
Diffstat (limited to 'media/webrtc/signaling/src/jsep')
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepCodecDescription.h | 780 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepSession.h | 243 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp | 2497 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepSessionImpl.h | 352 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepTrack.cpp | 531 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepTrack.h | 292 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepTrackEncoding.h | 60 | ||||
-rw-r--r-- | media/webrtc/signaling/src/jsep/JsepTransport.h | 116 |
8 files changed, 4871 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h new file mode 100644 index 000000000..6ae5c9380 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h @@ -0,0 +1,780 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JSEPCODECDESCRIPTION_H_ +#define _JSEPCODECDESCRIPTION_H_ + +#include <string> +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/sdp/SdpHelper.h" +#include "nsCRT.h" + +namespace mozilla { + +#define JSEP_CODEC_CLONE(T) \ + virtual JsepCodecDescription* Clone() const override \ + { \ + return new T(*this); \ + } + +// A single entry in our list of known codecs. +class JsepCodecDescription { + public: + JsepCodecDescription(mozilla::SdpMediaSection::MediaType type, + const std::string& defaultPt, + const std::string& name, + uint32_t clock, + uint32_t channels, + bool enabled) + : mType(type), + mDefaultPt(defaultPt), + mName(name), + mClock(clock), + mChannels(channels), + mEnabled(enabled), + mStronglyPreferred(false), + mDirection(sdp::kSend) + { + } + virtual ~JsepCodecDescription() {} + + virtual JsepCodecDescription* Clone() const = 0; + + bool + GetPtAsInt(uint16_t* ptOutparam) const + { + return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam); + } + + virtual bool + Matches(const std::string& fmt, const SdpMediaSection& remoteMsection) const + { + // note: fmt here is remote fmt (to go with remoteMsection) + if (mType != remoteMsection.GetMediaType()) { + return false; + } + + const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt)); + + if (entry) { + if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str()) + && (mClock == entry->clock) + && (mChannels == entry->channels)) { + return ParametersMatch(fmt, remoteMsection); + } + } else if (!fmt.compare("9") && mName == "G722") { + return true; + } else if (!fmt.compare("0") && mName == "PCMU") { + return true; + } else if (!fmt.compare("8") && mName == "PCMA") { + return true; + } + return false; + } + + virtual bool + ParametersMatch(const std::string& fmt, + const SdpMediaSection& remoteMsection) const + { + return true; + } + + virtual bool + Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection) + { + mDefaultPt = pt; + return true; + } + + virtual void + AddToMediaSection(SdpMediaSection& msection) const + { + if (mEnabled && msection.GetMediaType() == mType) { + // Both send and recv codec will have the same pt, so don't add twice + if (!msection.HasFormat(mDefaultPt)) { + if (mType == SdpMediaSection::kApplication) { + // Hack: using mChannels for number of streams + msection.AddDataChannel(mDefaultPt, mName, mChannels); + } else { + msection.AddCodec(mDefaultPt, mName, mClock, mChannels); + } + } + + AddParametersToMSection(msection); + } + } + + virtual void AddParametersToMSection(SdpMediaSection& msection) const {} + + mozilla::SdpMediaSection::MediaType mType; + std::string mDefaultPt; + std::string mName; + uint32_t mClock; + uint32_t mChannels; + bool mEnabled; + bool mStronglyPreferred; + sdp::Direction mDirection; + // Will hold constraints from both fmtp and rid + EncodingConstraints mConstraints; +}; + +class JsepAudioCodecDescription : public JsepCodecDescription { + public: + JsepAudioCodecDescription(const std::string& defaultPt, + const std::string& name, + uint32_t clock, + uint32_t channels, + uint32_t packetSize, + uint32_t bitRate, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kAudio, defaultPt, name, + clock, channels, enabled), + mPacketSize(packetSize), + mBitrate(bitRate), + mMaxPlaybackRate(0), + mForceMono(false), + mFECEnabled(false), + mDtmfEnabled(false) + { + } + + JSEP_CODEC_CLONE(JsepAudioCodecDescription) + + SdpFmtpAttributeList::OpusParameters + GetOpusParameters(const std::string& pt, + const SdpMediaSection& msection) const + { + // Will contain defaults if nothing else + SdpFmtpAttributeList::OpusParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) { + result = + static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::TelephoneEventParameters + GetTelephoneEventParameters(const std::string& pt, + const SdpMediaSection& msection) const + { + // Will contain defaults if nothing else + SdpFmtpAttributeList::TelephoneEventParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) { + result = + static_cast<const SdpFmtpAttributeList::TelephoneEventParameters&> + (*params); + } + + return result; + } + + void + AddParametersToMSection(SdpMediaSection& msection) const override + { + if (mDirection == sdp::kSend) { + return; + } + + if (mName == "opus") { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, msection)); + if (mMaxPlaybackRate) { + opusParams.maxplaybackrate = mMaxPlaybackRate; + } + if (mChannels == 2 && !mForceMono) { + // We prefer to receive stereo, if available. + opusParams.stereo = 1; + } + opusParams.useInBandFec = mFECEnabled ? 1 : 0; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, opusParams)); + } else if (mName == "telephone-event") { + // add the default dtmf tones + SdpFmtpAttributeList::TelephoneEventParameters teParams( + GetTelephoneEventParameters(mDefaultPt, msection)); + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams)); + } + } + + bool + Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection) override + { + JsepCodecDescription::Negotiate(pt, remoteMsection); + if (mName == "opus" && mDirection == sdp::kSend) { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, remoteMsection)); + + mMaxPlaybackRate = opusParams.maxplaybackrate; + mForceMono = !opusParams.stereo; + // draft-ietf-rtcweb-fec-03.txt section 4.2 says support for FEC + // at the received side is declarative and can be negotiated + // separately for either media direction. + mFECEnabled = opusParams.useInBandFec; + } + + return true; + } + + uint32_t mPacketSize; + uint32_t mBitrate; + uint32_t mMaxPlaybackRate; + bool mForceMono; + bool mFECEnabled; + bool mDtmfEnabled; +}; + +class JsepVideoCodecDescription : public JsepCodecDescription { + public: + JsepVideoCodecDescription(const std::string& defaultPt, + const std::string& name, + uint32_t clock, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kVideo, defaultPt, name, + clock, 0, enabled), + mTmmbrEnabled(false), + mRembEnabled(false), + mFECEnabled(false), + mPacketizationMode(0) + { + // Add supported rtcp-fb types + mNackFbTypes.push_back(""); + mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli); + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir); + } + + virtual void + EnableTmmbr() { + // EnableTmmbr can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mTmmbrEnabled) { + mTmmbrEnabled = true; + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr); + } + } + + virtual void + EnableRemb() { + // EnableRemb can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mRembEnabled) { + mRembEnabled = true; + mOtherFbTypes.push_back({ "", SdpRtcpFbAttributeList::kRemb, "", ""}); + } + } + + virtual void + EnableFec() { + // Enabling FEC for video works a little differently than enabling + // REMB or TMMBR. Support for FEC is indicated by the presence of + // particular codes (red and ulpfec) instead of using rtcpfb + // attributes on a given codec. There is no rtcpfb to push for FEC + // as can be seen above when REMB or TMMBR are enabled. + mFECEnabled = true; + } + + void + AddParametersToMSection(SdpMediaSection& msection) const override + { + AddFmtpsToMSection(msection); + AddRtcpFbsToMSection(msection); + } + + void + AddFmtpsToMSection(SdpMediaSection& msection) const + { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, msection)); + + if (mDirection == sdp::kSend) { + if (!h264Params.level_asymmetry_allowed) { + // First time the fmtp has been set; set just in case this is for a + // sendonly m-line, since even though we aren't receiving the level + // negotiation still needs to happen (sigh). + h264Params.profile_level_id = mProfileLevelId; + } + } else { + // Parameters that only apply to what we receive + h264Params.max_mbps = mConstraints.maxMbps; + h264Params.max_fs = mConstraints.maxFs; + h264Params.max_cpb = mConstraints.maxCpb; + h264Params.max_dpb = mConstraints.maxDpb; + h264Params.max_br = mConstraints.maxBr; + strncpy(h264Params.sprop_parameter_sets, + mSpropParameterSets.c_str(), + sizeof(h264Params.sprop_parameter_sets) - 1); + h264Params.profile_level_id = mProfileLevelId; + } + + // Parameters that apply to both the send and recv directions + h264Params.packetization_mode = mPacketizationMode; + // Hard-coded, may need to change someday? + h264Params.level_asymmetry_allowed = true; + + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params)); + } else if (mName == "red") { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, msection)); + redParams.encodings = mRedundantEncodings; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams)); + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kRecv) { + // VP8 and VP9 share the same SDP parameters thus far + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, msection)); + + vp8Params.max_fs = mConstraints.maxFs; + vp8Params.max_fr = mConstraints.maxFps; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, vp8Params)); + } + } + } + + void + AddRtcpFbsToMSection(SdpMediaSection& msection) const + { + SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs()); + for (const auto& rtcpfb : rtcpfbs.mFeedbacks) { + if (rtcpfb.pt == mDefaultPt) { + // Already set by the codec for the other direction. + return; + } + } + + for (const std::string& type : mAckFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type); + } + for (const std::string& type : mNackFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type); + } + for (const std::string& type : mCcmFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type); + } + for (const auto& fb : mOtherFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra); + } + + msection.SetRtcpFbs(rtcpfbs); + } + + SdpFmtpAttributeList::H264Parameters + GetH264Parameters(const std::string& pt, + const SdpMediaSection& msection) const + { + // Will contain defaults if nothing else + SdpFmtpAttributeList::H264Parameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kH264) { + result = + static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::RedParameters + GetRedParameters(const std::string& pt, + const SdpMediaSection& msection) const + { + SdpFmtpAttributeList::RedParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kRed) { + result = + static_cast<const SdpFmtpAttributeList::RedParameters&>(*params); + } + + return result; + } + + SdpFmtpAttributeList::VP8Parameters + GetVP8Parameters(const std::string& pt, + const SdpMediaSection& msection) const + { + SdpRtpmapAttributeList::CodecType expectedType( + mName == "VP8" ? + SdpRtpmapAttributeList::kVP8 : + SdpRtpmapAttributeList::kVP9); + + // Will contain defaults if nothing else + SdpFmtpAttributeList::VP8Parameters result(expectedType); + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == expectedType) { + result = + static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*params); + } + + return result; + } + + void + NegotiateRtcpFb(const SdpMediaSection& remoteMsection, + SdpRtcpFbAttributeList::Type type, + std::vector<std::string>* supportedTypes) + { + std::vector<std::string> temp; + for (auto& subType : *supportedTypes) { + if (remoteMsection.HasRtcpFb(mDefaultPt, type, subType)) { + temp.push_back(subType); + } + } + *supportedTypes = temp; + } + + void + NegotiateRtcpFb(const SdpMediaSection& remoteMsection, + std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) { + std::vector<SdpRtcpFbAttributeList::Feedback> temp; + for (auto& fb : *supportedFbs) { + if (remoteMsection.HasRtcpFb(mDefaultPt, fb.type, fb.parameter)) { + temp.push_back(fb); + } + } + *supportedFbs = temp; + } + + void + NegotiateRtcpFb(const SdpMediaSection& remote) + { + // Removes rtcp-fb types that the other side doesn't support + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes); + NegotiateRtcpFb(remote, &mOtherFbTypes); + } + + virtual bool + Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection) override + { + JsepCodecDescription::Negotiate(pt, remoteMsection); + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, remoteMsection)); + + // Level is negotiated symmetrically if level asymmetry is disallowed + if (!h264Params.level_asymmetry_allowed) { + SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id), + GetSaneH264Level(mProfileLevelId)), + &mProfileLevelId); + } + + if (mDirection == sdp::kSend) { + // Remote values of these apply only to the send codec. + mConstraints.maxFs = h264Params.max_fs; + mConstraints.maxMbps = h264Params.max_mbps; + mConstraints.maxCpb = h264Params.max_cpb; + mConstraints.maxDpb = h264Params.max_dpb; + mConstraints.maxBr = h264Params.max_br; + mSpropParameterSets = h264Params.sprop_parameter_sets; + // Only do this if we didn't symmetrically negotiate above + if (h264Params.level_asymmetry_allowed) { + SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id), + &mProfileLevelId); + } + } else { + // TODO(bug 1143709): max-recv-level support + } + } else if (mName == "red") { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, remoteMsection)); + mRedundantEncodings = redParams.encodings; + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kSend) { + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, remoteMsection)); + + mConstraints.maxFs = vp8Params.max_fs; + mConstraints.maxFps = vp8Params.max_fr; + } + } + + NegotiateRtcpFb(remoteMsection); + return true; + } + + // Maps the not-so-sane encoding of H264 level into something that is + // ordered in the way one would expect + // 1b is 0xAB, everything else is the level left-shifted one half-byte + // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0) + static uint32_t + GetSaneH264Level(uint32_t profileLevelId) + { + uint32_t profileIdc = (profileLevelId >> 16); + + if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) { + if ((profileLevelId & 0x10FF) == 0x100B) { + // Level 1b + return 0xAB; + } + } + + uint32_t level = profileLevelId & 0xFF; + + if (level == 0x09) { + // Another way to encode level 1b + return 0xAB; + } + + return level << 4; + } + + static void + SetSaneH264Level(uint32_t level, uint32_t* profileLevelId) + { + uint32_t profileIdc = (*profileLevelId >> 16); + uint32_t levelMask = 0xFF; + + if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) { + levelMask = 0x10FF; + if (level == 0xAB) { + // Level 1b + level = 0x100B; + } else { + // Not 1b, just shift + level = level >> 4; + } + } else if (level == 0xAB) { + // Another way to encode 1b + level = 0x09; + } else { + // Not 1b, just shift + level = level >> 4; + } + + *profileLevelId = (*profileLevelId & ~levelMask) | level; + } + + enum Subprofile { + kH264ConstrainedBaseline, + kH264Baseline, + kH264Main, + kH264Extended, + kH264High, + kH264High10, + kH264High42, + kH264High44, + kH264High10I, + kH264High42I, + kH264High44I, + kH264CALVC44, + kH264UnknownSubprofile + }; + + static Subprofile + GetSubprofile(uint32_t profileLevelId) + { + // Based on Table 5 from RFC 6184: + // Profile profile_idc profile-iop + // (hexadecimal) (binary) + + // CB 42 (B) x1xx0000 + // same as: 4D (M) 1xxx0000 + // same as: 58 (E) 11xx0000 + // B 42 (B) x0xx0000 + // same as: 58 (E) 10xx0000 + // M 4D (M) 0x0x0000 + // E 58 00xx0000 + // H 64 00000000 + // H10 6E 00000000 + // H42 7A 00000000 + // H44 F4 00000000 + // H10I 6E 00010000 + // H42I 7A 00010000 + // H44I F4 00010000 + // C44I 2C 00010000 + + if ((profileLevelId & 0xFF4F00) == 0x424000) { + // 01001111 (mask, 0x4F) + // x1xx0000 (from table) + // 01000000 (expected value, 0x40) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF8F00) == 0x4D8000) { + // 10001111 (mask, 0x8F) + // 1xxx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x58C000) { + // 11001111 (mask, 0xCF) + // 11xx0000 (from table) + // 11000000 (expected value, 0xC0) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF4F00) == 0x420000) { + // 01001111 (mask, 0x4F) + // x0xx0000 (from table) + // 00000000 (expected value) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x588000) { + // 11001111 (mask, 0xCF) + // 10xx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFAF00) == 0x4D0000) { + // 10101111 (mask, 0xAF) + // 0x0x0000 (from table) + // 00000000 (expected value) + return kH264Main; + } + + if ((profileLevelId & 0xFF0000) == 0x580000) { + // 11001111 (mask, 0xCF) + // 00xx0000 (from table) + // 00000000 (expected value) + return kH264Extended; + } + + if ((profileLevelId & 0xFFFF00) == 0x640000) { + return kH264High; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E0000) { + return kH264High10; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A0000) { + return kH264High42; + } + + if ((profileLevelId & 0xFFFF00) == 0xF40000) { + return kH264High44; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E1000) { + return kH264High10I; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A1000) { + return kH264High42I; + } + + if ((profileLevelId & 0xFFFF00) == 0xF41000) { + return kH264High44I; + } + + if ((profileLevelId & 0xFFFF00) == 0x2C1000) { + return kH264CALVC44; + } + + return kH264UnknownSubprofile; + } + + virtual bool + ParametersMatch(const std::string& fmt, + const SdpMediaSection& remoteMsection) const override + { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(fmt, remoteMsection)); + + if (h264Params.packetization_mode != mPacketizationMode) { + return false; + } + + if (GetSubprofile(h264Params.profile_level_id) != + GetSubprofile(mProfileLevelId)) { + return false; + } + } + + return true; + } + + virtual bool + RtcpFbRembIsSet() const + { + for (const auto& fb : mOtherFbTypes) { + if (fb.type == SdpRtcpFbAttributeList::kRemb) { + return true; + } + } + return false; + } + + virtual void + UpdateRedundantEncodings(std::vector<JsepCodecDescription*> codecs) + { + for (const auto codec : codecs) { + if (codec->mType == SdpMediaSection::kVideo && + codec->mEnabled && + codec->mName != "red") { + uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); + // returns 0 if failed to convert, and since zero could + // be valid, check the defaultPt for 0 + if (pt == 0 && codec->mDefaultPt != "0") { + continue; + } + mRedundantEncodings.push_back(pt); + } + } + } + + JSEP_CODEC_CLONE(JsepVideoCodecDescription) + + std::vector<std::string> mAckFbTypes; + std::vector<std::string> mNackFbTypes; + std::vector<std::string> mCcmFbTypes; + std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes; + bool mTmmbrEnabled; + bool mRembEnabled; + bool mFECEnabled; + std::vector<uint8_t> mRedundantEncodings; + + // H264-specific stuff + uint32_t mProfileLevelId; + uint32_t mPacketizationMode; + std::string mSpropParameterSets; +}; + +class JsepApplicationCodecDescription : public JsepCodecDescription { + public: + JsepApplicationCodecDescription(const std::string& defaultPt, + const std::string& name, + uint16_t channels, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kApplication, defaultPt, + name, 0, channels, enabled) + { + } + + JSEP_CODEC_CLONE(JsepApplicationCodecDescription) + + // Override, uses sctpmap instead of rtpmap + virtual bool + Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const override + { + if (mType != remoteMsection.GetMediaType()) { + return false; + } + + const SdpSctpmapAttributeList::Sctpmap* entry( + remoteMsection.FindSctpmap(fmt)); + + if (entry && !nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())) { + return true; + } + return false; + } +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepSession.h b/media/webrtc/signaling/src/jsep/JsepSession.h new file mode 100644 index 000000000..29bcbde05 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSession.h @@ -0,0 +1,243 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JSEPSESSION_H_ +#define _JSEPSESSION_H_ + +#include <string> +#include <vector> +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsError.h" + +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/sdp/Sdp.h" + +#include "JsepTrack.h" + +namespace mozilla { + +// Forward declarations +class JsepCodecDescription; +class JsepTrack; + +enum JsepSignalingState { + kJsepStateStable, + kJsepStateHaveLocalOffer, + kJsepStateHaveRemoteOffer, + kJsepStateHaveLocalPranswer, + kJsepStateHaveRemotePranswer, + kJsepStateClosed +}; + +enum JsepSdpType { + kJsepSdpOffer, + kJsepSdpAnswer, + kJsepSdpPranswer, + kJsepSdpRollback +}; + +struct JsepOAOptions {}; +struct JsepOfferOptions : public JsepOAOptions { + Maybe<size_t> mOfferToReceiveAudio; + Maybe<size_t> mOfferToReceiveVideo; + Maybe<bool> mDontOfferDataChannel; + Maybe<bool> mIceRestart; // currently ignored by JsepSession +}; +struct JsepAnswerOptions : public JsepOAOptions {}; + +enum JsepBundlePolicy { + kBundleBalanced, + kBundleMaxCompat, + kBundleMaxBundle +}; + +class JsepSession +{ +public: + explicit JsepSession(const std::string& name) + : mName(name), mState(kJsepStateStable), mNegotiations(0) + { + } + virtual ~JsepSession() {} + + virtual nsresult Init() = 0; + + // Accessors for basic properties. + virtual const std::string& + GetName() const + { + return mName; + } + virtual JsepSignalingState + GetState() const + { + return mState; + } + virtual uint32_t + GetNegotiations() const + { + return mNegotiations; + } + + // Set up the ICE And DTLS data. + virtual nsresult SetIceCredentials(const std::string& ufrag, + const std::string& pwd) = 0; + virtual const std::string& GetUfrag() const = 0; + virtual const std::string& GetPwd() const = 0; + virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0; + virtual bool RemoteIsIceLite() const = 0; + virtual bool RemoteIceIsRestarting() const = 0; + virtual std::vector<std::string> GetIceOptions() const = 0; + + virtual nsresult AddDtlsFingerprint(const std::string& algorithm, + const std::vector<uint8_t>& value) = 0; + + virtual nsresult AddAudioRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + virtual nsresult AddVideoRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + + // Kinda gross to be locking down the data structure type like this, but + // returning by value is problematic due to the lack of stl move semantics in + // our build config, since we can't use UniquePtr in the container. The + // alternative is writing a raft of accessor functions that allow arbitrary + // manipulation (which will be unwieldy), or allowing functors to be injected + // that manipulate the data structure (still pretty unwieldy). + virtual std::vector<JsepCodecDescription*>& Codecs() = 0; + + template <class UnaryFunction> + void ForEachCodec(UnaryFunction& function) + { + std::for_each(Codecs().begin(), Codecs().end(), function); + for (RefPtr<JsepTrack>& track : GetLocalTracks()) { + track->ForEachCodec(function); + } + for (RefPtr<JsepTrack>& track : GetRemoteTracks()) { + track->ForEachCodec(function); + } + } + + template <class BinaryPredicate> + void SortCodecs(BinaryPredicate& sorter) + { + std::stable_sort(Codecs().begin(), Codecs().end(), sorter); + for (RefPtr<JsepTrack>& track : GetLocalTracks()) { + track->SortCodecs(sorter); + } + for (RefPtr<JsepTrack>& track : GetRemoteTracks()) { + track->SortCodecs(sorter); + } + } + + // Manage tracks. We take shared ownership of any track. + virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) = 0; + virtual nsresult RemoveTrack(const std::string& streamId, + const std::string& trackId) = 0; + virtual nsresult ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) = 0; + virtual nsresult SetParameters( + const std::string& streamId, + const std::string& trackId, + const std::vector<JsepTrack::JsConstraints>& constraints) = 0; + + virtual nsresult GetParameters( + const std::string& streamId, + const std::string& trackId, + std::vector<JsepTrack::JsConstraints>* outConstraints) = 0; + + virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const = 0; + + virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const = 0; + + virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksAdded() const = 0; + + virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracksRemoved() const = 0; + + // Access the negotiated track pairs. + virtual std::vector<JsepTrackPair> GetNegotiatedTrackPairs() const = 0; + + // Access transports. + virtual std::vector<RefPtr<JsepTransport>> GetTransports() const = 0; + + // Basic JSEP operations. + virtual nsresult CreateOffer(const JsepOfferOptions& options, + std::string* offer) = 0; + virtual nsresult CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) = 0; + virtual std::string GetLocalDescription() const = 0; + virtual std::string GetRemoteDescription() const = 0; + virtual nsresult SetLocalDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual nsresult SetRemoteDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual nsresult AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) = 0; + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + uint16_t level, + std::string* mid, + bool* skipped) = 0; + virtual nsresult UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, + uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, + uint16_t level) = 0; + virtual nsresult EndOfLocalCandidates(uint16_t level) = 0; + virtual nsresult Close() = 0; + + // ICE controlling or controlled + virtual bool IsIceControlling() const = 0; + + virtual const std::string + GetLastError() const + { + return "Error"; + } + + static const char* + GetStateStr(JsepSignalingState state) + { + static const char* states[] = { "stable", "have-local-offer", + "have-remote-offer", "have-local-pranswer", + "have-remote-pranswer", "closed" }; + + return states[state]; + } + + virtual bool AllLocalTracksAreAssigned() const = 0; + + void + CountTracks(uint16_t (&receiving)[SdpMediaSection::kMediaTypes], + uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const + { + auto trackPairs = GetNegotiatedTrackPairs(); + + memset(receiving, 0, sizeof(receiving)); + memset(sending, 0, sizeof(sending)); + + for (auto& pair : trackPairs) { + if (pair.mReceiving) { + receiving[pair.mReceiving->GetMediaType()]++; + } + + if (pair.mSending) { + sending[pair.mSending->GetMediaType()]++; + } + } + } + +protected: + const std::string mName; + JsepSignalingState mState; + uint32_t mNegotiations; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp new file mode 100644 index 000000000..f5015dda2 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp @@ -0,0 +1,2497 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "logging.h" + +#include "signaling/src/jsep/JsepSessionImpl.h" +#include <string> +#include <set> +#include <bitset> +#include <stdlib.h> + +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" +#include "nsDebug.h" + +#include <mozilla/Move.h> +#include <mozilla/UniquePtr.h> + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SipccSdp.h" +#include "signaling/src/sdp/SipccSdpParser.h" +#include "mozilla/net/DataChannelProtocol.h" + +namespace mozilla { + +MOZ_MTLOG_MODULE("jsep") + +#define JSEP_SET_ERROR(error) \ + do { \ + std::ostringstream os; \ + os << error; \ + mLastError = os.str(); \ + MOZ_MTLOG(ML_ERROR, mLastError); \ + } while (0); + +static std::bitset<128> GetForbiddenSdpPayloadTypes() { + std::bitset<128> forbidden(0); + forbidden[1] = true; + forbidden[2] = true; + forbidden[19] = true; + for (uint16_t i = 64; i < 96; ++i) { + forbidden[i] = true; + } + return forbidden; +} + +nsresult +JsepSessionImpl::Init() +{ + mLastError.clear(); + + MOZ_ASSERT(!mSessionId, "Init called more than once"); + + nsresult rv = SetupIds(); + NS_ENSURE_SUCCESS(rv, rv); + + SetupDefaultCodecs(); + SetupDefaultRtpExtensions(); + + return NS_OK; +} + +// Helper function to find the track for a given m= section. +template <class T> +typename std::vector<T>::iterator +FindTrackByLevel(std::vector<T>& tracks, size_t level) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (t->mAssignedMLine.isSome() && + (*t->mAssignedMLine == level)) { + return t; + } + } + + return tracks.end(); +} + +template <class T> +typename std::vector<T>::iterator +FindTrackByIds(std::vector<T>& tracks, + const std::string& streamId, + const std::string& trackId) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (t->mTrack->GetStreamId() == streamId && + (t->mTrack->GetTrackId() == trackId)) { + return t; + } + } + + return tracks.end(); +} + +template <class T> +typename std::vector<T>::iterator +FindUnassignedTrackByType(std::vector<T>& tracks, + SdpMediaSection::MediaType type) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (!t->mAssignedMLine.isSome() && + (t->mTrack->GetMediaType() == type)) { + return t; + } + } + + return tracks.end(); +} + +nsresult +JsepSessionImpl::AddTrack(const RefPtr<JsepTrack>& track) +{ + mLastError.clear(); + MOZ_ASSERT(track->GetDirection() == sdp::kSend); + + if (track->GetMediaType() != SdpMediaSection::kApplication) { + track->SetCNAME(mCNAME); + + if (track->GetSsrcs().empty()) { + uint32_t ssrc; + nsresult rv = CreateSsrc(&ssrc); + NS_ENSURE_SUCCESS(rv, rv); + track->AddSsrc(ssrc); + } + } + + track->PopulateCodecs(mSupportedCodecs.values); + + JsepSendingTrack strack; + strack.mTrack = track; + + mLocalTracks.push_back(strack); + + return NS_OK; +} + +nsresult +JsepSessionImpl::RemoveTrack(const std::string& streamId, + const std::string& trackId) +{ + if (mState != kJsepStateStable) { + JSEP_SET_ERROR("Removing tracks outside of stable is unsupported."); + return NS_ERROR_UNEXPECTED; + } + + auto track = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (track == mLocalTracks.end()) { + return NS_ERROR_INVALID_ARG; + } + + mLocalTracks.erase(track); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetIceCredentials(const std::string& ufrag, + const std::string& pwd) +{ + mLastError.clear(); + mIceUfrag = ufrag; + mIcePwd = pwd; + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) +{ + mLastError.clear(); + if (mCurrentLocalDescription) { + JSEP_SET_ERROR("Changing the bundle policy is only supported before the " + "first SetLocalDescription."); + return NS_ERROR_UNEXPECTED; + } + + mBundlePolicy = policy; + return NS_OK; +} + +nsresult +JsepSessionImpl::AddDtlsFingerprint(const std::string& algorithm, + const std::vector<uint8_t>& value) +{ + mLastError.clear(); + JsepDtlsFingerprint fp; + + fp.mAlgorithm = algorithm; + fp.mValue = value; + + mDtlsFingerprints.push_back(fp); + + return NS_OK; +} + +nsresult +JsepSessionImpl::AddRtpExtension(std::vector<SdpExtmapAttributeList::Extmap>& extensions, + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + mLastError.clear(); + + if (extensions.size() + 1 > UINT16_MAX) { + JSEP_SET_ERROR("Too many rtp extensions have been added"); + return NS_ERROR_FAILURE; + } + + SdpExtmapAttributeList::Extmap extmap = + { static_cast<uint16_t>(extensions.size() + 1), + direction, + direction != SdpDirectionAttribute::kSendrecv, // do we want to specify direction? + extensionName, + "" }; + + extensions.push_back(extmap); + return NS_OK; +} + +nsresult +JsepSessionImpl::AddAudioRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + return AddRtpExtension(mAudioRtpExtensions, extensionName, direction); +} + +nsresult +JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + return AddRtpExtension(mVideoRtpExtensions, extensionName, direction); +} + +template<class T> +std::vector<RefPtr<JsepTrack>> +GetTracks(const std::vector<T>& wrappedTracks) +{ + std::vector<RefPtr<JsepTrack>> result; + for (auto i = wrappedTracks.begin(); i != wrappedTracks.end(); ++i) { + result.push_back(i->mTrack); + } + return result; +} + +nsresult +JsepSessionImpl::ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) +{ + auto it = FindTrackByIds(mLocalTracks, oldStreamId, oldTrackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << oldStreamId << "/" << oldTrackId + << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + if (FindTrackByIds(mLocalTracks, newStreamId, newTrackId) != + mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << newStreamId << "/" << newTrackId + << " was already added."); + return NS_ERROR_INVALID_ARG; + } + + it->mTrack->SetStreamId(newStreamId); + it->mTrack->SetTrackId(newTrackId); + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetParameters(const std::string& streamId, + const std::string& trackId, + const std::vector<JsepTrack::JsConstraints>& constraints) +{ + auto it = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + // Add RtpStreamId Extmap + // SdpDirectionAttribute::Direction is a bitmask + SdpDirectionAttribute::Direction addVideoExt = SdpDirectionAttribute::kInactive; + for (auto constraintEntry: constraints) { + if (constraintEntry.rid != "") { + if (it->mTrack->GetMediaType() == SdpMediaSection::kVideo) { + addVideoExt = static_cast<SdpDirectionAttribute::Direction>(addVideoExt + | it->mTrack->GetDirection()); + } + } + } + if (addVideoExt != SdpDirectionAttribute::kInactive) { + AddVideoRtpExtension("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", addVideoExt); + } + + it->mTrack->SetJsConstraints(constraints); + return NS_OK; +} + +nsresult +JsepSessionImpl::GetParameters(const std::string& streamId, + const std::string& trackId, + std::vector<JsepTrack::JsConstraints>* outConstraints) +{ + auto it = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + it->mTrack->GetJsConstraints(outConstraints); + return NS_OK; +} + +std::vector<RefPtr<JsepTrack>> +JsepSessionImpl::GetLocalTracks() const +{ + return GetTracks(mLocalTracks); +} + +std::vector<RefPtr<JsepTrack>> +JsepSessionImpl::GetRemoteTracks() const +{ + return GetTracks(mRemoteTracks); +} + +std::vector<RefPtr<JsepTrack>> +JsepSessionImpl::GetRemoteTracksAdded() const +{ + return GetTracks(mRemoteTracksAdded); +} + +std::vector<RefPtr<JsepTrack>> +JsepSessionImpl::GetRemoteTracksRemoved() const +{ + return GetTracks(mRemoteTracksRemoved); +} + +nsresult +JsepSessionImpl::SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp) +{ + // First audio, then video, then datachannel, for interop + // TODO(bug 1121756): We need to group these by stream-id, _then_ by media + // type, according to the spec. However, this is not going to interop with + // older versions of Firefox if a video-only stream is added before an + // audio-only stream. + // We should probably wait until 38 is ESR before trying to do this. + nsresult rv = SetupOfferMSectionsByType( + SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetupOfferMSectionsByType( + SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!(options.mDontOfferDataChannel.isSome() && + *options.mDontOfferDataChannel)) { + rv = SetupOfferMSectionsByType( + SdpMediaSection::kApplication, Maybe<size_t>(), sdp); + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!sdp->GetMediaSectionCount()) { + JSEP_SET_ERROR("Cannot create an offer with no local tracks, " + "no offerToReceiveAudio/Video, and no DataChannel."); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetupOfferMSectionsByType(SdpMediaSection::MediaType mediatype, + Maybe<size_t> offerToReceiveMaybe, + Sdp* sdp) +{ + // Convert the Maybe into a size_t*, since that is more readable, especially + // when using it as an in/out param. + size_t offerToReceiveCount; + size_t* offerToReceiveCountPtr = nullptr; + + if (offerToReceiveMaybe) { + offerToReceiveCount = *offerToReceiveMaybe; + offerToReceiveCountPtr = &offerToReceiveCount; + } + + // Make sure every local track has an m-section + nsresult rv = BindLocalTracks(mediatype, sdp); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure that m-sections that previously had a remote track have the + // recv bit set. Only matters for renegotiation. + rv = BindRemoteTracks(mediatype, sdp, offerToReceiveCountPtr); + NS_ENSURE_SUCCESS(rv, rv); + + // If we need more recv sections, start setting the recv bit on other + // msections. If not, disable msections that have no tracks. + rv = SetRecvAsNeededOrDisable(mediatype, + sdp, + offerToReceiveCountPtr); + NS_ENSURE_SUCCESS(rv, rv); + + // If we still don't have enough recv m-sections, add some. + if (offerToReceiveCountPtr && *offerToReceiveCountPtr) { + rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype, Sdp* sdp) +{ + for (JsepSendingTrack& track : mLocalTracks) { + if (mediatype != track.mTrack->GetMediaType()) { + continue; + } + + SdpMediaSection* msection; + if (track.mAssignedMLine.isSome()) { + msection = &sdp->GetMediaSection(*track.mAssignedMLine); + } else { + nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(), + sdp, + &msection); + NS_ENSURE_SUCCESS(rv, rv); + track.mAssignedMLine = Some(msection->GetLevel()); + } + + track.mTrack->AddToOffer(msection); + } + return NS_OK; +} + +nsresult +JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToReceive) +{ + for (JsepReceivingTrack& track : mRemoteTracks) { + if (mediatype != track.mTrack->GetMediaType()) { + continue; + } + + if (!track.mAssignedMLine.isSome()) { + MOZ_ASSERT(false); + continue; + } + + auto& msection = sdp->GetMediaSection(*track.mAssignedMLine); + + if (mSdpHelper.MsectionIsDisabled(msection)) { + // TODO(bug 1095226) Content probably disabled this? Should we allow + // content to do this? + continue; + } + + track.mTrack->AddToOffer(&msection); + + if (offerToReceive && *offerToReceive) { + --(*offerToReceive); + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToRecv) +{ + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + auto& msection = sdp->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(msection) || + msection.GetMediaType() != mediatype || + msection.IsReceiving()) { + continue; + } + + if (offerToRecv) { + if (*offerToRecv) { + SetupOfferToReceiveMsection(&msection); + --(*offerToRecv); + continue; + } + } else if (msection.IsSending()) { + SetupOfferToReceiveMsection(&msection); + continue; + } + + if (!msection.IsSending()) { + // Unused m-section, and no reason to offer to recv on it + mSdpHelper.DisableMsection(sdp, &msection); + } + } + + return NS_OK; +} + +void +JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer) +{ + // Create a dummy recv track, and have it add codecs, set direction, etc. + RefPtr<JsepTrack> dummy = new JsepTrack(offer->GetMediaType(), + "", + "", + sdp::kRecv); + dummy->PopulateCodecs(mSupportedCodecs.values); + dummy->AddToOffer(offer); +} + +nsresult +JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype, + size_t count, + Sdp* sdp) +{ + while (count--) { + nsresult rv = CreateOfferMSection( + mediatype, + mSdpHelper.GetProtocolForMediaType(mediatype), + SdpDirectionAttribute::kRecvonly, + sdp); + + NS_ENSURE_SUCCESS(rv, rv); + SetupOfferToReceiveMsection( + &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1)); + } + return NS_OK; +} + +// This function creates a skeleton SDP based on the old descriptions +// (ie; all m-sections are inactive). +nsresult +JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp, + const Sdp& oldAnswer, + Sdp* newSdp) +{ + nsresult rv; + + for (size_t i = 0; i < oldLocalSdp.GetMediaSectionCount(); ++i) { + // We do not set the direction in this function (or disable when previously + // disabled), that happens in |SetupOfferMSectionsByType| + rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(), + oldLocalSdp.GetMediaSection(i).GetProtocol(), + SdpDirectionAttribute::kInactive, + newSdp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mSdpHelper.CopyStickyParams(oldAnswer.GetMediaSection(i), + &newSdp->GetMediaSection(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +void +JsepSessionImpl::SetupBundle(Sdp* sdp) const +{ + std::vector<std::string> mids; + std::set<SdpMediaSection::MediaType> observedTypes; + + // This has the effect of changing the bundle level if the first m-section + // goes from disabled to enabled. This is kinda inefficient. + + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + auto& attrs = sdp->GetMediaSection(i).GetAttributeList(); + if (attrs.HasAttribute(SdpAttribute::kMidAttribute)) { + bool useBundleOnly = false; + switch (mBundlePolicy) { + case kBundleMaxCompat: + // We don't use bundle-only for max-compat + break; + case kBundleBalanced: + // balanced means we use bundle-only on everything but the first + // m-section of a given type + if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) { + useBundleOnly = true; + } + observedTypes.insert(sdp->GetMediaSection(i).GetMediaType()); + break; + case kBundleMaxBundle: + // max-bundle means we use bundle-only on everything but the first + // m-section + useBundleOnly = !mids.empty(); + break; + } + + if (useBundleOnly) { + attrs.SetAttribute( + new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute)); + } + + mids.push_back(attrs.GetMid()); + } + } + + if (mids.size() > 1) { + UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList); + groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids); + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + } +} + +nsresult +JsepSessionImpl::GetRemoteIds(const Sdp& sdp, + const SdpMediaSection& msection, + std::string* streamId, + std::string* trackId) +{ + nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamId, trackId); + if (rv == NS_ERROR_NOT_AVAILABLE) { + *streamId = mDefaultRemoteStreamId; + + if (!mDefaultRemoteTrackIdsByLevel.count(msection.GetLevel())) { + // Generate random track ids. + if (!mUuidGen->Generate(trackId)) { + JSEP_SET_ERROR("Failed to generate UUID for JsepTrack"); + return NS_ERROR_FAILURE; + } + + mDefaultRemoteTrackIdsByLevel[msection.GetLevel()] = *trackId; + } else { + *trackId = mDefaultRemoteTrackIdsByLevel[msection.GetLevel()]; + } + return NS_OK; + } + + if (NS_SUCCEEDED(rv)) { + // If, for whatever reason, the other end renegotiates with an msid where + // there wasn't one before, don't allow the old default to pop up again + // later. + mDefaultRemoteTrackIdsByLevel.erase(msection.GetLevel()); + } + + return rv; +} + +nsresult +JsepSessionImpl::CreateOffer(const JsepOfferOptions& options, + std::string* offer) +{ + mLastError.clear(); + + if (mState != kJsepStateStable) { + JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // Undo track assignments from a previous call to CreateOffer + // (ie; if the track has not been negotiated yet, it doesn't necessarily need + // to stay in the same m-section that it was in) + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + if (!trackWrapper.mTrack->GetNegotiatedDetails()) { + trackWrapper.mAssignedMLine.reset(); + } + } + + UniquePtr<Sdp> sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, rv); + + if (mCurrentLocalDescription) { + rv = AddReofferMsections(*mCurrentLocalDescription, + *GetAnswer(), + sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Ensure that we have all the m-sections we need, and disable extras + rv = SetupOfferMSections(options, sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + + SetupBundle(sdp.get()); + + if (mCurrentLocalDescription) { + rv = CopyPreviousTransportParams(*GetAnswer(), + *mCurrentLocalDescription, + *sdp, + sdp.get()); + NS_ENSURE_SUCCESS(rv,rv); + } + + *offer = sdp->ToString(); + mGeneratedLocalDescription = Move(sdp); + ++mSessionVersion; + + return NS_OK; +} + +std::string +JsepSessionImpl::GetLocalDescription() const +{ + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedLocalDescription(); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +std::string +JsepSessionImpl::GetRemoteDescription() const +{ + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedRemoteDescription(); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +void +JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const +{ + const auto* extensions = GetRtpExtensions(msection->GetMediaType()); + + if (extensions && !extensions->empty()) { + SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList; + extmap->mExtmaps = *extensions; + msection->GetAttributeList().SetAttribute(extmap); + } +} + +void +JsepSessionImpl::AddMid(const std::string& mid, + SdpMediaSection* msection) const +{ + msection->GetAttributeList().SetAttribute(new SdpStringAttribute( + SdpAttribute::kMidAttribute, mid)); +} + +const std::vector<SdpExtmapAttributeList::Extmap>* +JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const +{ + switch (type) { + case SdpMediaSection::kAudio: + return &mAudioRtpExtensions; + case SdpMediaSection::kVideo: + return &mVideoRtpExtensions; + default: + return nullptr; + } +} + +void +JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection) +{ + auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType()); + + if (ourExtensions) { + mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection); + } +} + +nsresult +JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) +{ + mLastError.clear(); + + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // This is the heart of the negotiation code. Depressing that it's + // so bad. + // + // Here's the current algorithm: + // 1. Walk through all the m-lines on the other side. + // 2. For each m-line, walk through all of our local tracks + // in sequence and see if any are unassigned. If so, assign + // them and mark it sendrecv, otherwise it's recvonly. + // 3. Just replicate their media attributes. + // 4. Profit. + UniquePtr<Sdp> sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, rv); + + const Sdp& offer = *mPendingRemoteDescription; + + // Copy the bundle groups into our answer + UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList); + mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups); + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + + // Disable send for local tracks if the offer no longer allows it + // (i.e., the m-section is recvonly, inactive or disabled) + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + if (!trackWrapper.mAssignedMLine.isSome()) { + continue; + } + + // Get rid of all m-line assignments that have not been negotiated + if (!trackWrapper.mTrack->GetNegotiatedDetails()) { + trackWrapper.mAssignedMLine.reset(); + continue; + } + + if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) { + trackWrapper.mAssignedMLine.reset(); + } + } + + size_t numMsections = offer.GetMediaSectionCount(); + + for (size_t i = 0; i < numMsections; ++i) { + const SdpMediaSection& remoteMsection = offer.GetMediaSection(i); + rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mCurrentLocalDescription) { + // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf) + rv = CopyPreviousTransportParams(*GetAnswer(), + *mCurrentRemoteDescription, + offer, + sdp.get()); + NS_ENSURE_SUCCESS(rv,rv); + } + + *answer = sdp->ToString(); + mGeneratedLocalDescription = Move(sdp); + ++mSessionVersion; + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateOfferMSection(SdpMediaSection::MediaType mediatype, + SdpMediaSection::Protocol proto, + SdpDirectionAttribute::Direction dir, + Sdp* sdp) +{ + SdpMediaSection* msection = + &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0"); + + return EnableOfferMsection(msection); +} + +nsresult +JsepSessionImpl::GetFreeMsectionForSend( + SdpMediaSection::MediaType type, + Sdp* sdp, + SdpMediaSection** msectionOut) +{ + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + SdpMediaSection& msection = sdp->GetMediaSection(i); + // draft-ietf-rtcweb-jsep-08 says we should reclaim disabled m-sections + // regardless of media type. This breaks some pretty fundamental rules of + // SDP offer/answer, so we probably should not do it. + if (msection.GetMediaType() != type) { + continue; + } + + if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) { + // Not free + continue; + } + + if (mSdpHelper.MsectionIsDisabled(msection)) { + // Was disabled; revive + nsresult rv = EnableOfferMsection(&msection); + NS_ENSURE_SUCCESS(rv, rv); + } + + *msectionOut = &msection; + return NS_OK; + } + + // Ok, no pre-existing m-section. Make a new one. + nsresult rv = CreateOfferMSection(type, + mSdpHelper.GetProtocolForMediaType(type), + SdpDirectionAttribute::kInactive, + sdp); + NS_ENSURE_SUCCESS(rv, rv); + + *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1); + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options, + size_t mlineIndex, + const SdpMediaSection& remoteMsection, + Sdp* sdp) +{ + SdpMediaSection& msection = + sdp->AddMediaSection(remoteMsection.GetMediaType(), + SdpDirectionAttribute::kInactive, + 9, + remoteMsection.GetProtocol(), + sdp::kIPv4, + "0.0.0.0"); + + nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection); + NS_ENSURE_SUCCESS(rv, rv); + + if (mSdpHelper.MsectionIsDisabled(remoteMsection)) { + mSdpHelper.DisableMsection(sdp, &msection); + return NS_OK; + } + + SdpSetupAttribute::Role role; + rv = DetermineAnswererSetupRole(remoteMsection, &role); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddTransportAttributes(&msection, role); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetRecvonlySsrc(&msection); + NS_ENSURE_SUCCESS(rv, rv); + + // Only attempt to match up local tracks if the offerer has elected to + // receive traffic. + if (remoteMsection.IsReceiving()) { + rv = BindMatchingLocalTrackToAnswer(&msection); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (remoteMsection.IsSending()) { + BindMatchingRemoteTrackToAnswer(&msection); + } + + if (!msection.IsReceiving() && !msection.IsSending()) { + mSdpHelper.DisableMsection(sdp, &msection); + return NS_OK; + } + + // Add extmap attributes. + AddCommonExtmaps(remoteMsection, &msection); + + if (msection.GetFormats().empty()) { + // Could not negotiate anything. Disable m-section. + mSdpHelper.DisableMsection(sdp, &msection); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection) +{ + // If previous m-sections are disabled, we do not call this function for them + while (mRecvonlySsrcs.size() <= msection->GetLevel()) { + uint32_t ssrc; + nsresult rv = CreateSsrc(&ssrc); + NS_ENSURE_SUCCESS(rv, rv); + mRecvonlySsrcs.push_back(ssrc); + } + + std::vector<uint32_t> ssrcs; + ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]); + msection->SetSsrcs(ssrcs, mCNAME); + return NS_OK; +} + +nsresult +JsepSessionImpl::BindMatchingLocalTrackToAnswer(SdpMediaSection* msection) +{ + auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel()); + + if (track == mLocalTracks.end()) { + track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType()); + } + + if (track == mLocalTracks.end() && + msection->GetMediaType() == SdpMediaSection::kApplication) { + // If we are offered datachannel, we need to play along even if no track + // for it has been added yet. + std::string streamId; + std::string trackId; + + if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) { + JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track"); + return NS_ERROR_FAILURE; + } + + AddTrack(RefPtr<JsepTrack>( + new JsepTrack(SdpMediaSection::kApplication, streamId, trackId))); + track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType()); + MOZ_ASSERT(track != mLocalTracks.end()); + } + + if (track != mLocalTracks.end()) { + track->mAssignedMLine = Some(msection->GetLevel()); + track->mTrack->AddToAnswer( + mPendingRemoteDescription->GetMediaSection(msection->GetLevel()), + msection); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection) +{ + auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel()); + if (it == mRemoteTracks.end()) { + MOZ_ASSERT(false); + JSEP_SET_ERROR("Failed to find remote track for local answer m-section"); + return NS_ERROR_FAILURE; + } + + it->mTrack->AddToAnswer( + mPendingRemoteDescription->GetMediaSection(msection->GetLevel()), + msection); + return NS_OK; +} + +nsresult +JsepSessionImpl::DetermineAnswererSetupRole( + const SdpMediaSection& remoteMsection, + SdpSetupAttribute::Role* rolep) +{ + // Determine the role. + // RFC 5763 says: + // + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + // + // We default to assuming that the offerer is passive and we are active. + SdpSetupAttribute::Role role = SdpSetupAttribute::kActive; + + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)) { + switch (remoteMsection.GetAttributeList().GetSetup().mRole) { + case SdpSetupAttribute::kActive: + role = SdpSetupAttribute::kPassive; + break; + case SdpSetupAttribute::kPassive: + case SdpSetupAttribute::kActpass: + role = SdpSetupAttribute::kActive; + break; + case SdpSetupAttribute::kHoldconn: + // This should have been caught by ParseSdp + MOZ_ASSERT(false); + JSEP_SET_ERROR("The other side used an illegal setup attribute" + " (\"holdconn\")."); + return NS_ERROR_INVALID_ARG; + } + } + + *rolep = role; + return NS_OK; +} + +nsresult +JsepSessionImpl::SetLocalDescription(JsepSdpType type, const std::string& sdp) +{ + mLastError.clear(); + + MOZ_MTLOG(ML_DEBUG, "SetLocalDescription type=" << type << "\nSDP=\n" + << sdp); + + if (type == kJsepSdpRollback) { + if (mState != kJsepStateHaveLocalOffer) { + JSEP_SET_ERROR("Cannot rollback local description in " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + mPendingLocalDescription.reset(); + SetState(kJsepStateStable); + mTransports = mOldTransports; + mOldTransports.clear(); + return NS_OK; + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set local answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + mIsOfferer = true; + break; + case kJsepStateHaveRemoteOffer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set local offer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + break; + default: + JSEP_SET_ERROR("Cannot set local offer or answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + UniquePtr<Sdp> parsed; + nsresult rv = ParseSdp(sdp, &parsed); + NS_ENSURE_SUCCESS(rv, rv); + + // Check that content hasn't done anything unsupported with the SDP + rv = ValidateLocalDescription(*parsed); + NS_ENSURE_SUCCESS(rv, rv); + + // Create transport objects. + mOldTransports = mTransports; // Save in case we need to rollback + mTransports.clear(); + for (size_t t = 0; t < parsed->GetMediaSectionCount(); ++t) { + mTransports.push_back(RefPtr<JsepTransport>(new JsepTransport)); + InitTransport(parsed->GetMediaSection(t), mTransports[t].get()); + } + + switch (type) { + case kJsepSdpOffer: + rv = SetLocalDescriptionOffer(Move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetLocalDescriptionAnswer(type, Move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + return rv; +} + +nsresult +JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer) +{ + MOZ_ASSERT(mState == kJsepStateStable); + mPendingLocalDescription = Move(offer); + SetState(kJsepStateHaveLocalOffer); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type, + UniquePtr<Sdp> answer) +{ + MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer); + mPendingLocalDescription = Move(answer); + + nsresult rv = ValidateAnswer(*mPendingRemoteDescription, + *mPendingLocalDescription); + NS_ENSURE_SUCCESS(rv, rv); + + rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = Move(mPendingRemoteDescription); + mCurrentLocalDescription = Move(mPendingLocalDescription); + mWasOffererLastTime = mIsOfferer; + + SetState(kJsepStateStable); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescription(JsepSdpType type, const std::string& sdp) +{ + mLastError.clear(); + mRemoteTracksAdded.clear(); + mRemoteTracksRemoved.clear(); + + MOZ_MTLOG(ML_DEBUG, "SetRemoteDescription type=" << type << "\nSDP=\n" + << sdp); + + if (type == kJsepSdpRollback) { + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot rollback remote description in " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + mPendingRemoteDescription.reset(); + SetState(kJsepStateStable); + + // Update the remote tracks to what they were before the SetRemote + return SetRemoteTracksFromDescription(mCurrentRemoteDescription.get()); + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set remote answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + mIsOfferer = false; + break; + case kJsepStateHaveLocalOffer: + case kJsepStateHaveRemotePranswer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set remote offer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + break; + default: + JSEP_SET_ERROR("Cannot set remote offer or answer in current state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // Parse. + UniquePtr<Sdp> parsed; + nsresult rv = ParseSdp(sdp, &parsed); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ValidateRemoteDescription(*parsed); + NS_ENSURE_SUCCESS(rv, rv); + + bool iceLite = + parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute); + + // check for mismatch ufrag/pwd indicating ice restart + // can't just check the first one because it might be disabled + bool iceRestarting = false; + if (mCurrentRemoteDescription.get()) { + for (size_t i = 0; + !iceRestarting && + i < mCurrentRemoteDescription->GetMediaSectionCount(); + ++i) { + + const SdpMediaSection& newMsection = parsed->GetMediaSection(i); + const SdpMediaSection& oldMsection = + mCurrentRemoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(newMsection) || + mSdpHelper.MsectionIsDisabled(oldMsection)) { + continue; + } + + iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection); + } + } + + std::vector<std::string> iceOptions; + if (parsed->GetAttributeList().HasAttribute( + SdpAttribute::kIceOptionsAttribute)) { + iceOptions = parsed->GetAttributeList().GetIceOptions().mValues; + } + + switch (type) { + case kJsepSdpOffer: + rv = SetRemoteDescriptionOffer(Move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetRemoteDescriptionAnswer(type, Move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + if (NS_SUCCEEDED(rv)) { + mRemoteIsIceLite = iceLite; + mIceOptions = iceOptions; + mRemoteIceIsRestarting = iceRestarting; + } + + return rv; +} + +nsresult +JsepSessionImpl::HandleNegotiatedSession(const UniquePtr<Sdp>& local, + const UniquePtr<Sdp>& remote) +{ + bool remoteIceLite = + remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute); + + mIceControlling = remoteIceLite || mIsOfferer; + + const Sdp& answer = mIsOfferer ? *remote : *local; + + SdpHelper::BundledMids bundledMids; + nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTransports.size() < local->GetMediaSectionCount()) { + JSEP_SET_ERROR("Fewer transports set up than m-lines"); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + trackWrapper.mTrack->ClearNegotiatedDetails(); + } + + for (JsepReceivingTrack& trackWrapper : mRemoteTracks) { + trackWrapper.mTrack->ClearNegotiatedDetails(); + } + + std::vector<JsepTrackPair> trackPairs; + + // Now walk through the m-sections, make sure they match, and create + // track pairs that describe the media to be set up. + for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) { + // Skip disabled m-sections. + if (answer.GetMediaSection(i).GetPort() == 0) { + mTransports[i]->Close(); + continue; + } + + // The transport details are not necessarily on the m-section we're + // currently processing. + size_t transportLevel = i; + bool usingBundle = false; + { + const SdpMediaSection& answerMsection(answer.GetMediaSection(i)); + if (answerMsection.GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)) { + if (bundledMids.count(answerMsection.GetAttributeList().GetMid())) { + const SdpMediaSection* masterBundleMsection = + bundledMids[answerMsection.GetAttributeList().GetMid()]; + transportLevel = masterBundleMsection->GetLevel(); + usingBundle = true; + if (i != transportLevel) { + mTransports[i]->Close(); + } + } + } + } + + RefPtr<JsepTransport> transport = mTransports[transportLevel]; + + rv = FinalizeTransport( + remote->GetMediaSection(transportLevel).GetAttributeList(), + answer.GetMediaSection(transportLevel).GetAttributeList(), + transport); + NS_ENSURE_SUCCESS(rv, rv); + + JsepTrackPair trackPair; + rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i), + local->GetMediaSection(i), + transport, + usingBundle, + transportLevel, + &trackPair); + NS_ENSURE_SUCCESS(rv, rv); + + trackPairs.push_back(trackPair); + } + + JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks)); + + // Ouch, this probably needs some dirty bit instead of just clearing + // stuff for renegotiation. + mNegotiatedTrackPairs = trackPairs; + + mGeneratedLocalDescription.reset(); + + mNegotiations++; + return NS_OK; +} + +nsresult +JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote, + const SdpMediaSection& local, + const RefPtr<JsepTransport>& transport, + bool usingBundle, + size_t transportLevel, + JsepTrackPair* trackPairOut) +{ + MOZ_ASSERT(transport->mComponents); + const SdpMediaSection& answer = mIsOfferer ? remote : local; + + bool sending; + bool receiving; + + if (mIsOfferer) { + receiving = answer.IsSending(); + sending = answer.IsReceiving(); + } else { + sending = answer.IsSending(); + receiving = answer.IsReceiving(); + } + + MOZ_MTLOG(ML_DEBUG, "Negotiated m= line" + << " index=" << local.GetLevel() + << " type=" << local.GetMediaType() + << " sending=" << sending + << " receiving=" << receiving); + + trackPairOut->mLevel = local.GetLevel(); + + MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(), + "Failed to set the default ssrc for an active m-section"); + trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()]; + + if (usingBundle) { + trackPairOut->mBundleLevel = Some(transportLevel); + } + + auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel()); + if (sendTrack != mLocalTracks.end()) { + sendTrack->mTrack->Negotiate(answer, remote); + sendTrack->mTrack->SetActive(sending); + trackPairOut->mSending = sendTrack->mTrack; + } else if (sending) { + JSEP_SET_ERROR("Failed to find local track for level " << + local.GetLevel() + << " in local SDP. This should never happen."); + NS_ASSERTION(false, "Failed to find local track for level"); + return NS_ERROR_FAILURE; + } + + auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel()); + if (recvTrack != mRemoteTracks.end()) { + recvTrack->mTrack->Negotiate(answer, remote); + recvTrack->mTrack->SetActive(receiving); + trackPairOut->mReceiving = recvTrack->mTrack; + + if (receiving && + trackPairOut->mBundleLevel.isSome() && + recvTrack->mTrack->GetSsrcs().empty() && + recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) { + MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. " + "This may cause media packets to be dropped."); + } + } else if (receiving) { + JSEP_SET_ERROR("Failed to find remote track for level " + << local.GetLevel() + << " in remote SDP. This should never happen."); + NS_ASSERTION(false, "Failed to find remote track for level"); + return NS_ERROR_FAILURE; + } + + trackPairOut->mRtpTransport = transport; + + if (transport->mComponents == 2) { + // RTCP MUX or not. + // TODO(bug 1095743): verify that the PTs are consistent with mux. + MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off"); + trackPairOut->mRtcpTransport = transport; + } + + return NS_OK; +} + +void +JsepSessionImpl::InitTransport(const SdpMediaSection& msection, + JsepTransport* transport) +{ + if (mSdpHelper.MsectionIsDisabled(msection)) { + transport->Close(); + return; + } + + if (mSdpHelper.HasRtcp(msection.GetProtocol())) { + transport->mComponents = 2; + } else { + transport->mComponents = 1; + } + + if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) { + transport->mTransportId = msection.GetAttributeList().GetMid(); + } else { + std::ostringstream os; + os << "level_" << msection.GetLevel() << "(no mid)"; + transport->mTransportId = os.str(); + } +} + +nsresult +JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + const RefPtr<JsepTransport>& transport) +{ + UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>(); + + // We do sanity-checking for these in ParseSdp + ice->mUfrag = remote.GetIceUfrag(); + ice->mPwd = remote.GetIcePwd(); + if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) { + ice->mCandidates = remote.GetCandidate(); + } + + // RFC 5763 says: + // + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>(); + dtls->mFingerprints = remote.GetFingerprint(); + if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) { + dtls->mRole = mIsOfferer ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + if (mIsOfferer) { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsClient + : JsepDtlsTransport::kJsepDtlsServer; + } + } + + transport->mIce = Move(ice); + transport->mDtls = Move(dtls); + + if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) { + transport->mComponents = 1; + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::AddTransportAttributes(SdpMediaSection* msection, + SdpSetupAttribute::Role dtlsRole) +{ + if (mIceUfrag.empty() || mIcePwd.empty()) { + JSEP_SET_ERROR("Missing ICE ufrag or password"); + return NS_ERROR_FAILURE; + } + + SdpAttributeList& attrList = msection->GetAttributeList(); + attrList.SetAttribute( + new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag)); + attrList.SetAttribute( + new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd)); + + msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole)); + + return NS_OK; +} + +nsresult +JsepSessionImpl::CopyPreviousTransportParams(const Sdp& oldAnswer, + const Sdp& offerersPreviousSdp, + const Sdp& newOffer, + Sdp* newLocal) +{ + for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) { + if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) && + mSdpHelper.AreOldTransportParamsValid(oldAnswer, + offerersPreviousSdp, + newOffer, + i) && + !mRemoteIceIsRestarting + ) { + // If newLocal is an offer, this will be the number of components we used + // last time, and if it is an answer, this will be the number of + // components we've decided we're using now. + size_t numComponents = mTransports[i]->mComponents; + nsresult rv = mSdpHelper.CopyTransportParams( + numComponents, + mCurrentLocalDescription->GetMediaSection(i), + &newLocal->GetMediaSection(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp) +{ + UniquePtr<Sdp> parsed = mParser.Parse(sdp); + if (!parsed) { + std::string error = "Failed to parse SDP: "; + mSdpHelper.appendSdpParseErrors(mParser.GetParseErrors(), &error); + JSEP_SET_ERROR(error); + return NS_ERROR_INVALID_ARG; + } + + // Verify that the JSEP rules for all SDP are followed + if (!parsed->GetMediaSectionCount()) { + JSEP_SET_ERROR("Description has no media sections"); + return NS_ERROR_INVALID_ARG; + } + + std::set<std::string> trackIds; + + for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) { + if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) { + // Disabled, let this stuff slide. + continue; + } + + const SdpMediaSection& msection(parsed->GetMediaSection(i)); + auto& mediaAttrs = msection.GetAttributeList(); + + if (mediaAttrs.GetIceUfrag().empty()) { + JSEP_SET_ERROR("Invalid description, no ice-ufrag attribute"); + return NS_ERROR_INVALID_ARG; + } + + if (mediaAttrs.GetIcePwd().empty()) { + JSEP_SET_ERROR("Invalid description, no ice-pwd attribute"); + return NS_ERROR_INVALID_ARG; + } + + if (!mediaAttrs.HasAttribute(SdpAttribute::kFingerprintAttribute)) { + JSEP_SET_ERROR("Invalid description, no fingerprint attribute"); + return NS_ERROR_INVALID_ARG; + } + + const SdpFingerprintAttributeList& fingerprints( + mediaAttrs.GetFingerprint()); + if (fingerprints.mFingerprints.empty()) { + JSEP_SET_ERROR("Invalid description, no supported fingerprint algorithms " + "present"); + return NS_ERROR_INVALID_ARG; + } + + if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute) && + mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) { + JSEP_SET_ERROR("Description has illegal setup attribute " + "\"holdconn\" at level " + << i); + return NS_ERROR_INVALID_ARG; + } + + auto& formats = parsed->GetMediaSection(i).GetFormats(); + for (auto f = formats.begin(); f != formats.end(); ++f) { + uint16_t pt; + if (!SdpHelper::GetPtAsInt(*f, &pt)) { + JSEP_SET_ERROR("Payload type \"" + << *f << "\" is not a 16-bit unsigned int at level " + << i); + return NS_ERROR_INVALID_ARG; + } + } + + std::string streamId; + std::string trackId; + nsresult rv = mSdpHelper.GetIdsFromMsid(*parsed, + parsed->GetMediaSection(i), + &streamId, + &trackId); + + if (NS_SUCCEEDED(rv)) { + if (trackIds.count(trackId)) { + JSEP_SET_ERROR("track id:" << trackId + << " appears in more than one m-section at level " << i); + return NS_ERROR_INVALID_ARG; + } + + trackIds.insert(trackId); + } else if (rv != NS_ERROR_NOT_AVAILABLE) { + // Error has already been set + return rv; + } + + static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes(); + if (msection.GetMediaType() == SdpMediaSection::kAudio || + msection.GetMediaType() == SdpMediaSection::kVideo) { + // Sanity-check that payload type can work with RTP + for (const std::string& fmt : msection.GetFormats()) { + uint16_t payloadType; + // TODO (bug 1204099): Make this check for reserved ranges. + if (!SdpHelper::GetPtAsInt(fmt, &payloadType) || payloadType > 127) { + JSEP_SET_ERROR("audio/video payload type is too large: " << fmt); + return NS_ERROR_INVALID_ARG; + } + if (forbidden.test(payloadType)) { + JSEP_SET_ERROR("Illegal audio/video payload type: " << fmt); + return NS_ERROR_INVALID_ARG; + } + } + } + } + + *parsedp = Move(parsed); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer) +{ + MOZ_ASSERT(mState == kJsepStateStable); + + // TODO(bug 1095780): Note that we create remote tracks even when + // They contain only codecs we can't negotiate or other craziness. + nsresult rv = SetRemoteTracksFromDescription(offer.get()); + NS_ENSURE_SUCCESS(rv, rv); + + mPendingRemoteDescription = Move(offer); + + SetState(kJsepStateHaveRemoteOffer); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type, + UniquePtr<Sdp> answer) +{ + MOZ_ASSERT(mState == kJsepStateHaveLocalOffer || + mState == kJsepStateHaveRemotePranswer); + + mPendingRemoteDescription = Move(answer); + + nsresult rv = ValidateAnswer(*mPendingLocalDescription, + *mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO(bug 1095780): Note that this creates remote tracks even if + // we offered sendonly and other side offered sendrecv or recvonly. + rv = SetRemoteTracksFromDescription(mPendingRemoteDescription.get()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = Move(mPendingRemoteDescription); + mCurrentLocalDescription = Move(mPendingLocalDescription); + mWasOffererLastTime = mIsOfferer; + + SetState(kJsepStateStable); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteTracksFromDescription(const Sdp* remoteDescription) +{ + // Unassign all remote tracks + for (auto i = mRemoteTracks.begin(); i != mRemoteTracks.end(); ++i) { + i->mAssignedMLine.reset(); + } + + // This will not exist if we're rolling back the first remote description + if (remoteDescription) { + size_t numMlines = remoteDescription->GetMediaSectionCount(); + nsresult rv; + + // Iterate over the sdp, re-assigning or creating remote tracks as we go + for (size_t i = 0; i < numMlines; ++i) { + const SdpMediaSection& msection = remoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(msection) || !msection.IsSending()) { + continue; + } + + std::vector<JsepReceivingTrack>::iterator track; + + if (msection.GetMediaType() == SdpMediaSection::kApplication) { + // Datachannel doesn't have msid, just search by type + track = FindUnassignedTrackByType(mRemoteTracks, + msection.GetMediaType()); + } else { + std::string streamId; + std::string trackId; + rv = GetRemoteIds(*remoteDescription, msection, &streamId, &trackId); + NS_ENSURE_SUCCESS(rv, rv); + + track = FindTrackByIds(mRemoteTracks, streamId, trackId); + } + + if (track == mRemoteTracks.end()) { + RefPtr<JsepTrack> track; + rv = CreateReceivingTrack(i, *remoteDescription, msection, &track); + NS_ENSURE_SUCCESS(rv, rv); + + JsepReceivingTrack rtrack; + rtrack.mTrack = track; + rtrack.mAssignedMLine = Some(i); + mRemoteTracks.push_back(rtrack); + mRemoteTracksAdded.push_back(rtrack); + } else { + track->mAssignedMLine = Some(i); + } + } + } + + // Remove any unassigned remote track ids + for (size_t i = 0; i < mRemoteTracks.size();) { + if (!mRemoteTracks[i].mAssignedMLine.isSome()) { + mRemoteTracksRemoved.push_back(mRemoteTracks[i]); + mRemoteTracks.erase(mRemoteTracks.begin() + i); + } else { + ++i; + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::ValidateLocalDescription(const Sdp& description) +{ + // TODO(bug 1095226): Better checking. + if (!mGeneratedLocalDescription) { + JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer" + " is not supported."); + return NS_ERROR_UNEXPECTED; + } + + if (description.GetMediaSectionCount() != + mGeneratedLocalDescription->GetMediaSectionCount()) { + JSEP_SET_ERROR("Changing the number of m-sections is not allowed"); + return NS_ERROR_INVALID_ARG; + } + + for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) { + auto& origMsection = mGeneratedLocalDescription->GetMediaSection(i); + auto& finalMsection = description.GetMediaSection(i); + if (origMsection.GetMediaType() != finalMsection.GetMediaType()) { + JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed"); + return NS_ERROR_INVALID_ARG; + } + + // These will be present in reoffer + if (!mCurrentLocalDescription) { + if (finalMsection.GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)) { + JSEP_SET_ERROR("Adding your own candidate attributes is not supported"); + return NS_ERROR_INVALID_ARG; + } + + if (finalMsection.GetAttributeList().HasAttribute( + SdpAttribute::kEndOfCandidatesAttribute)) { + JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?"); + return NS_ERROR_INVALID_ARG; + } + } + + // TODO(bug 1095218): Check msid + // TODO(bug 1095226): Check ice-ufrag and ice-pwd + // TODO(bug 1095226): Check fingerprints + // TODO(bug 1095226): Check payload types (at least ensure that payload + // types we don't actually support weren't added) + // TODO(bug 1095226): Check ice-options? + } + + if (description.GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)) { + JSEP_SET_ERROR("Running ICE in lite mode is unsupported"); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) +{ + if (!mCurrentRemoteDescription || !mCurrentLocalDescription) { + // Not renegotiation; checks for whether a remote answer are consistent + // with our offer are handled in ValidateAnswer() + return NS_OK; + } + + if (mCurrentRemoteDescription->GetMediaSectionCount() > + description.GetMediaSectionCount()) { + JSEP_SET_ERROR("New remote description has fewer m-sections than the " + "previous remote description."); + return NS_ERROR_INVALID_ARG; + } + + // These are solely to check that bundle is valid + SdpHelper::BundledMids bundledMids; + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + SdpHelper::BundledMids newBundledMids; + rv = mSdpHelper.GetBundledMids(description, &newBundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + // check for partial ice restart, which is not supported + Maybe<bool> iceCredsDiffer; + for (size_t i = 0; + i < mCurrentRemoteDescription->GetMediaSectionCount(); + ++i) { + + const SdpMediaSection& newMsection = description.GetMediaSection(i); + const SdpMediaSection& oldMsection = + mCurrentRemoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(newMsection) || + mSdpHelper.MsectionIsDisabled(oldMsection)) { + continue; + } + + if (oldMsection.GetMediaType() != newMsection.GetMediaType()) { + JSEP_SET_ERROR("Remote description changes the media type of m-line " + << i); + return NS_ERROR_INVALID_ARG; + } + + bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection); + // Detect whether all the creds are the same or all are different + if (!iceCredsDiffer.isSome()) { + // for the first msection capture whether creds are different or same + iceCredsDiffer = mozilla::Some(differ); + } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) { + // subsequent msections must match the first sections + JSEP_SET_ERROR("Partial ICE restart is unsupported at this time " + "(new remote description changes either the ice-ufrag " + "or ice-pwd on fewer than all msections)"); + return NS_ERROR_INVALID_ARG; + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer) +{ + if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) { + JSEP_SET_ERROR("Offer and answer have different number of m-lines " + << "(" << offer.GetMediaSectionCount() << " vs " + << answer.GetMediaSectionCount() << ")"); + return NS_ERROR_INVALID_ARG; + } + + for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) { + const SdpMediaSection& offerMsection = offer.GetMediaSection(i); + const SdpMediaSection& answerMsection = answer.GetMediaSection(i); + + if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) { + JSEP_SET_ERROR( + "Answer and offer have different media types at m-line " << i); + return NS_ERROR_INVALID_ARG; + } + + if (!offerMsection.IsSending() && answerMsection.IsReceiving()) { + JSEP_SET_ERROR("Answer tried to set recv when offer did not set send"); + return NS_ERROR_INVALID_ARG; + } + + if (!offerMsection.IsReceiving() && answerMsection.IsSending()) { + JSEP_SET_ERROR("Answer tried to set send when offer did not set recv"); + return NS_ERROR_INVALID_ARG; + } + + const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList()); + const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList()); + if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) && + offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) && + offerAttrs.GetMid() != answerAttrs.GetMid()) { + JSEP_SET_ERROR("Answer changes mid for level, was \'" + << offerMsection.GetAttributeList().GetMid() + << "\', now \'" + << answerMsection.GetAttributeList().GetMid() << "\'"); + return NS_ERROR_INVALID_ARG; + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateReceivingTrack(size_t mline, + const Sdp& sdp, + const SdpMediaSection& msection, + RefPtr<JsepTrack>* track) +{ + std::string streamId; + std::string trackId; + + nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId); + NS_ENSURE_SUCCESS(rv, rv); + + *track = new JsepTrack(msection.GetMediaType(), + streamId, + trackId, + sdp::kRecv); + + (*track)->SetCNAME(mSdpHelper.GetCNAME(msection)); + (*track)->PopulateCodecs(mSupportedCodecs.values); + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp) +{ + // draft-ietf-rtcweb-jsep-08 Section 5.2.1: + // o The second SDP line MUST be an "o=" line, as specified in + // [RFC4566], Section 5.2. The value of the <username> field SHOULD + // be "-". The value of the <sess-id> field SHOULD be a + // cryptographically random number. To ensure uniqueness, this + // number SHOULD be at least 64 bits long. The value of the <sess- + // version> field SHOULD be zero. The value of the <nettype> + // <addrtype> <unicast-address> tuple SHOULD be set to a non- + // meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the + // local address in this field. As mentioned in [RFC4566], the + // entire o= line needs to be unique, but selecting a random number + // for <sess-id> is sufficient to accomplish this. + + auto origin = + SdpOrigin("mozilla...THIS_IS_SDPARTA-" MOZ_APP_UA_VERSION, + mSessionId, + mSessionVersion, + sdp::kIPv4, + "0.0.0.0"); + + UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin); + + if (mDtlsFingerprints.empty()) { + JSEP_SET_ERROR("Missing DTLS fingerprint"); + return NS_ERROR_FAILURE; + } + + UniquePtr<SdpFingerprintAttributeList> fpl = + MakeUnique<SdpFingerprintAttributeList>(); + for (auto fp = mDtlsFingerprints.begin(); fp != mDtlsFingerprints.end(); + ++fp) { + fpl->PushEntry(fp->mAlgorithm, fp->mValue); + } + sdp->GetAttributeList().SetAttribute(fpl.release()); + + auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute); + iceOpts->PushEntry("trickle"); + sdp->GetAttributeList().SetAttribute(iceOpts); + + // This assumes content doesn't add a bunch of msid attributes with a + // different semantic in mind. + std::vector<std::string> msids; + msids.push_back("*"); + mSdpHelper.SetupMsidSemantic(msids, sdp.get()); + + *sdpp = Move(sdp); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetupIds() +{ + SECStatus rv = PK11_GenerateRandom( + reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId)); + // RFC 3264 says that session-ids MUST be representable as a _signed_ + // 64 bit number, meaning the MSB cannot be set. + mSessionId = mSessionId >> 1; + if (rv != SECSuccess) { + JSEP_SET_ERROR("Failed to generate session id: " << rv); + return NS_ERROR_FAILURE; + } + + if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) { + JSEP_SET_ERROR("Failed to generate default uuid for streams"); + return NS_ERROR_FAILURE; + } + + if (!mUuidGen->Generate(&mCNAME)) { + JSEP_SET_ERROR("Failed to generate CNAME"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateSsrc(uint32_t* ssrc) +{ + do { + SECStatus rv = PK11_GenerateRandom( + reinterpret_cast<unsigned char*>(ssrc), sizeof(uint32_t)); + if (rv != SECSuccess) { + JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv); + return NS_ERROR_FAILURE; + } + } while (mSsrcs.count(*ssrc)); + mSsrcs.insert(*ssrc); + + return NS_OK; +} + +void +JsepSessionImpl::SetupDefaultCodecs() +{ + // Supported audio codecs. + // Per jmspeex on IRC: + // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good + // quality. Note that 1-2Kbps will be wasted on a stereo Opus channel + // with mono input compared to configuring it for mono. + // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a + // 9KHz tone. This should be adaptive when we're at the low-end of video + // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or + // 12Kbps. + mSupportedCodecs.values.push_back(new JsepAudioCodecDescription( + "109", + "opus", + 48000, + 2, + 960, +#ifdef WEBRTC_GONK + // TODO Move this elsewhere to be adaptive to rate - Bug 1207925 + 16000 // B2G uses lower capture sampling rate +#else + 40000 +#endif + )); + + mSupportedCodecs.values.push_back(new JsepAudioCodecDescription( + "9", + "G722", + 8000, + 1, + 320, + 64000)); + + // packet size and bitrate values below copied from sipcc. + // May need reevaluation from a media expert. + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("0", + "PCMU", + 8000, + 1, + 8000 / 50, // frequency / 50 + 8 * 8000 * 1 // 8 * frequency * channels + )); + + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("8", + "PCMA", + 8000, + 1, + 8000 / 50, // frequency / 50 + 8 * 8000 * 1 // 8 * frequency * channels + )); + + // note: because telephone-event is effectively a marker codec that indicates + // that dtmf rtp packets may be passed, the packetSize and bitRate fields + // don't make sense here. For now, use zero. (mjf) + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("101", + "telephone-event", + 8000, + 1, + 0, // packetSize doesn't make sense here + 0 // bitRate doesn't make sense here + )); + + // Supported video codecs. + // Note: order here implies priority for building offers! + JsepVideoCodecDescription* vp8 = new JsepVideoCodecDescription( + "120", + "VP8", + 90000 + ); + // Defaults for mandatory params + vp8->mConstraints.maxFs = 12288; // Enough for 2048x1536 + vp8->mConstraints.maxFps = 60; + mSupportedCodecs.values.push_back(vp8); + + JsepVideoCodecDescription* vp9 = new JsepVideoCodecDescription( + "121", + "VP9", + 90000 + ); + // Defaults for mandatory params + vp9->mConstraints.maxFs = 12288; // Enough for 2048x1536 + vp9->mConstraints.maxFps = 60; + mSupportedCodecs.values.push_back(vp9); + + JsepVideoCodecDescription* h264_1 = new JsepVideoCodecDescription( + "126", + "H264", + 90000 + ); + h264_1->mPacketizationMode = 1; + // Defaults for mandatory params + h264_1->mProfileLevelId = 0x42E00D; + mSupportedCodecs.values.push_back(h264_1); + + JsepVideoCodecDescription* h264_0 = new JsepVideoCodecDescription( + "97", + "H264", + 90000 + ); + h264_0->mPacketizationMode = 0; + // Defaults for mandatory params + h264_0->mProfileLevelId = 0x42E00D; + mSupportedCodecs.values.push_back(h264_0); + + JsepVideoCodecDescription* red = new JsepVideoCodecDescription( + "122", // payload type + "red", // codec name + 90000 // clock rate (match other video codecs) + ); + mSupportedCodecs.values.push_back(red); + + JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription( + "123", // payload type + "ulpfec", // codec name + 90000 // clock rate (match other video codecs) + ); + mSupportedCodecs.values.push_back(ulpfec); + + mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription( + "5000", + "webrtc-datachannel", + WEBRTC_DATACHANNEL_STREAMS_DEFAULT + )); + + // Update the redundant encodings for the RED codec with the supported + // codecs. Note: only uses the video codecs. + red->UpdateRedundantEncodings(mSupportedCodecs.values); +} + +void +JsepSessionImpl::SetupDefaultRtpExtensions() +{ + AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", + SdpDirectionAttribute::Direction::kSendonly); +} + +void +JsepSessionImpl::SetState(JsepSignalingState state) +{ + if (state == mState) + return; + + MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << + GetStateStr(mState) << " -> " << GetStateStr(state)); + mState = state; +} + +nsresult +JsepSessionImpl::AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedRemoteDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + return mSdpHelper.AddCandidateToSdp(sdp, candidate, mid, level); +} + +nsresult +JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate, + uint16_t level, + std::string* mid, + bool* skipped) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (sdp->GetMediaSectionCount() <= level) { + // mainly here to make some testing less complicated, but also just in case + *skipped = true; + return NS_OK; + } + + if (mState == kJsepStateStable) { + const Sdp* answer(GetAnswer()); + if (mSdpHelper.IsBundleSlave(*answer, level)) { + // We do not add candidate attributes to bundled m-sections unless they + // are the "master" bundle m-section. + *skipped = true; + return NS_OK; + } + } + + nsresult rv = mSdpHelper.GetMidFromLevel(*sdp, level, mid); + if (NS_FAILED(rv)) { + return rv; + } + + *skipped = false; + + return mSdpHelper.AddCandidateToSdp(sdp, candidate, *mid, level); +} + +nsresult +JsepSessionImpl::UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, + uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, + uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (level >= sdp->GetMediaSectionCount()) { + return NS_OK; + } + + std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr); + if (mState == kJsepStateStable && mTransports[level]->mComponents == 1) { + // We know we're doing rtcp-mux by now. Don't create an rtcp attr. + defaultRtcpCandidateAddrCopy = ""; + defaultRtcpCandidatePort = 0; + } + + // If offer/answer isn't done, it is too early to tell whether these defaults + // need to be applied to other m-sections. + SdpHelper::BundledMids bundledMids; + if (mState == kJsepStateStable) { + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + mLastError += " (This should have been caught sooner!)"; + return NS_ERROR_FAILURE; + } + } + + mSdpHelper.SetDefaultAddresses( + defaultCandidateAddr, + defaultCandidatePort, + defaultRtcpCandidateAddrCopy, + defaultRtcpCandidatePort, + sdp, + level, + bundledMids); + + return NS_OK; +} + +nsresult +JsepSessionImpl::EndOfLocalCandidates(uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot mark end of local ICE candidates in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (level >= sdp->GetMediaSectionCount()) { + return NS_OK; + } + + // If offer/answer isn't done, it is too early to tell whether this update + // needs to be applied to other m-sections. + SdpHelper::BundledMids bundledMids; + if (mState == kJsepStateStable) { + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + mLastError += " (This should have been caught sooner!)"; + return NS_ERROR_FAILURE; + } + } + + mSdpHelper.SetIceGatheringComplete(sdp, + level, + bundledMids); + + return NS_OK; +} + +nsresult +JsepSessionImpl::GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids) +{ + const Sdp* answerSdp = GetAnswer(); + + if (!answerSdp) { + return NS_OK; + } + + return mSdpHelper.GetBundledMids(*answerSdp, bundledMids); +} + +nsresult +JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection) +{ + // We assert here because adding rtcp-mux to a non-disabled m-section that + // did not already have rtcp-mux can cause problems. + MOZ_ASSERT(mSdpHelper.MsectionIsDisabled(*msection)); + + msection->SetPort(9); + + // We don't do this in AddTransportAttributes because that is also used for + // making answers, and we don't want to unconditionally set rtcp-mux there. + if (mSdpHelper.HasRtcp(msection->GetProtocol())) { + // Set RTCP-MUX. + msection->GetAttributeList().SetAttribute( + new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); + } + + nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetRecvonlySsrc(msection); + NS_ENSURE_SUCCESS(rv, rv); + + AddExtmap(msection); + + std::ostringstream osMid; + osMid << "sdparta_" << msection->GetLevel(); + AddMid(osMid.str(), msection); + + return NS_OK; +} + +mozilla::Sdp* +JsepSessionImpl::GetParsedLocalDescription() const +{ + if (mPendingLocalDescription) { + return mPendingLocalDescription.get(); + } else if (mCurrentLocalDescription) { + return mCurrentLocalDescription.get(); + } + + return nullptr; +} + +mozilla::Sdp* +JsepSessionImpl::GetParsedRemoteDescription() const +{ + if (mPendingRemoteDescription) { + return mPendingRemoteDescription.get(); + } else if (mCurrentRemoteDescription) { + return mCurrentRemoteDescription.get(); + } + + return nullptr; +} + +const Sdp* +JsepSessionImpl::GetAnswer() const +{ + return mWasOffererLastTime ? mCurrentRemoteDescription.get() + : mCurrentLocalDescription.get(); +} + +nsresult +JsepSessionImpl::Close() +{ + mLastError.clear(); + SetState(kJsepStateClosed); + return NS_OK; +} + +const std::string +JsepSessionImpl::GetLastError() const +{ + return mLastError; +} + +bool +JsepSessionImpl::AllLocalTracksAreAssigned() const +{ + for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) { + if (!i->mAssignedMLine.isSome()) { + return false; + } + } + + return true; +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h new file mode 100644 index 000000000..00c07d25e --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h @@ -0,0 +1,352 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JSEPSESSIONIMPL_H_ +#define _JSEPSESSIONIMPL_H_ + +#include <set> +#include <string> +#include <vector> + +#include "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepSession.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/sdp/SipccSdpParser.h" +#include "signaling/src/sdp/SdpHelper.h" +#include "signaling/src/common/PtrVector.h" + +namespace mozilla { + +class JsepUuidGenerator +{ +public: + virtual ~JsepUuidGenerator() {} + virtual bool Generate(std::string* id) = 0; +}; + +class JsepSessionImpl : public JsepSession +{ +public: + JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen) + : JsepSession(name), + mIsOfferer(false), + mWasOffererLastTime(false), + mIceControlling(false), + mRemoteIsIceLite(false), + mRemoteIceIsRestarting(false), + mBundlePolicy(kBundleBalanced), + mSessionId(0), + mSessionVersion(0), + mUuidGen(Move(uuidgen)), + mSdpHelper(&mLastError) + { + } + + // Implement JsepSession methods. + virtual nsresult Init() override; + + virtual nsresult AddTrack(const RefPtr<JsepTrack>& track) override; + + virtual nsresult RemoveTrack(const std::string& streamId, + const std::string& trackId) override; + + virtual nsresult SetIceCredentials(const std::string& ufrag, + const std::string& pwd) override; + virtual const std::string& GetUfrag() const override { return mIceUfrag; } + virtual const std::string& GetPwd() const override { return mIcePwd; } + nsresult SetBundlePolicy(JsepBundlePolicy policy) override; + + virtual bool + RemoteIsIceLite() const override + { + return mRemoteIsIceLite; + } + + virtual bool + RemoteIceIsRestarting() const override + { + return mRemoteIceIsRestarting; + } + + virtual std::vector<std::string> + GetIceOptions() const override + { + return mIceOptions; + } + + virtual nsresult AddDtlsFingerprint(const std::string& algorithm, + const std::vector<uint8_t>& value) override; + + nsresult AddRtpExtension(std::vector<SdpExtmapAttributeList::Extmap>& extensions, + const std::string& extensionName, + SdpDirectionAttribute::Direction direction); + virtual nsresult AddAudioRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction = + SdpDirectionAttribute::Direction::kSendrecv) override; + + virtual nsresult AddVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction = + SdpDirectionAttribute::Direction::kSendrecv) override; + + virtual std::vector<JsepCodecDescription*>& + Codecs() override + { + return mSupportedCodecs.values; + } + + virtual nsresult ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) override; + + virtual nsresult SetParameters( + const std::string& streamId, + const std::string& trackId, + const std::vector<JsepTrack::JsConstraints>& constraints) override; + + virtual nsresult GetParameters( + const std::string& streamId, + const std::string& trackId, + std::vector<JsepTrack::JsConstraints>* outConstraints) override; + + virtual std::vector<RefPtr<JsepTrack>> GetLocalTracks() const override; + + virtual std::vector<RefPtr<JsepTrack>> GetRemoteTracks() const override; + + virtual std::vector<RefPtr<JsepTrack>> + GetRemoteTracksAdded() const override; + + virtual std::vector<RefPtr<JsepTrack>> + GetRemoteTracksRemoved() const override; + + virtual nsresult CreateOffer(const JsepOfferOptions& options, + std::string* offer) override; + + virtual nsresult CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) override; + + virtual std::string GetLocalDescription() const override; + + virtual std::string GetRemoteDescription() const override; + + virtual nsresult SetLocalDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual nsresult SetRemoteDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual nsresult AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) override; + + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + uint16_t level, + std::string* mid, + bool* skipped) override; + + virtual nsresult UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, + uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, + uint16_t level) override; + + virtual nsresult EndOfLocalCandidates(uint16_t level) override; + + virtual nsresult Close() override; + + virtual const std::string GetLastError() const override; + + virtual bool + IsIceControlling() const override + { + return mIceControlling; + } + + virtual bool + IsOfferer() const + { + return mIsOfferer; + } + + // Access transports. + virtual std::vector<RefPtr<JsepTransport>> + GetTransports() const override + { + return mTransports; + } + + virtual std::vector<JsepTrackPair> + GetNegotiatedTrackPairs() const override + { + return mNegotiatedTrackPairs; + } + + virtual bool AllLocalTracksAreAssigned() const override; + +private: + struct JsepDtlsFingerprint { + std::string mAlgorithm; + std::vector<uint8_t> mValue; + }; + + struct JsepSendingTrack { + RefPtr<JsepTrack> mTrack; + Maybe<size_t> mAssignedMLine; + }; + + struct JsepReceivingTrack { + RefPtr<JsepTrack> mTrack; + Maybe<size_t> mAssignedMLine; + }; + + // Non-const so it can set mLastError + nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp); + void AddExtmap(SdpMediaSection* msection) const; + void AddMid(const std::string& mid, SdpMediaSection* msection) const; + const std::vector<SdpExtmapAttributeList::Extmap>* GetRtpExtensions( + SdpMediaSection::MediaType type) const; + + void AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection); + nsresult SetupIds(); + nsresult CreateSsrc(uint32_t* ssrc); + void SetupDefaultCodecs(); + void SetupDefaultRtpExtensions(); + void SetState(JsepSignalingState state); + // Non-const so it can set mLastError + nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp); + nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer); + nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer); + nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer); + nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer); + nsresult ValidateLocalDescription(const Sdp& description); + nsresult ValidateRemoteDescription(const Sdp& description); + nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer); + nsresult SetRemoteTracksFromDescription(const Sdp* remoteDescription); + // Non-const because we use our Uuid generator + nsresult CreateReceivingTrack(size_t mline, + const Sdp& sdp, + const SdpMediaSection& msection, + RefPtr<JsepTrack>* track); + nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local, + const UniquePtr<Sdp>& remote); + nsresult AddTransportAttributes(SdpMediaSection* msection, + SdpSetupAttribute::Role dtlsRole); + nsresult CopyPreviousTransportParams(const Sdp& oldAnswer, + const Sdp& offerersPreviousSdp, + const Sdp& newOffer, + Sdp* newLocal); + nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp); + // Non-const so it can assign m-line index to tracks + nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type, + Maybe<size_t> offerToReceive, + Sdp* sdp); + nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp); + nsresult BindRemoteTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToReceive); + nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToRecv); + void SetupOfferToReceiveMsection(SdpMediaSection* offer); + nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype, + size_t count, + Sdp* sdp); + nsresult AddReofferMsections(const Sdp& oldLocalSdp, + const Sdp& oldAnswer, + Sdp* newSdp); + void SetupBundle(Sdp* sdp) const; + nsresult GetRemoteIds(const Sdp& sdp, + const SdpMediaSection& msection, + std::string* streamId, + std::string* trackId); + nsresult CreateOfferMSection(SdpMediaSection::MediaType type, + SdpMediaSection::Protocol proto, + SdpDirectionAttribute::Direction direction, + Sdp* sdp); + nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type, + Sdp* sdp, + SdpMediaSection** msection); + nsresult CreateAnswerMSection(const JsepAnswerOptions& options, + size_t mlineIndex, + const SdpMediaSection& remoteMsection, + Sdp* sdp); + nsresult SetRecvonlySsrc(SdpMediaSection* msection); + nsresult BindMatchingLocalTrackToAnswer(SdpMediaSection* msection); + nsresult BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection); + nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection, + SdpSetupAttribute::Role* rolep); + nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote, + const SdpMediaSection& local, + const RefPtr<JsepTransport>& transport, + bool usingBundle, + size_t transportLevel, + JsepTrackPair* trackPairOut); + void InitTransport(const SdpMediaSection& msection, JsepTransport* transport); + + nsresult FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + const RefPtr<JsepTransport>& transport); + + nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids); + + nsresult EnableOfferMsection(SdpMediaSection* msection); + + mozilla::Sdp* GetParsedLocalDescription() const; + mozilla::Sdp* GetParsedRemoteDescription() const; + const Sdp* GetAnswer() const; + + std::vector<JsepSendingTrack> mLocalTracks; + std::vector<JsepReceivingTrack> mRemoteTracks; + // By the most recent SetRemoteDescription + std::vector<JsepReceivingTrack> mRemoteTracksAdded; + std::vector<JsepReceivingTrack> mRemoteTracksRemoved; + std::vector<RefPtr<JsepTransport> > mTransports; + // So we can rollback + std::vector<RefPtr<JsepTransport> > mOldTransports; + std::vector<JsepTrackPair> mNegotiatedTrackPairs; + + bool mIsOfferer; + bool mWasOffererLastTime; + bool mIceControlling; + std::string mIceUfrag; + std::string mIcePwd; + bool mRemoteIsIceLite; + bool mRemoteIceIsRestarting; + std::vector<std::string> mIceOptions; + JsepBundlePolicy mBundlePolicy; + std::vector<JsepDtlsFingerprint> mDtlsFingerprints; + uint64_t mSessionId; + uint64_t mSessionVersion; + std::vector<SdpExtmapAttributeList::Extmap> mAudioRtpExtensions; + std::vector<SdpExtmapAttributeList::Extmap> mVideoRtpExtensions; + UniquePtr<JsepUuidGenerator> mUuidGen; + std::string mDefaultRemoteStreamId; + std::map<size_t, std::string> mDefaultRemoteTrackIdsByLevel; + std::string mCNAME; + // Used to prevent duplicate local SSRCs. Not used to prevent local/remote or + // remote-only duplication, which will be important for EKT but not now. + std::set<uint32_t> mSsrcs; + // When an m-section doesn't have a local track, it still needs an ssrc, which + // is stored here. + std::vector<uint32_t> mRecvonlySsrcs; + UniquePtr<Sdp> mGeneratedLocalDescription; // Created but not set. + UniquePtr<Sdp> mCurrentLocalDescription; + UniquePtr<Sdp> mCurrentRemoteDescription; + UniquePtr<Sdp> mPendingLocalDescription; + UniquePtr<Sdp> mPendingRemoteDescription; + PtrVector<JsepCodecDescription> mSupportedCodecs; + std::string mLastError; + SipccSdpParser mParser; + SdpHelper mSdpHelper; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.cpp b/media/webrtc/signaling/src/jsep/JsepTrack.cpp new file mode 100644 index 000000000..1b045d8ec --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp @@ -0,0 +1,531 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/jsep/JsepTrackEncoding.h" + +#include <algorithm> + +namespace mozilla +{ +void +JsepTrack::GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes) +{ + if (!mNegotiatedDetails) { + return; + } + + for (const auto* encoding : mNegotiatedDetails->mEncodings.values) { + GetPayloadTypes(encoding->GetCodecs(), payloadTypes); + } + + // Prune out dupes + std::sort(payloadTypes->begin(), payloadTypes->end()); + auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end()); + payloadTypes->erase(newEnd, payloadTypes->end()); +} + +/* static */ +void +JsepTrack::GetPayloadTypes( + const std::vector<JsepCodecDescription*>& codecs, + std::vector<uint16_t>* payloadTypes) +{ + for (JsepCodecDescription* codec : codecs) { + uint16_t pt; + if (!codec->GetPtAsInt(&pt)) { + MOZ_ASSERT(false); + continue; + } + payloadTypes->push_back(pt); + } +} + +void +JsepTrack::EnsureNoDuplicatePayloadTypes( + std::vector<JsepCodecDescription*>* codecs) +{ + std::set<uint16_t> uniquePayloadTypes; + + for (JsepCodecDescription* codec : *codecs) { + // We assume there are no dupes in negotiated codecs; unnegotiated codecs + // need to change if there is a clash. + if (!codec->mEnabled) { + continue; + } + + // Disable, and only re-enable if we can ensure it has a unique pt. + codec->mEnabled = false; + + uint16_t currentPt; + if (!codec->GetPtAsInt(¤tPt)) { + MOZ_ASSERT(false); + continue; + } + + if (!uniquePayloadTypes.count(currentPt)) { + codec->mEnabled = true; + uniquePayloadTypes.insert(currentPt); + continue; + } + + // |codec| cannot use its current payload type. Try to find another. + for (uint16_t freePt = 96; freePt <= 127; ++freePt) { + // Not super efficient, but readability is probably more important. + if (!uniquePayloadTypes.count(freePt)) { + uniquePayloadTypes.insert(freePt); + codec->mEnabled = true; + std::ostringstream os; + os << freePt; + codec->mDefaultPt = os.str(); + break; + } + } + } +} + +void +JsepTrack::PopulateCodecs(const std::vector<JsepCodecDescription*>& prototype) +{ + for (const JsepCodecDescription* prototypeCodec : prototype) { + if (prototypeCodec->mType == mType) { + mPrototypeCodecs.values.push_back(prototypeCodec->Clone()); + mPrototypeCodecs.values.back()->mDirection = mDirection; + } + } + + EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values); +} + +void +JsepTrack::AddToOffer(SdpMediaSection* offer) const +{ + AddToMsection(mPrototypeCodecs.values, offer); + if (mDirection == sdp::kSend) { + AddToMsection(mJsEncodeConstraints, sdp::kSend, offer); + } +} + +void +JsepTrack::AddToAnswer(const SdpMediaSection& offer, + SdpMediaSection* answer) const +{ + // We do not modify mPrototypeCodecs here, since we're only creating an + // answer. Once offer/answer concludes, we will update mPrototypeCodecs. + PtrVector<JsepCodecDescription> codecs; + codecs.values = GetCodecClones(); + NegotiateCodecs(offer, &codecs.values); + if (codecs.values.empty()) { + return; + } + + AddToMsection(codecs.values, answer); + + if (mDirection == sdp::kSend) { + std::vector<JsConstraints> constraints; + std::vector<SdpRidAttributeList::Rid> rids; + GetRids(offer, sdp::kRecv, &rids); + NegotiateRids(rids, &constraints); + AddToMsection(constraints, sdp::kSend, answer); + } +} + +void +JsepTrack::AddToMsection(const std::vector<JsepCodecDescription*>& codecs, + SdpMediaSection* msection) const +{ + MOZ_ASSERT(msection->GetMediaType() == mType); + MOZ_ASSERT(!codecs.empty()); + + for (const JsepCodecDescription* codec : codecs) { + codec->AddToMediaSection(*msection); + } + + if (mDirection == sdp::kSend) { + if (msection->GetMediaType() != SdpMediaSection::kApplication) { + msection->SetSsrcs(mSsrcs, mCNAME); + msection->AddMsid(mStreamId, mTrackId); + } + msection->SetSending(true); + } else { + msection->SetReceiving(true); + } +} + +// Updates the |id| values in |constraintsList| with the rid values in |rids|, +// where necessary. +void +JsepTrack::NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids, + std::vector<JsConstraints>* constraintsList) const +{ + for (const SdpRidAttributeList::Rid& rid : rids) { + if (!FindConstraints(rid.id, *constraintsList)) { + // Pair up the first JsConstraints with an empty id, if it exists. + JsConstraints* constraints = FindConstraints("", *constraintsList); + if (constraints) { + constraints->rid = rid.id; + } + } + } +} + +/* static */ +void +JsepTrack::AddToMsection(const std::vector<JsConstraints>& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection) +{ + UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute); + UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList); + for (const JsConstraints& constraints : constraintsList) { + if (!constraints.rid.empty()) { + SdpRidAttributeList::Rid rid; + rid.id = constraints.rid; + rid.direction = direction; + rids->mRids.push_back(rid); + + SdpSimulcastAttribute::Version version; + version.choices.push_back(constraints.rid); + if (direction == sdp::kSend) { + simulcast->sendVersions.push_back(version); + } else { + simulcast->recvVersions.push_back(version); + } + } + } + + if (!rids->mRids.empty()) { + msection->GetAttributeList().SetAttribute(simulcast.release()); + msection->GetAttributeList().SetAttribute(rids.release()); + } +} + +void +JsepTrack::GetRids(const SdpMediaSection& msection, + sdp::Direction direction, + std::vector<SdpRidAttributeList::Rid>* rids) const +{ + rids->clear(); + if (!msection.GetAttributeList().HasAttribute( + SdpAttribute::kSimulcastAttribute)) { + return; + } + + const SdpSimulcastAttribute& simulcast( + msection.GetAttributeList().GetSimulcast()); + + const SdpSimulcastAttribute::Versions* versions = nullptr; + switch (direction) { + case sdp::kSend: + versions = &simulcast.sendVersions; + break; + case sdp::kRecv: + versions = &simulcast.recvVersions; + break; + } + + if (!versions->IsSet()) { + return; + } + + if (versions->type != SdpSimulcastAttribute::Versions::kRid) { + // No support for PT-based simulcast, yet. + return; + } + + for (const SdpSimulcastAttribute::Version& version : *versions) { + if (!version.choices.empty()) { + // We validate that rids are present (and sane) elsewhere. + rids->push_back(*msection.FindRid(version.choices[0])); + } + } +} + +JsepTrack::JsConstraints* +JsepTrack::FindConstraints(const std::string& id, + std::vector<JsConstraints>& constraintsList) const +{ + for (JsConstraints& constraints : constraintsList) { + if (constraints.rid == id) { + return &constraints; + } + } + return nullptr; +} + +void +JsepTrack::CreateEncodings( + const SdpMediaSection& remote, + const std::vector<JsepCodecDescription*>& negotiatedCodecs, + JsepTrackNegotiatedDetails* negotiatedDetails) +{ + std::vector<SdpRidAttributeList::Rid> rids; + GetRids(remote, sdp::kRecv, &rids); // Get rids we will send + NegotiateRids(rids, &mJsEncodeConstraints); + if (rids.empty()) { + // Add dummy value with an empty id to make sure we get a single unicast + // stream. + rids.push_back(SdpRidAttributeList::Rid()); + } + + // For each rid in the remote, make sure we have an encoding, and configure + // that encoding appropriately. + for (size_t i = 0; i < rids.size(); ++i) { + if (i == negotiatedDetails->mEncodings.values.size()) { + negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding); + } + + JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i]; + + for (const JsepCodecDescription* codec : negotiatedCodecs) { + if (rids[i].HasFormat(codec->mDefaultPt)) { + encoding->AddCodec(*codec); + } + } + + encoding->mRid = rids[i].id; + // If we end up supporting params for rid, we would handle that here. + + // Incorporate the corresponding JS encoding constraints, if they exist + for (const JsConstraints& jsConstraints : mJsEncodeConstraints) { + if (jsConstraints.rid == rids[i].id) { + encoding->mConstraints = jsConstraints.constraints; + } + } + + encoding->UpdateMaxBitrate(remote); + } +} + +std::vector<JsepCodecDescription*> +JsepTrack::GetCodecClones() const +{ + std::vector<JsepCodecDescription*> clones; + for (const JsepCodecDescription* codec : mPrototypeCodecs.values) { + clones.push_back(codec->Clone()); + } + return clones; +} + +static bool +CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs) +{ + return lhs->mStronglyPreferred && !rhs->mStronglyPreferred; +} + +void +JsepTrack::NegotiateCodecs( + const SdpMediaSection& remote, + std::vector<JsepCodecDescription*>* codecs, + std::map<std::string, std::string>* formatChanges) const +{ + PtrVector<JsepCodecDescription> unnegotiatedCodecs; + std::swap(unnegotiatedCodecs.values, *codecs); + + // Outer loop establishes the remote side's preference + for (const std::string& fmt : remote.GetFormats()) { + for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) { + JsepCodecDescription* codec = unnegotiatedCodecs.values[i]; + if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { + continue; + } + + std::string originalFormat = codec->mDefaultPt; + if(codec->Negotiate(fmt, remote)) { + codecs->push_back(codec); + unnegotiatedCodecs.values[i] = nullptr; + if (formatChanges) { + (*formatChanges)[originalFormat] = codec->mDefaultPt; + } + break; + } + } + } + + // Find the (potential) red codec and ulpfec codec or telephone-event + JsepVideoCodecDescription* red = nullptr; + JsepVideoCodecDescription* ulpfec = nullptr; + JsepAudioCodecDescription* dtmf = nullptr; + // We can safely cast here since JsepTrack has a MediaType and only codecs + // that match that MediaType (kAudio or kVideo) are added. + for (auto codec : *codecs) { + if (codec->mName == "red") { + red = static_cast<JsepVideoCodecDescription*>(codec); + } + else if (codec->mName == "ulpfec") { + ulpfec = static_cast<JsepVideoCodecDescription*>(codec); + } + else if (codec->mName == "telephone-event") { + dtmf = static_cast<JsepAudioCodecDescription*>(codec); + } + } + // if we have a red codec remove redundant encodings that don't exist + if (red) { + // Since we could have an externally specified redundant endcodings + // list, we shouldn't simply rebuild the redundant encodings list + // based on the current list of codecs. + std::vector<uint8_t> unnegotiatedEncodings; + std::swap(unnegotiatedEncodings, red->mRedundantEncodings); + for (auto redundantPt : unnegotiatedEncodings) { + std::string pt = std::to_string(redundantPt); + for (auto codec : *codecs) { + if (pt == codec->mDefaultPt) { + red->mRedundantEncodings.push_back(redundantPt); + break; + } + } + } + } + // Video FEC is indicated by the existence of the red and ulpfec + // codecs and not an attribute on the particular video codec (like in + // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC + // on all the other codecs. + if (red && ulpfec) { + for (auto codec : *codecs) { + if (codec->mName != "red" && codec->mName != "ulpfec") { + JsepVideoCodecDescription* videoCodec = + static_cast<JsepVideoCodecDescription*>(codec); + videoCodec->EnableFec(); + } + } + } + + // Dtmf support is indicated by the existence of the telephone-event + // codec, and not an attribute on the particular audio codec (like in a + // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf + // support on all the other audio codecs. + if (dtmf) { + for (auto codec : *codecs) { + JsepAudioCodecDescription* audioCodec = + static_cast<JsepAudioCodecDescription*>(codec); + audioCodec->mDtmfEnabled = true; + } + } + + // Make sure strongly preferred codecs are up front, overriding the remote + // side's preference. + std::stable_sort(codecs->begin(), codecs->end(), CompareCodec); + + // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an + // answer. For now, remove all but the first codec unless the red codec + // exists, and then we include the others per RFC 5109, section 14.2. + // Note: now allows keeping the telephone-event codec, if it appears, as the + // last codec in the list. + if (!codecs->empty() && !red) { + int newSize = dtmf ? 2 : 1; + for (size_t i = 1; i < codecs->size(); ++i) { + if (!dtmf || dtmf != (*codecs)[i]) { + delete (*codecs)[i]; + (*codecs)[i] = nullptr; + } + } + if (dtmf) { + (*codecs)[newSize-1] = dtmf; + } + codecs->resize(newSize); + } +} + +void +JsepTrack::Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote) +{ + PtrVector<JsepCodecDescription> negotiatedCodecs; + negotiatedCodecs.values = GetCodecClones(); + + std::map<std::string, std::string> formatChanges; + NegotiateCodecs(remote, + &negotiatedCodecs.values, + &formatChanges); + + // Use |formatChanges| to update mPrototypeCodecs + size_t insertPos = 0; + for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) { + if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) { + // Update the payload type to what was negotiated + mPrototypeCodecs.values[i]->mDefaultPt = + formatChanges[mPrototypeCodecs.values[i]->mDefaultPt]; + // Move this negotiated codec up front + std::swap(mPrototypeCodecs.values[insertPos], + mPrototypeCodecs.values[i]); + ++insertPos; + } + } + + EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values); + + UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails = + MakeUnique<JsepTrackNegotiatedDetails>(); + + CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get()); + + if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { + for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { + negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr; + } + } + + if (mDirection == sdp::kRecv) { + mSsrcs.clear(); + if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { + for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) { + AddSsrc(ssrcAttr.ssrc); + } + } + } + + mNegotiatedDetails = Move(negotiatedDetails); +} + +// When doing bundle, if all else fails we can try to figure out which m-line a +// given RTP packet belongs to by looking at the payload type field. This only +// works, however, if that payload type appeared in only one m-section. +// We figure that out here. +/* static */ +void +JsepTrack::SetUniquePayloadTypes(const std::vector<RefPtr<JsepTrack>>& tracks) +{ + // Maps to track details if no other track contains the payload type, + // otherwise maps to nullptr. + std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap; + + for (const RefPtr<JsepTrack>& track : tracks) { + if (track->GetMediaType() == SdpMediaSection::kApplication) { + continue; + } + + auto* details = track->GetNegotiatedDetails(); + if (!details) { + // Can happen if negotiation fails on a track + continue; + } + + std::vector<uint16_t> payloadTypesForTrack; + track->GetNegotiatedPayloadTypes(&payloadTypesForTrack); + + for (uint16_t pt : payloadTypesForTrack) { + if (payloadTypeToDetailsMap.count(pt)) { + // Found in more than one track, not unique + payloadTypeToDetailsMap[pt] = nullptr; + } else { + payloadTypeToDetailsMap[pt] = details; + } + } + } + + for (auto ptAndDetails : payloadTypeToDetailsMap) { + uint16_t uniquePt = ptAndDetails.first; + MOZ_ASSERT(uniquePt <= UINT8_MAX); + auto trackDetails = ptAndDetails.second; + + if (trackDetails) { + trackDetails->mUniquePayloadTypes.push_back( + static_cast<uint8_t>(uniquePt)); + } + } +} + +} // namespace mozilla + diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.h b/media/webrtc/signaling/src/jsep/JsepTrack.h new file mode 100644 index 000000000..5aa37404f --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrack.h @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JSEPTRACK_H_ +#define _JSEPTRACK_H_ + +#include <algorithm> +#include <string> +#include <map> +#include <set> + +#include <mozilla/RefPtr.h> +#include <mozilla/UniquePtr.h> +#include <mozilla/Maybe.h> +#include "nsISupportsImpl.h" +#include "nsError.h" + +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/jsep/JsepTrackEncoding.h" +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SdpAttribute.h" +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/common/PtrVector.h" + +namespace mozilla { + +class JsepTrackNegotiatedDetails +{ +public: + size_t + GetEncodingCount() const + { + return mEncodings.values.size(); + } + + const JsepTrackEncoding& + GetEncoding(size_t index) const + { + MOZ_RELEASE_ASSERT(index < mEncodings.values.size()); + return *mEncodings.values[index]; + } + + const SdpExtmapAttributeList::Extmap* + GetExt(const std::string& ext_name) const + { + auto it = mExtmap.find(ext_name); + if (it != mExtmap.end()) { + return &it->second; + } + return nullptr; + } + + std::vector<uint8_t> GetUniquePayloadTypes() const + { + return mUniquePayloadTypes; + } + +private: + friend class JsepTrack; + + std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap; + std::vector<uint8_t> mUniquePayloadTypes; + PtrVector<JsepTrackEncoding> mEncodings; +}; + +class JsepTrack +{ +public: + JsepTrack(mozilla::SdpMediaSection::MediaType type, + const std::string& streamid, + const std::string& trackid, + sdp::Direction direction = sdp::kSend) + : mType(type), + mStreamId(streamid), + mTrackId(trackid), + mDirection(direction), + mActive(false) + {} + + virtual mozilla::SdpMediaSection::MediaType + GetMediaType() const + { + return mType; + } + + virtual const std::string& + GetStreamId() const + { + return mStreamId; + } + + virtual void + SetStreamId(const std::string& id) + { + mStreamId = id; + } + + virtual const std::string& + GetTrackId() const + { + return mTrackId; + } + + virtual void + SetTrackId(const std::string& id) + { + mTrackId = id; + } + + virtual const std::string& + GetCNAME() const + { + return mCNAME; + } + + virtual void + SetCNAME(const std::string& cname) + { + mCNAME = cname; + } + + virtual sdp::Direction + GetDirection() const + { + return mDirection; + } + + virtual const std::vector<uint32_t>& + GetSsrcs() const + { + return mSsrcs; + } + + virtual void + AddSsrc(uint32_t ssrc) + { + mSsrcs.push_back(ssrc); + } + + bool + GetActive() const + { + return mActive; + } + + void + SetActive(bool active) + { + mActive = active; + } + + virtual void PopulateCodecs( + const std::vector<JsepCodecDescription*>& prototype); + + template <class UnaryFunction> + void ForEachCodec(UnaryFunction func) + { + std::for_each(mPrototypeCodecs.values.begin(), + mPrototypeCodecs.values.end(), func); + } + + template <class BinaryPredicate> + void SortCodecs(BinaryPredicate sorter) + { + std::stable_sort(mPrototypeCodecs.values.begin(), + mPrototypeCodecs.values.end(), sorter); + } + + virtual void AddToOffer(SdpMediaSection* offer) const; + virtual void AddToAnswer(const SdpMediaSection& offer, + SdpMediaSection* answer) const; + virtual void Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote); + static void SetUniquePayloadTypes( + const std::vector<RefPtr<JsepTrack>>& tracks); + virtual void GetNegotiatedPayloadTypes(std::vector<uint16_t>* payloadTypes); + + // This will be set when negotiation is carried out. + virtual const JsepTrackNegotiatedDetails* + GetNegotiatedDetails() const + { + if (mNegotiatedDetails) { + return mNegotiatedDetails.get(); + } + return nullptr; + } + + virtual JsepTrackNegotiatedDetails* + GetNegotiatedDetails() + { + if (mNegotiatedDetails) { + return mNegotiatedDetails.get(); + } + return nullptr; + } + + virtual void + ClearNegotiatedDetails() + { + mNegotiatedDetails.reset(); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack); + + struct JsConstraints + { + std::string rid; + EncodingConstraints constraints; + }; + + void SetJsConstraints(const std::vector<JsConstraints>& constraintsList) + { + mJsEncodeConstraints = constraintsList; + } + + void GetJsConstraints(std::vector<JsConstraints>* outConstraintsList) const + { + MOZ_ASSERT(outConstraintsList); + *outConstraintsList = mJsEncodeConstraints; + } + + static void AddToMsection(const std::vector<JsConstraints>& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection); + +protected: + virtual ~JsepTrack() {} + +private: + std::vector<JsepCodecDescription*> GetCodecClones() const; + static void EnsureNoDuplicatePayloadTypes( + std::vector<JsepCodecDescription*>* codecs); + static void GetPayloadTypes( + const std::vector<JsepCodecDescription*>& codecs, + std::vector<uint16_t>* pts); + static void EnsurePayloadTypeIsUnique(std::set<uint16_t>* uniquePayloadTypes, + JsepCodecDescription* codec); + void AddToMsection(const std::vector<JsepCodecDescription*>& codecs, + SdpMediaSection* msection) const; + void GetRids(const SdpMediaSection& msection, + sdp::Direction direction, + std::vector<SdpRidAttributeList::Rid>* rids) const; + void CreateEncodings( + const SdpMediaSection& remote, + const std::vector<JsepCodecDescription*>& negotiatedCodecs, + JsepTrackNegotiatedDetails* details); + + // |formatChanges| is set on completion of offer/answer, and records how the + // formats in |codecs| were changed, which is used by |Negotiate| to update + // |mPrototypeCodecs|. + virtual void NegotiateCodecs( + const SdpMediaSection& remote, + std::vector<JsepCodecDescription*>* codecs, + std::map<std::string, std::string>* formatChanges = nullptr) const; + + JsConstraints* FindConstraints( + const std::string& rid, + std::vector<JsConstraints>& constraintsList) const; + void NegotiateRids(const std::vector<SdpRidAttributeList::Rid>& rids, + std::vector<JsConstraints>* constraints) const; + + const mozilla::SdpMediaSection::MediaType mType; + std::string mStreamId; + std::string mTrackId; + std::string mCNAME; + const sdp::Direction mDirection; + PtrVector<JsepCodecDescription> mPrototypeCodecs; + // Holds encoding params/constraints from JS. Simulcast happens when there are + // multiple of these. If there are none, we assume unconstrained unicast with + // no rid. + std::vector<JsConstraints> mJsEncodeConstraints; + UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails; + std::vector<uint32_t> mSsrcs; + bool mActive; +}; + +// Need a better name for this. +struct JsepTrackPair { + size_t mLevel; + // Is this track pair sharing a transport with another? + Maybe<size_t> mBundleLevel; + uint32_t mRecvonlySsrc; + RefPtr<JsepTrack> mSending; + RefPtr<JsepTrack> mReceiving; + RefPtr<JsepTransport> mRtpTransport; + RefPtr<JsepTransport> mRtcpTransport; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h new file mode 100644 index 000000000..b59b672e4 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JESPTRACKENCODING_H_ +#define _JESPTRACKENCODING_H_ + +#include "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/common/EncodingConstraints.h" +#include "signaling/src/common/PtrVector.h" + +namespace mozilla { +// Represents a single encoding of a media track. When simulcast is used, there +// may be multiple. Each encoding may have some constraints (imposed by JS), and +// may be able to use any one of multiple codecs (JsepCodecDescription) at any +// given time. +class JsepTrackEncoding +{ +public: + const std::vector<JsepCodecDescription*>& GetCodecs() const + { + return mCodecs.values; + } + + void AddCodec(const JsepCodecDescription& codec) + { + mCodecs.values.push_back(codec.Clone()); + } + + bool HasFormat(const std::string& format) const + { + for (const JsepCodecDescription* codec : mCodecs.values) { + if (codec->mDefaultPt == format) { + return true; + } + } + return false; + } + + void UpdateMaxBitrate(const SdpMediaSection& remote) + { + uint32_t tias = remote.GetBandwidth("TIAS"); + // select minimum of the two which is not zero + mConstraints.maxBr = std::min(tias ? tias : mConstraints.maxBr, + mConstraints.maxBr ? mConstraints.maxBr : + tias); + // TODO add support for b=AS if TIAS is not set (bug 976521) + } + + EncodingConstraints mConstraints; + std::string mRid; + +private: + PtrVector<JsepCodecDescription> mCodecs; +}; +} + +#endif // _JESPTRACKENCODING_H_ diff --git a/media/webrtc/signaling/src/jsep/JsepTransport.h b/media/webrtc/signaling/src/jsep/JsepTransport.h new file mode 100644 index 000000000..3b0d38ad6 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTransport.h @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _JSEPTRANSPORT_H_ +#define _JSEPTRANSPORT_H_ + +#include <string> +#include <vector> + +#include <mozilla/RefPtr.h> +#include <mozilla/UniquePtr.h> +#include "nsISupportsImpl.h" + +#include "signaling/src/sdp/SdpAttribute.h" + +namespace mozilla { + +class JsepDtlsTransport +{ +public: + JsepDtlsTransport() : mRole(kJsepDtlsInvalidRole) {} + + virtual ~JsepDtlsTransport() {} + + enum Role { + kJsepDtlsClient, + kJsepDtlsServer, + kJsepDtlsInvalidRole + }; + + virtual const SdpFingerprintAttributeList& + GetFingerprints() const + { + return mFingerprints; + } + + virtual Role + GetRole() const + { + return mRole; + } + +private: + friend class JsepSessionImpl; + + SdpFingerprintAttributeList mFingerprints; + Role mRole; +}; + +class JsepIceTransport +{ +public: + JsepIceTransport() {} + + virtual ~JsepIceTransport() {} + + const std::string& + GetUfrag() const + { + return mUfrag; + } + const std::string& + GetPassword() const + { + return mPwd; + } + const std::vector<std::string>& + GetCandidates() const + { + return mCandidates; + } + +private: + friend class JsepSessionImpl; + + std::string mUfrag; + std::string mPwd; + std::vector<std::string> mCandidates; +}; + +class JsepTransport +{ +public: + JsepTransport() + : mComponents(0) + { + } + + void Close() + { + mComponents = 0; + mTransportId.clear(); + mIce.reset(); + mDtls.reset(); + } + + // Unique identifier for this transport within this call. Group? + std::string mTransportId; + + // ICE stuff. + UniquePtr<JsepIceTransport> mIce; + UniquePtr<JsepDtlsTransport> mDtls; + + // Number of required components. + size_t mComponents; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTransport); + +protected: + ~JsepTransport() {} +}; + +} // namespace mozilla + +#endif |