/* 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 #include #include #include #include #include #include "CSFLog.h" #include "timecard.h" #include "jsapi.h" #include "nspr.h" #include "nss.h" #include "pk11pub.h" #include "nsNetCID.h" #include "nsIProperty.h" #include "nsIPropertyBag2.h" #include "nsIServiceManager.h" #include "nsISimpleEnumerator.h" #include "nsServiceManagerUtils.h" #include "nsISocketTransportService.h" #include "nsIConsoleService.h" #include "nsThreadUtils.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsProxyRelease.h" #include "nsQueryObject.h" #include "prtime.h" #include "AudioConduit.h" #include "VideoConduit.h" #include "runnable_utils.h" #include "PeerConnectionCtx.h" #include "PeerConnectionImpl.h" #include "PeerConnectionMedia.h" #include "nsDOMDataChannelDeclarations.h" #include "dtlsidentity.h" #include "signaling/src/sdp/SdpAttribute.h" #include "signaling/src/jsep/JsepTrack.h" #include "signaling/src/jsep/JsepSession.h" #include "signaling/src/jsep/JsepSessionImpl.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Sprintf.h" #if !defined(MOZILLA_EXTERNAL_LINKAGE) #ifdef XP_WIN // We need to undef the MS macro for nsIDocument::CreateEvent #ifdef CreateEvent #undef CreateEvent #endif #endif // XP_WIN #include "nsIDocument.h" #include "nsGlobalWindow.h" #include "nsDOMDataChannel.h" #include "mozilla/dom/Performance.h" #include "mozilla/TimeStamp.h" #include "mozilla/Preferences.h" #include "mozilla/PublicSSL.h" #include "nsXULAppAPI.h" #include "nsContentUtils.h" #include "nsDOMJSUtils.h" #include "nsIScriptError.h" #include "nsPrintfCString.h" #include "nsURLHelper.h" #include "nsNetUtil.h" #include "nsIURLParser.h" #include "nsIDOMDataChannel.h" #include "nsIDOMLocation.h" #include "nsNullPrincipal.h" #include "mozilla/PeerIdentity.h" #include "mozilla/dom/RTCCertificate.h" #include "mozilla/dom/RTCConfigurationBinding.h" #include "mozilla/dom/RTCDTMFSenderBinding.h" #include "mozilla/dom/RTCDTMFToneChangeEvent.h" #include "mozilla/dom/RTCRtpSenderBinding.h" #include "mozilla/dom/RTCStatsReportBinding.h" #include "mozilla/dom/RTCPeerConnectionBinding.h" #include "mozilla/dom/PeerConnectionImplBinding.h" #include "mozilla/dom/DataChannelBinding.h" #include "mozilla/dom/PerformanceTiming.h" #include "mozilla/dom/PluginCrashedEvent.h" #include "MediaStreamList.h" #include "MediaStreamTrack.h" #include "AudioStreamTrack.h" #include "VideoStreamTrack.h" #include "nsIScriptGlobalObject.h" #include "MediaStreamGraph.h" #include "DOMMediaStream.h" #include "rlogconnector.h" #include "WebrtcGlobalInformation.h" #include "mozilla/dom/Event.h" #include "nsIDOMCustomEvent.h" #include "mozilla/EventDispatcher.h" #include "mozilla/net/DataChannelProtocol.h" #endif #ifndef USE_FAKE_MEDIA_STREAMS #include "MediaStreamGraphImpl.h" #endif #ifdef XP_WIN // We need to undef the MS macro again in case the windows include file // got imported after we included nsIDocument.h #ifdef CreateEvent #undef CreateEvent #endif #endif // XP_WIN #ifndef USE_FAKE_MEDIA_STREAMS #include "MediaSegment.h" #endif #ifdef USE_FAKE_PCOBSERVER #include "FakePCObserver.h" #else #include "mozilla/dom/PeerConnectionObserverBinding.h" #endif #include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" #ifdef MOZ_WEBRTC_OMX #include "OMXVideoCodec.h" #include "OMXCodecWrapper.h" #endif #define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor" using namespace mozilla; using namespace mozilla::dom; typedef PCObserverString ObString; static const char* logTag = "PeerConnectionImpl"; // Getting exceptions back down from PCObserver is generally not harmful. namespace { // This is a terrible hack. The problem is that SuppressException is not // inline, and we link this file without libxul in some cases (e.g. for our test // setup). So we can't use ErrorResult or IgnoredErrorResult because those call // SuppressException... And we can't use FastErrorResult because we can't // include BindingUtils.h, because our linking is completely fucked up. Use // BaseErrorResult directly. Please do not let me see _anyone_ doing this // without really careful review from someone who knows what they are doing. class JSErrorResult : public binding_danger::TErrorResult { public: ~JSErrorResult() { #if !defined(MOZILLA_EXTERNAL_LINKAGE) SuppressException(); #endif } }; // The WrapRunnable() macros copy passed-in args and passes them to the function // later on the other thread. ErrorResult cannot be passed like this because it // disallows copy-semantics. // // This WrappableJSErrorResult hack solves this by not actually copying the // ErrorResult, but creating a new one instead, which works because we don't // care about the result. // // Since this is for JS-calls, these can only be dispatched to the main thread. class WrappableJSErrorResult { public: WrappableJSErrorResult() : mRv(MakeUnique()), isCopy(false) {} WrappableJSErrorResult(const WrappableJSErrorResult &other) : mRv(MakeUnique()), isCopy(true) {} ~WrappableJSErrorResult() { if (isCopy) { MOZ_ASSERT(NS_IsMainThread()); } } operator JSErrorResult &() { return *mRv; } operator ErrorResult &() { return *mRv; } private: mozilla::UniquePtr mRv; bool isCopy; }; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) static nsresult InitNSSInContent() { NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); if (!XRE_IsContentProcess()) { MOZ_ASSERT_UNREACHABLE("Must be called in content process"); return NS_ERROR_FAILURE; } static bool nssStarted = false; if (nssStarted) { return NS_OK; } if (NSS_NoDB_Init(nullptr) != SECSuccess) { CSFLogError(logTag, "NSS_NoDB_Init failed."); return NS_ERROR_FAILURE; } if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { CSFLogError(logTag, "Fail to set up nss cipher suite."); return NS_ERROR_FAILURE; } mozilla::psm::DisableMD5(); nssStarted = true; return NS_OK; } #endif // MOZILLA_INTERNAL_API namespace mozilla { class DataChannel; } class nsIDOMDataChannel; PRLogModuleInfo *signalingLogInfo() { static PRLogModuleInfo *logModuleInfo = nullptr; if (!logModuleInfo) { logModuleInfo = PR_NewLogModule("signaling"); } return logModuleInfo; } // XXX Workaround for bug 998092 to maintain the existing broken semantics template<> struct nsISupportsWeakReference::COMTypeInfo { static const nsIID kIID; }; const nsIID nsISupportsWeakReference::COMTypeInfo::kIID = NS_ISUPPORTSWEAKREFERENCE_IID; namespace mozilla { #if !defined(MOZILLA_EXTERNAL_LINKAGE) RTCStatsQuery::RTCStatsQuery(bool internal) : failed(false), internalStats(internal), grabAllLevels(false) { } RTCStatsQuery::~RTCStatsQuery() { MOZ_ASSERT(NS_IsMainThread()); } #endif NS_IMPL_ISUPPORTS0(PeerConnectionImpl) #if !defined(MOZILLA_EXTERNAL_LINKAGE) bool PeerConnectionImpl::WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aReflector) { return PeerConnectionImplBinding::Wrap(aCx, this, aGivenProto, aReflector); } #endif bool PCUuidGenerator::Generate(std::string* idp) { nsresult rv; if(!mGenerator) { mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv); if (NS_FAILED(rv)) { return false; } if (!mGenerator) { return false; } } nsID id; rv = mGenerator->GenerateUUIDInPlace(&id); if (NS_FAILED(rv)) { return false; } char buffer[NSID_LENGTH]; id.ToProvidedString(buffer); idp->assign(buffer); return true; } bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) { #if defined(MOZILLA_EXTERNAL_LINKAGE) return false; #else if (!aWindow) { return false; } nsIDocument *doc = aWindow->GetExtantDoc(); if (!doc) { return false; } nsILoadContext *loadContext = doc->GetLoadContext(); return loadContext && loadContext->UsePrivateBrowsing(); #endif } PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) : mTimeCard(MOZ_LOG_TEST(signalingLogInfo(),LogLevel::Error) ? create_timecard() : nullptr) , mSignalingState(PCImplSignalingState::SignalingStable) , mIceConnectionState(PCImplIceConnectionState::New) , mIceGatheringState(PCImplIceGatheringState::New) , mDtlsConnected(false) , mWindow(nullptr) #if !defined(MOZILLA_EXTERNAL_LINKAGE) , mCertificate(nullptr) #else , mIdentity(nullptr) #endif , mPrivacyRequested(false) , mSTSThread(nullptr) , mAllowIceLoopback(false) , mAllowIceLinkLocal(false) , mMedia(nullptr) , mUuidGen(MakeUnique()) , mNumAudioStreams(0) , mNumVideoStreams(0) , mHaveConfiguredCodecs(false) , mHaveDataStream(false) , mAddCandidateErrorCount(0) , mTrickle(true) // TODO(ekr@rtfm.com): Use pref , mNegotiationNeeded(false) , mPrivateWindow(false) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) MOZ_ASSERT(NS_IsMainThread()); auto log = RLogConnector::CreateInstance(); if (aGlobal) { mWindow = do_QueryInterface(aGlobal->GetAsSupports()); if (IsPrivateBrowsing(mWindow)) { mPrivateWindow = true; log->EnterPrivateMode(); } } #endif CSFLogInfo(logTag, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__, mHandle.c_str()); STAMP_TIMECARD(mTimeCard, "Constructor Completed"); #if !defined(MOZILLA_EXTERNAL_LINKAGE) mAllowIceLoopback = Preferences::GetBool( "media.peerconnection.ice.loopback", false); mAllowIceLinkLocal = Preferences::GetBool( "media.peerconnection.ice.link_local", false); #endif memset(mMaxReceiving, 0, sizeof(mMaxReceiving)); memset(mMaxSending, 0, sizeof(mMaxSending)); } PeerConnectionImpl::~PeerConnectionImpl() { if (mTimeCard) { STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); print_timecard(mTimeCard); destroy_timecard(mTimeCard); mTimeCard = nullptr; } // This aborts if not on main thread (in Debug builds) PC_AUTO_ENTER_API_CALL_NO_CHECK(); #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (mPrivateWindow) { auto * log = RLogConnector::GetInstance(); if (log) { log->ExitPrivateMode(); } mPrivateWindow = false; } #endif if (PeerConnectionCtx::isActive()) { PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle); } else { CSFLogError(logTag, "PeerConnectionCtx is already gone. Ignoring..."); } CSFLogInfo(logTag, "%s: PeerConnectionImpl destructor invoked for %s", __FUNCTION__, mHandle.c_str()); Close(); // Since this and Initialize() occur on MainThread, they can't both be // running at once // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we // probably want to shut it down more aggressively to save memory. We // could shut down here when there are no uses. It might be more optimal // to release off a timer (and XPCOM Shutdown) to avoid churn } already_AddRefed PeerConnectionImpl::MakeMediaStream() { MediaStreamGraph* graph = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, AudioChannel::Normal); RefPtr stream = DOMMediaStream::CreateSourceStreamAsInput(GetWindow(), graph); CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetInputStream()); return stream.forget(); } nsresult PeerConnectionImpl::CreateRemoteSourceStreamInfo(RefPtr* aInfo, const std::string& aStreamID) { MOZ_ASSERT(aInfo); PC_AUTO_ENTER_API_CALL_NO_CHECK(); RefPtr stream = MakeMediaStream(); if (!stream) { return NS_ERROR_FAILURE; } RefPtr remote; remote = new RemoteSourceStreamInfo(stream.forget(), mMedia, aStreamID); *aInfo = remote; return NS_OK; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) /** * In JS, an RTCConfiguration looks like this: * * { "iceServers": [ { url:"stun:stun.example.org" }, * { url:"turn:turn.example.org?transport=udp", * username: "jib", credential:"mypass"} ] } * * This function converts that into an internal PeerConnectionConfiguration * object. */ nsresult PeerConnectionConfiguration::Init(const RTCConfiguration& aSrc) { if (aSrc.mIceServers.WasPassed()) { for (size_t i = 0; i < aSrc.mIceServers.Value().Length(); i++) { nsresult rv = AddIceServer(aSrc.mIceServers.Value()[i]); NS_ENSURE_SUCCESS(rv, rv); } } switch (aSrc.mBundlePolicy) { case dom::RTCBundlePolicy::Balanced: setBundlePolicy(kBundleBalanced); break; case dom::RTCBundlePolicy::Max_compat: setBundlePolicy(kBundleMaxCompat); break; case dom::RTCBundlePolicy::Max_bundle: setBundlePolicy(kBundleMaxBundle); break; default: MOZ_CRASH(); } switch (aSrc.mIceTransportPolicy) { case dom::RTCIceTransportPolicy::Relay: setIceTransportPolicy(NrIceCtx::ICE_POLICY_RELAY); break; case dom::RTCIceTransportPolicy::All: if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) { setIceTransportPolicy(NrIceCtx::ICE_POLICY_NO_HOST); } else { setIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL); } break; default: MOZ_CRASH(); } return NS_OK; } nsresult PeerConnectionConfiguration::AddIceServer(const RTCIceServer &aServer) { NS_ENSURE_STATE(aServer.mUrls.WasPassed()); NS_ENSURE_STATE(aServer.mUrls.Value().IsStringSequence()); auto &urls = aServer.mUrls.Value().GetAsStringSequence(); for (size_t i = 0; i < urls.Length(); i++) { // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than // nsStandardURL. To parse STUN/TURN URI's to spec // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3 // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3 // we parse out the query-string, and use ParseAuthority() on the rest RefPtr url; nsresult rv = NS_NewURI(getter_AddRefs(url), urls[i]); NS_ENSURE_SUCCESS(rv, rv); bool isStun = false, isStuns = false, isTurn = false, isTurns = false; url->SchemeIs("stun", &isStun); url->SchemeIs("stuns", &isStuns); url->SchemeIs("turn", &isTurn); url->SchemeIs("turns", &isTurns); if (!(isStun || isStuns || isTurn || isTurns)) { return NS_ERROR_FAILURE; } if (isTurns || isStuns) { continue; // TODO: Support TURNS and STUNS (Bug 1056934) } nsAutoCString spec; rv = url->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509) int32_t port; nsAutoCString host; nsAutoCString transport; { uint32_t hostPos; int32_t hostLen; nsAutoCString path; rv = url->GetPath(path); NS_ENSURE_SUCCESS(rv, rv); // Tolerate query-string + parse 'transport=[udp|tcp]' by hand. int32_t questionmark = path.FindChar('?'); if (questionmark >= 0) { const nsCString match = NS_LITERAL_CSTRING("transport="); for (int32_t i = questionmark, endPos; i >= 0; i = endPos) { endPos = path.FindCharInSet("&", i + 1); const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1, endPos); if (StringBeginsWith(fieldvaluepair, match)) { transport = Substring(fieldvaluepair, match.Length()); ToLowerCase(transport); } } path.SetLength(questionmark); } rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(), nullptr, nullptr, nullptr, nullptr, &hostPos, &hostLen, &port); NS_ENSURE_SUCCESS(rv, rv); if (!hostLen) { return NS_ERROR_FAILURE; } if (hostPos > 1) /* The username was removed */ return NS_ERROR_FAILURE; path.Mid(host, hostPos, hostLen); } if (port == -1) port = (isStuns || isTurns)? 5349 : 3478; if (isTurn || isTurns) { NS_ConvertUTF16toUTF8 credential(aServer.mCredential.Value()); NS_ConvertUTF16toUTF8 username(aServer.mUsername.Value()); if (!addTurnServer(host.get(), port, username.get(), credential.get(), (transport.IsEmpty() ? kNrIceTransportUdp : transport.get()))) { return NS_ERROR_FAILURE; } } else { if (!addStunServer(host.get(), port, (transport.IsEmpty() ? kNrIceTransportUdp : transport.get()))) { return NS_ERROR_FAILURE; } } } return NS_OK; } #endif nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, nsGlobalWindow* aWindow, const PeerConnectionConfiguration& aConfiguration, nsISupports* aThread) { nsresult res; MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aThread); if (!mThread) { mThread = do_QueryInterface(aThread); MOZ_ASSERT(mThread); } CheckThread(); mPCObserver = do_GetWeakReference(&aObserver); // Find the STS thread mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); MOZ_ASSERT(mSTSThread); #if !defined(MOZILLA_EXTERNAL_LINKAGE) // Initialize NSS if we are in content process. For chrome process, NSS should already // been initialized. if (XRE_IsParentProcess()) { // This code interferes with the C++ unit test startup code. nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &res); NS_ENSURE_SUCCESS(res, res); } else { NS_ENSURE_SUCCESS(res = InitNSSInContent(), res); } // Currently no standalone unit tests for DataChannel, // which is the user of mWindow MOZ_ASSERT(aWindow); mWindow = aWindow->AsInner(); NS_ENSURE_STATE(mWindow); #endif // MOZILLA_INTERNAL_API PRTime timestamp = PR_Now(); // Ok if we truncate this. char temp[128]; #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsAutoCString locationCStr; if (nsCOMPtr location = mWindow->GetLocation()) { nsAutoString locationAStr; location->ToString(locationAStr); CopyUTF16toUTF8(locationAStr, locationCStr); } SprintfLiteral(temp, "%" PRIu64 " (id=%" PRIu64 " url=%s)", static_cast(timestamp), static_cast(mWindow ? mWindow->WindowID() : 0), locationCStr.get() ? locationCStr.get() : "NULL"); #else SprintfLiteral(temp, "%" PRIu64, static_cast(timestamp)); #endif // MOZILLA_INTERNAL_API mName = temp; // Generate a random handle unsigned char handle_bin[8]; SECStatus rv; rv = PK11_GenerateRandom(handle_bin, sizeof(handle_bin)); if (rv != SECSuccess) { MOZ_CRASH(); return NS_ERROR_UNEXPECTED; } char hex[17]; SprintfLiteral(hex, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", handle_bin[0], handle_bin[1], handle_bin[2], handle_bin[3], handle_bin[4], handle_bin[5], handle_bin[6], handle_bin[7]); mHandle = hex; STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx"); res = PeerConnectionCtx::InitializeGlobal(mThread, mSTSThread); NS_ENSURE_SUCCESS(res, res); mMedia = new PeerConnectionMedia(this); // Connect ICE slots. mMedia->SignalIceGatheringStateChange.connect( this, &PeerConnectionImpl::IceGatheringStateChange); mMedia->SignalUpdateDefaultCandidate.connect( this, &PeerConnectionImpl::UpdateDefaultCandidate); mMedia->SignalEndOfLocalCandidates.connect( this, &PeerConnectionImpl::EndOfLocalCandidates); mMedia->SignalIceConnectionStateChange.connect( this, &PeerConnectionImpl::IceConnectionStateChange); mMedia->SignalCandidate.connect(this, &PeerConnectionImpl::CandidateReady); // Initialize the media object. res = mMedia->Init(aConfiguration.getStunServers(), aConfiguration.getTurnServers(), aConfiguration.getIceTransportPolicy()); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Couldn't initialize media object", __FUNCTION__); return res; } PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this; mJsepSession = MakeUnique(mName, MakeUnique()); res = mJsepSession->Init(); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__, static_cast(res)); return res; } res = mJsepSession->SetIceCredentials(mMedia->ice_ctx()->ufrag(), mMedia->ice_ctx()->pwd()); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u", __FUNCTION__, static_cast(res)); return res; } #if defined(MOZILLA_EXTERNAL_LINKAGE) { mIdentity = DtlsIdentity::Generate(); if (!mIdentity) { return NS_ERROR_FAILURE; } std::vector fingerprint; res = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint); NS_ENSURE_SUCCESS(res, res); res = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint); NS_ENSURE_SUCCESS(res, res); } #endif res = mJsepSession->SetBundlePolicy(aConfiguration.getBundlePolicy()); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Couldn't set bundle policy, res=%u, error=%s", __FUNCTION__, static_cast(res), mJsepSession->GetLastError().c_str()); return res; } return NS_OK; } #ifndef MOZILLA_EXTERNAL_LINKAGE void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, nsGlobalWindow& aWindow, const RTCConfiguration& aConfiguration, nsISupports* aThread, ErrorResult &rv) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aThread); mThread = do_QueryInterface(aThread); PeerConnectionConfiguration converted; nsresult res = converted.Init(aConfiguration); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Invalid RTCConfiguration", __FUNCTION__); rv.Throw(res); return; } res = Initialize(aObserver, &aWindow, converted, aThread); if (NS_FAILED(res)) { rv.Throw(res); } if (!aConfiguration.mPeerIdentity.IsEmpty()) { mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity); mPrivacyRequested = true; } } #endif #if !defined(MOZILLA_EXTERNAL_LINKAGE) void PeerConnectionImpl::SetCertificate(mozilla::dom::RTCCertificate& aCertificate) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(!mCertificate, "This can only be called once"); mCertificate = &aCertificate; std::vector fingerprint; nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint); if (NS_FAILED(rv)) { CSFLogError(logTag, "%s: Couldn't calculate fingerprint, rv=%u", __FUNCTION__, static_cast(rv)); mCertificate = nullptr; return; } rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint); if (NS_FAILED(rv)) { CSFLogError(logTag, "%s: Couldn't set DTLS credentials, rv=%u", __FUNCTION__, static_cast(rv)); mCertificate = nullptr; } } const RefPtr& PeerConnectionImpl::Certificate() const { PC_AUTO_ENTER_API_CALL_NO_CHECK(); return mCertificate; } #endif RefPtr PeerConnectionImpl::Identity() const { PC_AUTO_ENTER_API_CALL_NO_CHECK(); #if !defined(MOZILLA_EXTERNAL_LINKAGE) MOZ_ASSERT(mCertificate); return mCertificate->CreateDtlsIdentity(); #else RefPtr id = mIdentity; return id; #endif } class CompareCodecPriority { public: void SetPreferredCodec(int32_t preferredCodec) { // This pref really ought to be a string, preferably something like // "H264" or "VP8" instead of a payload type. // Bug 1101259. std::ostringstream os; os << preferredCodec; mPreferredCodec = os.str(); } bool operator()(JsepCodecDescription* lhs, JsepCodecDescription* rhs) const { if (!mPreferredCodec.empty() && lhs->mDefaultPt == mPreferredCodec && rhs->mDefaultPt != mPreferredCodec) { return true; } if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) { return true; } return false; } private: std::string mPreferredCodec; }; class ConfigureCodec { public: explicit ConfigureCodec(nsCOMPtr& branch) : mHardwareH264Enabled(false), mHardwareH264Supported(false), mSoftwareH264Enabled(false), mH264Enabled(false), mVP9Enabled(false), mH264Level(13), // minimum suggested for WebRTC spec mH264MaxBr(0), // Unlimited mH264MaxMbps(0), // Unlimited mVP8MaxFs(0), mVP8MaxFr(0), mUseTmmbr(false), mUseRemb(false), mUseAudioFec(false), mRedUlpfecEnabled(false), mDtmfEnabled(false) { #ifdef MOZ_WEBRTC_OMX // Check to see if what HW codecs are available (not in use) at this moment. // Note that streaming video decode can reserve a decoder // XXX See bug 1018791 Implement W3 codec reservation policy // Note that currently, OMXCodecReservation needs to be held by an sp<> because it puts // 'this' into an sp to talk to the resource reservation code // This pref is a misnomer; it is solely for h264 _hardware_ support. branch->GetBoolPref("media.peerconnection.video.h264_enabled", &mHardwareH264Enabled); if (mHardwareH264Enabled) { // Ok, it is preffed on. Can we actually do it? android::sp encode = new android::OMXCodecReservation(true); android::sp decode = new android::OMXCodecReservation(false); // Currently we just check if they're available right now, which will fail if we're // trying to call ourself, for example. It will work for most real-world cases, like // if we try to add a person to a 2-way call to make a 3-way mesh call if (encode->ReserveOMXCodec() && decode->ReserveOMXCodec()) { CSFLogDebug( logTag, "%s: H264 hardware codec available", __FUNCTION__); mHardwareH264Supported = true; } } #endif // MOZ_WEBRTC_OMX #if !defined(MOZILLA_EXTERNAL_LINKAGE) mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264(); #else // For unit-tests mSoftwareH264Enabled = true; #endif mH264Enabled = mHardwareH264Supported || mSoftwareH264Enabled; branch->GetIntPref("media.navigator.video.h264.level", &mH264Level); mH264Level &= 0xFF; branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr); #ifdef MOZ_WEBRTC_OMX // Level 1.2; but let's allow CIF@30 or QVGA@30+ by default mH264MaxMbps = 11880; #endif branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps); branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &mVP9Enabled); branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs); if (mVP8MaxFs <= 0) { mVP8MaxFs = 12288; // We must specify something other than 0 } branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr); if (mVP8MaxFr <= 0) { mVP8MaxFr = 60; // We must specify something other than 0 } // TMMBR is enabled from a pref in about:config branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr); // REMB is enabled by default, but can be disabled from about:config branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb); branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec); branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled", &mRedUlpfecEnabled); // media.peerconnection.dtmf.enabled controls both sdp generation for // DTMF support as well as DTMF exposure to DOM branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled); } void operator()(JsepCodecDescription* codec) const { switch (codec->mType) { case SdpMediaSection::kAudio: { JsepAudioCodecDescription& audioCodec = static_cast(*codec); if (audioCodec.mName == "opus") { audioCodec.mFECEnabled = mUseAudioFec; } else if (audioCodec.mName == "telephone-event") { audioCodec.mEnabled = mDtmfEnabled; } } break; case SdpMediaSection::kVideo: { JsepVideoCodecDescription& videoCodec = static_cast(*codec); if (videoCodec.mName == "H264") { // Override level videoCodec.mProfileLevelId &= 0xFFFF00; videoCodec.mProfileLevelId |= mH264Level; videoCodec.mConstraints.maxBr = mH264MaxBr; videoCodec.mConstraints.maxMbps = mH264MaxMbps; // Might disable it, but we set up other params anyway videoCodec.mEnabled = mH264Enabled; if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) { // We're assuming packetization mode 0 is unsupported by // hardware. videoCodec.mEnabled = false; } if (mHardwareH264Supported) { videoCodec.mStronglyPreferred = true; } } else if (videoCodec.mName == "red") { videoCodec.mEnabled = mRedUlpfecEnabled; } else if (videoCodec.mName == "ulpfec") { videoCodec.mEnabled = mRedUlpfecEnabled; } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") { if (videoCodec.mName == "VP9" && !mVP9Enabled) { videoCodec.mEnabled = false; break; } videoCodec.mConstraints.maxFs = mVP8MaxFs; videoCodec.mConstraints.maxFps = mVP8MaxFr; } if (mUseTmmbr) { videoCodec.EnableTmmbr(); } if (mUseRemb) { videoCodec.EnableRemb(); } } break; case SdpMediaSection::kText: case SdpMediaSection::kApplication: case SdpMediaSection::kMessage: {} // Nothing to configure for these. } } private: bool mHardwareH264Enabled; bool mHardwareH264Supported; bool mSoftwareH264Enabled; bool mH264Enabled; bool mVP9Enabled; int32_t mH264Level; int32_t mH264MaxBr; int32_t mH264MaxMbps; int32_t mVP8MaxFs; int32_t mVP8MaxFr; bool mUseTmmbr; bool mUseRemb; bool mUseAudioFec; bool mRedUlpfecEnabled; bool mDtmfEnabled; }; class ConfigureRedCodec { public: explicit ConfigureRedCodec(nsCOMPtr& branch, std::vector* redundantEncodings) : mRedundantEncodings(redundantEncodings) { // if we wanted to override or modify which encodings are considered // for redundant encodings, we'd probably want to handle it here by // checking prefs modifying the operator() code below } void operator()(JsepCodecDescription* codec) const { if (codec->mType == SdpMediaSection::kVideo && codec->mEnabled == false) { uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); // don't search for the codec payload type unless we have a valid // conversion (non-zero) if (pt != 0) { std::vector::iterator it = std::find(mRedundantEncodings->begin(), mRedundantEncodings->end(), pt); if (it != mRedundantEncodings->end()) { mRedundantEncodings->erase(it); } } } } private: std::vector* mRedundantEncodings; }; nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() { nsresult res; nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &res); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Couldn't get prefs service, res=%u", __FUNCTION__, static_cast(res)); return res; } nsCOMPtr branch = do_QueryInterface(prefs); if (!branch) { CSFLogError(logTag, "%s: Couldn't get prefs branch", __FUNCTION__); return NS_ERROR_FAILURE; } ConfigureCodec configurer(branch); mJsepSession->ForEachCodec(configurer); // first find the red codec description std::vector& codecs = mJsepSession->Codecs(); JsepVideoCodecDescription* redCodec = nullptr; for (auto codec : codecs) { // we only really care about finding the RED codec if it is // enabled if (codec->mName == "red" && codec->mEnabled) { redCodec = static_cast(codec); break; } } // if red codec was found, configure it for the other enabled codecs if (redCodec) { ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings)); mJsepSession->ForEachCodec(configureRed); } // We use this to sort the list of codecs once everything is configured CompareCodecPriority comparator; // Sort by priority int32_t preferredCodec = 0; branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec); if (preferredCodec) { comparator.SetPreferredCodec(preferredCodec); } mJsepSession->SortCodecs(comparator); return NS_OK; } // Data channels won't work without a window, so in order for the C++ unit // tests to work (it doesn't have a window available) we ifdef the following // two implementations. NS_IMETHODIMP PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams) { PC_AUTO_ENTER_API_CALL(false); #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (mDataConnection) { CSFLogDebug(logTag,"%s DataConnection already connected",__FUNCTION__); // Ignore the request to connect when already connected. This entire // implementation is temporary. Ignore aNumstreams as it's merely advisory // and we increase the number of streams dynamically as needed. return NS_OK; } mDataConnection = new DataChannelConnection(this); if (!mDataConnection->Init(5000, aNumstreams, true)) { CSFLogError(logTag,"%s DataConnection Init Failed",__FUNCTION__); return NS_ERROR_FAILURE; } CSFLogDebug(logTag,"%s DataChannelConnection %p attached to %s", __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str()); #endif return NS_OK; } nsresult PeerConnectionImpl::GetDatachannelParameters( const mozilla::JsepApplicationCodecDescription** datachannelCodec, uint16_t* level) const { auto trackPairs = mJsepSession->GetNegotiatedTrackPairs(); for (auto j = trackPairs.begin(); j != trackPairs.end(); ++j) { JsepTrackPair& trackPair = *j; bool sendDataChannel = trackPair.mSending && trackPair.mSending->GetMediaType() == SdpMediaSection::kApplication; bool recvDataChannel = trackPair.mReceiving && trackPair.mReceiving->GetMediaType() == SdpMediaSection::kApplication; (void)recvDataChannel; MOZ_ASSERT(sendDataChannel == recvDataChannel); if (sendDataChannel) { // This will release assert if there is no such index, and that's ok const JsepTrackEncoding& encoding = trackPair.mSending->GetNegotiatedDetails()->GetEncoding(0); if (encoding.GetCodecs().empty()) { CSFLogError(logTag, "%s: Negotiated m=application with no codec. " "This is likely to be broken.", __FUNCTION__); return NS_ERROR_FAILURE; } for (const JsepCodecDescription* codec : encoding.GetCodecs()) { if (codec->mType != SdpMediaSection::kApplication) { CSFLogError(logTag, "%s: Codec type for m=application was %u, this " "is a bug.", __FUNCTION__, static_cast(codec->mType)); MOZ_ASSERT(false, "Codec for m=application was not \"application\""); return NS_ERROR_FAILURE; } if (codec->mName != "webrtc-datachannel") { CSFLogWarn(logTag, "%s: Codec for m=application was not " "webrtc-datachannel (was instead %s). ", __FUNCTION__, codec->mName.c_str()); continue; } *datachannelCodec = static_cast(codec); if (trackPair.mBundleLevel.isSome()) { *level = static_cast(*trackPair.mBundleLevel); } else { *level = static_cast(trackPair.mLevel); } return NS_OK; } } } *datachannelCodec = nullptr; *level = 0; return NS_OK; } /* static */ void PeerConnectionImpl::DeferredAddTrackToJsepSession( const std::string& pcHandle, SdpMediaSection::MediaType type, const std::string& streamId, const std::string& trackId) { PeerConnectionWrapper wrapper(pcHandle); if (wrapper.impl()) { if (!PeerConnectionCtx::GetInstance()->isReady()) { MOZ_CRASH("Why is DeferredAddTrackToJsepSession being executed when the " "PeerConnectionCtx isn't ready?"); } wrapper.impl()->AddTrackToJsepSession(type, streamId, trackId); } } nsresult PeerConnectionImpl::AddTrackToJsepSession(SdpMediaSection::MediaType type, const std::string& streamId, const std::string& trackId) { nsresult res = ConfigureJsepSessionCodecs(); if (NS_FAILED(res)) { CSFLogError(logTag, "Failed to configure codecs"); return res; } res = mJsepSession->AddTrack( new JsepTrack(type, streamId, trackId, sdp::kSend)); if (NS_FAILED(res)) { std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "%s (%s) : pc = %s, error = %s", __FUNCTION__, type == SdpMediaSection::kAudio ? "audio" : "video", mHandle.c_str(), errorString.c_str()); return NS_ERROR_FAILURE; } return NS_OK; } nsresult PeerConnectionImpl::InitializeDataChannel() { PC_AUTO_ENTER_API_CALL(false); CSFLogDebug(logTag, "%s", __FUNCTION__); const JsepApplicationCodecDescription* codec; uint16_t level; nsresult rv = GetDatachannelParameters(&codec, &level); NS_ENSURE_SUCCESS(rv, rv); if (!codec) { CSFLogDebug(logTag, "%s: We did not negotiate datachannel", __FUNCTION__); return NS_OK; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) uint32_t channels = codec->mChannels; if (channels > MAX_NUM_STREAMS) { channels = MAX_NUM_STREAMS; } rv = EnsureDataConnection(channels); if (NS_SUCCEEDED(rv)) { uint16_t localport = 5000; uint16_t remoteport = 0; // The logic that reflects the remote payload type is what sets the remote // port here. if (!codec->GetPtAsInt(&remoteport)) { return NS_ERROR_FAILURE; } // use the specified TransportFlow RefPtr flow = mMedia->GetTransportFlow(level, false).get(); CSFLogDebug(logTag, "Transportflow[%u] = %p", static_cast(level), flow.get()); if (flow) { if (mDataConnection->ConnectViaTransportFlow(flow, localport, remoteport)) { return NS_OK; } } // If we inited the DataConnection, call Destroy() before releasing it mDataConnection->Destroy(); } mDataConnection = nullptr; #endif return NS_ERROR_FAILURE; } already_AddRefed PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType, bool outOfOrderAllowed, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated, uint16_t aStream, ErrorResult &rv) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) RefPtr result; rv = CreateDataChannel(aLabel, aProtocol, aType, outOfOrderAllowed, aMaxTime, aMaxNum, aExternalNegotiated, aStream, getter_AddRefs(result)); return result.forget(); #else return nullptr; #endif } NS_IMETHODIMP PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType, bool outOfOrderAllowed, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated, uint16_t aStream, nsDOMDataChannel** aRetval) { PC_AUTO_ENTER_API_CALL(false); MOZ_ASSERT(aRetval); #if !defined(MOZILLA_EXTERNAL_LINKAGE) RefPtr dataChannel; DataChannelConnection::Type theType = static_cast(aType); nsresult rv = EnsureDataConnection(WEBRTC_DATACHANNEL_STREAMS_DEFAULT); if (NS_FAILED(rv)) { return rv; } dataChannel = mDataConnection->Open( NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType, !outOfOrderAllowed, aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT ? aMaxNum : (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime : 0), nullptr, nullptr, aExternalNegotiated, aStream ); NS_ENSURE_TRUE(dataChannel,NS_ERROR_FAILURE); CSFLogDebug(logTag, "%s: making DOMDataChannel", __FUNCTION__); if (!mHaveDataStream) { std::string streamId; std::string trackId; // Generate random ids because these aren't linked to any local streams. if (!mUuidGen->Generate(&streamId)) { return NS_ERROR_FAILURE; } if (!mUuidGen->Generate(&trackId)) { return NS_ERROR_FAILURE; } RefPtr track(new JsepTrack( mozilla::SdpMediaSection::kApplication, streamId, trackId, sdp::kSend)); rv = mJsepSession->AddTrack(track); if (NS_FAILED(rv)) { CSFLogError(logTag, "%s: Failed to add application track.", __FUNCTION__); return rv; } mHaveDataStream = true; OnNegotiationNeeded(); } nsIDOMDataChannel *retval; rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, &retval); if (NS_FAILED(rv)) { return rv; } *aRetval = static_cast(retval); #endif return NS_OK; } // do_QueryObjectReferent() - Helps get PeerConnectionObserver from nsWeakPtr. // // nsWeakPtr deals in XPCOM interfaces, while webidl bindings are concrete objs. // TODO: Turn this into a central (template) function somewhere (Bug 939178) // // Without it, each weak-ref call in this file would look like this: // // nsCOMPtr tmp = do_QueryReferent(mPCObserver); // if (!tmp) { // return; // } // RefPtr tmp2 = do_QueryObject(tmp); // RefPtr pco = static_cast(&*tmp2); static already_AddRefed do_QueryObjectReferent(nsIWeakReference* aRawPtr) { nsCOMPtr tmp = do_QueryReferent(aRawPtr); if (!tmp) { return nullptr; } RefPtr tmp2 = do_QueryObject(tmp); RefPtr tmp3 = static_cast(&*tmp2); return tmp3.forget(); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) // Not a member function so that we don't need to keep the PC live. static void NotifyDataChannel_m(RefPtr aChannel, RefPtr aObserver) { MOZ_ASSERT(NS_IsMainThread()); JSErrorResult rv; RefPtr channel = static_cast(&*aChannel); aObserver->NotifyDataChannel(*channel, rv); NS_DataChannelAppReady(aChannel); } #endif void PeerConnectionImpl::NotifyDataChannel(already_AddRefed aChannel) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); // XXXkhuey this is completely fucked up. We can't use RefPtr // here because DataChannel's AddRef/Release are non-virtual and not visible // if !MOZILLA_INTERNAL_API, but this function leaks the DataChannel if // !MOZILLA_INTERNAL_API because it never transfers the ref to // NS_NewDOMDataChannel. DataChannel* channel = aChannel.take(); MOZ_ASSERT(channel); CSFLogDebug(logTag, "%s: channel: %p", __FUNCTION__, channel); #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsCOMPtr domchannel; nsresult rv = NS_NewDOMDataChannel(already_AddRefed(channel), mWindow, getter_AddRefs(domchannel)); NS_ENSURE_SUCCESS_VOID(rv); mHaveDataStream = true; RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return; } RUN_ON_THREAD(mThread, WrapRunnableNM(NotifyDataChannel_m, domchannel.get(), pco), NS_DISPATCH_NORMAL); #endif } NS_IMETHODIMP PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) { JsepOfferOptions options; #if !defined(MOZILLA_EXTERNAL_LINKAGE) // convert the RTCOfferOptions to JsepOfferOptions if (aOptions.mOfferToReceiveAudio.WasPassed()) { options.mOfferToReceiveAudio = mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value())); } if (aOptions.mOfferToReceiveVideo.WasPassed()) { options.mOfferToReceiveVideo = mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value())); } options.mIceRestart = mozilla::Some(aOptions.mIceRestart); if (aOptions.mMozDontOfferDataChannel.WasPassed()) { options.mDontOfferDataChannel = mozilla::Some(aOptions.mMozDontOfferDataChannel.Value()); } #endif return CreateOffer(options); } static void DeferredCreateOffer(const std::string& aPcHandle, const JsepOfferOptions& aOptions) { PeerConnectionWrapper wrapper(aPcHandle); if (wrapper.impl()) { if (!PeerConnectionCtx::GetInstance()->isReady()) { MOZ_CRASH("Why is DeferredCreateOffer being executed when the " "PeerConnectionCtx isn't ready?"); } wrapper.impl()->CreateOffer(aOptions); } } // Used by unit tests and the IDL CreateOffer. NS_IMETHODIMP PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) { PC_AUTO_ENTER_API_CALL(true); bool restartIce = aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart); if (!restartIce && mMedia->GetIceRestartState() == PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { RollbackIceRestart(); } RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_OK; } if (!PeerConnectionCtx::GetInstance()->isReady()) { // Uh oh. We're not ready yet. Enqueue this operation. PeerConnectionCtx::GetInstance()->queueJSEPOperation( WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions)); STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)"); return NS_OK; } CSFLogDebug(logTag, "CreateOffer()"); nsresult nrv; if (restartIce && !mJsepSession->GetLocalDescription().empty()) { // If restart is requested and a restart is already in progress, we // need to make room for the restart request so we either rollback // or finalize to "clear" the previous restart. if (mMedia->GetIceRestartState() == PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { // we're mid-restart and can rollback RollbackIceRestart(); } else if (mMedia->GetIceRestartState() == PeerConnectionMedia::ICE_RESTART_COMMITTED) { // we're mid-restart and can't rollback, finalize restart even // though we're not really ready yet FinalizeIceRestart(); } CSFLogInfo(logTag, "Offerer restarting ice"); nrv = SetupIceRestart(); if (NS_FAILED(nrv)) { CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u", __FUNCTION__, static_cast(nrv)); return nrv; } } nrv = ConfigureJsepSessionCodecs(); if (NS_FAILED(nrv)) { CSFLogError(logTag, "Failed to configure codecs"); return nrv; } STAMP_TIMECARD(mTimeCard, "Create Offer"); std::string offer; nrv = mJsepSession->CreateOffer(aOptions, &offer); JSErrorResult rv; if (NS_FAILED(nrv)) { Error error; switch (nrv) { case NS_ERROR_UNEXPECTED: error = kInvalidState; break; default: error = kInternalError; } std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "%s: pc = %s, error = %s", __FUNCTION__, mHandle.c_str(), errorString.c_str()); pco->OnCreateOfferError(error, ObString(errorString.c_str()), rv); } else { pco->OnCreateOfferSuccess(ObString(offer.c_str()), rv); } UpdateSignalingState(); return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::CreateAnswer() { PC_AUTO_ENTER_API_CALL(true); RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_OK; } CSFLogDebug(logTag, "CreateAnswer()"); nsresult nrv; if (mJsepSession->RemoteIceIsRestarting()) { if (mMedia->GetIceRestartState() == PeerConnectionMedia::ICE_RESTART_COMMITTED) { FinalizeIceRestart(); } else if (!mMedia->IsIceRestarting()) { CSFLogInfo(logTag, "Answerer restarting ice"); nrv = SetupIceRestart(); if (NS_FAILED(nrv)) { CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u", __FUNCTION__, static_cast(nrv)); return nrv; } } } STAMP_TIMECARD(mTimeCard, "Create Answer"); // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to // add it as a param to CreateAnswer, and convert it here. JsepAnswerOptions options; std::string answer; nrv = mJsepSession->CreateAnswer(options, &answer); JSErrorResult rv; if (NS_FAILED(nrv)) { Error error; switch (nrv) { case NS_ERROR_UNEXPECTED: error = kInvalidState; break; default: error = kInternalError; } std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "%s: pc = %s, error = %s", __FUNCTION__, mHandle.c_str(), errorString.c_str()); pco->OnCreateAnswerError(error, ObString(errorString.c_str()), rv); } else { pco->OnCreateAnswerSuccess(ObString(answer.c_str()), rv); } UpdateSignalingState(); return NS_OK; } nsresult PeerConnectionImpl::SetupIceRestart() { if (mMedia->IsIceRestarting()) { CSFLogError(logTag, "%s: ICE already restarting", __FUNCTION__); return NS_ERROR_UNEXPECTED; } std::string ufrag = mMedia->ice_ctx()->GetNewUfrag(); std::string pwd = mMedia->ice_ctx()->GetNewPwd(); if (ufrag.empty() || pwd.empty()) { CSFLogError(logTag, "%s: Bad ICE credentials (ufrag:'%s'/pwd:'%s')", __FUNCTION__, ufrag.c_str(), pwd.c_str()); return NS_ERROR_UNEXPECTED; } // hold on to the current ice creds in case of rollback mPreviousIceUfrag = mJsepSession->GetUfrag(); mPreviousIcePwd = mJsepSession->GetPwd(); mMedia->BeginIceRestart(ufrag, pwd); nsresult nrv = mJsepSession->SetIceCredentials(ufrag, pwd); if (NS_FAILED(nrv)) { CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u", __FUNCTION__, static_cast(nrv)); return nrv; } return NS_OK; } nsresult PeerConnectionImpl::RollbackIceRestart() { mMedia->RollbackIceRestart(); // put back the previous ice creds nsresult nrv = mJsepSession->SetIceCredentials(mPreviousIceUfrag, mPreviousIcePwd); if (NS_FAILED(nrv)) { CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u", __FUNCTION__, static_cast(nrv)); return nrv; } mPreviousIceUfrag = ""; mPreviousIcePwd = ""; return NS_OK; } void PeerConnectionImpl::FinalizeIceRestart() { mMedia->FinalizeIceRestart(); // clear the previous ice creds since they are no longer needed mPreviousIceUfrag = ""; mPreviousIcePwd = ""; } NS_IMETHODIMP PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { PC_AUTO_ENTER_API_CALL(true); if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; } JSErrorResult rv; RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_OK; } STAMP_TIMECARD(mTimeCard, "Set Local Description"); #if !defined(MOZILLA_EXTERNAL_LINKAGE) bool isolated = mMedia->AnyLocalTrackHasPeerIdentity(); mPrivacyRequested = mPrivacyRequested || isolated; #endif mLocalRequestedSDP = aSDP; JsepSdpType sdpType; switch (aAction) { case IPeerConnection::kActionOffer: sdpType = mozilla::kJsepSdpOffer; break; case IPeerConnection::kActionAnswer: sdpType = mozilla::kJsepSdpAnswer; break; case IPeerConnection::kActionPRAnswer: sdpType = mozilla::kJsepSdpPranswer; break; case IPeerConnection::kActionRollback: sdpType = mozilla::kJsepSdpRollback; break; default: MOZ_ASSERT(false); return NS_ERROR_FAILURE; } nsresult nrv = mJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP); if (NS_FAILED(nrv)) { Error error; switch (nrv) { case NS_ERROR_INVALID_ARG: error = kInvalidSessionDescription; break; case NS_ERROR_UNEXPECTED: error = kInvalidState; break; default: error = kInternalError; } std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "%s: pc = %s, error = %s", __FUNCTION__, mHandle.c_str(), errorString.c_str()); pco->OnSetLocalDescriptionError(error, ObString(errorString.c_str()), rv); } else { pco->OnSetLocalDescriptionSuccess(rv); } UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback); return NS_OK; } static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction, const std::string& aSdp) { PeerConnectionWrapper wrapper(aPcHandle); if (wrapper.impl()) { if (!PeerConnectionCtx::GetInstance()->isReady()) { MOZ_CRASH("Why is DeferredSetRemote being executed when the " "PeerConnectionCtx isn't ready?"); } wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str()); } } static void StartTrack(MediaStream* aSource, TrackID aTrackId, nsAutoPtr&& aSegment) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) class Message : public ControlMessage { public: Message(MediaStream* aStream, TrackID aTrack, nsAutoPtr&& aSegment) : ControlMessage(aStream), track_id_(aTrack), segment_(aSegment) {} virtual void Run() override { TrackRate track_rate = segment_->GetType() == MediaSegment::AUDIO ? WEBRTC_DEFAULT_SAMPLE_RATE : mStream->GraphRate(); StreamTime current_end = mStream->GetTracksEnd(); TrackTicks current_ticks = mStream->TimeToTicksRoundUp(track_rate, current_end); // Add a track 'now' to avoid possible underrun, especially if we add // a track "later". if (current_end != 0L) { CSFLogDebug(logTag, "added track @ %u -> %f", static_cast(current_end), mStream->StreamTimeToSeconds(current_end)); } // To avoid assertions, we need to insert a dummy segment that covers up // to the "start" time for the track segment_->AppendNullData(current_ticks); if (segment_->GetType() == MediaSegment::AUDIO) { mStream->AsSourceStream()->AddAudioTrack( track_id_, WEBRTC_DEFAULT_SAMPLE_RATE, 0, static_cast(segment_.forget())); } else { mStream->AsSourceStream()->AddTrack(track_id_, 0, segment_.forget()); } } private: TrackID track_id_; nsAutoPtr segment_; }; aSource->GraphImpl()->AppendMessage( MakeUnique(aSource, aTrackId, Move(aSegment))); CSFLogInfo(logTag, "Dispatched track-add for track id %u on stream %p", aTrackId, aSource); #endif } nsresult PeerConnectionImpl::CreateNewRemoteTracks(RefPtr& aPco) { JSErrorResult jrv; std::vector> newTracks = mJsepSession->GetRemoteTracksAdded(); // Group new tracks by stream id std::map>> tracksByStreamId; for (auto i = newTracks.begin(); i != newTracks.end(); ++i) { RefPtr track = *i; if (track->GetMediaType() == mozilla::SdpMediaSection::kApplication) { // Ignore datachannel continue; } tracksByStreamId[track->GetStreamId()].push_back(track); } for (auto i = tracksByStreamId.begin(); i != tracksByStreamId.end(); ++i) { std::string streamId = i->first; std::vector>& tracks = i->second; bool newStream = false; RefPtr info = mMedia->GetRemoteStreamById(streamId); if (!info) { newStream = true; nsresult nrv = CreateRemoteSourceStreamInfo(&info, streamId); if (NS_FAILED(nrv)) { aPco->OnSetRemoteDescriptionError( kInternalError, ObString("CreateRemoteSourceStreamInfo failed"), jrv); return nrv; } nrv = mMedia->AddRemoteStream(info); if (NS_FAILED(nrv)) { aPco->OnSetRemoteDescriptionError( kInternalError, ObString("AddRemoteStream failed"), jrv); return nrv; } CSFLogDebug(logTag, "Added remote stream %s", info->GetId().c_str()); #if !defined(MOZILLA_EXTERNAL_LINKAGE) info->GetMediaStream()->AssignId(NS_ConvertUTF8toUTF16(streamId.c_str())); info->GetMediaStream()->SetLogicalStreamStartTime( info->GetMediaStream()->GetPlaybackStream()->GetCurrentTime()); #else info->GetMediaStream()->AssignId((streamId)); #endif } #if !defined(MOZILLA_EXTERNAL_LINKAGE) Sequence> streams; if (!streams.AppendElement(OwningNonNull( *info->GetMediaStream()), fallible)) { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } // Set the principal used for creating the tracks. This makes the stream // data (audio/video samples) accessible to the receiving page. We're // only certain that privacy hasn't been requested if we're connected. nsCOMPtr principal; nsIDocument* doc = GetWindow()->GetExtantDoc(); MOZ_ASSERT(doc); if (mDtlsConnected && !PrivacyRequested()) { principal = doc->NodePrincipal(); } else { // we're either certain that we need isolation for the streams, OR // we're not sure and we can fix the stream in SetDtlsConnected principal = nsNullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal()); } #endif // We need to select unique ids, just use max + 1 TrackID maxTrackId = 0; { nsTArray> domTracks; info->GetMediaStream()->GetTracks(domTracks); for (auto& track : domTracks) { maxTrackId = std::max(maxTrackId, track->mTrackID); } } for (RefPtr& track : tracks) { std::string webrtcTrackId(track->GetTrackId()); if (!info->HasTrack(webrtcTrackId)) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) RefPtr source = new RemoteTrackSource(principal, nsString()); #else RefPtr source = new MediaStreamTrackSource(); #endif TrackID trackID = ++maxTrackId; RefPtr domTrack; nsAutoPtr segment; if (track->GetMediaType() == SdpMediaSection::kAudio) { domTrack = info->GetMediaStream()->CreateDOMTrack(trackID, MediaSegment::AUDIO, source); info->GetMediaStream()->AddTrackInternal(domTrack); segment = new AudioSegment; } else { domTrack = info->GetMediaStream()->CreateDOMTrack(trackID, MediaSegment::VIDEO, source); info->GetMediaStream()->AddTrackInternal(domTrack); #if !defined(MOZILLA_EXTERNAL_LINKAGE) segment = new VideoSegment; #endif } StartTrack(info->GetMediaStream()->GetInputStream()->AsSourceStream(), trackID, Move(segment)); info->AddTrack(webrtcTrackId, domTrack); CSFLogDebug(logTag, "Added remote track %s/%s", info->GetId().c_str(), webrtcTrackId.c_str()); #if !defined(MOZILLA_EXTERNAL_LINKAGE) domTrack->AssignId(NS_ConvertUTF8toUTF16(webrtcTrackId.c_str())); aPco->OnAddTrack(*domTrack, streams, jrv); if (jrv.Failed()) { CSFLogError(logTag, ": OnAddTrack(%s) failed! Error: %u", webrtcTrackId.c_str(), jrv.ErrorCodeAsInt()); } #endif } } if (newStream) { aPco->OnAddStream(*info->GetMediaStream(), jrv); if (jrv.Failed()) { CSFLogError(logTag, ": OnAddStream() failed! Error: %u", jrv.ErrorCodeAsInt()); } } } return NS_OK; } void PeerConnectionImpl::RemoveOldRemoteTracks(RefPtr& aPco) { JSErrorResult jrv; std::vector> removedTracks = mJsepSession->GetRemoteTracksRemoved(); for (auto i = removedTracks.begin(); i != removedTracks.end(); ++i) { const std::string& streamId = (*i)->GetStreamId(); const std::string& trackId = (*i)->GetTrackId(); RefPtr info = mMedia->GetRemoteStreamById(streamId); if (!info) { MOZ_ASSERT(false, "A stream/track was removed that wasn't in PCMedia. " "This is a bug."); continue; } mMedia->RemoveRemoteTrack(streamId, trackId); DOMMediaStream* stream = info->GetMediaStream(); nsTArray> tracks; stream->GetTracks(tracks); for (auto& track : tracks) { if (PeerConnectionImpl::GetTrackId(*track) == trackId) { aPco->OnRemoveTrack(*track, jrv); break; } } // We might be holding the last ref, but that's ok. if (!info->GetTrackCount()) { aPco->OnRemoveStream(*stream, jrv); } } } NS_IMETHODIMP PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { PC_AUTO_ENTER_API_CALL(true); if (!aSDP) { CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); return NS_ERROR_FAILURE; } JSErrorResult jrv; RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_OK; } if (action == IPeerConnection::kActionOffer) { if (!PeerConnectionCtx::GetInstance()->isReady()) { // Uh oh. We're not ready yet. Enqueue this operation. (This must be a // remote offer, or else we would not have gotten this far) PeerConnectionCtx::GetInstance()->queueJSEPOperation( WrapRunnableNM(DeferredSetRemote, mHandle, action, std::string(aSDP))); STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)"); return NS_OK; } nsresult nrv = ConfigureJsepSessionCodecs(); if (NS_FAILED(nrv)) { CSFLogError(logTag, "Failed to configure codecs"); return nrv; } } STAMP_TIMECARD(mTimeCard, "Set Remote Description"); mRemoteRequestedSDP = aSDP; JsepSdpType sdpType; switch (action) { case IPeerConnection::kActionOffer: sdpType = mozilla::kJsepSdpOffer; break; case IPeerConnection::kActionAnswer: sdpType = mozilla::kJsepSdpAnswer; break; case IPeerConnection::kActionPRAnswer: sdpType = mozilla::kJsepSdpPranswer; break; case IPeerConnection::kActionRollback: sdpType = mozilla::kJsepSdpRollback; break; default: MOZ_ASSERT(false); return NS_ERROR_FAILURE; } nsresult nrv = mJsepSession->SetRemoteDescription(sdpType, mRemoteRequestedSDP); if (NS_FAILED(nrv)) { Error error; switch (nrv) { case NS_ERROR_INVALID_ARG: error = kInvalidSessionDescription; break; case NS_ERROR_UNEXPECTED: error = kInvalidState; break; default: error = kInternalError; } std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "%s: pc = %s, error = %s", __FUNCTION__, mHandle.c_str(), errorString.c_str()); pco->OnSetRemoteDescriptionError(error, ObString(errorString.c_str()), jrv); } else { nrv = CreateNewRemoteTracks(pco); if (NS_FAILED(nrv)) { // aPco was already notified, just return early. return NS_OK; } RemoveOldRemoteTracks(pco); pco->OnSetRemoteDescriptionSuccess(jrv); } UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback); return NS_OK; } // WebRTC uses highres time relative to the UNIX epoch (Jan 1, 1970, UTC). #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsresult PeerConnectionImpl::GetTimeSinceEpoch(DOMHighResTimeStamp *result) { MOZ_ASSERT(NS_IsMainThread()); Performance *perf = mWindow->GetPerformance(); NS_ENSURE_TRUE(perf && perf->Timing(), NS_ERROR_UNEXPECTED); *result = perf->Now() + perf->Timing()->NavigationStart(); return NS_OK; } class RTCStatsReportInternalConstruct : public RTCStatsReportInternal { public: RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) { mPcid = pcid; mInboundRTPStreamStats.Construct(); mOutboundRTPStreamStats.Construct(); mMediaStreamTrackStats.Construct(); mMediaStreamStats.Construct(); mTransportStats.Construct(); mIceComponentStats.Construct(); mIceCandidatePairStats.Construct(); mIceCandidateStats.Construct(); mCodecStats.Construct(); mTimestamp.Construct(now); } }; #endif NS_IMETHODIMP PeerConnectionImpl::GetStats(MediaStreamTrack *aSelector) { PC_AUTO_ENTER_API_CALL(true); #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (!mMedia) { // Since we zero this out before the d'tor, we should check. return NS_ERROR_UNEXPECTED; } nsAutoPtr query(new RTCStatsQuery(false)); nsresult rv = BuildStatsQuery_m(aSelector, query.get()); NS_ENSURE_SUCCESS(rv, rv); RUN_ON_THREAD(mSTSThread, WrapRunnableNM(&PeerConnectionImpl::GetStatsForPCObserver_s, mHandle, query), NS_DISPATCH_NORMAL); #endif return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::AddIceCandidate(const char* aCandidate, const char* aMid, unsigned short aLevel) { PC_AUTO_ENTER_API_CALL(true); JSErrorResult rv; RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_OK; } STAMP_TIMECARD(mTimeCard, "Add Ice Candidate"); CSFLogDebug(logTag, "AddIceCandidate: %s", aCandidate); nsresult res = mJsepSession->AddRemoteIceCandidate(aCandidate, aMid, aLevel); if (NS_SUCCEEDED(res)) { // We do not bother PCMedia about this before offer/answer concludes. // Once offer/answer concludes, PCMedia will extract these candidates from // the remote SDP. if (mSignalingState == PCImplSignalingState::SignalingStable) { mMedia->AddIceCandidate(aCandidate, aMid, aLevel); } pco->OnAddIceCandidateSuccess(rv); } else { ++mAddCandidateErrorCount; Error error; switch (res) { case NS_ERROR_UNEXPECTED: error = kInvalidState; break; case NS_ERROR_INVALID_ARG: error = kInvalidCandidate; break; default: error = kInternalError; } std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "Failed to incorporate remote candidate into SDP:" " res = %u, candidate = %s, level = %u, error = %s", static_cast(res), aCandidate, static_cast(aLevel), errorString.c_str()); pco->OnAddIceCandidateError(error, ObString(errorString.c_str()), rv); } UpdateSignalingState(); return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::CloseStreams() { PC_AUTO_ENTER_API_CALL(false); return NS_OK; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsresult PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) { PC_AUTO_ENTER_API_CALL(true); MOZ_ASSERT(!aPeerIdentity.IsEmpty()); // once set, this can't be changed if (mPeerIdentity) { if (!mPeerIdentity->Equals(aPeerIdentity)) { return NS_ERROR_FAILURE; } } else { mPeerIdentity = new PeerIdentity(aPeerIdentity); nsIDocument* doc = GetWindow()->GetExtantDoc(); if (!doc) { CSFLogInfo(logTag, "Can't update principal on streams; document gone"); return NS_ERROR_FAILURE; } MediaStreamTrack* allTracks = nullptr; mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(), mPeerIdentity); } return NS_OK; } #endif nsresult PeerConnectionImpl::SetDtlsConnected(bool aPrivacyRequested) { PC_AUTO_ENTER_API_CALL(false); // For this, as with mPrivacyRequested, once we've connected to a peer, we // fixate on that peer. Dealing with multiple peers or connections is more // than this run-down wreck of an object can handle. // Besides, this is only used to say if we have been connected ever. #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (!mPrivacyRequested && !aPrivacyRequested && !mDtlsConnected) { // now we know that privacy isn't needed for sure nsIDocument* doc = GetWindow()->GetExtantDoc(); if (!doc) { CSFLogInfo(logTag, "Can't update principal on streams; document gone"); return NS_ERROR_FAILURE; } mMedia->UpdateRemoteStreamPrincipals_m(doc->NodePrincipal()); } #endif mDtlsConnected = true; mPrivacyRequested = mPrivacyRequested || aPrivacyRequested; return NS_OK; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) void PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) { nsIDocument* doc = GetWindow()->GetExtantDoc(); if (doc) { mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity); } else { CSFLogInfo(logTag, "Can't update sink principal; document gone"); } } #endif std::string PeerConnectionImpl::GetTrackId(const MediaStreamTrack& aTrack) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsString wideTrackId; aTrack.GetId(wideTrackId); return NS_ConvertUTF16toUTF8(wideTrackId).get(); #else return aTrack.GetId(); #endif } std::string PeerConnectionImpl::GetStreamId(const DOMMediaStream& aStream) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsString wideStreamId; aStream.GetId(wideStreamId); return NS_ConvertUTF16toUTF8(wideStreamId).get(); #else return aStream.GetId(); #endif } void PeerConnectionImpl::OnMediaError(const std::string& aError) { CSFLogError(logTag, "Encountered media error! %s", aError.c_str()); // TODO: Let content know about this somehow. } nsresult PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack, const Sequence>& aStreams) { PC_AUTO_ENTER_API_CALL(true); if (!aStreams.Length()) { CSFLogError(logTag, "%s: At least one stream arg required", __FUNCTION__); return NS_ERROR_FAILURE; } return AddTrack(aTrack, aStreams[0]); } nsresult PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack, DOMMediaStream& aMediaStream) { std::string streamId = PeerConnectionImpl::GetStreamId(aMediaStream); std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); nsresult res = mMedia->AddTrack(aMediaStream, streamId, aTrack, trackId); if (NS_FAILED(res)) { return res; } CSFLogDebug(logTag, "Added track (%s) to stream %s", trackId.c_str(), streamId.c_str()); aTrack.AddPrincipalChangeObserver(this); #if !defined(MOZILLA_EXTERNAL_LINKAGE) PrincipalChanged(&aTrack); #endif if (aTrack.AsAudioStreamTrack()) { res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId); if (NS_FAILED(res)) { return res; } mNumAudioStreams++; } if (aTrack.AsVideoStreamTrack()) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) { // Before this code was moved, this would silently ignore just like it // does now. Is this actually what we want to do? return NS_OK; } #endif res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId); if (NS_FAILED(res)) { return res; } mNumVideoStreams++; } OnNegotiationNeeded(); return NS_OK; } nsresult PeerConnectionImpl::SelectSsrc(MediaStreamTrack& aRecvTrack, unsigned short aSsrcIndex) { for (size_t i = 0; i < mMedia->RemoteStreamsLength(); ++i) { if (mMedia->GetRemoteStreamByIndex(i)->GetMediaStream()-> HasTrack(aRecvTrack)) { auto& pipelines = mMedia->GetRemoteStreamByIndex(i)->GetPipelines(); std::string trackId = PeerConnectionImpl::GetTrackId(aRecvTrack); auto it = pipelines.find(trackId); if (it != pipelines.end()) { it->second->SelectSsrc_m(aSsrcIndex); } } } return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) { PC_AUTO_ENTER_API_CALL(true); std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsString wideTrackId; aTrack.GetId(wideTrackId); for (size_t i = 0; i < mDTMFStates.Length(); ++i) { if (mDTMFStates[i]->mTrackId == wideTrackId) { mDTMFStates[i]->mSendTimer->Cancel(); mDTMFStates.RemoveElementAt(i); break; } } #endif RefPtr info = media()->GetLocalStreamByTrackId(trackId); if (!info) { CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); return NS_ERROR_INVALID_ARG; } nsresult rv = mJsepSession->RemoveTrack(info->GetId(), trackId); if (NS_FAILED(rv)) { CSFLogError(logTag, "%s: Unknown stream/track ids %s %s", __FUNCTION__, info->GetId().c_str(), trackId.c_str()); return rv; } media()->RemoveLocalTrack(info->GetId(), trackId); aTrack.RemovePrincipalChangeObserver(this); OnNegotiationNeeded(); return NS_OK; } static int GetDTMFToneCode(uint16_t c) { const char* DTMF_TONECODES = "0123456789*#ABCD"; if (c == ',') { // , is a special character indicating a 2 second delay return -1; } const char* i = strchr(DTMF_TONECODES, c); MOZ_ASSERT(i); return i - DTMF_TONECODES; } NS_IMETHODIMP PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender, const nsAString& tones, uint32_t duration, uint32_t interToneGap) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) PC_AUTO_ENTER_API_CALL(false); // Check values passed in from PeerConnection.js MOZ_ASSERT(duration >= 40, "duration must be at least 40"); MOZ_ASSERT(duration <= 6000, "duration must be at most 6000"); MOZ_ASSERT(interToneGap >= 30, "interToneGap must be at least 30"); JSErrorResult jrv; // Retrieve track RefPtr mst = sender.GetTrack(jrv); if (jrv.Failed()) { NS_WARNING("Failed to retrieve track for RTCRtpSender!"); return jrv.StealNSResult(); } nsString senderTrackId; mst->GetId(senderTrackId); // Attempt to locate state for the DTMFSender RefPtr state; for (auto& dtmfState : mDTMFStates) { if (dtmfState->mTrackId == senderTrackId) { state = dtmfState; break; } } // No state yet, create a new one if (!state) { state = *mDTMFStates.AppendElement(new DTMFState); state->mPeerConnectionImpl = this; state->mTrackId = senderTrackId; state->mSendTimer = do_CreateInstance(NS_TIMER_CONTRACTID); MOZ_ASSERT(state->mSendTimer); } MOZ_ASSERT(state); auto trackPairs = mJsepSession->GetNegotiatedTrackPairs(); state->mLevel = -1; for (auto& trackPair : trackPairs) { if (state->mTrackId.EqualsASCII(trackPair.mSending->GetTrackId().c_str())) { if (trackPair.mBundleLevel.isSome()) { state->mLevel = *trackPair.mBundleLevel; } else { state->mLevel = trackPair.mLevel; } break; } } state->mTones = tones; state->mDuration = duration; state->mInterToneGap = interToneGap; if (!state->mTones.IsEmpty()) { state->mSendTimer->InitWithCallback(state, 0, nsITimer::TYPE_ONE_SHOT); } #endif return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::GetDTMFToneBuffer(mozilla::dom::RTCRtpSender& sender, nsAString& outToneBuffer) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) PC_AUTO_ENTER_API_CALL(false); JSErrorResult jrv; // Retrieve track RefPtr mst = sender.GetTrack(jrv); if (jrv.Failed()) { NS_WARNING("Failed to retrieve track for RTCRtpSender!"); return jrv.StealNSResult(); } nsString senderTrackId; mst->GetId(senderTrackId); // Attempt to locate state for the DTMFSender for (auto& dtmfState : mDTMFStates) { if (dtmfState->mTrackId == senderTrackId) { outToneBuffer = dtmfState->mTones; break; } } #endif return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack, MediaStreamTrack& aWithTrack) { PC_AUTO_ENTER_API_CALL(true); #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsString trackId; aThisTrack.GetId(trackId); for (size_t i = 0; i < mDTMFStates.Length(); ++i) { if (mDTMFStates[i]->mTrackId == trackId) { mDTMFStates[i]->mSendTimer->Cancel(); mDTMFStates.RemoveElementAt(i); break; } } #endif RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return NS_ERROR_UNEXPECTED; } JSErrorResult jrv; #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (&aThisTrack == &aWithTrack) { pco->OnReplaceTrackSuccess(jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack success callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } nsString thisKind; aThisTrack.GetKind(thisKind); nsString withKind; aWithTrack.GetKind(withKind); if (thisKind != withKind) { pco->OnReplaceTrackError(kIncompatibleMediaStreamTrack, ObString(mJsepSession->GetLastError().c_str()), jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack success callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } #endif std::string origTrackId = PeerConnectionImpl::GetTrackId(aThisTrack); std::string newTrackId = PeerConnectionImpl::GetTrackId(aWithTrack); RefPtr info = media()->GetLocalStreamByTrackId(origTrackId); if (!info) { CSFLogError(logTag, "Could not find stream from trackId"); return NS_ERROR_UNEXPECTED; } std::string origStreamId = info->GetId(); std::string newStreamId = PeerConnectionImpl::GetStreamId(*aWithTrack.mOwningStream); nsresult rv = mJsepSession->ReplaceTrack(origStreamId, origTrackId, newStreamId, newTrackId); if (NS_FAILED(rv)) { pco->OnReplaceTrackError(kInvalidMediastreamTrack, ObString(mJsepSession->GetLastError().c_str()), jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack error callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } rv = media()->ReplaceTrack(origStreamId, origTrackId, aWithTrack, newStreamId, newTrackId); if (NS_FAILED(rv)) { CSFLogError(logTag, "Unexpected error in ReplaceTrack: %d", static_cast(rv)); pco->OnReplaceTrackError(kInvalidMediastreamTrack, ObString("Failed to replace track"), jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack error callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } aThisTrack.RemovePrincipalChangeObserver(this); aWithTrack.AddPrincipalChangeObserver(this); #if !defined(MOZILLA_EXTERNAL_LINKAGE) PrincipalChanged(&aWithTrack); #endif // We update the media pipelines here so we can apply different codec // settings for different sources (e.g. screensharing as opposed to camera.) // TODO: We should probably only do this if the source has in fact changed. mMedia->UpdateMediaPipelines(*mJsepSession); pco->OnReplaceTrackSuccess(jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack success callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) NS_IMETHODIMP PeerConnectionImpl::SetParameters(MediaStreamTrack& aTrack, const RTCRtpParameters& aParameters) { PC_AUTO_ENTER_API_CALL(true); std::vector constraints; if (aParameters.mEncodings.WasPassed()) { for (auto& encoding : aParameters.mEncodings.Value()) { JsepTrack::JsConstraints constraint; if (encoding.mRid.WasPassed()) { constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); } if (encoding.mMaxBitrate.WasPassed()) { constraint.constraints.maxBr = encoding.mMaxBitrate.Value(); } constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy; constraints.push_back(constraint); } } return SetParameters(aTrack, constraints); } #endif nsresult PeerConnectionImpl::SetParameters( MediaStreamTrack& aTrack, const std::vector& aConstraints) { std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); RefPtr info = media()->GetLocalStreamByTrackId(trackId); if (!info) { CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); return NS_ERROR_INVALID_ARG; } std::string streamId = info->GetId(); return mJsepSession->SetParameters(streamId, trackId, aConstraints); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) NS_IMETHODIMP PeerConnectionImpl::GetParameters(MediaStreamTrack& aTrack, RTCRtpParameters& aOutParameters) { PC_AUTO_ENTER_API_CALL(true); std::vector constraints; nsresult rv = GetParameters(aTrack, &constraints); if (NS_FAILED(rv)) { return rv; } aOutParameters.mEncodings.Construct(); for (auto& constraint : constraints) { RTCRtpEncodingParameters encoding; encoding.mRid.Construct(NS_ConvertASCIItoUTF16(constraint.rid.c_str())); encoding.mMaxBitrate.Construct(constraint.constraints.maxBr); encoding.mScaleResolutionDownBy = constraint.constraints.scaleDownBy; aOutParameters.mEncodings.Value().AppendElement(Move(encoding), fallible); } return NS_OK; } #endif nsresult PeerConnectionImpl::GetParameters( MediaStreamTrack& aTrack, std::vector* aOutConstraints) { std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); RefPtr info = media()->GetLocalStreamByTrackId(trackId); if (!info) { CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); return NS_ERROR_INVALID_ARG; } std::string streamId = info->GetId(); return mJsepSession->GetParameters(streamId, trackId, aOutConstraints); } nsresult PeerConnectionImpl::CalculateFingerprint( const std::string& algorithm, std::vector* fingerprint) const { uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH]; size_t len = 0; MOZ_ASSERT(fingerprint); #if !defined(MOZILLA_EXTERNAL_LINKAGE) const UniqueCERTCertificate& cert = mCertificate->Certificate(); #else const UniqueCERTCertificate& cert = mIdentity->cert(); #endif nsresult rv = DtlsIdentity::ComputeFingerprint(cert, algorithm, &buf[0], sizeof(buf), &len); if (NS_FAILED(rv)) { CSFLogError(logTag, "Unable to calculate certificate fingerprint, rv=%u", static_cast(rv)); return rv; } MOZ_ASSERT(len > 0 && len <= DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH); fingerprint->assign(buf, buf + len); return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::GetFingerprint(char** fingerprint) { MOZ_ASSERT(fingerprint); #if !defined(MOZILLA_EXTERNAL_LINKAGE) MOZ_ASSERT(mCertificate); #endif std::vector fp; nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp); NS_ENSURE_SUCCESS(rv, rv); std::ostringstream os; os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' ' << SdpFingerprintAttributeList::FormatFingerprint(fp); std::string fpStr = os.str(); char* tmp = new char[fpStr.size() + 1]; std::copy(fpStr.begin(), fpStr.end(), tmp); tmp[fpStr.size()] = '\0'; *fingerprint = tmp; return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::GetLocalDescription(char** aSDP) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aSDP); std::string localSdp = mJsepSession->GetLocalDescription(); char* tmp = new char[localSdp.size() + 1]; std::copy(localSdp.begin(), localSdp.end(), tmp); tmp[localSdp.size()] = '\0'; *aSDP = tmp; return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::GetRemoteDescription(char** aSDP) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aSDP); std::string remoteSdp = mJsepSession->GetRemoteDescription(); char* tmp = new char[remoteSdp.size() + 1]; std::copy(remoteSdp.begin(), remoteSdp.end(), tmp); tmp[remoteSdp.size()] = '\0'; *aSDP = tmp; return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::SignalingState(PCImplSignalingState* aState) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); *aState = mSignalingState; return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::IceConnectionState(PCImplIceConnectionState* aState) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); *aState = mIceConnectionState; return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::IceGatheringState(PCImplIceGatheringState* aState) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aState); *aState = mIceGatheringState; return NS_OK; } nsresult PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(mTrickle || !assert_ice_ready || (mIceGatheringState == PCImplIceGatheringState::Complete)); if (IsClosed()) { CSFLogError(logTag, "%s: called API while closed", __FUNCTION__); return NS_ERROR_FAILURE; } if (!mMedia) { CSFLogError(logTag, "%s: called API with disposed mMedia", __FUNCTION__); return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::Close() { CSFLogDebug(logTag, "%s: for %s", __FUNCTION__, mHandle.c_str()); PC_AUTO_ENTER_API_CALL_NO_CHECK(); SetSignalingState_m(PCImplSignalingState::SignalingClosed); return NS_OK; } bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID, const nsAString& aPluginName) { // fire an event to the DOM window if this is "ours" bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false; if (!result) { return false; } CSFLogError(logTag, "%s: Our plugin %llu crashed", __FUNCTION__, static_cast(aPluginID)); #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsCOMPtr doc = mWindow->GetExtantDoc(); if (!doc) { NS_WARNING("Couldn't get document for PluginCrashed event!"); return true; } PluginCrashedEventInit init; init.mPluginID = aPluginID; init.mPluginName = aPluginName; init.mSubmittedCrashReport = false; init.mGmpPlugin = true; init.mBubbles = true; init.mCancelable = true; RefPtr event = PluginCrashedEvent::Constructor(doc, NS_LITERAL_STRING("PluginCrashed"), init); event->SetTrusted(true); event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; EventDispatcher::DispatchDOMEvent(mWindow, nullptr, event, nullptr, nullptr); #endif return true; } nsresult PeerConnectionImpl::CloseInt() { PC_AUTO_ENTER_API_CALL_NO_CHECK(); for (auto& dtmfState : mDTMFStates) { dtmfState->mSendTimer->Cancel(); } // We do this at the end of the call because we want to make sure we've waited // for all trickle ICE candidates to come in; this can happen well after we've // transitioned to connected. As a bonus, this allows us to detect race // conditions where a stats dispatch happens right as the PC closes. if (!mPrivateWindow) { RecordLongtermICEStatistics(); } CSFLogInfo(logTag, "%s: Closing PeerConnectionImpl %s; " "ending call", __FUNCTION__, mHandle.c_str()); if (mJsepSession) { mJsepSession->Close(); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) if (mDataConnection) { CSFLogInfo(logTag, "%s: Destroying DataChannelConnection %p for %s", __FUNCTION__, (void *) mDataConnection.get(), mHandle.c_str()); mDataConnection->Destroy(); mDataConnection = nullptr; // it may not go away until the runnables are dead } #endif ShutdownMedia(); // DataConnection will need to stay alive until all threads/runnables exit return NS_OK; } void PeerConnectionImpl::ShutdownMedia() { PC_AUTO_ENTER_API_CALL_NO_CHECK(); if (!mMedia) return; #if !defined(MOZILLA_EXTERNAL_LINKAGE) // before we destroy references to local tracks, detach from them for(uint32_t i = 0; i < media()->LocalStreamsLength(); ++i) { LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i); for (const auto& pair : info->GetMediaStreamTracks()) { pair.second->RemovePrincipalChangeObserver(this); } } #endif // Forget the reference so that we can transfer it to // SelfDestruct(). mMedia.forget().take()->SelfDestruct(); } void PeerConnectionImpl::SetSignalingState_m(PCImplSignalingState aSignalingState, bool rollback) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); if (mSignalingState == aSignalingState || mSignalingState == PCImplSignalingState::SignalingClosed) { return; } if (aSignalingState == PCImplSignalingState::SignalingHaveLocalOffer || (aSignalingState == PCImplSignalingState::SignalingStable && mSignalingState == PCImplSignalingState::SignalingHaveRemoteOffer && !rollback)) { mMedia->EnsureTransports(*mJsepSession); } mSignalingState = aSignalingState; bool fireNegotiationNeeded = false; if (mSignalingState == PCImplSignalingState::SignalingStable) { if (mMedia->GetIceRestartState() == PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { if (rollback) { RollbackIceRestart(); } else { mMedia->CommitIceRestart(); } } // Either negotiation is done, or we've rolled back. In either case, we // need to re-evaluate whether further negotiation is required. mNegotiationNeeded = false; // If we're rolling back a local offer, we might need to remove some // transports, but nothing further needs to be done. mMedia->ActivateOrRemoveTransports(*mJsepSession); if (!rollback) { mMedia->UpdateMediaPipelines(*mJsepSession); InitializeDataChannel(); mMedia->StartIceChecks(*mJsepSession); } if (!mJsepSession->AllLocalTracksAreAssigned()) { CSFLogInfo(logTag, "Not all local tracks were assigned to an " "m-section, either because the offerer did not offer" " to receive enough tracks, or because tracks were " "added after CreateOffer/Answer, but before " "offer/answer completed. This requires " "renegotiation."); fireNegotiationNeeded = true; } // Update the max channels used with each direction for each type uint16_t receiving[SdpMediaSection::kMediaTypes]; uint16_t sending[SdpMediaSection::kMediaTypes]; mJsepSession->CountTracks(receiving, sending); for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) { if (mMaxReceiving[i] < receiving[i]) { mMaxReceiving[i] = receiving[i]; } if (mMaxSending[i] < sending[i]) { mMaxSending[i] = sending[i]; } } } if (mSignalingState == PCImplSignalingState::SignalingClosed) { CloseInt(); } RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return; } JSErrorResult rv; pco->OnStateChange(PCObserverStateType::SignalingState, rv); if (fireNegotiationNeeded) { // We don't use MaybeFireNegotiationNeeded here, since content might have // already cased a transition from stable. OnNegotiationNeeded(); } } void PeerConnectionImpl::UpdateSignalingState(bool rollback) { mozilla::JsepSignalingState state = mJsepSession->GetState(); PCImplSignalingState newState; switch(state) { case kJsepStateStable: newState = PCImplSignalingState::SignalingStable; break; case kJsepStateHaveLocalOffer: newState = PCImplSignalingState::SignalingHaveLocalOffer; break; case kJsepStateHaveRemoteOffer: newState = PCImplSignalingState::SignalingHaveRemoteOffer; break; case kJsepStateHaveLocalPranswer: newState = PCImplSignalingState::SignalingHaveLocalPranswer; break; case kJsepStateHaveRemotePranswer: newState = PCImplSignalingState::SignalingHaveRemotePranswer; break; case kJsepStateClosed: newState = PCImplSignalingState::SignalingClosed; break; default: MOZ_CRASH(); } SetSignalingState_m(newState, rollback); } bool PeerConnectionImpl::IsClosed() const { return mSignalingState == PCImplSignalingState::SignalingClosed; } bool PeerConnectionImpl::HasMedia() const { return mMedia; } PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) : impl_(nullptr) { if (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) == PeerConnectionCtx::GetInstance()->mPeerConnections.end()) { return; } PeerConnectionImpl *impl = PeerConnectionCtx::GetInstance()->mPeerConnections[handle]; if (!impl->media()) return; impl_ = impl; } const std::string& PeerConnectionImpl::GetHandle() { PC_AUTO_ENTER_API_CALL_NO_CHECK(); return mHandle; } const std::string& PeerConnectionImpl::GetName() { PC_AUTO_ENTER_API_CALL_NO_CHECK(); return mName; } static mozilla::dom::PCImplIceConnectionState toDomIceConnectionState(NrIceCtx::ConnectionState state) { switch (state) { case NrIceCtx::ICE_CTX_INIT: return PCImplIceConnectionState::New; case NrIceCtx::ICE_CTX_CHECKING: return PCImplIceConnectionState::Checking; case NrIceCtx::ICE_CTX_CONNECTED: return PCImplIceConnectionState::Connected; case NrIceCtx::ICE_CTX_COMPLETED: return PCImplIceConnectionState::Completed; case NrIceCtx::ICE_CTX_FAILED: return PCImplIceConnectionState::Failed; case NrIceCtx::ICE_CTX_DISCONNECTED: return PCImplIceConnectionState::Disconnected; case NrIceCtx::ICE_CTX_CLOSED: return PCImplIceConnectionState::Closed; } MOZ_CRASH(); } static mozilla::dom::PCImplIceGatheringState toDomIceGatheringState(NrIceCtx::GatheringState state) { switch (state) { case NrIceCtx::ICE_CTX_GATHER_INIT: return PCImplIceGatheringState::New; case NrIceCtx::ICE_CTX_GATHER_STARTED: return PCImplIceGatheringState::Gathering; case NrIceCtx::ICE_CTX_GATHER_COMPLETE: return PCImplIceGatheringState::Complete; } MOZ_CRASH(); } void PeerConnectionImpl::CandidateReady(const std::string& candidate, uint16_t level) { PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); std::string mid; bool skipped = false; nsresult res = mJsepSession->AddLocalIceCandidate(candidate, level, &mid, &skipped); if (NS_FAILED(res)) { std::string errorString = mJsepSession->GetLastError(); CSFLogError(logTag, "Failed to incorporate local candidate into SDP:" " res = %u, candidate = %s, level = %u, error = %s", static_cast(res), candidate.c_str(), static_cast(level), errorString.c_str()); return; } if (skipped) { CSFLogDebug(logTag, "Skipped adding local candidate %s (level %u) to SDP, " "this typically happens because the m-section is " "bundled, which means it doesn't make sense for it to " "have its own transport-related attributes.", candidate.c_str(), static_cast(level)); return; } CSFLogDebug(logTag, "Passing local candidate to content: %s", candidate.c_str()); SendLocalIceCandidateToContent(level, mid, candidate); UpdateSignalingState(); } static void SendLocalIceCandidateToContentImpl(nsWeakPtr weakPCObserver, uint16_t level, const std::string& mid, const std::string& candidate) { RefPtr pco = do_QueryObjectReferent(weakPCObserver); if (!pco) { return; } JSErrorResult rv; pco->OnIceCandidate(level, ObString(mid.c_str()), ObString(candidate.c_str()), rv); } void PeerConnectionImpl::SendLocalIceCandidateToContent( uint16_t level, const std::string& mid, const std::string& candidate) { // We dispatch this because OnSetLocalDescriptionSuccess does a setTimeout(0) // to unwind the stack, but the event handlers don't. We need to ensure that // the candidates do not skip ahead of the callback. NS_DispatchToMainThread( WrapRunnableNM(&SendLocalIceCandidateToContentImpl, mPCObserver, level, mid, candidate), NS_DISPATCH_NORMAL); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) static bool isDone(PCImplIceConnectionState state) { return state != PCImplIceConnectionState::Checking && state != PCImplIceConnectionState::New; } static bool isSucceeded(PCImplIceConnectionState state) { return state == PCImplIceConnectionState::Connected || state == PCImplIceConnectionState::Completed; } static bool isFailed(PCImplIceConnectionState state) { return state == PCImplIceConnectionState::Failed; } #endif void PeerConnectionImpl::IceConnectionStateChange( NrIceCtx* ctx, NrIceCtx::ConnectionState state) { PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); CSFLogDebug(logTag, "%s", __FUNCTION__); auto domState = toDomIceConnectionState(state); if (domState == mIceConnectionState) { // no work to be done since the states are the same. // this can happen during ICE rollback situations. return; } mIceConnectionState = domState; if (mIceConnectionState == PCImplIceConnectionState::Connected || mIceConnectionState == PCImplIceConnectionState::Completed || mIceConnectionState == PCImplIceConnectionState::Failed) { if (mMedia->IsIceRestarting()) { FinalizeIceRestart(); } } // Would be nice if we had a means of converting one of these dom enums // to a string that wasn't almost as much text as this switch statement... switch (mIceConnectionState) { case PCImplIceConnectionState::New: STAMP_TIMECARD(mTimeCard, "Ice state: new"); break; case PCImplIceConnectionState::Checking: STAMP_TIMECARD(mTimeCard, "Ice state: checking"); break; case PCImplIceConnectionState::Connected: STAMP_TIMECARD(mTimeCard, "Ice state: connected"); break; case PCImplIceConnectionState::Completed: STAMP_TIMECARD(mTimeCard, "Ice state: completed"); break; case PCImplIceConnectionState::Failed: STAMP_TIMECARD(mTimeCard, "Ice state: failed"); break; case PCImplIceConnectionState::Disconnected: STAMP_TIMECARD(mTimeCard, "Ice state: disconnected"); break; case PCImplIceConnectionState::Closed: STAMP_TIMECARD(mTimeCard, "Ice state: closed"); break; default: MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!"); } RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return; } WrappableJSErrorResult rv; RUN_ON_THREAD(mThread, WrapRunnable(pco, &PeerConnectionObserver::OnStateChange, PCObserverStateType::IceConnectionState, rv, static_cast(nullptr)), NS_DISPATCH_NORMAL); } void PeerConnectionImpl::IceGatheringStateChange( NrIceCtx* ctx, NrIceCtx::GatheringState state) { PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); CSFLogDebug(logTag, "%s", __FUNCTION__); mIceGatheringState = toDomIceGatheringState(state); // Would be nice if we had a means of converting one of these dom enums // to a string that wasn't almost as much text as this switch statement... switch (mIceGatheringState) { case PCImplIceGatheringState::New: STAMP_TIMECARD(mTimeCard, "Ice gathering state: new"); break; case PCImplIceGatheringState::Gathering: STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering"); break; case PCImplIceGatheringState::Complete: STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete"); break; default: MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!"); } RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return; } WrappableJSErrorResult rv; mThread->Dispatch(WrapRunnable(pco, &PeerConnectionObserver::OnStateChange, PCObserverStateType::IceGatheringState, rv, static_cast(nullptr)), NS_DISPATCH_NORMAL); if (mIceGatheringState == PCImplIceGatheringState::Complete) { SendLocalIceCandidateToContent(0, "", ""); } } void PeerConnectionImpl::UpdateDefaultCandidate(const std::string& defaultAddr, uint16_t defaultPort, const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort, uint16_t level) { CSFLogDebug(logTag, "%s", __FUNCTION__); mJsepSession->UpdateDefaultCandidate(defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, level); } void PeerConnectionImpl::EndOfLocalCandidates(uint16_t level) { CSFLogDebug(logTag, "%s", __FUNCTION__); mJsepSession->EndOfLocalCandidates(level); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsresult PeerConnectionImpl::BuildStatsQuery_m( mozilla::dom::MediaStreamTrack *aSelector, RTCStatsQuery *query) { if (!HasMedia()) { return NS_ERROR_UNEXPECTED; } if (!mThread) { CSFLogError(logTag, "Could not build stats query, no MainThread"); return NS_ERROR_UNEXPECTED; } nsresult rv = GetTimeSinceEpoch(&(query->now)); if (NS_FAILED(rv)) { CSFLogError(logTag, "Could not build stats query, could not get timestamp"); return rv; } // Note: mMedia->ice_ctx() is deleted on STS thread; so make sure we grab and hold // a ref instead of making multiple calls. NrIceCtx uses threadsafe refcounting. // NOTE: Do this after all other failure tests, to ensure we don't // accidentally release the Ctx on Mainthread. query->iceCtx = mMedia->ice_ctx(); if (!query->iceCtx) { CSFLogError(logTag, "Could not build stats query, no ice_ctx"); return NS_ERROR_UNEXPECTED; } // We do not use the pcHandle here, since that's risky to expose to content. query->report = new RTCStatsReportInternalConstruct( NS_ConvertASCIItoUTF16(mName.c_str()), query->now); query->iceStartTime = mIceStartTime; query->failed = isFailed(mIceConnectionState); // Populate SDP on main if (query->internalStats) { if (mJsepSession) { std::string localDescription = mJsepSession->GetLocalDescription(); std::string remoteDescription = mJsepSession->GetRemoteDescription(); query->report->mLocalSdp.Construct( NS_ConvertASCIItoUTF16(localDescription.c_str())); query->report->mRemoteSdp.Construct( NS_ConvertASCIItoUTF16(remoteDescription.c_str())); } } // Gather up pipelines from mMedia so they may be inspected on STS std::string trackId; if (aSelector) { trackId = PeerConnectionImpl::GetTrackId(*aSelector); } for (int i = 0, len = mMedia->LocalStreamsLength(); i < len; i++) { for (auto pipeline : mMedia->GetLocalStreamByIndex(i)->GetPipelines()) { if (!aSelector || pipeline.second->trackid() == trackId) { query->pipelines.AppendElement(pipeline.second); } } } for (int i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) { for (auto pipeline : mMedia->GetRemoteStreamByIndex(i)->GetPipelines()) { if (!aSelector || pipeline.second->trackid() == trackId) { query->pipelines.AppendElement(pipeline.second); } } } if (!aSelector) { query->grabAllLevels = true; } return rv; } static void ToRTCIceCandidateStats( const std::vector& candidates, RTCStatsType candidateType, const nsString& componentId, DOMHighResTimeStamp now, RTCStatsReportInternal* report) { MOZ_ASSERT(report); for (auto c = candidates.begin(); c != candidates.end(); ++c) { RTCIceCandidateStats cand; cand.mType.Construct(candidateType); NS_ConvertASCIItoUTF16 codeword(c->codeword.c_str()); cand.mComponentId.Construct(componentId); cand.mId.Construct(codeword); cand.mTimestamp.Construct(now); cand.mCandidateType.Construct( RTCStatsIceCandidateType(c->type)); cand.mIpAddress.Construct( NS_ConvertASCIItoUTF16(c->cand_addr.host.c_str())); cand.mPortNumber.Construct(c->cand_addr.port); cand.mTransport.Construct( NS_ConvertASCIItoUTF16(c->cand_addr.transport.c_str())); if (candidateType == RTCStatsType::Localcandidate) { cand.mMozLocalTransport.Construct( NS_ConvertASCIItoUTF16(c->local_addr.transport.c_str())); } report->mIceCandidateStats.Value().AppendElement(cand, fallible); } } static void RecordIceStats_s( NrIceMediaStream& mediaStream, bool internalStats, DOMHighResTimeStamp now, RTCStatsReportInternal* report) { NS_ConvertASCIItoUTF16 componentId(mediaStream.name().c_str()); std::vector candPairs; nsresult res = mediaStream.GetCandidatePairs(&candPairs); if (NS_FAILED(res)) { CSFLogError(logTag, "%s: Error getting candidate pairs", __FUNCTION__); return; } for (auto p = candPairs.begin(); p != candPairs.end(); ++p) { NS_ConvertASCIItoUTF16 codeword(p->codeword.c_str()); NS_ConvertASCIItoUTF16 localCodeword(p->local.codeword.c_str()); NS_ConvertASCIItoUTF16 remoteCodeword(p->remote.codeword.c_str()); // Only expose candidate-pair statistics to chrome, until we've thought // through the implications of exposing it to content. RTCIceCandidatePairStats s; s.mId.Construct(codeword); s.mComponentId.Construct(componentId); s.mTimestamp.Construct(now); s.mType.Construct(RTCStatsType::Candidatepair); s.mLocalCandidateId.Construct(localCodeword); s.mRemoteCandidateId.Construct(remoteCodeword); s.mNominated.Construct(p->nominated); s.mPriority.Construct(p->priority); s.mSelected.Construct(p->selected); s.mState.Construct(RTCStatsIceCandidatePairState(p->state)); report->mIceCandidatePairStats.Value().AppendElement(s, fallible); } std::vector candidates; if (NS_SUCCEEDED(mediaStream.GetLocalCandidates(&candidates))) { ToRTCIceCandidateStats(candidates, RTCStatsType::Localcandidate, componentId, now, report); } candidates.clear(); if (NS_SUCCEEDED(mediaStream.GetRemoteCandidates(&candidates))) { ToRTCIceCandidateStats(candidates, RTCStatsType::Remotecandidate, componentId, now, report); } } nsresult PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { ASSERT_ON_THREAD(query->iceCtx->thread()); // Gather stats from pipelines provided (can't touch mMedia + stream on STS) for (size_t p = 0; p < query->pipelines.Length(); ++p) { const MediaPipeline& mp = *query->pipelines[p]; bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO); nsString mediaType = isAudio ? NS_LITERAL_STRING("audio") : NS_LITERAL_STRING("video"); nsString idstr = mediaType; idstr.AppendLiteral("_"); idstr.AppendInt(mp.level()); // Gather pipeline stats. switch (mp.direction()) { case MediaPipeline::TRANSMIT: { nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr; nsString remoteId; nsString ssrc; unsigned int ssrcval; if (mp.Conduit()->GetLocalSSRC(&ssrcval)) { ssrc.AppendInt(ssrcval); } { // First, fill in remote stat with rtcp receiver data, if present. // ReceiverReports have less information than SenderReports, // so fill in what we can. DOMHighResTimeStamp timestamp; uint32_t jitterMs; uint32_t packetsReceived; uint64_t bytesReceived; uint32_t packetsLost; int32_t rtt; if (mp.Conduit()->GetRTCPReceiverReport(×tamp, &jitterMs, &packetsReceived, &bytesReceived, &packetsLost, &rtt)) { remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr; RTCInboundRTPStreamStats s; s.mTimestamp.Construct(timestamp); s.mId.Construct(remoteId); s.mType.Construct(RTCStatsType::Inboundrtp); if (ssrc.Length()) { s.mSsrc.Construct(ssrc); } s.mMediaType.Construct(mediaType); s.mJitter.Construct(double(jitterMs)/1000); s.mRemoteId.Construct(localId); s.mIsRemote = true; s.mPacketsReceived.Construct(packetsReceived); s.mBytesReceived.Construct(bytesReceived); s.mPacketsLost.Construct(packetsLost); s.mMozRtt.Construct(rtt); query->report->mInboundRTPStreamStats.Value().AppendElement(s, fallible); } } // Then, fill in local side (with cross-link to remote only if present) { RTCOutboundRTPStreamStats s; s.mTimestamp.Construct(query->now); s.mId.Construct(localId); s.mType.Construct(RTCStatsType::Outboundrtp); if (ssrc.Length()) { s.mSsrc.Construct(ssrc); } s.mMediaType.Construct(mediaType); s.mRemoteId.Construct(remoteId); s.mIsRemote = false; s.mPacketsSent.Construct(mp.rtp_packets_sent()); s.mBytesSent.Construct(mp.rtp_bytes_sent()); // Lastly, fill in video encoder stats if this is video if (!isAudio) { double framerateMean; double framerateStdDev; double bitrateMean; double bitrateStdDev; uint32_t droppedFrames; if (mp.Conduit()->GetVideoEncoderStats(&framerateMean, &framerateStdDev, &bitrateMean, &bitrateStdDev, &droppedFrames)) { s.mFramerateMean.Construct(framerateMean); s.mFramerateStdDev.Construct(framerateStdDev); s.mBitrateMean.Construct(bitrateMean); s.mBitrateStdDev.Construct(bitrateStdDev); s.mDroppedFrames.Construct(droppedFrames); } } query->report->mOutboundRTPStreamStats.Value().AppendElement(s, fallible); } break; } case MediaPipeline::RECEIVE: { nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr; nsString remoteId; nsString ssrc; unsigned int ssrcval; if (mp.Conduit()->GetRemoteSSRC(&ssrcval)) { ssrc.AppendInt(ssrcval); } { // First, fill in remote stat with rtcp sender data, if present. DOMHighResTimeStamp timestamp; uint32_t packetsSent; uint64_t bytesSent; if (mp.Conduit()->GetRTCPSenderReport(×tamp, &packetsSent, &bytesSent)) { remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr; RTCOutboundRTPStreamStats s; s.mTimestamp.Construct(timestamp); s.mId.Construct(remoteId); s.mType.Construct(RTCStatsType::Outboundrtp); if (ssrc.Length()) { s.mSsrc.Construct(ssrc); } s.mMediaType.Construct(mediaType); s.mRemoteId.Construct(localId); s.mIsRemote = true; s.mPacketsSent.Construct(packetsSent); s.mBytesSent.Construct(bytesSent); query->report->mOutboundRTPStreamStats.Value().AppendElement(s, fallible); } } // Then, fill in local side (with cross-link to remote only if present) RTCInboundRTPStreamStats s; s.mTimestamp.Construct(query->now); s.mId.Construct(localId); s.mType.Construct(RTCStatsType::Inboundrtp); if (ssrc.Length()) { s.mSsrc.Construct(ssrc); } s.mMediaType.Construct(mediaType); unsigned int jitterMs, packetsLost; if (mp.Conduit()->GetRTPStats(&jitterMs, &packetsLost)) { s.mJitter.Construct(double(jitterMs)/1000); s.mPacketsLost.Construct(packetsLost); } if (remoteId.Length()) { s.mRemoteId.Construct(remoteId); } s.mIsRemote = false; s.mPacketsReceived.Construct(mp.rtp_packets_received()); s.mBytesReceived.Construct(mp.rtp_bytes_received()); if (query->internalStats && isAudio) { int32_t jitterBufferDelay; int32_t playoutBufferDelay; int32_t avSyncDelta; if (mp.Conduit()->GetAVStats(&jitterBufferDelay, &playoutBufferDelay, &avSyncDelta)) { s.mMozJitterBufferDelay.Construct(jitterBufferDelay); s.mMozAvSyncDelay.Construct(avSyncDelta); } } // Lastly, fill in video decoder stats if this is video if (!isAudio) { double framerateMean; double framerateStdDev; double bitrateMean; double bitrateStdDev; uint32_t discardedPackets; if (mp.Conduit()->GetVideoDecoderStats(&framerateMean, &framerateStdDev, &bitrateMean, &bitrateStdDev, &discardedPackets)) { s.mFramerateMean.Construct(framerateMean); s.mFramerateStdDev.Construct(framerateStdDev); s.mBitrateMean.Construct(bitrateMean); s.mBitrateStdDev.Construct(bitrateStdDev); s.mDiscardedPackets.Construct(discardedPackets); } } query->report->mInboundRTPStreamStats.Value().AppendElement(s, fallible); break; } } if (!query->grabAllLevels) { // If we're grabbing all levels, that means we want datachannels too, // which don't have pipelines. if (query->iceCtx->GetStream(p)) { RecordIceStats_s(*query->iceCtx->GetStream(p), query->internalStats, query->now, query->report); } } } if (query->grabAllLevels) { for (size_t i = 0; i < query->iceCtx->GetStreamCount(); ++i) { if (query->iceCtx->GetStream(i)) { RecordIceStats_s(*query->iceCtx->GetStream(i), query->internalStats, query->now, query->report); } } } // NrIceCtx must be destroyed on STS, so it is not safe // to dispatch it back to main. query->iceCtx = nullptr; return NS_OK; } void PeerConnectionImpl::GetStatsForPCObserver_s( const std::string& pcHandle, // The Runnable holds the memory nsAutoPtr query) { MOZ_ASSERT(query); MOZ_ASSERT(query->iceCtx); ASSERT_ON_THREAD(query->iceCtx->thread()); nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get()); NS_DispatchToMainThread( WrapRunnableNM( &PeerConnectionImpl::DeliverStatsReportToPCObserver_m, pcHandle, rv, query), NS_DISPATCH_NORMAL); } void PeerConnectionImpl::DeliverStatsReportToPCObserver_m( const std::string& pcHandle, nsresult result, nsAutoPtr query) { // Is the PeerConnectionImpl still around? PeerConnectionWrapper pcw(pcHandle); if (pcw.impl()) { RefPtr pco = do_QueryObjectReferent(pcw.impl()->mPCObserver); if (pco) { JSErrorResult rv; if (NS_SUCCEEDED(result)) { pco->OnGetStatsSuccess(*query->report, rv); } else { pco->OnGetStatsError(kInternalError, ObString("Failed to fetch statistics"), rv); } if (rv.Failed()) { CSFLogError(logTag, "Error firing stats observer callback"); } } } } #endif void PeerConnectionImpl::RecordLongtermICEStatistics() { #if !defined(MOZILLA_EXTERNAL_LINKAGE) WebrtcGlobalInformation::StoreLongTermICEStatistics(*this); #endif } void PeerConnectionImpl::OnNegotiationNeeded() { if (mSignalingState != PCImplSignalingState::SignalingStable) { // We will check whether we need to renegotiate when we reach stable again return; } if (mNegotiationNeeded) { return; } mNegotiationNeeded = true; RUN_ON_THREAD(mThread, WrapRunnableNM(&MaybeFireNegotiationNeeded_static, mHandle), NS_DISPATCH_NORMAL); } /* static */ void PeerConnectionImpl::MaybeFireNegotiationNeeded_static( const std::string& pcHandle) { PeerConnectionWrapper wrapper(pcHandle); if (!wrapper.impl()) { return; } wrapper.impl()->MaybeFireNegotiationNeeded(); } void PeerConnectionImpl::MaybeFireNegotiationNeeded() { if (!mNegotiationNeeded) { return; } RefPtr pco = do_QueryObjectReferent(mPCObserver); if (!pco) { return; } JSErrorResult rv; pco->OnNegotiationNeeded(rv); } void PeerConnectionImpl::IceStreamReady(NrIceMediaStream *aStream) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); MOZ_ASSERT(aStream); CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str()); } NS_IMETHODIMP PeerConnectionImpl::GetLocalStreams(nsTArray >& result) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); #if !defined(MOZILLA_EXTERNAL_LINKAGE) for(uint32_t i=0; i < media()->LocalStreamsLength(); i++) { LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i); NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED); result.AppendElement(info->GetMediaStream()); } return NS_OK; #else return NS_ERROR_FAILURE; #endif } NS_IMETHODIMP PeerConnectionImpl::GetRemoteStreams(nsTArray >& result) { PC_AUTO_ENTER_API_CALL_NO_CHECK(); #if !defined(MOZILLA_EXTERNAL_LINKAGE) for(uint32_t i=0; i < media()->RemoteStreamsLength(); i++) { RemoteSourceStreamInfo *info = media()->GetRemoteStreamByIndex(i); NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED); result.AppendElement(info->GetMediaStream()); } return NS_OK; #else return NS_ERROR_FAILURE; #endif } nsresult PeerConnectionImpl::DTMFState::Notify(nsITimer* timer) { MOZ_ASSERT(NS_IsMainThread()); nsString eventTone; if (!mTones.IsEmpty()) { uint16_t toneChar = mTones.CharAt(0); int tone = GetDTMFToneCode(toneChar); eventTone.Assign(toneChar); mTones.Cut(0, 1); if (tone == -1) { mSendTimer->InitWithCallback(this, 2000, nsITimer::TYPE_ONE_SHOT); } else { // Reset delay if necessary mSendTimer->InitWithCallback(this, mDuration + mInterToneGap, nsITimer::TYPE_ONE_SHOT); RefPtr conduit = mPeerConnectionImpl->mMedia->GetAudioConduit(mLevel); if (conduit) { uint32_t duration = mDuration; mPeerConnectionImpl->mSTSThread->Dispatch(WrapRunnableNM([conduit, tone, duration] () { //Note: We default to channel 0, not inband, and 6dB attenuation. // here. We might want to revisit these choices in the future. conduit->InsertDTMFTone(0, tone, true, duration, 6); }), NS_DISPATCH_NORMAL); } } } else { mSendTimer->Cancel(); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) RefPtr pco = do_QueryObjectReferent(mPeerConnectionImpl->mPCObserver); if (!pco) { NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!"); return NS_OK; // Return is ignored anyhow } JSErrorResult jrv; pco->OnDTMFToneChange(mTrackId, eventTone, jrv); if (jrv.Failed()) { NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!"); } #endif return NS_OK; } PeerConnectionImpl::DTMFState::DTMFState() = default; PeerConnectionImpl::DTMFState::~DTMFState() = default; NS_IMPL_ISUPPORTS(PeerConnectionImpl::DTMFState, nsITimerCallback) } // end mozilla namespace