michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/histogram.h" michael@0: #include "vcm.h" michael@0: #include "CSFLog.h" michael@0: #include "timecard.h" michael@0: #include "ccapi_call_info.h" michael@0: #include "CC_SIPCCCallInfo.h" michael@0: #include "ccapi_device_info.h" michael@0: #include "CC_SIPCCDeviceInfo.h" michael@0: #include "cpr_string.h" michael@0: #include "cpr_stdlib.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "nspr.h" michael@0: #include "nss.h" michael@0: #include "pk11pub.h" michael@0: michael@0: #include "nsNetCID.h" michael@0: #include "nsIProperty.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsISocketTransportService.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "prtime.h" michael@0: michael@0: #include "AudioConduit.h" michael@0: #include "VideoConduit.h" michael@0: #include "runnable_utils.h" michael@0: #include "PeerConnectionCtx.h" michael@0: #include "PeerConnectionImpl.h" michael@0: #include "PeerConnectionMedia.h" michael@0: #include "nsDOMDataChannelDeclarations.h" michael@0: #include "dtlsidentity.h" michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #include "nsPerformance.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsDOMDataChannel.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/PublicSSL.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIDOMDataChannel.h" michael@0: #include "nsIDOMLocation.h" michael@0: #include "mozilla/dom/RTCConfigurationBinding.h" michael@0: #include "mozilla/dom/RTCStatsReportBinding.h" michael@0: #include "mozilla/dom/RTCPeerConnectionBinding.h" michael@0: #include "mozilla/dom/PeerConnectionImplBinding.h" michael@0: #include "mozilla/dom/DataChannelBinding.h" michael@0: #include "MediaStreamList.h" michael@0: #include "MediaStreamTrack.h" michael@0: #include "AudioStreamTrack.h" michael@0: #include "VideoStreamTrack.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "DOMMediaStream.h" michael@0: #include "rlogringbuffer.h" michael@0: #include "WebrtcGlobalInformation.h" michael@0: #endif michael@0: michael@0: #ifndef USE_FAKE_MEDIA_STREAMS michael@0: #include "MediaSegment.h" michael@0: #endif michael@0: michael@0: #ifdef USE_FAKE_PCOBSERVER michael@0: #include "FakePCObserver.h" michael@0: #else michael@0: #include "mozilla/dom/PeerConnectionObserverBinding.h" michael@0: #endif michael@0: #include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" michael@0: michael@0: #define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: typedef PCObserverString ObString; michael@0: michael@0: static const char* logTag = "PeerConnectionImpl"; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: static nsresult InitNSSInContent() michael@0: { michael@0: NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Content) { michael@0: MOZ_ASSUME_UNREACHABLE("Must be called in content process"); michael@0: } michael@0: michael@0: static bool nssStarted = false; michael@0: if (nssStarted) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (NSS_NoDB_Init(nullptr) != SECSuccess) { michael@0: CSFLogError(logTag, "NSS_NoDB_Init failed."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { michael@0: CSFLogError(logTag, "Fail to set up nss cipher suite."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mozilla::psm::DisableMD5(); michael@0: michael@0: nssStarted = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: #endif // MOZILLA_INTERNAL_API michael@0: michael@0: namespace mozilla { michael@0: class DataChannel; michael@0: } michael@0: michael@0: class nsIDOMDataChannel; michael@0: michael@0: static const int MEDIA_STREAM_MUTE = 0x80; michael@0: michael@0: PRLogModuleInfo *signalingLogInfo() { michael@0: static PRLogModuleInfo *logModuleInfo = nullptr; michael@0: if (!logModuleInfo) { michael@0: logModuleInfo = PR_NewLogModule("signaling"); michael@0: } michael@0: return logModuleInfo; michael@0: } michael@0: michael@0: michael@0: namespace sipcc { michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: RTCStatsQuery::RTCStatsQuery(bool internal) : internalStats(internal) { michael@0: } michael@0: michael@0: RTCStatsQuery::~RTCStatsQuery() { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: // Getting exceptions back down from PCObserver is generally not harmful. michael@0: namespace { michael@0: class JSErrorResult : public ErrorResult michael@0: { michael@0: public: michael@0: ~JSErrorResult() michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: WouldReportJSException(); michael@0: if (IsJSException()) { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: AutoJSContext cx; michael@0: Optional > value(cx); michael@0: StealJSException(cx, &value.Value()); michael@0: } michael@0: #endif michael@0: } michael@0: }; michael@0: michael@0: // The WrapRunnable() macros copy passed-in args and passes them to the function michael@0: // later on the other thread. ErrorResult cannot be passed like this because it michael@0: // disallows copy-semantics. michael@0: // michael@0: // This WrappableJSErrorResult hack solves this by not actually copying the michael@0: // ErrorResult, but creating a new one instead, which works because we don't michael@0: // care about the result. michael@0: // michael@0: // Since this is for JS-calls, these can only be dispatched to the main thread. michael@0: michael@0: class WrappableJSErrorResult { michael@0: public: michael@0: WrappableJSErrorResult() : isCopy(false) {} michael@0: WrappableJSErrorResult(WrappableJSErrorResult &other) : mRv(), isCopy(true) {} michael@0: ~WrappableJSErrorResult() { michael@0: if (isCopy) { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: #endif michael@0: } michael@0: } michael@0: operator JSErrorResult &() { return mRv; } michael@0: private: michael@0: JSErrorResult mRv; michael@0: bool isCopy; michael@0: }; michael@0: } michael@0: michael@0: class PeerConnectionObserverDispatch : public nsRunnable { michael@0: michael@0: public: michael@0: PeerConnectionObserverDispatch(CSF::CC_CallInfoPtr aInfo, michael@0: nsRefPtr aPC, michael@0: PeerConnectionObserver* aObserver) michael@0: : mPC(aPC), michael@0: mObserver(aObserver), michael@0: mCode(static_cast(aInfo->getStatusCode())), michael@0: mReason(aInfo->getStatus()), michael@0: mSdpStr(), michael@0: mCandidateStr(), michael@0: mCallState(aInfo->getCallState()), michael@0: mFsmState(aInfo->getFsmState()), michael@0: mStateStr(aInfo->callStateToString(mCallState)), michael@0: mFsmStateStr(aInfo->fsmStateToString(mFsmState)) { michael@0: if (mCallState == REMOTESTREAMADD) { michael@0: MediaStreamTable *streams = nullptr; michael@0: streams = aInfo->getMediaStreams(); michael@0: mRemoteStream = mPC->media()->GetRemoteStream(streams->media_stream_id); michael@0: MOZ_ASSERT(mRemoteStream); michael@0: } else if (mCallState == FOUNDICECANDIDATE) { michael@0: mCandidateStr = aInfo->getCandidate(); michael@0: } else if ((mCallState == CREATEOFFERSUCCESS) || michael@0: (mCallState == CREATEANSWERSUCCESS)) { michael@0: mSdpStr = aInfo->getSDP(); michael@0: } michael@0: } michael@0: michael@0: ~PeerConnectionObserverDispatch(){} michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback michael@0: { michael@0: public: michael@0: TracksAvailableCallback(DOMMediaStream::TrackTypeHints aTrackTypeHints, michael@0: nsRefPtr aObserver) michael@0: : DOMMediaStream::OnTracksAvailableCallback(aTrackTypeHints) michael@0: , mObserver(aObserver) {} michael@0: michael@0: virtual void NotifyTracksAvailable(DOMMediaStream* aStream) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Start currentTime from the point where this stream was successfully michael@0: // returned. michael@0: aStream->SetLogicalStreamStartTime(aStream->GetStream()->GetCurrentTime()); michael@0: michael@0: CSFLogInfo(logTag, "Returning success for OnAddStream()"); michael@0: // We are running on main thread here so we shouldn't have a race michael@0: // on this callback michael@0: JSErrorResult rv; michael@0: mObserver->OnAddStream(*aStream, rv); michael@0: if (rv.Failed()) { michael@0: CSFLogError(logTag, ": OnAddStream() failed! Error: %d", rv.ErrorCode()); michael@0: } michael@0: } michael@0: private: michael@0: nsRefPtr mObserver; michael@0: }; michael@0: #endif michael@0: michael@0: NS_IMETHOD Run() { michael@0: michael@0: CSFLogInfo(logTag, "PeerConnectionObserverDispatch processing " michael@0: "mCallState = %d (%s), mFsmState = %d (%s)", michael@0: mCallState, mStateStr.c_str(), mFsmState, mFsmStateStr.c_str()); michael@0: michael@0: if (mCallState == SETLOCALDESCERROR || mCallState == SETREMOTEDESCERROR) { michael@0: const std::vector &errors = mPC->GetSdpParseErrors(); michael@0: std::vector::const_iterator i; michael@0: for (i = errors.begin(); i != errors.end(); ++i) { michael@0: mReason += " | SDP Parsing Error: " + *i; michael@0: } michael@0: if (errors.size()) { michael@0: mCode = PeerConnectionImpl::kInvalidSessionDescription; michael@0: } michael@0: mPC->ClearSdpParseErrorMessages(); michael@0: } michael@0: michael@0: if (mReason.length()) { michael@0: CSFLogInfo(logTag, "Message contains error: %d: %s", michael@0: mCode, mReason.c_str()); michael@0: } michael@0: michael@0: /* michael@0: * While the fsm_states_t (FSM_DEF_*) constants are a proper superset michael@0: * of SignalingState, and the order in which the SignalingState values michael@0: * appear matches the order they appear in fsm_states_t, their underlying michael@0: * numeric representation is different. Hence, we need to perform an michael@0: * offset calculation to map from one to the other. michael@0: */ michael@0: michael@0: if (mFsmState >= FSMDEF_S_STABLE && mFsmState <= FSMDEF_S_CLOSED) { michael@0: int offset = FSMDEF_S_STABLE - int(PCImplSignalingState::SignalingStable); michael@0: mPC->SetSignalingState_m(static_cast(mFsmState - offset)); michael@0: } else { michael@0: CSFLogError(logTag, ": **** UNHANDLED SIGNALING STATE : %d (%s)", michael@0: mFsmState, mFsmStateStr.c_str()); michael@0: } michael@0: michael@0: JSErrorResult rv; michael@0: michael@0: switch (mCallState) { michael@0: case CREATEOFFERSUCCESS: michael@0: mObserver->OnCreateOfferSuccess(ObString(mSdpStr.c_str()), rv); michael@0: break; michael@0: michael@0: case CREATEANSWERSUCCESS: michael@0: mObserver->OnCreateAnswerSuccess(ObString(mSdpStr.c_str()), rv); michael@0: break; michael@0: michael@0: case CREATEOFFERERROR: michael@0: mObserver->OnCreateOfferError(mCode, ObString(mReason.c_str()), rv); michael@0: break; michael@0: michael@0: case CREATEANSWERERROR: michael@0: mObserver->OnCreateAnswerError(mCode, ObString(mReason.c_str()), rv); michael@0: break; michael@0: michael@0: case SETLOCALDESCSUCCESS: michael@0: // TODO: The SDP Parse error list should be copied out and sent up michael@0: // to the Javascript layer before being cleared here. Even though michael@0: // there was not a failure, it is possible that the SDP parse generated michael@0: // warnings. The WebRTC spec does not currently have a mechanism for michael@0: // providing non-fatal warnings. michael@0: mPC->ClearSdpParseErrorMessages(); michael@0: mObserver->OnSetLocalDescriptionSuccess(rv); michael@0: break; michael@0: michael@0: case SETREMOTEDESCSUCCESS: michael@0: // TODO: The SDP Parse error list should be copied out and sent up michael@0: // to the Javascript layer before being cleared here. Even though michael@0: // there was not a failure, it is possible that the SDP parse generated michael@0: // warnings. The WebRTC spec does not currently have a mechanism for michael@0: // providing non-fatal warnings. michael@0: mPC->ClearSdpParseErrorMessages(); michael@0: mObserver->OnSetRemoteDescriptionSuccess(rv); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: mPC->startCallTelem(); michael@0: #endif michael@0: break; michael@0: michael@0: case SETLOCALDESCERROR: michael@0: mObserver->OnSetLocalDescriptionError(mCode, michael@0: ObString(mReason.c_str()), rv); michael@0: break; michael@0: michael@0: case SETREMOTEDESCERROR: michael@0: mObserver->OnSetRemoteDescriptionError(mCode, michael@0: ObString(mReason.c_str()), rv); michael@0: break; michael@0: michael@0: case ADDICECANDIDATE: michael@0: mObserver->OnAddIceCandidateSuccess(rv); michael@0: break; michael@0: michael@0: case ADDICECANDIDATEERROR: michael@0: mObserver->OnAddIceCandidateError(mCode, ObString(mReason.c_str()), rv); michael@0: break; michael@0: michael@0: case FOUNDICECANDIDATE: michael@0: { michael@0: size_t end_of_level = mCandidateStr.find('\t'); michael@0: if (end_of_level == std::string::npos) { michael@0: MOZ_ASSERT(false); michael@0: return NS_OK; michael@0: } michael@0: std::string level = mCandidateStr.substr(0, end_of_level); michael@0: if (!level.size()) { michael@0: MOZ_ASSERT(false); michael@0: return NS_OK; michael@0: } michael@0: char *endptr; michael@0: errno = 0; michael@0: unsigned long level_long = michael@0: strtoul(level.c_str(), &endptr, 10); michael@0: if (errno || *endptr != 0 || level_long > 65535) { michael@0: /* Conversion failure */ michael@0: MOZ_ASSERT(false); michael@0: return NS_OK; michael@0: } michael@0: size_t end_of_mid = mCandidateStr.find('\t', end_of_level + 1); michael@0: if (end_of_mid == std::string::npos) { michael@0: MOZ_ASSERT(false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: std::string mid = mCandidateStr.substr(end_of_level + 1, michael@0: end_of_mid - (end_of_level + 1)); michael@0: michael@0: std::string candidate = mCandidateStr.substr(end_of_mid + 1); michael@0: michael@0: mObserver->OnIceCandidate(level_long & 0xffff, michael@0: ObString(mid.c_str()), michael@0: ObString(candidate.c_str()), rv); michael@0: } michael@0: break; michael@0: case REMOTESTREAMADD: michael@0: { michael@0: DOMMediaStream* stream = nullptr; michael@0: michael@0: if (!mRemoteStream) { michael@0: CSFLogError(logTag, "%s: GetRemoteStream returned NULL", __FUNCTION__); michael@0: } else { michael@0: stream = mRemoteStream->GetMediaStream(); michael@0: } michael@0: michael@0: if (!stream) { michael@0: CSFLogError(logTag, "%s: GetMediaStream returned NULL", __FUNCTION__); michael@0: } else { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: TracksAvailableCallback* tracksAvailableCallback = michael@0: new TracksAvailableCallback(mRemoteStream->mTrackTypeHints, mObserver); michael@0: michael@0: stream->OnTracksAvailable(tracksAvailableCallback); michael@0: #else michael@0: mObserver->OnAddStream(stream, rv); michael@0: #endif michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case UPDATELOCALDESC: michael@0: /* No action necessary */ michael@0: break; michael@0: michael@0: default: michael@0: CSFLogError(logTag, ": **** UNHANDLED CALL STATE : %d (%s)", michael@0: mCallState, mStateStr.c_str()); michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mPC; michael@0: nsRefPtr mObserver; michael@0: PeerConnectionImpl::Error mCode; michael@0: std::string mReason; michael@0: std::string mSdpStr; michael@0: std::string mCandidateStr; michael@0: cc_call_state_t mCallState; michael@0: fsmdef_states_t mFsmState; michael@0: std::string mStateStr; michael@0: std::string mFsmStateStr; michael@0: nsRefPtr mRemoteStream; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS0(PeerConnectionImpl) michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: JSObject* michael@0: PeerConnectionImpl::WrapObject(JSContext* aCx) michael@0: { michael@0: return PeerConnectionImplBinding::Wrap(aCx, this); michael@0: } michael@0: #endif michael@0: michael@0: struct PeerConnectionImpl::Internal { michael@0: CSF::CC_CallPtr mCall; michael@0: }; michael@0: michael@0: PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) michael@0: : mTimeCard(PR_LOG_TEST(signalingLogInfo(),PR_LOG_ERROR) ? michael@0: create_timecard() : nullptr) michael@0: , mInternal(new Internal()) michael@0: , mReadyState(PCImplReadyState::New) michael@0: , mSignalingState(PCImplSignalingState::SignalingStable) michael@0: , mIceConnectionState(PCImplIceConnectionState::New) michael@0: , mIceGatheringState(PCImplIceGatheringState::New) michael@0: , mWindow(nullptr) michael@0: , mIdentity(nullptr) michael@0: , mSTSThread(nullptr) michael@0: , mLoadManager(nullptr) michael@0: , mMedia(nullptr) michael@0: , mNumAudioStreams(0) michael@0: , mNumVideoStreams(0) michael@0: , mHaveDataStream(false) michael@0: , mTrickle(true) // TODO(ekr@rtfm.com): Use pref michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (aGlobal) { michael@0: mWindow = do_QueryInterface(aGlobal->GetAsSupports()); michael@0: } michael@0: #endif michael@0: MOZ_ASSERT(mInternal); michael@0: CSFLogInfo(logTag, "%s: PeerConnectionImpl constructor for %s", michael@0: __FUNCTION__, mHandle.c_str()); michael@0: STAMP_TIMECARD(mTimeCard, "Constructor Completed"); michael@0: } michael@0: michael@0: PeerConnectionImpl::~PeerConnectionImpl() michael@0: { michael@0: if (mTimeCard) { michael@0: STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); michael@0: print_timecard(mTimeCard); michael@0: destroy_timecard(mTimeCard); michael@0: mTimeCard = nullptr; michael@0: } michael@0: // This aborts if not on main thread (in Debug builds) michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: if (PeerConnectionCtx::isActive()) { michael@0: PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle); michael@0: } else { michael@0: CSFLogError(logTag, "PeerConnectionCtx is already gone. Ignoring..."); michael@0: } michael@0: michael@0: CSFLogInfo(logTag, "%s: PeerConnectionImpl destructor invoked for %s", michael@0: __FUNCTION__, mHandle.c_str()); michael@0: CloseInt(); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: { michael@0: // Deregister as an NSS Shutdown Object michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!isAlreadyShutDown()) { michael@0: destructorSafeDestroyNSSReference(); michael@0: shutdown(calledFromObject); michael@0: } michael@0: } michael@0: if (mLoadManager) { michael@0: mozilla::LoadManagerDestroy(mLoadManager); michael@0: mLoadManager = nullptr; michael@0: } michael@0: #endif michael@0: michael@0: // Since this and Initialize() occur on MainThread, they can't both be michael@0: // running at once michael@0: michael@0: // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we michael@0: // probably want to shut it down more aggressively to save memory. We michael@0: // could shut down here when there are no uses. It might be more optimal michael@0: // to release off a timer (and XPCOM Shutdown) to avoid churn michael@0: } michael@0: michael@0: already_AddRefed michael@0: PeerConnectionImpl::MakeMediaStream(nsPIDOMWindow* aWindow, michael@0: uint32_t aHint) michael@0: { michael@0: nsRefPtr stream = michael@0: DOMMediaStream::CreateSourceStream(aWindow, aHint); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsIDocument* doc = aWindow->GetExtantDoc(); michael@0: if (!doc) { michael@0: return nullptr; michael@0: } michael@0: // Make the stream data (audio/video samples) accessible to the receiving page. michael@0: stream->CombineWithPrincipal(doc->NodePrincipal()); michael@0: #endif michael@0: michael@0: CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetStream()); michael@0: michael@0: return stream.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::CreateRemoteSourceStreamInfo(nsRefPtr* michael@0: aInfo) michael@0: { michael@0: MOZ_ASSERT(aInfo); michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: // We need to pass a dummy hint here because FakeMediaStream currently michael@0: // needs to actually propagate a hint for local streams. michael@0: // TODO(ekr@rtfm.com): Clean up when we have explicit track lists. michael@0: // See bug 834835. michael@0: nsRefPtr stream = MakeMediaStream(mWindow, 0); michael@0: if (!stream) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: static_cast(stream->GetStream())->SetPullEnabled(true); michael@0: michael@0: nsRefPtr remote; michael@0: remote = new RemoteSourceStreamInfo(stream.forget(), mMedia); michael@0: *aInfo = remote; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * In JS, an RTCConfiguration looks like this: michael@0: * michael@0: * { "iceServers": [ { url:"stun:stun.example.org" }, michael@0: * { url:"turn:turn.example.org?transport=udp", michael@0: * username: "jib", credential:"mypass"} ] } michael@0: * michael@0: * This function converts that into an internal IceConfiguration object. michael@0: */ michael@0: nsresult michael@0: PeerConnectionImpl::ConvertRTCConfiguration(const RTCConfiguration& aSrc, michael@0: IceConfiguration *aDst) michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (!aSrc.mIceServers.WasPassed()) { michael@0: return NS_OK; michael@0: } michael@0: for (uint32_t i = 0; i < aSrc.mIceServers.Value().Length(); i++) { michael@0: const RTCIceServer& server = aSrc.mIceServers.Value()[i]; michael@0: NS_ENSURE_TRUE(server.mUrl.WasPassed(), NS_ERROR_UNEXPECTED); michael@0: michael@0: // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than michael@0: // nsStandardURL. To parse STUN/TURN URI's to spec michael@0: // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3 michael@0: // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3 michael@0: // we parse out the query-string, and use ParseAuthority() on the rest michael@0: nsRefPtr url; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(url), server.mUrl.Value()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: bool isStun = false, isStuns = false, isTurn = false, isTurns = false; michael@0: url->SchemeIs("stun", &isStun); michael@0: url->SchemeIs("stuns", &isStuns); michael@0: url->SchemeIs("turn", &isTurn); michael@0: url->SchemeIs("turns", &isTurns); michael@0: if (!(isStun || isStuns || isTurn || isTurns)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsAutoCString spec; michael@0: rv = url->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509) michael@0: int32_t port; michael@0: nsAutoCString host; michael@0: nsAutoCString transport; michael@0: { michael@0: uint32_t hostPos; michael@0: int32_t hostLen; michael@0: nsAutoCString path; michael@0: rv = url->GetPath(path); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Tolerate query-string + parse 'transport=[udp|tcp]' by hand. michael@0: int32_t questionmark = path.FindChar('?'); michael@0: if (questionmark >= 0) { michael@0: const nsCString match = NS_LITERAL_CSTRING("transport="); michael@0: michael@0: for (int32_t i = questionmark, endPos; i >= 0; i = endPos) { michael@0: endPos = path.FindCharInSet("&", i + 1); michael@0: const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1, michael@0: endPos); michael@0: if (StringBeginsWith(fieldvaluepair, match)) { michael@0: transport = Substring(fieldvaluepair, match.Length()); michael@0: ToLowerCase(transport); michael@0: } michael@0: } michael@0: path.SetLength(questionmark); michael@0: } michael@0: michael@0: rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(), michael@0: nullptr, nullptr, michael@0: nullptr, nullptr, michael@0: &hostPos, &hostLen, &port); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!hostLen) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (hostPos > 1) /* The username was removed */ michael@0: return NS_ERROR_FAILURE; michael@0: path.Mid(host, hostPos, hostLen); michael@0: } michael@0: if (port == -1) michael@0: port = (isStuns || isTurns)? 5349 : 3478; michael@0: michael@0: if (isTurn || isTurns) { michael@0: NS_ConvertUTF16toUTF8 credential(server.mCredential); michael@0: NS_ConvertUTF16toUTF8 username(server.mUsername); michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: if (transport == kNrIceTransportTcp) michael@0: continue; michael@0: #endif michael@0: if (!aDst->addTurnServer(host.get(), port, michael@0: username.get(), michael@0: credential.get(), michael@0: (transport.IsEmpty() ? michael@0: kNrIceTransportUdp : transport.get()))) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else { michael@0: if (!aDst->addStunServer(host.get(), port)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, michael@0: nsGlobalWindow* aWindow, michael@0: const IceConfiguration* aConfiguration, michael@0: const RTCConfiguration* aRTCConfiguration, michael@0: nsISupports* aThread) michael@0: { michael@0: nsresult res; michael@0: michael@0: // Invariant: we receive configuration one way or the other but not both (XOR) michael@0: MOZ_ASSERT(!aConfiguration != !aRTCConfiguration); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: #endif michael@0: MOZ_ASSERT(aThread); michael@0: mThread = do_QueryInterface(aThread); michael@0: michael@0: mPCObserver = do_GetWeakReference(&aObserver); michael@0: michael@0: // Find the STS thread michael@0: michael@0: mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); michael@0: MOZ_ASSERT(mSTSThread); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: michael@0: // Initialize NSS if we are in content process. For chrome process, NSS should already michael@0: // been initialized. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: // This code interferes with the C++ unit test startup code. michael@0: nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: NS_ENSURE_SUCCESS(res = InitNSSInContent(), res); michael@0: } michael@0: michael@0: // Currently no standalone unit tests for DataChannel, michael@0: // which is the user of mWindow michael@0: MOZ_ASSERT(aWindow); michael@0: mWindow = aWindow; michael@0: NS_ENSURE_STATE(mWindow); michael@0: michael@0: #endif // MOZILLA_INTERNAL_API michael@0: michael@0: PRTime timestamp = PR_Now(); michael@0: // Ok if we truncate this. michael@0: char temp[128]; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsAutoCString locationCStr; michael@0: nsIDOMLocation* location; michael@0: res = mWindow->GetLocation(&location); michael@0: michael@0: if (location && NS_SUCCEEDED(res)) { michael@0: nsAutoString locationAStr; michael@0: location->ToString(locationAStr); michael@0: location->Release(); michael@0: michael@0: CopyUTF16toUTF8(locationAStr, locationCStr); michael@0: } michael@0: michael@0: PR_snprintf( michael@0: temp, michael@0: sizeof(temp), michael@0: "%llu (id=%llu url=%s)", michael@0: static_cast(timestamp), michael@0: static_cast(mWindow ? mWindow->WindowID() : 0), michael@0: locationCStr.get() ? locationCStr.get() : "NULL"); michael@0: michael@0: #else michael@0: PR_snprintf(temp, sizeof(temp), "%llu", (unsigned long long)timestamp); michael@0: #endif // MOZILLA_INTERNAL_API michael@0: michael@0: mName = temp; michael@0: michael@0: // Generate a random handle michael@0: unsigned char handle_bin[8]; michael@0: SECStatus rv; michael@0: rv = PK11_GenerateRandom(handle_bin, sizeof(handle_bin)); michael@0: if (rv != SECSuccess) { michael@0: MOZ_CRASH(); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: char hex[17]; michael@0: PR_snprintf(hex,sizeof(hex),"%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", michael@0: handle_bin[0], michael@0: handle_bin[1], michael@0: handle_bin[2], michael@0: handle_bin[3], michael@0: handle_bin[4], michael@0: handle_bin[5], michael@0: handle_bin[6], michael@0: handle_bin[7]); michael@0: michael@0: mHandle = hex; michael@0: michael@0: STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx"); michael@0: res = PeerConnectionCtx::InitializeGlobal(mThread, mSTSThread); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: PeerConnectionCtx *pcctx = PeerConnectionCtx::GetInstance(); michael@0: MOZ_ASSERT(pcctx); michael@0: STAMP_TIMECARD(mTimeCard, "Done Initializing PC Ctx"); michael@0: michael@0: mInternal->mCall = pcctx->createCall(); michael@0: if (!mInternal->mCall.get()) { michael@0: CSFLogError(logTag, "%s: Couldn't Create Call Object", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: IceConfiguration converted; michael@0: if (aRTCConfiguration) { michael@0: res = ConvertRTCConfiguration(*aRTCConfiguration, &converted); michael@0: if (NS_FAILED(res)) { michael@0: CSFLogError(logTag, "%s: Invalid RTCConfiguration", __FUNCTION__); michael@0: return res; michael@0: } michael@0: aConfiguration = &converted; michael@0: } michael@0: michael@0: mMedia = new PeerConnectionMedia(this); michael@0: michael@0: // Connect ICE slots. michael@0: mMedia->SignalIceGatheringStateChange.connect( michael@0: this, michael@0: &PeerConnectionImpl::IceGatheringStateChange); michael@0: mMedia->SignalIceConnectionStateChange.connect( michael@0: this, michael@0: &PeerConnectionImpl::IceConnectionStateChange); michael@0: michael@0: // Initialize the media object. michael@0: res = mMedia->Init(aConfiguration->getStunServers(), michael@0: aConfiguration->getTurnServers()); michael@0: if (NS_FAILED(res)) { michael@0: CSFLogError(logTag, "%s: Couldn't initialize media object", __FUNCTION__); michael@0: return res; michael@0: } michael@0: michael@0: // Store under mHandle michael@0: mInternal->mCall->setPeerConnection(mHandle); michael@0: PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this; michael@0: michael@0: STAMP_TIMECARD(mTimeCard, "Generating DTLS Identity"); michael@0: // Create the DTLS Identity michael@0: mIdentity = DtlsIdentity::Generate(); michael@0: STAMP_TIMECARD(mTimeCard, "Done Generating DTLS Identity"); michael@0: michael@0: if (!mIdentity) { michael@0: CSFLogError(logTag, "%s: Generate returned NULL", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mFingerprint = mIdentity->GetFormattedFingerprint(); michael@0: if (mFingerprint.empty()) { michael@0: CSFLogError(logTag, "%s: unable to get fingerprint", __FUNCTION__); michael@0: return res; michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (mozilla::Preferences::GetBool("media.navigator.load_adapt", false)) { michael@0: mLoadManager = mozilla::LoadManagerBuild(); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: RefPtr const michael@0: PeerConnectionImpl::GetIdentity() const michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: return mIdentity; michael@0: } michael@0: michael@0: std::string michael@0: PeerConnectionImpl::GetFingerprint() const michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: return mFingerprint; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::FingerprintSplitHelper(std::string& fingerprint, michael@0: size_t& spaceIdx) const michael@0: { michael@0: fingerprint = GetFingerprint(); michael@0: spaceIdx = fingerprint.find_first_of(' '); michael@0: if (spaceIdx == std::string::npos) { michael@0: CSFLogError(logTag, "%s: fingerprint is messed up: %s", michael@0: __FUNCTION__, fingerprint.c_str()); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: std::string michael@0: PeerConnectionImpl::GetFingerprintAlgorithm() const michael@0: { michael@0: std::string fp; michael@0: size_t spc; michael@0: if (NS_SUCCEEDED(FingerprintSplitHelper(fp, spc))) { michael@0: return fp.substr(0, spc); michael@0: } michael@0: return ""; michael@0: } michael@0: michael@0: std::string michael@0: PeerConnectionImpl::GetFingerprintHexValue() const michael@0: { michael@0: std::string fp; michael@0: size_t spc; michael@0: if (NS_SUCCEEDED(FingerprintSplitHelper(fp, spc))) { michael@0: return fp.substr(spc + 1); michael@0: } michael@0: return ""; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::CreateFakeMediaStream(uint32_t aHint, nsIDOMMediaStream** aRetval) michael@0: { michael@0: MOZ_ASSERT(aRetval); michael@0: PC_AUTO_ENTER_API_CALL(false); michael@0: michael@0: bool mute = false; michael@0: michael@0: // Hack to allow you to mute the stream michael@0: if (aHint & MEDIA_STREAM_MUTE) { michael@0: mute = true; michael@0: aHint &= ~MEDIA_STREAM_MUTE; michael@0: } michael@0: michael@0: nsRefPtr stream = MakeMediaStream(mWindow, aHint); michael@0: if (!stream) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mute) { michael@0: if (aHint & DOMMediaStream::HINT_CONTENTS_AUDIO) { michael@0: new Fake_AudioGenerator(stream); michael@0: } else { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: new Fake_VideoGenerator(stream); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: stream.forget(aRetval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Stubbing this call out for now. michael@0: // We can remove it when we are confident of datachannels being started michael@0: // correctly on SDP negotiation (bug 852908) michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::ConnectDataConnection(uint16_t aLocalport, michael@0: uint16_t aRemoteport, michael@0: uint16_t aNumstreams) michael@0: { michael@0: return NS_OK; // InitializeDataChannel(aLocalport, aRemoteport, aNumstreams); michael@0: } michael@0: michael@0: // Data channels won't work without a window, so in order for the C++ unit michael@0: // tests to work (it doesn't have a window available) we ifdef the following michael@0: // two implementations. michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (mDataConnection) { michael@0: CSFLogDebug(logTag,"%s DataConnection already connected",__FUNCTION__); michael@0: // Ignore the request to connect when already connected. This entire michael@0: // implementation is temporary. Ignore aNumstreams as it's merely advisory michael@0: // and we increase the number of streams dynamically as needed. michael@0: return NS_OK; michael@0: } michael@0: mDataConnection = new DataChannelConnection(this); michael@0: if (!mDataConnection->Init(5000, aNumstreams, true)) { michael@0: CSFLogError(logTag,"%s DataConnection Init Failed",__FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: CSFLogDebug(logTag,"%s DataChannelConnection %p attached to %s", michael@0: __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str()); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::InitializeDataChannel(int track_id, michael@0: uint16_t aLocalport, michael@0: uint16_t aRemoteport, michael@0: uint16_t aNumstreams) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsresult rv = EnsureDataConnection(aNumstreams); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // use the specified TransportFlow michael@0: nsRefPtr flow = mMedia->GetTransportFlow(track_id, false).get(); michael@0: CSFLogDebug(logTag, "Transportflow[%d] = %p", track_id, flow.get()); michael@0: if (flow) { michael@0: if (mDataConnection->ConnectViaTransportFlow(flow, aLocalport, aRemoteport)) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // If we inited the DataConnection, call Destroy() before releasing it michael@0: mDataConnection->Destroy(); michael@0: } michael@0: mDataConnection = nullptr; michael@0: #endif michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: already_AddRefed michael@0: PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel, michael@0: const nsAString& aProtocol, michael@0: uint16_t aType, michael@0: bool outOfOrderAllowed, michael@0: uint16_t aMaxTime, michael@0: uint16_t aMaxNum, michael@0: bool aExternalNegotiated, michael@0: uint16_t aStream, michael@0: ErrorResult &rv) michael@0: { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsRefPtr result; michael@0: rv = CreateDataChannel(aLabel, aProtocol, aType, outOfOrderAllowed, michael@0: aMaxTime, aMaxNum, aExternalNegotiated, michael@0: aStream, getter_AddRefs(result)); michael@0: return result.forget(); michael@0: #else michael@0: return nullptr; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CreateDataChannel(const nsAString& aLabel, michael@0: const nsAString& aProtocol, michael@0: uint16_t aType, michael@0: bool outOfOrderAllowed, michael@0: uint16_t aMaxTime, michael@0: uint16_t aMaxNum, michael@0: bool aExternalNegotiated, michael@0: uint16_t aStream, michael@0: nsDOMDataChannel** aRetval) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aRetval); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsRefPtr dataChannel; michael@0: DataChannelConnection::Type theType = michael@0: static_cast(aType); michael@0: michael@0: nsresult rv = EnsureDataConnection(WEBRTC_DATACHANNEL_STREAMS_DEFAULT); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: dataChannel = mDataConnection->Open( michael@0: NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType, michael@0: !outOfOrderAllowed, michael@0: aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT ? aMaxNum : michael@0: (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime : 0), michael@0: nullptr, nullptr, aExternalNegotiated, aStream michael@0: ); michael@0: NS_ENSURE_TRUE(dataChannel,NS_ERROR_FAILURE); michael@0: michael@0: CSFLogDebug(logTag, "%s: making DOMDataChannel", __FUNCTION__); michael@0: michael@0: if (!mHaveDataStream) { michael@0: // XXX stream_id of 0 might confuse things... michael@0: mInternal->mCall->addStream(0, 2, DATA, 0); michael@0: mHaveDataStream = true; michael@0: } michael@0: nsIDOMDataChannel *retval; michael@0: rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, &retval); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: *aRetval = static_cast(retval); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: // do_QueryObjectReferent() - Helps get PeerConnectionObserver from nsWeakPtr. michael@0: // michael@0: // nsWeakPtr deals in XPCOM interfaces, while webidl bindings are concrete objs. michael@0: // TODO: Turn this into a central (template) function somewhere (Bug 939178) michael@0: // michael@0: // Without it, each weak-ref call in this file would look like this: michael@0: // michael@0: // nsCOMPtr tmp = do_QueryReferent(mPCObserver); michael@0: // if (!tmp) { michael@0: // return; michael@0: // } michael@0: // nsRefPtr tmp2 = do_QueryObject(tmp); michael@0: // nsRefPtr pco = static_cast(&*tmp2); michael@0: michael@0: static already_AddRefed michael@0: do_QueryObjectReferent(nsIWeakReference* aRawPtr) { michael@0: nsCOMPtr tmp = do_QueryReferent(aRawPtr); michael@0: if (!tmp) { michael@0: return nullptr; michael@0: } michael@0: nsRefPtr tmp2 = do_QueryObject(tmp); michael@0: nsRefPtr tmp3 = static_cast(&*tmp2); michael@0: return tmp3.forget(); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::NotifyConnection() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: WrappableJSErrorResult rv; michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnable(pco, michael@0: &PeerConnectionObserver::NotifyConnection, michael@0: rv, static_cast(nullptr)), michael@0: NS_DISPATCH_NORMAL); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::NotifyClosedConnection() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: WrappableJSErrorResult rv; michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnable(pco, &PeerConnectionObserver::NotifyClosedConnection, michael@0: rv, static_cast(nullptr)), michael@0: NS_DISPATCH_NORMAL); michael@0: #endif michael@0: } michael@0: michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // Not a member function so that we don't need to keep the PC live. michael@0: static void NotifyDataChannel_m(nsRefPtr aChannel, michael@0: nsRefPtr aObserver) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: JSErrorResult rv; michael@0: nsRefPtr channel = static_cast(&*aChannel); michael@0: aObserver->NotifyDataChannel(*channel, rv); michael@0: NS_DataChannelAppReady(aChannel); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: PeerConnectionImpl::NotifyDataChannel(already_AddRefed aChannel) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: // XXXkhuey this is completely fucked up. We can't use nsRefPtr michael@0: // here because DataChannel's AddRef/Release are non-virtual and not visible michael@0: // if !MOZILLA_INTERNAL_API, but this function leaks the DataChannel if michael@0: // !MOZILLA_INTERNAL_API because it never transfers the ref to michael@0: // NS_NewDOMDataChannel. michael@0: DataChannel* channel = aChannel.take(); michael@0: MOZ_ASSERT(channel); michael@0: michael@0: CSFLogDebug(logTag, "%s: channel: %p", __FUNCTION__, channel); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsCOMPtr domchannel; michael@0: nsresult rv = NS_NewDOMDataChannel(already_AddRefed(channel), michael@0: mWindow, getter_AddRefs(domchannel)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnableNM(NotifyDataChannel_m, michael@0: domchannel.get(), michael@0: pco), michael@0: NS_DISPATCH_NORMAL); michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CreateOffer(const MediaConstraintsInternal& aConstraints) michael@0: { michael@0: return CreateOffer(MediaConstraintsExternal (aConstraints)); michael@0: } michael@0: michael@0: // Used by unit tests and the IDL CreateOffer. michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CreateOffer(const MediaConstraintsExternal& aConstraints) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: Timecard *tc = mTimeCard; michael@0: mTimeCard = nullptr; michael@0: STAMP_TIMECARD(tc, "Create Offer"); michael@0: michael@0: cc_media_constraints_t* cc_constraints = aConstraints.build(); michael@0: NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED); michael@0: mInternal->mCall->createOffer(cc_constraints, tc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CreateAnswer(const MediaConstraintsInternal& aConstraints) michael@0: { michael@0: return CreateAnswer(MediaConstraintsExternal (aConstraints)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CreateAnswer(const MediaConstraintsExternal& aConstraints) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: Timecard *tc = mTimeCard; michael@0: mTimeCard = nullptr; michael@0: STAMP_TIMECARD(tc, "Create Answer"); michael@0: michael@0: cc_media_constraints_t* cc_constraints = aConstraints.build(); michael@0: NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED); michael@0: mInternal->mCall->createAnswer(cc_constraints, tc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: if (!aSDP) { michael@0: CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: Timecard *tc = mTimeCard; michael@0: mTimeCard = nullptr; michael@0: STAMP_TIMECARD(tc, "Set Local Description"); michael@0: michael@0: mLocalRequestedSDP = aSDP; michael@0: mInternal->mCall->setLocalDescription((cc_jsep_action_t)aAction, michael@0: mLocalRequestedSDP, tc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: if (!aSDP) { michael@0: CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: Timecard *tc = mTimeCard; michael@0: mTimeCard = nullptr; michael@0: STAMP_TIMECARD(tc, "Set Remote Description"); michael@0: michael@0: mRemoteRequestedSDP = aSDP; michael@0: mInternal->mCall->setRemoteDescription((cc_jsep_action_t)action, michael@0: mRemoteRequestedSDP, tc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // WebRTC uses highres time relative to the UNIX epoch (Jan 1, 1970, UTC). michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsresult michael@0: PeerConnectionImpl::GetTimeSinceEpoch(DOMHighResTimeStamp *result) { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: nsPerformance *perf = mWindow->GetPerformance(); michael@0: NS_ENSURE_TRUE(perf && perf->Timing(), NS_ERROR_UNEXPECTED); michael@0: *result = perf->Now() + perf->Timing()->NavigationStart(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class RTCStatsReportInternalConstruct : public RTCStatsReportInternal { michael@0: public: michael@0: RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) { michael@0: mPcid = pcid; michael@0: mInboundRTPStreamStats.Construct(); michael@0: mOutboundRTPStreamStats.Construct(); michael@0: mMediaStreamTrackStats.Construct(); michael@0: mMediaStreamStats.Construct(); michael@0: mTransportStats.Construct(); michael@0: mIceComponentStats.Construct(); michael@0: mIceCandidatePairStats.Construct(); michael@0: mIceCandidateStats.Construct(); michael@0: mCodecStats.Construct(); michael@0: } michael@0: }; michael@0: michael@0: // Specialized helper - push map[key] if specified or all map values onto array michael@0: michael@0: static void michael@0: PushBackSelect(nsTArray>& aDst, michael@0: const std::map> & aSrc, michael@0: TrackID aKey = 0) { michael@0: auto begin = aKey ? aSrc.find(aKey) : aSrc.begin(), it = begin; michael@0: for (auto end = (aKey && begin != aSrc.end())? ++begin : aSrc.end(); michael@0: it != end; ++it) { michael@0: aDst.AppendElement(it->second); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetStats(MediaStreamTrack *aSelector) { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (!mMedia) { michael@0: // Since we zero this out before the d'tor, we should check. michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsAutoPtr query(new RTCStatsQuery(false)); michael@0: michael@0: nsresult rv = BuildStatsQuery_m(aSelector, query.get()); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: RUN_ON_THREAD(mSTSThread, michael@0: WrapRunnableNM(&PeerConnectionImpl::GetStatsForPCObserver_s, michael@0: mHandle, michael@0: query), michael@0: NS_DISPATCH_NORMAL); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::AddIceCandidate(const char* aCandidate, const char* aMid, unsigned short aLevel) { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: Timecard *tc = mTimeCard; michael@0: mTimeCard = nullptr; michael@0: STAMP_TIMECARD(tc, "Add Ice Candidate"); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // When remote candidates are added before our ICE ctx is up and running michael@0: // (the transition to New is async through STS, so this is not impossible), michael@0: // we won't record them as trickle candidates. Is this what we want? michael@0: if(!mIceStartTime.IsNull()) { michael@0: TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime; michael@0: if (mIceConnectionState == PCImplIceConnectionState::Failed) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_ICE_LATE_TRICKLE_ARRIVAL_TIME, michael@0: timeDelta.ToMilliseconds()); michael@0: } else { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_ICE_ON_TIME_TRICKLE_ARRIVAL_TIME, michael@0: timeDelta.ToMilliseconds()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: mInternal->mCall->addICECandidate(aCandidate, aMid, aLevel, tc); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::CloseStreams() { michael@0: PC_AUTO_ENTER_API_CALL(false); michael@0: michael@0: if (mReadyState != PCImplReadyState::Closed) { michael@0: ChangeReadyState(PCImplReadyState::Closing); michael@0: } michael@0: michael@0: CSFLogInfo(logTag, "%s: Ending associated call", __FUNCTION__); michael@0: michael@0: mInternal->mCall->endCall(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::AddStream(DOMMediaStream &aMediaStream, michael@0: const MediaConstraintsInternal& aConstraints) michael@0: { michael@0: return AddStream(aMediaStream, MediaConstraintsExternal(aConstraints)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::AddStream(DOMMediaStream& aMediaStream, michael@0: const MediaConstraintsExternal& aConstraints) { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: uint32_t hints = aMediaStream.GetHintContents(); michael@0: michael@0: // XXX Remove this check once addStream has an error callback michael@0: // available and/or we have plumbing to handle multiple michael@0: // local audio streams. michael@0: if ((hints & DOMMediaStream::HINT_CONTENTS_AUDIO) && michael@0: mNumAudioStreams > 0) { michael@0: CSFLogError(logTag, "%s: Only one local audio stream is supported for now", michael@0: __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // XXX Remove this check once addStream has an error callback michael@0: // available and/or we have plumbing to handle multiple michael@0: // local video streams. michael@0: if ((hints & DOMMediaStream::HINT_CONTENTS_VIDEO) && michael@0: mNumVideoStreams > 0) { michael@0: CSFLogError(logTag, "%s: Only one local video stream is supported for now", michael@0: __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint32_t stream_id; michael@0: nsresult res = mMedia->AddStream(&aMediaStream, &stream_id); michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: michael@0: // TODO(ekr@rtfm.com): these integers should be the track IDs michael@0: if (hints & DOMMediaStream::HINT_CONTENTS_AUDIO) { michael@0: cc_media_constraints_t* cc_constraints = aConstraints.build(); michael@0: NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED); michael@0: mInternal->mCall->addStream(stream_id, 0, AUDIO, cc_constraints); michael@0: mNumAudioStreams++; michael@0: } michael@0: michael@0: if (hints & DOMMediaStream::HINT_CONTENTS_VIDEO) { michael@0: cc_media_constraints_t* cc_constraints = aConstraints.build(); michael@0: NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED); michael@0: mInternal->mCall->addStream(stream_id, 1, VIDEO, cc_constraints); michael@0: mNumVideoStreams++; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::RemoveStream(DOMMediaStream& aMediaStream) { michael@0: PC_AUTO_ENTER_API_CALL(true); michael@0: michael@0: uint32_t stream_id; michael@0: nsresult res = mMedia->RemoveStream(&aMediaStream, &stream_id); michael@0: michael@0: if (NS_FAILED(res)) michael@0: return res; michael@0: michael@0: uint32_t hints = aMediaStream.GetHintContents(); michael@0: michael@0: if (hints & DOMMediaStream::HINT_CONTENTS_AUDIO) { michael@0: mInternal->mCall->removeStream(stream_id, 0, AUDIO); michael@0: MOZ_ASSERT(mNumAudioStreams > 0); michael@0: mNumAudioStreams--; michael@0: } michael@0: michael@0: if (hints & DOMMediaStream::HINT_CONTENTS_VIDEO) { michael@0: mInternal->mCall->removeStream(stream_id, 1, VIDEO); michael@0: MOZ_ASSERT(mNumVideoStreams > 0); michael@0: mNumVideoStreams--; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::SetRemoteFingerprint(const char* hash, const char* fingerprint) michael@0: { michael@0: MOZ_ASSERT(hash); michael@0: MOZ_ASSERT(fingerprint); michael@0: michael@0: if (fingerprint != nullptr && (strcmp(hash, "sha-1") == 0)) { michael@0: mRemoteFingerprint = std::string(fingerprint); michael@0: CSFLogDebug(logTag, "Setting remote fingerprint to %s", mRemoteFingerprint.c_str()); michael@0: return NS_OK; michael@0: } else { michael@0: CSFLogError(logTag, "%s: Invalid Remote Finger Print", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: */ michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetFingerprint(char** fingerprint) michael@0: { michael@0: MOZ_ASSERT(fingerprint); michael@0: michael@0: if (!mIdentity) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: char* tmp = new char[mFingerprint.size() + 1]; michael@0: std::copy(mFingerprint.begin(), mFingerprint.end(), tmp); michael@0: tmp[mFingerprint.size()] = '\0'; michael@0: michael@0: *fingerprint = tmp; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetLocalDescription(char** aSDP) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aSDP); michael@0: michael@0: char* tmp = new char[mLocalSDP.size() + 1]; michael@0: std::copy(mLocalSDP.begin(), mLocalSDP.end(), tmp); michael@0: tmp[mLocalSDP.size()] = '\0'; michael@0: michael@0: *aSDP = tmp; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetRemoteDescription(char** aSDP) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aSDP); michael@0: michael@0: char* tmp = new char[mRemoteSDP.size() + 1]; michael@0: std::copy(mRemoteSDP.begin(), mRemoteSDP.end(), tmp); michael@0: tmp[mRemoteSDP.size()] = '\0'; michael@0: michael@0: *aSDP = tmp; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::ReadyState(PCImplReadyState* aState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aState); michael@0: michael@0: *aState = mReadyState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::SignalingState(PCImplSignalingState* aState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aState); michael@0: michael@0: *aState = mSignalingState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::SipccState(PCImplSipccState* aState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aState); michael@0: michael@0: PeerConnectionCtx* pcctx = PeerConnectionCtx::GetInstance(); michael@0: // Avoid B2G error: operands to ?: have different types michael@0: // 'mozilla::dom::PCImplSipccState' and 'mozilla::dom::PCImplSipccState::Enum' michael@0: if (pcctx) { michael@0: *aState = pcctx->sipcc_state(); michael@0: } else { michael@0: *aState = PCImplSipccState::Idle; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::IceConnectionState(PCImplIceConnectionState* aState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aState); michael@0: michael@0: *aState = mIceConnectionState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::IceGatheringState(PCImplIceGatheringState* aState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aState); michael@0: michael@0: *aState = mIceGatheringState; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(mTrickle || !assert_ice_ready || michael@0: (mIceGatheringState == PCImplIceGatheringState::Complete)); michael@0: michael@0: if (mReadyState == PCImplReadyState::Closed) { michael@0: CSFLogError(logTag, "%s: called API while closed", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (!mMedia) { michael@0: CSFLogError(logTag, "%s: called API with disposed mMedia", __FUNCTION__); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::Close() michael@0: { michael@0: CSFLogDebug(logTag, "%s: for %s", __FUNCTION__, mHandle.c_str()); michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: nsresult res = CloseInt(); michael@0: michael@0: SetSignalingState_m(PCImplSignalingState::SignalingClosed); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::CloseInt() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: // We do this at the end of the call because we want to make sure we've waited michael@0: // for all trickle ICE candidates to come in; this can happen well after we've michael@0: // transitioned to connected. As a bonus, this allows us to detect race michael@0: // conditions where a stats dispatch happens right as the PC closes. michael@0: if (!IsClosed()) { michael@0: RecordLongtermICEStatistics(); michael@0: } michael@0: michael@0: if (mInternal->mCall) { michael@0: CSFLogInfo(logTag, "%s: Closing PeerConnectionImpl %s; " michael@0: "ending call", __FUNCTION__, mHandle.c_str()); michael@0: mInternal->mCall->endCall(); michael@0: } michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (mDataConnection) { michael@0: CSFLogInfo(logTag, "%s: Destroying DataChannelConnection %p for %s", michael@0: __FUNCTION__, (void *) mDataConnection.get(), mHandle.c_str()); michael@0: mDataConnection->Destroy(); michael@0: mDataConnection = nullptr; // it may not go away until the runnables are dead michael@0: } michael@0: #endif michael@0: michael@0: ShutdownMedia(); michael@0: michael@0: // DataConnection will need to stay alive until all threads/runnables exit michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::ShutdownMedia() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: michael@0: if (!mMedia) michael@0: return; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // End of call to be recorded in Telemetry michael@0: if (!mStartTime.IsNull()){ michael@0: TimeDuration timeDelta = TimeStamp::Now() - mStartTime; michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_CALL_DURATION, timeDelta.ToSeconds()); michael@0: } michael@0: #endif michael@0: michael@0: // Forget the reference so that we can transfer it to michael@0: // SelfDestruct(). michael@0: mMedia.forget().take()->SelfDestruct(); michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // If NSS is shutting down, then we need to get rid of the DTLS michael@0: // identity right now; otherwise, we'll cause wreckage when we do michael@0: // finally deallocate it in our destructor. michael@0: void michael@0: PeerConnectionImpl::virtualDestroyNSSReference() michael@0: { michael@0: destructorSafeDestroyNSSReference(); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::destructorSafeDestroyNSSReference() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: CSFLogDebug(logTag, "%s: NSS shutting down; freeing our DtlsIdentity.", __FUNCTION__); michael@0: mIdentity = nullptr; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: PeerConnectionImpl::onCallEvent(const OnCallEventArgs& args) michael@0: { michael@0: const ccapi_call_event_e &aCallEvent = args.mCallEvent; michael@0: const CSF::CC_CallInfoPtr &aInfo = args.mInfo; michael@0: michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aInfo.get()); michael@0: michael@0: cc_call_state_t event = aInfo->getCallState(); michael@0: std::string statestr = aInfo->callStateToString(event); michael@0: Timecard *timecard = aInfo->takeTimecard(); michael@0: michael@0: if (timecard) { michael@0: mTimeCard = timecard; michael@0: STAMP_TIMECARD(mTimeCard, "Operation Completed"); michael@0: } michael@0: michael@0: if (CCAPI_CALL_EV_CREATED != aCallEvent && CCAPI_CALL_EV_STATE != aCallEvent) { michael@0: CSFLogDebug(logTag, "%s: **** CALL HANDLE IS: %s, **** CALL STATE IS: %s", michael@0: __FUNCTION__, mHandle.c_str(), statestr.c_str()); michael@0: return; michael@0: } michael@0: michael@0: switch (event) { michael@0: case SETLOCALDESCSUCCESS: michael@0: case UPDATELOCALDESC: michael@0: mLocalSDP = aInfo->getSDP(); michael@0: break; michael@0: michael@0: case SETREMOTEDESCSUCCESS: michael@0: case ADDICECANDIDATE: michael@0: mRemoteSDP = aInfo->getSDP(); michael@0: break; michael@0: michael@0: case CONNECTED: michael@0: CSFLogDebug(logTag, "Setting PeerConnnection state to kActive"); michael@0: ChangeReadyState(PCImplReadyState::Active); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: michael@0: PeerConnectionObserverDispatch* runnable = michael@0: new PeerConnectionObserverDispatch(aInfo, this, pco); michael@0: michael@0: if (mThread) { michael@0: mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); michael@0: return; michael@0: } michael@0: runnable->Run(); michael@0: delete runnable; michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::ChangeReadyState(PCImplReadyState aReadyState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: mReadyState = aReadyState; michael@0: michael@0: // Note that we are passing an nsRefPtr which keeps the observer live. michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: WrappableJSErrorResult rv; michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnable(pco, michael@0: &PeerConnectionObserver::OnStateChange, michael@0: PCObserverStateType::ReadyState, michael@0: rv, static_cast(nullptr)), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::SetSignalingState_m(PCImplSignalingState aSignalingState) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: if (mSignalingState == aSignalingState || michael@0: mSignalingState == PCImplSignalingState::SignalingClosed) { michael@0: return; michael@0: } michael@0: michael@0: mSignalingState = aSignalingState; michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: JSErrorResult rv; michael@0: pco->OnStateChange(PCObserverStateType::SignalingState, rv); michael@0: MOZ_ASSERT(!rv.Failed()); michael@0: } michael@0: michael@0: bool michael@0: PeerConnectionImpl::IsClosed() const michael@0: { michael@0: return mSignalingState == PCImplSignalingState::SignalingClosed; michael@0: } michael@0: michael@0: bool michael@0: PeerConnectionImpl::HasMedia() const michael@0: { michael@0: return mMedia; michael@0: } michael@0: michael@0: PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) michael@0: : impl_(nullptr) { michael@0: if (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) == michael@0: PeerConnectionCtx::GetInstance()->mPeerConnections.end()) { michael@0: return; michael@0: } michael@0: michael@0: PeerConnectionImpl *impl = PeerConnectionCtx::GetInstance()->mPeerConnections[handle]; michael@0: michael@0: if (!impl->media()) michael@0: return; michael@0: michael@0: impl_ = impl; michael@0: } michael@0: michael@0: const std::string& michael@0: PeerConnectionImpl::GetHandle() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: return mHandle; michael@0: } michael@0: michael@0: const std::string& michael@0: PeerConnectionImpl::GetName() michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: return mName; michael@0: } michael@0: michael@0: static mozilla::dom::PCImplIceConnectionState michael@0: toDomIceConnectionState(NrIceCtx::ConnectionState state) { michael@0: switch (state) { michael@0: case NrIceCtx::ICE_CTX_INIT: michael@0: return PCImplIceConnectionState::New; michael@0: case NrIceCtx::ICE_CTX_CHECKING: michael@0: return PCImplIceConnectionState::Checking; michael@0: case NrIceCtx::ICE_CTX_OPEN: michael@0: return PCImplIceConnectionState::Connected; michael@0: case NrIceCtx::ICE_CTX_FAILED: michael@0: return PCImplIceConnectionState::Failed; michael@0: } michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: static mozilla::dom::PCImplIceGatheringState michael@0: toDomIceGatheringState(NrIceCtx::GatheringState state) { michael@0: switch (state) { michael@0: case NrIceCtx::ICE_CTX_GATHER_INIT: michael@0: return PCImplIceGatheringState::New; michael@0: case NrIceCtx::ICE_CTX_GATHER_STARTED: michael@0: return PCImplIceGatheringState::Gathering; michael@0: case NrIceCtx::ICE_CTX_GATHER_COMPLETE: michael@0: return PCImplIceGatheringState::Complete; michael@0: } michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: static bool isDone(PCImplIceConnectionState state) { michael@0: return state != PCImplIceConnectionState::Checking && michael@0: state != PCImplIceConnectionState::New; michael@0: } michael@0: michael@0: static bool isSucceeded(PCImplIceConnectionState state) { michael@0: return state == PCImplIceConnectionState::Connected || michael@0: state == PCImplIceConnectionState::Completed; michael@0: } michael@0: michael@0: static bool isFailed(PCImplIceConnectionState state) { michael@0: return state == PCImplIceConnectionState::Failed || michael@0: state == PCImplIceConnectionState::Disconnected; michael@0: } michael@0: #endif michael@0: michael@0: void PeerConnectionImpl::IceConnectionStateChange( michael@0: NrIceCtx* ctx, michael@0: NrIceCtx::ConnectionState state) { michael@0: PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); michael@0: michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: auto domState = toDomIceConnectionState(state); michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (!isDone(mIceConnectionState) && isDone(domState)) { michael@0: // mIceStartTime can be null if going directly from New to Closed, in which michael@0: // case we don't count it as a success or a failure. michael@0: if (!mIceStartTime.IsNull()){ michael@0: TimeDuration timeDelta = TimeStamp::Now() - mIceStartTime; michael@0: if (isSucceeded(domState)) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_ICE_SUCCESS_TIME, michael@0: timeDelta.ToMilliseconds()); michael@0: } else if (isFailed(domState)) { michael@0: Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FAILURE_TIME, michael@0: timeDelta.ToMilliseconds()); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: mIceConnectionState = domState; michael@0: michael@0: // Would be nice if we had a means of converting one of these dom enums michael@0: // to a string that wasn't almost as much text as this switch statement... michael@0: switch (mIceConnectionState) { michael@0: case PCImplIceConnectionState::New: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: new"); michael@0: break; michael@0: case PCImplIceConnectionState::Checking: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // For telemetry michael@0: mIceStartTime = TimeStamp::Now(); michael@0: #endif michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: checking"); michael@0: break; michael@0: case PCImplIceConnectionState::Connected: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: connected"); michael@0: break; michael@0: case PCImplIceConnectionState::Completed: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: completed"); michael@0: break; michael@0: case PCImplIceConnectionState::Failed: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: failed"); michael@0: break; michael@0: case PCImplIceConnectionState::Disconnected: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: disconnected"); michael@0: break; michael@0: case PCImplIceConnectionState::Closed: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: closed"); michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: WrappableJSErrorResult rv; michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnable(pco, michael@0: &PeerConnectionObserver::OnStateChange, michael@0: PCObserverStateType::IceConnectionState, michael@0: rv, static_cast(nullptr)), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::IceGatheringStateChange( michael@0: NrIceCtx* ctx, michael@0: NrIceCtx::GatheringState state) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); michael@0: michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: mIceGatheringState = toDomIceGatheringState(state); michael@0: michael@0: // Would be nice if we had a means of converting one of these dom enums michael@0: // to a string that wasn't almost as much text as this switch statement... michael@0: switch (mIceGatheringState) { michael@0: case PCImplIceGatheringState::New: michael@0: STAMP_TIMECARD(mTimeCard, "Ice gathering state: new"); michael@0: break; michael@0: case PCImplIceGatheringState::Gathering: michael@0: STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering"); michael@0: break; michael@0: case PCImplIceGatheringState::Complete: michael@0: STAMP_TIMECARD(mTimeCard, "Ice state: complete"); michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr pco = do_QueryObjectReferent(mPCObserver); michael@0: if (!pco) { michael@0: return; michael@0: } michael@0: WrappableJSErrorResult rv; michael@0: RUN_ON_THREAD(mThread, michael@0: WrapRunnable(pco, michael@0: &PeerConnectionObserver::OnStateChange, michael@0: PCObserverStateType::IceGatheringState, michael@0: rv, static_cast(nullptr)), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: nsresult michael@0: PeerConnectionImpl::BuildStatsQuery_m( michael@0: mozilla::dom::MediaStreamTrack *aSelector, michael@0: RTCStatsQuery *query) { michael@0: michael@0: if (!HasMedia()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mMedia->ice_ctx() || !mThread) { michael@0: CSFLogError(logTag, "Could not build stats query, critical components of " michael@0: "PeerConnectionImpl not set."); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult rv = GetTimeSinceEpoch(&(query->now)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: CSFLogError(logTag, "Could not build stats query, could not get timestamp"); michael@0: return rv; michael@0: } michael@0: michael@0: // We do not use the pcHandle here, since that's risky to expose to content. michael@0: query->report = RTCStatsReportInternalConstruct( michael@0: NS_ConvertASCIItoUTF16(mName.c_str()), michael@0: query->now); michael@0: michael@0: // Gather up pipelines from mMedia so they may be inspected on STS michael@0: TrackID trackId = aSelector ? aSelector->GetTrackID() : 0; michael@0: michael@0: for (int i = 0, len = mMedia->LocalStreamsLength(); i < len; i++) { michael@0: PushBackSelect(query->pipelines, michael@0: mMedia->GetLocalStream(i)->GetPipelines(), michael@0: trackId); michael@0: } michael@0: michael@0: for (int i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) { michael@0: PushBackSelect(query->pipelines, michael@0: mMedia->GetRemoteStream(i)->GetPipelines(), michael@0: trackId); michael@0: } michael@0: michael@0: query->iceCtx = mMedia->ice_ctx(); michael@0: michael@0: // From the list of MediaPipelines, determine the set of NrIceMediaStreams michael@0: // we are interested in. michael@0: std::set levelsToGrab; michael@0: if (trackId) { michael@0: for (size_t p = 0; p < query->pipelines.Length(); ++p) { michael@0: size_t level = query->pipelines[p]->level(); michael@0: MOZ_ASSERT(level); michael@0: levelsToGrab.insert(level); michael@0: } michael@0: } else { michael@0: // We want to grab all streams, so ignore the pipelines (this also ends up michael@0: // grabbing DataChannel streams, which is what we want) michael@0: for (size_t s = 0; s < mMedia->num_ice_media_streams(); ++s) { michael@0: levelsToGrab.insert(s + 1); // mIceStreams is 0-indexed michael@0: } michael@0: } michael@0: michael@0: for (auto s = levelsToGrab.begin(); s != levelsToGrab.end(); ++s) { michael@0: // TODO(bcampen@mozilla.com): I may need to revisit this for bundle. michael@0: // (Bug 786234) michael@0: RefPtr temp(mMedia->ice_media_stream(*s - 1)); michael@0: RefPtr flow(mMedia->GetTransportFlow(*s, false)); michael@0: // flow can be null for unused levels, such as unused DataChannels michael@0: if (temp && flow) { michael@0: query->streams.AppendElement(temp); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static void ToRTCIceCandidateStats( michael@0: const std::vector& candidates, michael@0: RTCStatsType candidateType, michael@0: const nsString& componentId, michael@0: DOMHighResTimeStamp now, michael@0: RTCStatsReportInternal* report) { michael@0: michael@0: MOZ_ASSERT(report); michael@0: for (auto c = candidates.begin(); c != candidates.end(); ++c) { michael@0: RTCIceCandidateStats cand; michael@0: cand.mType.Construct(candidateType); michael@0: NS_ConvertASCIItoUTF16 codeword(c->codeword.c_str()); michael@0: cand.mComponentId.Construct(componentId); michael@0: cand.mId.Construct(codeword); michael@0: cand.mTimestamp.Construct(now); michael@0: cand.mCandidateType.Construct( michael@0: RTCStatsIceCandidateType(c->type)); michael@0: cand.mIpAddress.Construct( michael@0: NS_ConvertASCIItoUTF16(c->cand_addr.host.c_str())); michael@0: cand.mPortNumber.Construct(c->cand_addr.port); michael@0: cand.mTransport.Construct( michael@0: NS_ConvertASCIItoUTF16(c->cand_addr.transport.c_str())); michael@0: if (candidateType == RTCStatsType::Localcandidate) { michael@0: cand.mMozLocalTransport.Construct( michael@0: NS_ConvertASCIItoUTF16(c->local_addr.transport.c_str())); michael@0: } michael@0: report->mIceCandidateStats.Value().AppendElement(cand); michael@0: } michael@0: } michael@0: michael@0: static void RecordIceStats_s( michael@0: NrIceMediaStream& mediaStream, michael@0: bool internalStats, michael@0: DOMHighResTimeStamp now, michael@0: RTCStatsReportInternal* report) { michael@0: michael@0: NS_ConvertASCIItoUTF16 componentId(mediaStream.name().c_str()); michael@0: if (internalStats) { michael@0: std::vector candPairs; michael@0: nsresult res = mediaStream.GetCandidatePairs(&candPairs); michael@0: if (NS_FAILED(res)) { michael@0: CSFLogError(logTag, "%s: Error getting candidate pairs", __FUNCTION__); michael@0: return; michael@0: } michael@0: michael@0: for (auto p = candPairs.begin(); p != candPairs.end(); ++p) { michael@0: NS_ConvertASCIItoUTF16 codeword(p->codeword.c_str()); michael@0: NS_ConvertASCIItoUTF16 localCodeword(p->local.codeword.c_str()); michael@0: NS_ConvertASCIItoUTF16 remoteCodeword(p->remote.codeword.c_str()); michael@0: // Only expose candidate-pair statistics to chrome, until we've thought michael@0: // through the implications of exposing it to content. michael@0: michael@0: RTCIceCandidatePairStats s; michael@0: s.mId.Construct(codeword); michael@0: s.mComponentId.Construct(componentId); michael@0: s.mTimestamp.Construct(now); michael@0: s.mType.Construct(RTCStatsType::Candidatepair); michael@0: s.mLocalCandidateId.Construct(localCodeword); michael@0: s.mRemoteCandidateId.Construct(remoteCodeword); michael@0: s.mNominated.Construct(p->nominated); michael@0: s.mMozPriority.Construct(p->priority); michael@0: s.mSelected.Construct(p->selected); michael@0: s.mState.Construct(RTCStatsIceCandidatePairState(p->state)); michael@0: report->mIceCandidatePairStats.Value().AppendElement(s); michael@0: } michael@0: } michael@0: michael@0: std::vector candidates; michael@0: if (NS_SUCCEEDED(mediaStream.GetLocalCandidates(&candidates))) { michael@0: ToRTCIceCandidateStats(candidates, michael@0: RTCStatsType::Localcandidate, michael@0: componentId, michael@0: now, michael@0: report); michael@0: } michael@0: candidates.clear(); michael@0: michael@0: if (NS_SUCCEEDED(mediaStream.GetRemoteCandidates(&candidates))) { michael@0: ToRTCIceCandidateStats(candidates, michael@0: RTCStatsType::Remotecandidate, michael@0: componentId, michael@0: now, michael@0: report); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { michael@0: michael@0: ASSERT_ON_THREAD(query->iceCtx->thread()); michael@0: michael@0: // Gather stats from pipelines provided (can't touch mMedia + stream on STS) michael@0: michael@0: for (size_t p = 0; p < query->pipelines.Length(); ++p) { michael@0: const MediaPipeline& mp = *query->pipelines[p]; michael@0: bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO); michael@0: nsString idstr = isAudio ? michael@0: NS_LITERAL_STRING("audio_") : NS_LITERAL_STRING("video_"); michael@0: idstr.AppendInt(mp.trackid()); michael@0: michael@0: switch (mp.direction()) { michael@0: case MediaPipeline::TRANSMIT: { michael@0: nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr; michael@0: nsString remoteId; michael@0: nsString ssrc; michael@0: unsigned int ssrcval; michael@0: if (mp.Conduit()->GetLocalSSRC(&ssrcval)) { michael@0: ssrc.AppendInt(ssrcval); michael@0: } michael@0: { michael@0: // First, fill in remote stat with rtcp receiver data, if present. michael@0: // ReceiverReports have less information than SenderReports, michael@0: // so fill in what we can. michael@0: DOMHighResTimeStamp timestamp; michael@0: uint32_t jitterMs; michael@0: uint32_t packetsReceived; michael@0: uint64_t bytesReceived; michael@0: uint32_t packetsLost; michael@0: int32_t rtt; michael@0: if (mp.Conduit()->GetRTCPReceiverReport(×tamp, &jitterMs, michael@0: &packetsReceived, michael@0: &bytesReceived, michael@0: &packetsLost, michael@0: &rtt)) { michael@0: remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr; michael@0: RTCInboundRTPStreamStats s; michael@0: s.mTimestamp.Construct(timestamp); michael@0: s.mId.Construct(remoteId); michael@0: s.mType.Construct(RTCStatsType::Inboundrtp); michael@0: if (ssrc.Length()) { michael@0: s.mSsrc.Construct(ssrc); michael@0: } michael@0: s.mJitter.Construct(double(jitterMs)/1000); michael@0: s.mRemoteId.Construct(localId); michael@0: s.mIsRemote = true; michael@0: s.mPacketsReceived.Construct(packetsReceived); michael@0: s.mBytesReceived.Construct(bytesReceived); michael@0: s.mPacketsLost.Construct(packetsLost); michael@0: s.mMozRtt.Construct(rtt); michael@0: query->report.mInboundRTPStreamStats.Value().AppendElement(s); michael@0: } michael@0: } michael@0: // Then, fill in local side (with cross-link to remote only if present) michael@0: { michael@0: RTCOutboundRTPStreamStats s; michael@0: s.mTimestamp.Construct(query->now); michael@0: s.mId.Construct(localId); michael@0: s.mType.Construct(RTCStatsType::Outboundrtp); michael@0: if (ssrc.Length()) { michael@0: s.mSsrc.Construct(ssrc); michael@0: } michael@0: s.mRemoteId.Construct(remoteId); michael@0: s.mIsRemote = false; michael@0: s.mPacketsSent.Construct(mp.rtp_packets_sent()); michael@0: s.mBytesSent.Construct(mp.rtp_bytes_sent()); michael@0: query->report.mOutboundRTPStreamStats.Value().AppendElement(s); michael@0: } michael@0: break; michael@0: } michael@0: case MediaPipeline::RECEIVE: { michael@0: nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr; michael@0: nsString remoteId; michael@0: nsString ssrc; michael@0: unsigned int ssrcval; michael@0: if (mp.Conduit()->GetRemoteSSRC(&ssrcval)) { michael@0: ssrc.AppendInt(ssrcval); michael@0: } michael@0: { michael@0: // First, fill in remote stat with rtcp sender data, if present. michael@0: DOMHighResTimeStamp timestamp; michael@0: uint32_t packetsSent; michael@0: uint64_t bytesSent; michael@0: if (mp.Conduit()->GetRTCPSenderReport(×tamp, michael@0: &packetsSent, &bytesSent)) { michael@0: remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr; michael@0: RTCOutboundRTPStreamStats s; michael@0: s.mTimestamp.Construct(timestamp); michael@0: s.mId.Construct(remoteId); michael@0: s.mType.Construct(RTCStatsType::Outboundrtp); michael@0: if (ssrc.Length()) { michael@0: s.mSsrc.Construct(ssrc); michael@0: } michael@0: s.mRemoteId.Construct(localId); michael@0: s.mIsRemote = true; michael@0: s.mPacketsSent.Construct(packetsSent); michael@0: s.mBytesSent.Construct(bytesSent); michael@0: query->report.mOutboundRTPStreamStats.Value().AppendElement(s); michael@0: } michael@0: } michael@0: // Then, fill in local side (with cross-link to remote only if present) michael@0: RTCInboundRTPStreamStats s; michael@0: s.mTimestamp.Construct(query->now); michael@0: s.mId.Construct(localId); michael@0: s.mType.Construct(RTCStatsType::Inboundrtp); michael@0: if (ssrc.Length()) { michael@0: s.mSsrc.Construct(ssrc); michael@0: } michael@0: unsigned int jitterMs, packetsLost; michael@0: if (mp.Conduit()->GetRTPStats(&jitterMs, &packetsLost)) { michael@0: s.mJitter.Construct(double(jitterMs)/1000); michael@0: s.mPacketsLost.Construct(packetsLost); michael@0: } michael@0: if (remoteId.Length()) { michael@0: s.mRemoteId.Construct(remoteId); michael@0: } michael@0: s.mIsRemote = false; michael@0: s.mPacketsReceived.Construct(mp.rtp_packets_received()); michael@0: s.mBytesReceived.Construct(mp.rtp_bytes_received()); michael@0: michael@0: if (query->internalStats && isAudio) { michael@0: int32_t jitterBufferDelay; michael@0: int32_t playoutBufferDelay; michael@0: int32_t avSyncDelta; michael@0: if (mp.Conduit()->GetAVStats(&jitterBufferDelay, michael@0: &playoutBufferDelay, michael@0: &avSyncDelta)) { michael@0: s.mMozJitterBufferDelay.Construct(jitterBufferDelay); michael@0: s.mMozAvSyncDelay.Construct(avSyncDelta); michael@0: } michael@0: } michael@0: query->report.mInboundRTPStreamStats.Value().AppendElement(s); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Gather stats from ICE michael@0: for (size_t s = 0; s != query->streams.Length(); ++s) { michael@0: RecordIceStats_s(*query->streams[s], michael@0: query->internalStats, michael@0: query->now, michael@0: &(query->report)); michael@0: } michael@0: michael@0: // NrIceCtx and NrIceMediaStream must be destroyed on STS, so it is not safe michael@0: // to dispatch them back to main. michael@0: // We clear streams first to maintain destruction order michael@0: query->streams.Clear(); michael@0: query->iceCtx = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void PeerConnectionImpl::GetStatsForPCObserver_s( michael@0: const std::string& pcHandle, // The Runnable holds the memory michael@0: nsAutoPtr query) { michael@0: michael@0: MOZ_ASSERT(query); michael@0: MOZ_ASSERT(query->iceCtx); michael@0: ASSERT_ON_THREAD(query->iceCtx->thread()); michael@0: michael@0: nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get()); michael@0: michael@0: NS_DispatchToMainThread( michael@0: WrapRunnableNM( michael@0: &PeerConnectionImpl::DeliverStatsReportToPCObserver_m, michael@0: pcHandle, michael@0: rv, michael@0: query), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void PeerConnectionImpl::DeliverStatsReportToPCObserver_m( michael@0: const std::string& pcHandle, michael@0: nsresult result, michael@0: nsAutoPtr query) { michael@0: michael@0: // Is the PeerConnectionImpl still around? michael@0: PeerConnectionWrapper pcw(pcHandle); michael@0: if (pcw.impl()) { michael@0: nsRefPtr pco = michael@0: do_QueryObjectReferent(pcw.impl()->mPCObserver); michael@0: if (pco) { michael@0: JSErrorResult rv; michael@0: if (NS_SUCCEEDED(result)) { michael@0: pco->OnGetStatsSuccess(query->report, rv); michael@0: } else { michael@0: pco->OnGetStatsError(kInternalError, michael@0: ObString("Failed to fetch statistics"), michael@0: rv); michael@0: } michael@0: michael@0: if (rv.Failed()) { michael@0: CSFLogError(logTag, "Error firing stats observer callback"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #endif michael@0: michael@0: void michael@0: PeerConnectionImpl::RecordLongtermICEStatistics() { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: WebrtcGlobalInformation::StoreLongTermICEStatistics(*this); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::IceStreamReady(NrIceMediaStream *aStream) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: MOZ_ASSERT(aStream); michael@0: michael@0: CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str()); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::OnSdpParseError(const char *message) { michael@0: CSFLogError(logTag, "%s SDP Parse Error: %s", __FUNCTION__, message); michael@0: // Save the parsing errors in the PC to be delivered with OnSuccess or OnError michael@0: mSDPParseErrorMessages.push_back(message); michael@0: } michael@0: michael@0: void michael@0: PeerConnectionImpl::ClearSdpParseErrorMessages() { michael@0: mSDPParseErrorMessages.clear(); michael@0: } michael@0: michael@0: const std::vector & michael@0: PeerConnectionImpl::GetSdpParseErrors() { michael@0: return mSDPParseErrorMessages; michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: //Telemetry for when calls start michael@0: void michael@0: PeerConnectionImpl::startCallTelem() { michael@0: // Start time for calls michael@0: mStartTime = TimeStamp::Now(); michael@0: michael@0: // Increment session call counter michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: int &cnt = PeerConnectionCtx::GetInstance()->mConnectionCounter; michael@0: Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Subtract(cnt); michael@0: cnt++; michael@0: Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(cnt); michael@0: #endif michael@0: } michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetLocalStreams(nsTArray >& result) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: for(uint32_t i=0; i < media()->LocalStreamsLength(); i++) { michael@0: LocalSourceStreamInfo *info = media()->GetLocalStream(i); michael@0: NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED); michael@0: result.AppendElement(info->GetMediaStream()); michael@0: } michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_FAILURE; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: PeerConnectionImpl::GetRemoteStreams(nsTArray >& result) michael@0: { michael@0: PC_AUTO_ENTER_API_CALL_NO_CHECK(); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: for(uint32_t i=0; i < media()->RemoteStreamsLength(); i++) { michael@0: RemoteSourceStreamInfo *info = media()->GetRemoteStream(i); michael@0: NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED); michael@0: result.AppendElement(info->GetMediaStream()); michael@0: } michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_FAILURE; michael@0: #endif michael@0: } michael@0: michael@0: } // end sipcc namespace