/* 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 "base/histogram.h" #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" #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 "nsPerformance.h" #include "nsGlobalWindow.h" #include "nsDOMDataChannel.h" #include "mozilla/TimeStamp.h" #include "mozilla/Telemetry.h" #include "mozilla/Preferences.h" #include "mozilla/PublicSSL.h" #include "nsNetUtil.h" // NS_CheckPortSafety #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/RTCStatsReportBinding.h" #include "mozilla/dom/RTCPeerConnectionBinding.h" #include "mozilla/dom/PeerConnectionImplBinding.h" #include "mozilla/dom/DataChannelBinding.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 "rlogringbuffer.h" #include "WebrtcGlobalInformation.h" #include "mozilla/dom/Event.h" #include "nsIDOMCustomEvent.h" #include "mozilla/EventDispatcher.h" #include "mozilla/net/DataChannelProtocol.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 { class JSErrorResult : public ErrorResult { 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() : isCopy(false) {} WrappableJSErrorResult(const WrappableJSErrorResult &other) : mRv(), isCopy(true) {} ~WrappableJSErrorResult() { if (isCopy) { MOZ_ASSERT(NS_IsMainThread()); } } operator JSErrorResult &() { return mRv; } private: JSErrorResult mRv; bool isCopy; }; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback { public: TracksAvailableCallback(size_t numNewAudioTracks, size_t numNewVideoTracks, const std::string& pcHandle, RefPtr aObserver) : DOMMediaStream::OnTracksAvailableCallback() , mObserver(aObserver) , mPcHandle(pcHandle) {} virtual void NotifyTracksAvailable(DOMMediaStream* aStream) override { MOZ_ASSERT(NS_IsMainThread()); PeerConnectionWrapper wrapper(mPcHandle); if (!wrapper.impl() || wrapper.impl()->IsClosed()) { return; } nsTArray> tracks; aStream->GetTracks(tracks); std::string streamId = PeerConnectionImpl::GetStreamId(*aStream); bool notifyStream = true; for (size_t i = 0; i < tracks.Length(); i++) { std::string trackId; // This is the first chance we get to set the string track id on this // track. It would be nice if we could specify this along with the numeric // track id from the start, but we're stuck doing this fixup after the // fact. nsresult rv = wrapper.impl()->GetRemoteTrackId(streamId, tracks[i]->GetTrackID(), &trackId); if (NS_FAILED(rv)) { CSFLogError(logTag, "%s: Failed to get string track id for %u, rv = %u", __FUNCTION__, static_cast(tracks[i]->GetTrackID()), static_cast(rv)); MOZ_ASSERT(false); continue; } std::string origTrackId = PeerConnectionImpl::GetTrackId(*tracks[i]); if (origTrackId == trackId) { // Pre-existing track notifyStream = false; continue; } tracks[i]->AssignId(NS_ConvertUTF8toUTF16(trackId.c_str())); JSErrorResult jrv; CSFLogInfo(logTag, "Calling OnAddTrack(%s)", trackId.c_str()); mObserver->OnAddTrack(*tracks[i], jrv); if (jrv.Failed()) { CSFLogError(logTag, ": OnAddTrack(%u) failed! Error: %u", static_cast(i), jrv.ErrorCodeAsInt()); } } if (notifyStream) { // Start currentTime from the point where this stream was successfully // returned. aStream->SetLogicalStreamStartTime( aStream->GetPlaybackStream()->GetCurrentTime()); JSErrorResult rv; CSFLogInfo(logTag, "Calling OnAddStream(%s)", streamId.c_str()); mObserver->OnAddStream(*aStream, rv); if (rv.Failed()) { CSFLogError(logTag, ": OnAddStream() failed! Error: %u", rv.ErrorCodeAsInt()); } } } private: RefPtr mObserver; const std::string mPcHandle; }; #endif #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; } 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) , mIsLoop(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) { #if !defined(MOZILLA_EXTERNAL_LINKAGE) MOZ_ASSERT(NS_IsMainThread()); if (aGlobal) { mWindow = do_QueryInterface(aGlobal->GetAsSupports()); } #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; } MOZ_ASSERT(NS_IsMainThread()); 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::CreateSourceStream(GetWindow(), graph); #if !defined(MOZILLA_EXTERNAL_LINKAGE) // Make 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. if (mDtlsConnected && !PrivacyRequested()) { nsIDocument* doc = GetWindow()->GetExtantDoc(); if (!doc) { return nullptr; } stream->CombineWithPrincipal(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 nsCOMPtr principal = do_CreateInstance(NS_NULLPRINCIPAL_CONTRACTID); stream->CombineWithPrincipal(principal); } #endif 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::None: setIceTransportPolicy(NrIceCtx::ICE_POLICY_NONE); break; case dom::RTCIceTransportPolicy::Relay: setIceTransportPolicy(NrIceCtx::ICE_POLICY_RELAY); break; case dom::RTCIceTransportPolicy::All: setIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL); break; default: MOZ_CRASH(); } return NS_OK; } // list of known acceptable ports for webrtc int16_t gGoodWebrtcPortList[] = { 3478, // stun or turn 5349, // stuns or turns 0, // Sentinel value: This MUST be zero }; 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; // First check the known good ports for webrtc bool goodPort = false; for (int i = 0; !goodPort && gGoodWebrtcPortList[i]; i++) { if (port == gGoodWebrtcPortList[i]) { goodPort = true; } } // if not in the list of known good ports for webrtc, check // the generic block list using NS_CheckPortSafety. if (!goodPort) { rv = NS_CheckPortSafety(port, nullptr); NS_ENSURE_SUCCESS(rv, rv); } if (isTurn || isTurns) { NS_ConvertUTF16toUTF8 credential(aServer.mCredential); NS_ConvertUTF16toUTF8 username(aServer.mUsername); 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; 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); #define HELLO_CLICKER_URL_START "https://hello.firefox.com/" #define HELLO_INITIATOR_URL_START "about:loop" mIsLoop = (strncmp(HELLO_CLICKER_URL_START, locationCStr.get(), strlen(HELLO_CLICKER_URL_START)) == 0) || (strncmp(HELLO_INITIATOR_URL_START, locationCStr.get(), strlen(HELLO_INITIATOR_URL_START)) == 0); } PR_snprintf( temp, sizeof(temp), "%llu (id=%llu url=%s)", static_cast(timestamp), static_cast(mWindow ? mWindow->WindowID() : 0), locationCStr.get() ? locationCStr.get() : "NULL"); #else PR_snprintf(temp, sizeof(temp), "%llu", (unsigned long long)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]; PR_snprintf(hex,sizeof(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; }; nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() { if (mHaveConfiguredCodecs) { return NS_OK; } mHaveConfiguredCodecs = true; #if !defined(MOZILLA_XPCOMRT_API) 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; } bool hardwareH264Supported = false; #ifdef MOZ_WEBRTC_OMX bool hardwareH264Enabled = false; // 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", &hardwareH264Enabled); if (hardwareH264Enabled) { // 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__); hardwareH264Supported = true; } } #endif // MOZ_WEBRTC_OMX #if !defined(MOZILLA_EXTERNAL_LINKAGE) bool softwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264(); #else // For unit-tests bool softwareH264Enabled = true; #endif bool h264Enabled = hardwareH264Supported || softwareH264Enabled; bool vp9Enabled = false; branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &vp9Enabled); auto& codecs = mJsepSession->Codecs(); // We use this to sort the list of codecs once everything is configured CompareCodecPriority comparator; // Set parameters for (auto i = codecs.begin(); i != codecs.end(); ++i) { auto &codec = **i; switch (codec.mType) { case SdpMediaSection::kAudio: // Nothing to configure here, for now. break; case SdpMediaSection::kVideo: { JsepVideoCodecDescription& videoCodec = static_cast(codec); if (videoCodec.mName == "H264") { int32_t level = 13; // minimum suggested for WebRTC spec branch->GetIntPref("media.navigator.video.h264.level", &level); level &= 0xFF; // Override level videoCodec.mProfileLevelId &= 0xFFFF00; videoCodec.mProfileLevelId |= level; int32_t maxBr = 0; // Unlimited branch->GetIntPref("media.navigator.video.h264.max_br", &maxBr); videoCodec.mConstraints.maxBr = maxBr; int32_t maxMbps = 0; // Unlimited #ifdef MOZ_WEBRTC_OMX // Level 1.2; but let's allow CIF@30 or QVGA@30+ by default maxMbps = 11880; #endif branch->GetIntPref("media.navigator.video.h264.max_mbps", &maxMbps); videoCodec.mConstraints.maxMbps = maxMbps; // Might disable it, but we set up other params anyway videoCodec.mEnabled = h264Enabled; if (videoCodec.mPacketizationMode == 0 && !softwareH264Enabled) { // We're assuming packetization mode 0 is unsupported by // hardware. videoCodec.mEnabled = false; } if (hardwareH264Supported) { videoCodec.mStronglyPreferred = true; } } else if (codec.mName == "VP8" || codec.mName == "VP9") { if (videoCodec.mName == "VP9" && !vp9Enabled) { videoCodec.mEnabled = false; break; } int32_t maxFs = 0; branch->GetIntPref("media.navigator.video.max_fs", &maxFs); if (maxFs <= 0) { maxFs = 12288; // We must specify something other than 0 } videoCodec.mConstraints.maxFs = maxFs; int32_t maxFr = 0; branch->GetIntPref("media.navigator.video.max_fr", &maxFr); if (maxFr <= 0) { maxFr = 60; // We must specify something other than 0 } videoCodec.mConstraints.maxFps = maxFr; } // TMMBR is enabled from a pref in about:config bool useTmmbr = false; branch->GetBoolPref("media.navigator.video.use_tmmbr", &useTmmbr); if (useTmmbr) { videoCodec.EnableTmmbr(); } } break; case SdpMediaSection::kText: case SdpMediaSection::kApplication: case SdpMediaSection::kMessage: {} // Nothing to configure for these. } } // Sort by priority int32_t preferredCodec = 0; branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec); if (preferredCodec) { comparator.SetPreferredCodec(preferredCodec); } std::stable_sort(codecs.begin(), codecs.end(), comparator); #endif // !defined(MOZILLA_XPCOMRT_API) 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) { if (!PeerConnectionCtx::GetInstance()->isReady()) { // We are not ready to configure codecs for this track. We need to defer. PeerConnectionCtx::GetInstance()->queueJSEPOperation( WrapRunnableNM(DeferredAddTrackToJsepSession, mHandle, type, streamId, trackId)); return NS_OK; } 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) 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())); } 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); 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 = 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()"); 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; nsresult 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; } 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->AnyLocalStreamHasPeerIdentity(); 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()); } } 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 { 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; RefPtr info = mMedia->GetRemoteStreamById(streamId); if (!info) { nsresult nrv = CreateRemoteSourceStreamInfo(&info, streamId); if (NS_FAILED(nrv)) { pco->OnSetRemoteDescriptionError( kInternalError, ObString("CreateRemoteSourceStreamInfo failed"), jrv); return NS_OK; } nrv = mMedia->AddRemoteStream(info); if (NS_FAILED(nrv)) { pco->OnSetRemoteDescriptionError( kInternalError, ObString("AddRemoteStream failed"), jrv); return NS_OK; } CSFLogDebug(logTag, "Added remote stream %s", info->GetId().c_str()); #if !defined(MOZILLA_EXTERNAL_LINKAGE) info->GetMediaStream()->AssignId(NS_ConvertUTF8toUTF16(streamId.c_str())); #else info->GetMediaStream()->AssignId((streamId)); #endif } size_t numNewAudioTracks = 0; size_t numNewVideoTracks = 0; size_t numPreexistingTrackIds = 0; for (auto j = tracks.begin(); j != tracks.end(); ++j) { RefPtr track = *j; if (!info->HasTrack(track->GetTrackId())) { if (track->GetMediaType() == SdpMediaSection::kAudio) { ++numNewAudioTracks; } else if (track->GetMediaType() == SdpMediaSection::kVideo) { ++numNewVideoTracks; } else { MOZ_ASSERT(false); continue; } info->AddTrack(track->GetTrackId()); CSFLogDebug(logTag, "Added remote track %s/%s", info->GetId().c_str(), track->GetTrackId().c_str()); } else { ++numPreexistingTrackIds; } } // Now that the streams are all set up, notify about track availability. #if !defined(MOZILLA_EXTERNAL_LINKAGE) TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(numNewAudioTracks, numNewVideoTracks, mHandle, pco); info->GetMediaStream()->OnTracksAvailable(tracksAvailableCallback); #else if (!numPreexistingTrackIds) { pco->OnAddStream(*info->GetMediaStream(), jrv); } #endif } std::vector> removedTracks = mJsepSession->GetRemoteTracksRemoved(); for (auto i = removedTracks.begin(); i != removedTracks.end(); ++i) { RefPtr info = mMedia->GetRemoteStreamById((*i)->GetStreamId()); if (!info) { MOZ_ASSERT(false, "A stream/track was removed that wasn't in PCMedia. " "This is a bug."); continue; } mMedia->RemoveRemoteTrack((*i)->GetStreamId(), (*i)->GetTrackId()); // We might be holding the last ref, but that's ok. if (!info->GetTrackCount()) { pco->OnRemoveStream(*info->GetMediaStream(), jrv); } } pco->OnSetRemoteDescriptionSuccess(jrv); #if !defined(MOZILLA_EXTERNAL_LINKAGE) startCallTelem(); #endif } 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()); nsPerformance *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); #if !defined(MOZILLA_EXTERNAL_LINKAGE) // When remote candidates are added before our ICE ctx is up and running // (the transition to New is async through STS, so this is not impossible), // we won't record them as trickle candidates. Is this what we want? if(!mIceStartTime.IsNull()) { TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime; if (mIceConnectionState == PCImplIceConnectionState::Failed) { Telemetry::Accumulate(Telemetry::WEBRTC_ICE_LATE_TRICKLE_ARRIVAL_TIME, timeDelta.ToMilliseconds()); } else { Telemetry::Accumulate(Telemetry::WEBRTC_ICE_ON_TIME_TRICKLE_ARRIVAL_TIME, timeDelta.ToMilliseconds()); } } #endif 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; } mMedia->UpdateSinkIdentity_m(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(DOMMediaStream* aMediaStream) { nsIDocument* doc = GetWindow()->GetExtantDoc(); if (doc) { mMedia->UpdateSinkIdentity_m(doc->NodePrincipal(), mPeerIdentity); } else { CSFLogInfo(logTag, "Can't update sink principal; document gone"); } } #endif #if !defined(MOZILLA_EXTERNAL_LINKAGE) nsresult PeerConnectionImpl::GetRemoteTrackId(const std::string streamId, TrackID numericTrackId, std::string* trackId) const { if (IsClosed()) { return NS_ERROR_UNEXPECTED; } return mMedia->GetRemoteTrackId(streamId, numericTrackId, trackId); } #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) { if (!aMediaStream.HasTrack(aTrack)) { CSFLogError(logTag, "%s: Track is not in stream", __FUNCTION__); return NS_ERROR_FAILURE; } uint32_t num = mMedia->LocalStreamsLength(); std::string streamId = PeerConnectionImpl::GetStreamId(aMediaStream); std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); nsresult res = mMedia->AddTrack(&aMediaStream, streamId, trackId); if (NS_FAILED(res)) { return res; } CSFLogDebug(logTag, "Added track (%s) to stream %s", trackId.c_str(), streamId.c_str()); if (num != mMedia->LocalStreamsLength()) { aMediaStream.AddPrincipalChangeObserver(this); } 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; } NS_IMETHODIMP PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) { PC_AUTO_ENTER_API_CALL(true); DOMMediaStream* stream = aTrack.GetStream(); if (!stream) { CSFLogError(logTag, "%s: Track has no stream", __FUNCTION__); return NS_ERROR_INVALID_ARG; } std::string streamId = PeerConnectionImpl::GetStreamId(*stream); RefPtr info = media()->GetLocalStreamById(streamId); if (!info) { CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); return NS_ERROR_INVALID_ARG; } std::string trackId(PeerConnectionImpl::GetTrackId(aTrack)); 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); OnNegotiationNeeded(); return NS_OK; } NS_IMETHODIMP PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack, MediaStreamTrack& aWithTrack) { PC_AUTO_ENTER_API_CALL(true); 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); std::string origStreamId = PeerConnectionImpl::GetStreamId(*aThisTrack.GetStream()); std::string newStreamId = PeerConnectionImpl::GetStreamId(*aWithTrack.GetStream()); 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.GetStream(), 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; } pco->OnReplaceTrackSuccess(jrv); if (jrv.Failed()) { CSFLogError(logTag, "Error firing replaceTrack success callback"); return NS_ERROR_UNEXPECTED; } return NS_OK; } nsresult PeerConnectionImpl::CalculateFingerprint( const std::string& algorithm, std::vector* fingerprint) const { uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH]; size_t len = 0; CERTCertificate* cert; MOZ_ASSERT(fingerprint); #if !defined(MOZILLA_EXTERNAL_LINKAGE) cert = mCertificate->Certificate(); #else 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->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true; EventDispatcher::DispatchDOMEvent(mWindow, nullptr, event, nullptr, nullptr); #endif return true; } void PeerConnectionImpl::RecordEndOfCallTelemetry() const { if (!mJsepSession) { return; } #if !defined(MOZILLA_EXTERNAL_LINKAGE) // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting static const uint32_t kAudioTypeMask = 1; static const uint32_t kVideoTypeMask = 2; static const uint32_t kDataChannelTypeMask = 4; // Report end-of-call Telemetry if (mJsepSession->GetNegotiations() > 0) { Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_RENEGOTIATIONS : Telemetry::WEBRTC_RENEGOTIATIONS, mJsepSession->GetNegotiations()-1); } Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_MAX_VIDEO_SEND_TRACK : Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK, mMaxSending[SdpMediaSection::MediaType::kVideo]); Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_MAX_VIDEO_RECEIVE_TRACK : Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK, mMaxReceiving[SdpMediaSection::MediaType::kVideo]); Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_MAX_AUDIO_SEND_TRACK : Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK, mMaxSending[SdpMediaSection::MediaType::kAudio]); Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_MAX_AUDIO_RECEIVE_TRACK : Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK, mMaxReceiving[SdpMediaSection::MediaType::kAudio]); // DataChannels appear in both Sending and Receiving Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_DATACHANNEL_NEGOTIATED : Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED, mMaxSending[SdpMediaSection::MediaType::kApplication]); // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel // A/V = 3, A/V/D = 7, etc uint32_t type = 0; if (mMaxSending[SdpMediaSection::MediaType::kAudio] || mMaxReceiving[SdpMediaSection::MediaType::kAudio]) { type = kAudioTypeMask; } if (mMaxSending[SdpMediaSection::MediaType::kVideo] || mMaxReceiving[SdpMediaSection::MediaType::kVideo]) { type |= kVideoTypeMask; } if (mMaxSending[SdpMediaSection::MediaType::kApplication]) { type |= kDataChannelTypeMask; } Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_CALL_TYPE : Telemetry::WEBRTC_CALL_TYPE, type); #endif } nsresult PeerConnectionImpl::CloseInt() { PC_AUTO_ENTER_API_CALL_NO_CHECK(); // 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. RecordLongtermICEStatistics(); RecordEndOfCallTelemetry(); 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() { MOZ_ASSERT(NS_IsMainThread()); if (!mMedia) return; #if !defined(MOZILLA_EXTERNAL_LINKAGE) // before we destroy references to local streams, detach from them for(uint32_t i = 0; i < media()->LocalStreamsLength(); ++i) { LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i); info->GetMediaStream()->RemovePrincipalChangeObserver(this); } // End of call to be recorded in Telemetry if (!mStartTime.IsNull()){ TimeDuration timeDelta = TimeStamp::Now() - mStartTime; Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_CALL_DURATION : Telemetry::WEBRTC_CALL_DURATION, timeDelta.ToSeconds()); } #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) { // 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; } // Telemetry: record info on the current state of streams/renegotiations/etc // Note: this code gets run on rollbacks as well! // 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_OPEN: return PCImplIceConnectionState::Connected; case NrIceCtx::ICE_CTX_FAILED: return PCImplIceConnectionState::Failed; } 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 || state == PCImplIceConnectionState::Disconnected; } #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 !defined(MOZILLA_EXTERNAL_LINKAGE) if (!isDone(mIceConnectionState) && isDone(domState)) { // mIceStartTime can be null if going directly from New to Closed, in which // case we don't count it as a success or a failure. if (!mIceStartTime.IsNull()){ TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime; if (isSucceeded(domState)) { Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_ICE_SUCCESS_TIME : Telemetry::WEBRTC_ICE_SUCCESS_TIME, timeDelta.ToMilliseconds()); } else if (isFailed(domState)) { Telemetry::Accumulate(mIsLoop ? Telemetry::LOOP_ICE_FAILURE_TIME : Telemetry::WEBRTC_ICE_FAILURE_TIME, timeDelta.ToMilliseconds()); } } if (isSucceeded(domState)) { Telemetry::Accumulate( Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_SUCCESS, mAddCandidateErrorCount); } else if (isFailed(domState)) { Telemetry::Accumulate( Telemetry::WEBRTC_ICE_ADD_CANDIDATE_ERRORS_GIVEN_FAILURE, mAddCandidateErrorCount); } } #endif mIceConnectionState = domState; // 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: #if !defined(MOZILLA_EXTERNAL_LINKAGE) // For telemetry mIceStartTime = TimeStamp::Now(); #endif 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); query->isHello = mIsLoop; // 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++) { auto& pipelines = mMedia->GetLocalStreamByIndex(i)->GetPipelines(); if (aSelector) { if (mMedia->GetLocalStreamByIndex(i)->GetMediaStream()-> HasTrack(*aSelector)) { auto it = pipelines.find(trackId); if (it != pipelines.end()) { query->pipelines.AppendElement(it->second); } } } else { for (auto it = pipelines.begin(); it != pipelines.end(); ++it) { query->pipelines.AppendElement(it->second); } } } for (size_t i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) { auto& pipelines = mMedia->GetRemoteStreamByIndex(i)->GetPipelines(); if (aSelector) { if (mMedia->GetRemoteStreamByIndex(i)-> GetMediaStream()->HasTrack(*aSelector)) { auto it = pipelines.find(trackId); if (it != pipelines.end()) { query->pipelines.AppendElement(it->second); } } } else { for (auto it = pipelines.begin(); it != pipelines.end(); ++it) { query->pipelines.AppendElement(it->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()); } #if !defined(MOZILLA_EXTERNAL_LINKAGE) //Telemetry for when calls start void PeerConnectionImpl::startCallTelem() { if (!mStartTime.IsNull()) { return; } // Start time for calls mStartTime = TimeStamp::Now(); // Increment session call counter // If we want to track Loop calls independently here, we need two mConnectionCounters int &cnt = PeerConnectionCtx::GetInstance()->mConnectionCounter; if (cnt > 0) { Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Subtract(cnt); } cnt++; Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(cnt); } #endif 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 } } // end mozilla namespace