diff options
Diffstat (limited to 'media/webrtc/signaling/src/peerconnection')
14 files changed, 10595 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp new file mode 100644 index 000000000..61c2719cd --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp @@ -0,0 +1,1076 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "logging.h" +#include "nsIGfxInfo.h" +#include "nsServiceManagerUtils.h" + +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.h" +#include "MediaPipelineFactory.h" +#include "MediaPipelineFilter.h" +#include "transportflow.h" +#include "transportlayer.h" +#include "transportlayerdtls.h" +#include "transportlayerice.h" + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/common/PtrVector.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "MediaStreamTrack.h" +#include "nsIPrincipal.h" +#include "nsIDocument.h" +#include "mozilla/Preferences.h" +#include "MediaEngine.h" +#endif + +#include "GmpVideoCodec.h" +#ifdef MOZ_WEBRTC_OMX +#include "OMXVideoCodec.h" +#include "OMXCodecWrapper.h" +#endif + +#ifdef MOZ_WEBRTC_MEDIACODEC +#include "MediaCodecVideoCodec.h" +#endif + +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/Preferences.h" +#endif + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "WebrtcGmpVideoCodec.h" +#endif + +#include <stdlib.h> + +namespace mozilla { + +MOZ_MTLOG_MODULE("MediaPipelineFactory") + +static nsresult +JsepCodecDescToCodecConfig(const JsepCodecDescription& aCodec, + AudioCodecConfig** aConfig) +{ + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kAudio); + if (aCodec.mType != SdpMediaSection::kAudio) + return NS_ERROR_INVALID_ARG; + + const JsepAudioCodecDescription& desc = + static_cast<const JsepAudioCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + *aConfig = new AudioCodecConfig(pt, + desc.mName, + desc.mClock, + desc.mPacketSize, + desc.mForceMono ? 1 : desc.mChannels, + desc.mBitrate, + desc.mFECEnabled); + (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate; + (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled; + + return NS_OK; +} + +static std::vector<JsepCodecDescription*> +GetCodecs(const JsepTrackNegotiatedDetails& aDetails) +{ + // We do not try to handle cases where a codec is not used on the primary + // encoding. + if (aDetails.GetEncodingCount()) { + return aDetails.GetEncoding(0).GetCodecs(); + } + return std::vector<JsepCodecDescription*>(); +} + +static nsresult +NegotiatedDetailsToAudioCodecConfigs(const JsepTrackNegotiatedDetails& aDetails, + PtrVector<AudioCodecConfig>* aConfigs) +{ + std::vector<JsepCodecDescription*> codecs(GetCodecs(aDetails)); + for (const JsepCodecDescription* codec : codecs) { + AudioCodecConfig* config; + if (NS_FAILED(JsepCodecDescToCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + aConfigs->values.push_back(config); + } + return NS_OK; +} + +static nsresult +JsepCodecDescToCodecConfig(const JsepCodecDescription& aCodec, + VideoCodecConfig** aConfig) +{ + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kVideo); + if (aCodec.mType != SdpMediaSection::kVideo) { + MOZ_ASSERT(false, "JsepCodecDescription has wrong type"); + return NS_ERROR_INVALID_ARG; + } + + const JsepVideoCodecDescription& desc = + static_cast<const JsepVideoCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + UniquePtr<VideoCodecConfigH264> h264Config; + + if (desc.mName == "H264") { + h264Config = MakeUnique<VideoCodecConfigH264>(); + size_t spropSize = sizeof(h264Config->sprop_parameter_sets); + strncpy(h264Config->sprop_parameter_sets, + desc.mSpropParameterSets.c_str(), + spropSize); + h264Config->sprop_parameter_sets[spropSize - 1] = '\0'; + h264Config->packetization_mode = desc.mPacketizationMode; + h264Config->profile_level_id = desc.mProfileLevelId; + h264Config->tias_bw = 0; // TODO. Issue 165. + } + + VideoCodecConfig* configRaw; + configRaw = new VideoCodecConfig( + pt, desc.mName, desc.mConstraints, h264Config.get()); + + configRaw->mAckFbTypes = desc.mAckFbTypes; + configRaw->mNackFbTypes = desc.mNackFbTypes; + configRaw->mCcmFbTypes = desc.mCcmFbTypes; + configRaw->mRembFbSet = desc.RtcpFbRembIsSet(); + configRaw->mFECFbSet = desc.mFECEnabled; + + *aConfig = configRaw; + return NS_OK; +} + +static nsresult +NegotiatedDetailsToVideoCodecConfigs(const JsepTrackNegotiatedDetails& aDetails, + PtrVector<VideoCodecConfig>* aConfigs) +{ + std::vector<JsepCodecDescription*> codecs(GetCodecs(aDetails)); + for (const JsepCodecDescription* codec : codecs) { + VideoCodecConfig* config; + if (NS_FAILED(JsepCodecDescToCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(codec->mDefaultPt)) { + VideoCodecConfig::SimulcastEncoding encoding; + encoding.rid = jsepEncoding.mRid; + encoding.constraints = jsepEncoding.mConstraints; + config->mSimulcastEncodings.push_back(encoding); + } + } + aConfigs->values.push_back(config); + } + + return NS_OK; +} + +// Accessing the PCMedia should be safe here because we shouldn't +// have enqueued this function unless it was still active and +// the ICE data is destroyed on the STS. +static void +FinalizeTransportFlow_s(RefPtr<PeerConnectionMedia> aPCMedia, + RefPtr<TransportFlow> aFlow, size_t aLevel, + bool aIsRtcp, + nsAutoPtr<PtrVector<TransportLayer> > aLayerList) +{ + TransportLayerIce* ice = + static_cast<TransportLayerIce*>(aLayerList->values.front()); + ice->SetParameters(aPCMedia->ice_ctx(), + aPCMedia->ice_media_stream(aLevel), + aIsRtcp ? 2 : 1); + nsAutoPtr<std::queue<TransportLayer*> > layerQueue( + new std::queue<TransportLayer*>); + for (auto i = aLayerList->values.begin(); i != aLayerList->values.end(); + ++i) { + layerQueue->push(*i); + } + aLayerList->values.clear(); + (void)aFlow->PushLayers(layerQueue); // TODO(bug 854518): Process errors. +} + +static void +AddNewIceStreamForRestart_s(RefPtr<PeerConnectionMedia> aPCMedia, + RefPtr<TransportFlow> aFlow, + size_t aLevel, + bool aIsRtcp) +{ + TransportLayerIce* ice = + static_cast<TransportLayerIce*>(aFlow->GetLayer("ice")); + ice->SetParameters(aPCMedia->ice_ctx(), + aPCMedia->ice_media_stream(aLevel), + aIsRtcp ? 2 : 1); +} + +nsresult +MediaPipelineFactory::CreateOrGetTransportFlow( + size_t aLevel, + bool aIsRtcp, + const JsepTransport& aTransport, + RefPtr<TransportFlow>* aFlowOutparam) +{ + nsresult rv; + RefPtr<TransportFlow> flow; + + flow = mPCMedia->GetTransportFlow(aLevel, aIsRtcp); + if (flow) { + if (mPCMedia->IsIceRestarting()) { + MOZ_MTLOG(ML_INFO, "Flow[" << flow->id() << "]: " + << "detected ICE restart - level: " + << aLevel << " rtcp: " << aIsRtcp); + + rv = mPCMedia->GetSTSThread()->Dispatch( + WrapRunnableNM(AddNewIceStreamForRestart_s, + mPCMedia, flow, aLevel, aIsRtcp), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Failed to dispatch AddNewIceStreamForRestart_s"); + return rv; + } + } + + *aFlowOutparam = flow; + return NS_OK; + } + + std::ostringstream osId; + osId << mPC->GetHandle() << ":" << aLevel << "," + << (aIsRtcp ? "rtcp" : "rtp"); + flow = new TransportFlow(osId.str()); + + // The media streams are made on STS so we need to defer setup. + auto ice = MakeUnique<TransportLayerIce>(mPC->GetHandle()); + auto dtls = MakeUnique<TransportLayerDtls>(); + dtls->SetRole(aTransport.mDtls->GetRole() == + JsepDtlsTransport::kJsepDtlsClient + ? TransportLayerDtls::CLIENT + : TransportLayerDtls::SERVER); + + RefPtr<DtlsIdentity> pcid = mPC->Identity(); + if (!pcid) { + MOZ_MTLOG(ML_ERROR, "Failed to get DTLS identity."); + return NS_ERROR_FAILURE; + } + dtls->SetIdentity(pcid); + + const SdpFingerprintAttributeList& fingerprints = + aTransport.mDtls->GetFingerprints(); + for (auto fp = fingerprints.mFingerprints.begin(); + fp != fingerprints.mFingerprints.end(); + ++fp) { + std::ostringstream ss; + ss << fp->hashFunc; + rv = dtls->SetVerificationDigest(ss.str(), &fp->fingerprint[0], + fp->fingerprint.size()); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Could not set fingerprint"); + return rv; + } + } + + std::vector<uint16_t> srtpCiphers; + srtpCiphers.push_back(SRTP_AES128_CM_HMAC_SHA1_80); + srtpCiphers.push_back(SRTP_AES128_CM_HMAC_SHA1_32); + + rv = dtls->SetSrtpCiphers(srtpCiphers); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't set SRTP ciphers"); + return rv; + } + + // Always permits negotiation of the confidential mode. + // Only allow non-confidential (which is an allowed default), + // if we aren't confidential. + std::set<std::string> alpn; + std::string alpnDefault = ""; + alpn.insert("c-webrtc"); + if (!mPC->PrivacyRequested()) { + alpnDefault = "webrtc"; + alpn.insert(alpnDefault); + } + rv = dtls->SetAlpn(alpn, alpnDefault); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN"); + return rv; + } + + nsAutoPtr<PtrVector<TransportLayer> > layers(new PtrVector<TransportLayer>); + layers->values.push_back(ice.release()); + layers->values.push_back(dtls.release()); + + rv = mPCMedia->GetSTSThread()->Dispatch( + WrapRunnableNM(FinalizeTransportFlow_s, mPCMedia, flow, aLevel, aIsRtcp, + layers), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Failed to dispatch FinalizeTransportFlow_s"); + return rv; + } + + mPCMedia->AddTransportFlow(aLevel, aIsRtcp, flow); + + *aFlowOutparam = flow; + + return NS_OK; +} + +nsresult +MediaPipelineFactory::GetTransportParameters( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t* aLevelOut, + RefPtr<TransportFlow>* aRtpOut, + RefPtr<TransportFlow>* aRtcpOut, + nsAutoPtr<MediaPipelineFilter>* aFilterOut) +{ + *aLevelOut = aTrackPair.mLevel; + + size_t transportLevel = aTrackPair.mBundleLevel.isSome() ? + *aTrackPair.mBundleLevel : + aTrackPair.mLevel; + + nsresult rv = CreateOrGetTransportFlow( + transportLevel, false, *aTrackPair.mRtpTransport, aRtpOut); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(aRtpOut); + + if (aTrackPair.mRtcpTransport) { + rv = CreateOrGetTransportFlow( + transportLevel, true, *aTrackPair.mRtcpTransport, aRtcpOut); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(aRtcpOut); + } + + if (aTrackPair.mBundleLevel.isSome()) { + bool receiving = aTrack.GetDirection() == sdp::kRecv; + + *aFilterOut = new MediaPipelineFilter; + + if (receiving) { + // Add remote SSRCs so we can distinguish which RTP packets actually + // belong to this pipeline (also RTCP sender reports). + for (auto i = aTrack.GetSsrcs().begin(); + i != aTrack.GetSsrcs().end(); ++i) { + (*aFilterOut)->AddRemoteSSRC(*i); + } + + // TODO(bug 1105005): Tell the filter about the mid for this track + + // Add unique payload types as a last-ditch fallback + auto uniquePts = aTrack.GetNegotiatedDetails()->GetUniquePayloadTypes(); + for (auto i = uniquePts.begin(); i != uniquePts.end(); ++i) { + (*aFilterOut)->AddUniquePT(*i); + } + } + } + + return NS_OK; +} + +nsresult +MediaPipelineFactory::CreateOrUpdateMediaPipeline( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack) +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // The GMP code is all the way on the other side of webrtc.org, and it is not + // feasible to plumb this information all the way through. So, we set it (for + // the duration of this call) in a global variable. This allows the GMP code + // to report errors to the PC. + WebrtcGmpPCHandleSetter setter(mPC->GetHandle()); +#endif + + MOZ_ASSERT(aTrackPair.mRtpTransport); + + bool receiving = aTrack.GetDirection() == sdp::kRecv; + + size_t level; + RefPtr<TransportFlow> rtpFlow; + RefPtr<TransportFlow> rtcpFlow; + nsAutoPtr<MediaPipelineFilter> filter; + + nsresult rv = GetTransportParameters(aTrackPair, + aTrack, + &level, + &rtpFlow, + &rtcpFlow, + &filter); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Failed to get transport parameters for pipeline, rv=" + << static_cast<unsigned>(rv)); + return rv; + } + + if (aTrack.GetMediaType() == SdpMediaSection::kApplication) { + // GetTransportParameters has already done everything we need for + // datachannel. + return NS_OK; + } + + // Find the stream we need + SourceStreamInfo* stream; + if (receiving) { + stream = mPCMedia->GetRemoteStreamById(aTrack.GetStreamId()); + } else { + stream = mPCMedia->GetLocalStreamById(aTrack.GetStreamId()); + } + + if (!stream) { + MOZ_MTLOG(ML_ERROR, "Negotiated " << (receiving ? "recv" : "send") + << " stream id " << aTrack.GetStreamId() << " was never added"); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + if (!stream->HasTrack(aTrack.GetTrackId())) { + MOZ_MTLOG(ML_ERROR, "Negotiated " << (receiving ? "recv" : "send") + << " track id " << aTrack.GetTrackId() << " was never added"); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + RefPtr<MediaSessionConduit> conduit; + if (aTrack.GetMediaType() == SdpMediaSection::kAudio) { + rv = GetOrCreateAudioConduit(aTrackPair, aTrack, &conduit); + if (NS_FAILED(rv)) + return rv; + } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) { + rv = GetOrCreateVideoConduit(aTrackPair, aTrack, &conduit); + if (NS_FAILED(rv)) + return rv; + } else { + // We've created the TransportFlow, nothing else to do here. + return NS_OK; + } + + if (aTrack.GetActive()) { + if (receiving) { + auto error = conduit->StartReceiving(); + if (error) { + MOZ_MTLOG(ML_ERROR, "StartReceiving failed: " << error); + return NS_ERROR_FAILURE; + } + } else { + auto error = conduit->StartTransmitting(); + if (error) { + MOZ_MTLOG(ML_ERROR, "StartTransmitting failed: " << error); + return NS_ERROR_FAILURE; + } + } + } else { + if (receiving) { + auto error = conduit->StopReceiving(); + if (error) { + MOZ_MTLOG(ML_ERROR, "StopReceiving failed: " << error); + return NS_ERROR_FAILURE; + } + } else { + auto error = conduit->StopTransmitting(); + if (error) { + MOZ_MTLOG(ML_ERROR, "StopTransmitting failed: " << error); + return NS_ERROR_FAILURE; + } + } + } + + RefPtr<MediaPipeline> pipeline = + stream->GetPipelineByTrackId_m(aTrack.GetTrackId()); + + if (pipeline && pipeline->level() != static_cast<int>(level)) { + MOZ_MTLOG(ML_WARNING, "Track " << aTrack.GetTrackId() << + " has moved from level " << pipeline->level() << + " to level " << level << + ". This requires re-creating the MediaPipeline."); + RefPtr<dom::MediaStreamTrack> domTrack = + stream->GetTrackById(aTrack.GetTrackId()); + MOZ_ASSERT(domTrack, "MediaPipeline existed for a track, but no MediaStreamTrack"); + + // Since we do not support changing the conduit on a pre-existing + // MediaPipeline + pipeline = nullptr; + stream->RemoveTrack(aTrack.GetTrackId()); + stream->AddTrack(aTrack.GetTrackId(), domTrack); + } + + if (pipeline) { + pipeline->UpdateTransport_m(level, rtpFlow, rtcpFlow, filter); + return NS_OK; + } + + MOZ_MTLOG(ML_DEBUG, + "Creating media pipeline" + << " m-line index=" << aTrackPair.mLevel + << " type=" << aTrack.GetMediaType() + << " direction=" << aTrack.GetDirection()); + + if (receiving) { + rv = CreateMediaPipelineReceiving(aTrackPair, aTrack, + level, rtpFlow, rtcpFlow, filter, + conduit); + if (NS_FAILED(rv)) + return rv; + } else { + rv = CreateMediaPipelineSending(aTrackPair, aTrack, + level, rtpFlow, rtcpFlow, filter, + conduit); + if (NS_FAILED(rv)) + return rv; + } + + return NS_OK; +} + +nsresult +MediaPipelineFactory::CreateMediaPipelineReceiving( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t aLevel, + RefPtr<TransportFlow> aRtpFlow, + RefPtr<TransportFlow> aRtcpFlow, + nsAutoPtr<MediaPipelineFilter> aFilter, + const RefPtr<MediaSessionConduit>& aConduit) +{ + // We will error out earlier if this isn't here. + RefPtr<RemoteSourceStreamInfo> stream = + mPCMedia->GetRemoteStreamById(aTrack.GetStreamId()); + + RefPtr<MediaPipelineReceive> pipeline; + + TrackID numericTrackId = stream->GetNumericTrackId(aTrack.GetTrackId()); + MOZ_ASSERT(IsTrackIDExplicit(numericTrackId)); + + MOZ_MTLOG(ML_DEBUG, __FUNCTION__ << ": Creating pipeline for " + << numericTrackId << " -> " << aTrack.GetTrackId()); + + if (aTrack.GetMediaType() == SdpMediaSection::kAudio) { + pipeline = new MediaPipelineReceiveAudio( + mPC->GetHandle(), + mPC->GetMainThread().get(), + mPC->GetSTSThread(), + stream->GetMediaStream()->GetInputStream()->AsSourceStream(), + aTrack.GetTrackId(), + numericTrackId, + aLevel, + static_cast<AudioSessionConduit*>(aConduit.get()), // Ugly downcast. + aRtpFlow, + aRtcpFlow, + aFilter); + } else if (aTrack.GetMediaType() == SdpMediaSection::kVideo) { + pipeline = new MediaPipelineReceiveVideo( + mPC->GetHandle(), + mPC->GetMainThread().get(), + mPC->GetSTSThread(), + stream->GetMediaStream()->GetInputStream()->AsSourceStream(), + aTrack.GetTrackId(), + numericTrackId, + aLevel, + static_cast<VideoSessionConduit*>(aConduit.get()), // Ugly downcast. + aRtpFlow, + aRtcpFlow, + aFilter); + } else { + MOZ_ASSERT(false); + MOZ_MTLOG(ML_ERROR, "Invalid media type in CreateMediaPipelineReceiving"); + return NS_ERROR_FAILURE; + } + + nsresult rv = pipeline->Init(); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't initialize receiving pipeline"); + return rv; + } + + rv = stream->StorePipeline(aTrack.GetTrackId(), + RefPtr<MediaPipeline>(pipeline)); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't store receiving pipeline " << + static_cast<unsigned>(rv)); + return rv; + } + + stream->SyncPipeline(pipeline); + + return NS_OK; +} + +nsresult +MediaPipelineFactory::CreateMediaPipelineSending( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t aLevel, + RefPtr<TransportFlow> aRtpFlow, + RefPtr<TransportFlow> aRtcpFlow, + nsAutoPtr<MediaPipelineFilter> aFilter, + const RefPtr<MediaSessionConduit>& aConduit) +{ + nsresult rv; + + // This is checked earlier + RefPtr<LocalSourceStreamInfo> stream = + mPCMedia->GetLocalStreamById(aTrack.GetStreamId()); + + dom::MediaStreamTrack* track = + stream->GetTrackById(aTrack.GetTrackId()); + MOZ_ASSERT(track); + + // Now we have all the pieces, create the pipeline + RefPtr<MediaPipelineTransmit> pipeline = new MediaPipelineTransmit( + mPC->GetHandle(), + mPC->GetMainThread().get(), + mPC->GetSTSThread(), + track, + aTrack.GetTrackId(), + aLevel, + aConduit, + aRtpFlow, + aRtcpFlow, + aFilter); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // implement checking for peerIdentity (where failure == black/silence) + nsIDocument* doc = mPC->GetWindow()->GetExtantDoc(); + if (doc) { + pipeline->UpdateSinkIdentity_m(track, + doc->NodePrincipal(), + mPC->GetPeerIdentity()); + } else { + MOZ_MTLOG(ML_ERROR, "Cannot initialize pipeline without attached doc"); + return NS_ERROR_FAILURE; // Don't remove this till we know it's safe. + } +#endif + + rv = pipeline->Init(); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't initialize sending pipeline"); + return rv; + } + + rv = stream->StorePipeline(aTrack.GetTrackId(), + RefPtr<MediaPipeline>(pipeline)); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Couldn't store receiving pipeline " << + static_cast<unsigned>(rv)); + return rv; + } + + return NS_OK; +} + +nsresult +MediaPipelineFactory::GetOrCreateAudioConduit( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + RefPtr<MediaSessionConduit>* aConduitp) +{ + + if (!aTrack.GetNegotiatedDetails()) { + MOZ_ASSERT(false, "Track is missing negotiated details"); + return NS_ERROR_INVALID_ARG; + } + + bool receiving = aTrack.GetDirection() == sdp::kRecv; + + RefPtr<AudioSessionConduit> conduit = + mPCMedia->GetAudioConduit(aTrackPair.mLevel); + + if (!conduit) { + conduit = AudioSessionConduit::Create(); + if (!conduit) { + MOZ_MTLOG(ML_ERROR, "Could not create audio conduit"); + return NS_ERROR_FAILURE; + } + + mPCMedia->AddAudioConduit(aTrackPair.mLevel, conduit); + } + + PtrVector<AudioCodecConfig> configs; + nsresult rv = NegotiatedDetailsToAudioCodecConfigs( + *aTrack.GetNegotiatedDetails(), &configs); + + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Failed to convert JsepCodecDescriptions to " + "AudioCodecConfigs."); + return rv; + } + + if (configs.values.empty()) { + MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs"); + return NS_ERROR_FAILURE; + } + + if (receiving) { + auto error = conduit->ConfigureRecvMediaCodecs(configs.values); + + if (error) { + MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error); + return NS_ERROR_FAILURE; + } + + if (!aTrackPair.mSending) { + // No send track, but we still need to configure an SSRC for receiver + // reports. + if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) { + MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed"); + return NS_ERROR_FAILURE; + } + } + } else { + // For now we only expect to have one ssrc per local track. + auto ssrcs = aTrack.GetSsrcs(); + if (!ssrcs.empty()) { + if (!conduit->SetLocalSSRC(ssrcs.front())) { + MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed"); + return NS_ERROR_FAILURE; + } + } + + conduit->SetLocalCNAME(aTrack.GetCNAME().c_str()); + + if (configs.values.size() > 1 + && configs.values.back()->mName == "telephone-event") { + // we have a telephone event codec, so we need to make sure + // the dynamic pt is set properly + conduit->SetDtmfPayloadType(configs.values.back()->mType); + } + + auto error = conduit->ConfigureSendMediaCodec(configs.values[0]); + if (error) { + MOZ_MTLOG(ML_ERROR, "ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + + const SdpExtmapAttributeList::Extmap* audioLevelExt = + aTrack.GetNegotiatedDetails()->GetExt( + "urn:ietf:params:rtp-hdrext:ssrc-audio-level"); + + if (audioLevelExt) { + MOZ_MTLOG(ML_DEBUG, "Calling EnableAudioLevelExtension"); + error = conduit->EnableAudioLevelExtension(true, audioLevelExt->entry); + + if (error) { + MOZ_MTLOG(ML_ERROR, "EnableAudioLevelExtension failed: " << error); + return NS_ERROR_FAILURE; + } + } + } + + *aConduitp = conduit; + + return NS_OK; +} + +nsresult +MediaPipelineFactory::GetOrCreateVideoConduit( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + RefPtr<MediaSessionConduit>* aConduitp) +{ + + if (!aTrack.GetNegotiatedDetails()) { + MOZ_ASSERT(false, "Track is missing negotiated details"); + return NS_ERROR_INVALID_ARG; + } + + bool receiving = aTrack.GetDirection() == sdp::kRecv; + + RefPtr<VideoSessionConduit> conduit = + mPCMedia->GetVideoConduit(aTrackPair.mLevel); + + if (!conduit) { + conduit = VideoSessionConduit::Create(); + if (!conduit) { + MOZ_MTLOG(ML_ERROR, "Could not create video conduit"); + return NS_ERROR_FAILURE; + } + + mPCMedia->AddVideoConduit(aTrackPair.mLevel, conduit); + } + + PtrVector<VideoCodecConfig> configs; + nsresult rv = NegotiatedDetailsToVideoCodecConfigs( + *aTrack.GetNegotiatedDetails(), &configs); + + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, "Failed to convert JsepCodecDescriptions to " + "VideoCodecConfigs."); + return rv; + } + + if (configs.values.empty()) { + MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs"); + return NS_ERROR_FAILURE; + } + + if (receiving) { + if (aTrackPair.mSending) { + auto ssrcs = &aTrackPair.mSending->GetSsrcs(); + if (!ssrcs->empty()) { + if (!conduit->SetLocalSSRC(ssrcs->front())) { + MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed(1)"); + return NS_ERROR_FAILURE; + } + } else { + MOZ_MTLOG(ML_ERROR, "Sending without an SSRC??"); + return NS_ERROR_FAILURE; + } + } else { + // No send track, but we still need to configure an SSRC for receiver + // reports. + if (!conduit->SetLocalSSRC(aTrackPair.mRecvonlySsrc)) { + MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed(2)"); + return NS_ERROR_FAILURE; + } + } + + // Prune out stuff we cannot actually do. We should work to eliminate the + // need for this. + bool configuredH264 = false; + for (size_t i = 0; i < configs.values.size();) { + // TODO(bug 1200768): We can only handle configuring one recv H264 codec + if (configuredH264 && (configs.values[i]->mName == "H264")) { + delete configs.values[i]; + configs.values.erase(configs.values.begin() + i); + continue; + } + + // TODO(bug 1018791): This really should be checked sooner + if (EnsureExternalCodec(*conduit, configs.values[i], false)) { + delete configs.values[i]; + configs.values.erase(configs.values.begin() + i); + continue; + } + + if (configs.values[i]->mName == "H264") { + configuredH264 = true; + } + ++i; + } + + auto error = conduit->ConfigureRecvMediaCodecs(configs.values); + + if (error) { + MOZ_MTLOG(ML_ERROR, "ConfigureRecvMediaCodecs failed: " << error); + return NS_ERROR_FAILURE; + } + } else { + // For now we only expect to have one ssrc per local track. + auto ssrcs = aTrack.GetSsrcs(); + if (!ssrcs.empty()) { + if (!conduit->SetLocalSSRC(ssrcs.front())) { + MOZ_MTLOG(ML_ERROR, "SetLocalSSRC failed"); + return NS_ERROR_FAILURE; + } + } + + conduit->SetLocalCNAME(aTrack.GetCNAME().c_str()); + + rv = ConfigureVideoCodecMode(aTrack,*conduit); + if (NS_FAILED(rv)) { + return rv; + } + + // TODO(bug 1018791): This really should be checked sooner + if (EnsureExternalCodec(*conduit, configs.values[0], true)) { + MOZ_MTLOG(ML_ERROR, "External codec not available"); + return NS_ERROR_FAILURE; + } + + + auto error = conduit->ConfigureSendMediaCodec(configs.values[0]); + + const SdpExtmapAttributeList::Extmap* rtpStreamIdExt = + aTrack.GetNegotiatedDetails()->GetExt( + "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"); + + if (rtpStreamIdExt) { + MOZ_MTLOG(ML_DEBUG, "Calling EnableRTPSenderIdExtension"); + error = conduit->EnableRTPStreamIdExtension(true, rtpStreamIdExt->entry); + + if (error) { + MOZ_MTLOG(ML_ERROR, "EnableRTPSenderIdExtension failed: " << error); + return NS_ERROR_FAILURE; + } + } + + if (error) { + MOZ_MTLOG(ML_ERROR, "ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + } + + *aConduitp = conduit; + + return NS_OK; +} + +nsresult +MediaPipelineFactory::ConfigureVideoCodecMode(const JsepTrack& aTrack, + VideoSessionConduit& aConduit) +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + RefPtr<LocalSourceStreamInfo> stream = + mPCMedia->GetLocalStreamByTrackId(aTrack.GetTrackId()); + + //get video track + RefPtr<mozilla::dom::MediaStreamTrack> track = + stream->GetTrackById(aTrack.GetTrackId()); + + RefPtr<mozilla::dom::VideoStreamTrack> videotrack = + track->AsVideoStreamTrack(); + + if (!videotrack) { + MOZ_MTLOG(ML_ERROR, "video track not available"); + return NS_ERROR_FAILURE; + } + + dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); + webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo; + switch (source) { + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Application: + case dom::MediaSourceEnum::Window: + mode = webrtc::kScreensharing; + break; + + case dom::MediaSourceEnum::Camera: + default: + mode = webrtc::kRealtimeVideo; + break; + } + + auto error = aConduit.ConfigureCodecMode(mode); + if (error) { + MOZ_MTLOG(ML_ERROR, "ConfigureCodecMode failed: " << error); + return NS_ERROR_FAILURE; + } + +#endif + return NS_OK; +} + +/* + * Add external H.264 video codec. + */ +MediaConduitErrorCode +MediaPipelineFactory::EnsureExternalCodec(VideoSessionConduit& aConduit, + VideoCodecConfig* aConfig, + bool aIsSend) +{ + if (aConfig->mName == "VP8") { +#ifdef MOZ_WEBRTC_MEDIACODEC + if (aIsSend) { +#ifdef MOZILLA_INTERNAL_API + bool enabled = mozilla::Preferences::GetBool("media.navigator.hardware.vp8_encode.acceleration_enabled", false); +#else + bool enabled = false; +#endif + if (enabled) { + nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); + if (gfxInfo) { + int32_t status; + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE, discardFailureId, &status))) { + if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + NS_WARNING("VP8 encoder hardware is not whitelisted: disabling.\n"); + } else { + VideoEncoder* encoder = nullptr; + encoder = MediaCodecVideoCodec::CreateEncoder(MediaCodecVideoCodec::CodecType::CODEC_VP8); + if (encoder) { + return aConduit.SetExternalSendCodec(aConfig, encoder); + } + return kMediaConduitNoError; + } + } + } + } + } else { +#ifdef MOZILLA_INTERNAL_API + bool enabled = mozilla::Preferences::GetBool("media.navigator.hardware.vp8_decode.acceleration_enabled", false); +#else + bool enabled = false; +#endif + if (enabled) { + nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); + if (gfxInfo) { + int32_t status; + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE, discardFailureId, &status))) { + if (status != nsIGfxInfo::FEATURE_STATUS_OK) { + NS_WARNING("VP8 decoder hardware is not whitelisted: disabling.\n"); + } else { + VideoDecoder* decoder; + decoder = MediaCodecVideoCodec::CreateDecoder(MediaCodecVideoCodec::CodecType::CODEC_VP8); + if (decoder) { + return aConduit.SetExternalRecvCodec(aConfig, decoder); + } + return kMediaConduitNoError; + } + } + } + } + } +#endif + return kMediaConduitNoError; + } + if (aConfig->mName == "VP9") { + return kMediaConduitNoError; + } + if (aConfig->mName == "H264") { + if (aConduit.CodecPluginID() != 0) { + return kMediaConduitNoError; + } + // Register H.264 codec. + if (aIsSend) { + VideoEncoder* encoder = nullptr; +#ifdef MOZ_WEBRTC_OMX + encoder = + OMXVideoCodec::CreateEncoder(OMXVideoCodec::CodecType::CODEC_H264); +#else + encoder = GmpVideoCodec::CreateEncoder(); +#endif + if (encoder) { + return aConduit.SetExternalSendCodec(aConfig, encoder); + } + return kMediaConduitInvalidSendCodec; + } + VideoDecoder* decoder = nullptr; +#ifdef MOZ_WEBRTC_OMX + decoder = + OMXVideoCodec::CreateDecoder(OMXVideoCodec::CodecType::CODEC_H264); +#else + decoder = GmpVideoCodec::CreateDecoder(); +#endif + if (decoder) { + return aConduit.SetExternalRecvCodec(aConfig, decoder); + } + return kMediaConduitInvalidReceiveCodec; + } + MOZ_MTLOG(ML_ERROR, + "Invalid video codec configured: " << aConfig->mName.c_str()); + return aIsSend ? kMediaConduitInvalidSendCodec + : kMediaConduitInvalidReceiveCodec; +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h new file mode 100644 index 000000000..972c4368a --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _MEDIAPIPELINEFACTORY_H_ +#define _MEDIAPIPELINEFACTORY_H_ + +#include "MediaConduitInterface.h" +#include "PeerConnectionMedia.h" +#include "transportflow.h" + +#include "signaling/src/jsep/JsepTrack.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class MediaPipelineFactory +{ +public: + explicit MediaPipelineFactory(PeerConnectionMedia* aPCMedia) + : mPCMedia(aPCMedia), mPC(aPCMedia->GetPC()) + { + } + + nsresult CreateOrUpdateMediaPipeline(const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack); + +private: + nsresult CreateMediaPipelineReceiving( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t level, + RefPtr<TransportFlow> aRtpFlow, + RefPtr<TransportFlow> aRtcpFlow, + nsAutoPtr<MediaPipelineFilter> filter, + const RefPtr<MediaSessionConduit>& aConduit); + + nsresult CreateMediaPipelineSending( + const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t level, + RefPtr<TransportFlow> aRtpFlow, + RefPtr<TransportFlow> aRtcpFlow, + nsAutoPtr<MediaPipelineFilter> filter, + const RefPtr<MediaSessionConduit>& aConduit); + + nsresult GetOrCreateAudioConduit(const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + RefPtr<MediaSessionConduit>* aConduitp); + + nsresult GetOrCreateVideoConduit(const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + RefPtr<MediaSessionConduit>* aConduitp); + + MediaConduitErrorCode EnsureExternalCodec(VideoSessionConduit& aConduit, + VideoCodecConfig* aConfig, + bool aIsSend); + + nsresult CreateOrGetTransportFlow(size_t aLevel, bool aIsRtcp, + const JsepTransport& transport, + RefPtr<TransportFlow>* out); + + nsresult GetTransportParameters(const JsepTrackPair& aTrackPair, + const JsepTrack& aTrack, + size_t* aLevelOut, + RefPtr<TransportFlow>* aRtpOut, + RefPtr<TransportFlow>* aRtcpOut, + nsAutoPtr<MediaPipelineFilter>* aFilterOut); + + nsresult ConfigureVideoCodecMode(const JsepTrack& aTrack, + VideoSessionConduit& aConduit); + +private: + // Not owned, and assumed to exist as long as the factory. + // The factory is a transient object, so this is fairly easy. + PeerConnectionMedia* mPCMedia; + PeerConnectionImpl* mPC; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp b/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp new file mode 100644 index 000000000..f10b4447f --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp @@ -0,0 +1,104 @@ +/* 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 "CSFLog.h" +#include "base/basictypes.h" +#include "MediaStreamList.h" +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/dom/MediaStreamListBinding.h" +#endif +#include "nsIScriptGlobalObject.h" +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.h" + +namespace mozilla { +namespace dom { + +MediaStreamList::MediaStreamList(PeerConnectionImpl* peerConnection, + StreamType type) + : mPeerConnection(peerConnection), + mType(type) +{ +} + +MediaStreamList::~MediaStreamList() +{ +} + +#ifdef MOZILLA_INTERNAL_API +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(MediaStreamList) +#else +NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamList) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamList) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MediaStreamList) +NS_IMPL_CYCLE_COLLECTION_TRACE_END +#endif + +NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaStreamList) +NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaStreamList) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamList) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +MediaStreamList::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) +{ +#ifdef MOZILLA_INTERNAL_API + return MediaStreamListBinding::Wrap(cx, this, aGivenProto); +#else + return nullptr; +#endif +} + +nsISupports* +MediaStreamList::GetParentObject() +{ + return mPeerConnection->GetWindow(); +} + +template<class T> +static DOMMediaStream* +GetStreamFromInfo(T* info, bool& found) +{ + if (!info) { + found = false; + return nullptr; + } + + found = true; + return info->GetMediaStream(); +} + +DOMMediaStream* +MediaStreamList::IndexedGetter(uint32_t index, bool& found) +{ + if (!mPeerConnection->media()) { // PeerConnection closed + found = false; + return nullptr; + } + if (mType == Local) { + return GetStreamFromInfo(mPeerConnection->media()-> + GetLocalStreamByIndex(index), found); + } + + return GetStreamFromInfo(mPeerConnection->media()-> + GetRemoteStreamByIndex(index), found); +} + +uint32_t +MediaStreamList::Length() +{ + if (!mPeerConnection->media()) { // PeerConnection closed + return 0; + } + return mType == Local ? mPeerConnection->media()->LocalStreamsLength() : + mPeerConnection->media()->RemoteStreamsLength(); +} + +} // namespace dom +} // namespace mozilla diff --git a/media/webrtc/signaling/src/peerconnection/MediaStreamList.h b/media/webrtc/signaling/src/peerconnection/MediaStreamList.h new file mode 100644 index 000000000..de9040227 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/MediaStreamList.h @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MediaStreamList_h__ +#define MediaStreamList_h__ + +#include "mozilla/ErrorResult.h" +#include "nsISupportsImpl.h" +#include "nsAutoPtr.h" +#include "nsWrapperCache.h" + +#ifdef USE_FAKE_MEDIA_STREAMS +#include "FakeMediaStreams.h" +#else +#include "DOMMediaStream.h" +#endif + +namespace mozilla { +class PeerConnectionImpl; +namespace dom { + +class MediaStreamList : public nsISupports, + public nsWrapperCache +{ +public: + enum StreamType { + Local, + Remote + }; + + MediaStreamList(PeerConnectionImpl* peerConnection, StreamType type); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaStreamList) + + virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) + override; + nsISupports* GetParentObject(); + + DOMMediaStream* IndexedGetter(uint32_t index, bool& found); + uint32_t Length(); + +private: + virtual ~MediaStreamList(); + + RefPtr<PeerConnectionImpl> mPeerConnection; + StreamType mType; +}; + +} // namespace dom +} // namespace mozilla + +#endif // MediaStreamList_h__ diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp new file mode 100644 index 000000000..515258efb --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp @@ -0,0 +1,452 @@ +/* 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 "CSFLog.h" + +#include "PeerConnectionImpl.h" +#include "PeerConnectionCtx.h" +#include "runnable_utils.h" +#include "prcvar.h" + +#include "mozilla/Telemetry.h" +#include "browser_logging/WebRtcLog.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/Preferences.h" +#include <mozilla/Types.h> +#endif + +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" + +#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER +#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER + +static const char* logTag = "PeerConnectionCtx"; + +namespace mozilla { + +using namespace dom; + +class PeerConnectionCtxShutdown : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + + PeerConnectionCtxShutdown() {} + + void Init() + { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (!observerService) + return; + + nsresult rv = NS_OK; + +#ifdef MOZILLA_INTERNAL_API + rv = observerService->AddObserver(this, + NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + MOZ_ALWAYS_SUCCEEDS(rv); +#endif + (void) rv; + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + CSFLogDebug(logTag, "Shutting down PeerConnectionCtx"); + PeerConnectionCtx::Destroy(); + + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + nsresult rv = observerService->RemoveObserver(this, + NS_XPCOM_SHUTDOWN_OBSERVER_ID); + MOZ_ALWAYS_SUCCEEDS(rv); + + // Make sure we're not deleted while still inside ::Observe() + RefPtr<PeerConnectionCtxShutdown> kungFuDeathGrip(this); + PeerConnectionCtx::gPeerConnectionCtxShutdown = nullptr; + } + return NS_OK; + } + +private: + virtual ~PeerConnectionCtxShutdown() + { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } +}; + +NS_IMPL_ISUPPORTS(PeerConnectionCtxShutdown, nsIObserver); +} + +namespace mozilla { + +PeerConnectionCtx* PeerConnectionCtx::gInstance; +nsIThread* PeerConnectionCtx::gMainThread; +StaticRefPtr<PeerConnectionCtxShutdown> PeerConnectionCtx::gPeerConnectionCtxShutdown; + +const std::map<const std::string, PeerConnectionImpl *>& +PeerConnectionCtx::mGetPeerConnections() +{ + return mPeerConnections; +} + +nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread, + nsIEventTarget* stsThread) { + if (!gMainThread) { + gMainThread = mainThread; + } else { + MOZ_ASSERT(gMainThread == mainThread); + } + + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + + if (!gInstance) { + CSFLogDebug(logTag, "Creating PeerConnectionCtx"); + PeerConnectionCtx *ctx = new PeerConnectionCtx(); + + res = ctx->Initialize(); + PR_ASSERT(NS_SUCCEEDED(res)); + if (!NS_SUCCEEDED(res)) + return res; + + gInstance = ctx; + + if (!PeerConnectionCtx::gPeerConnectionCtxShutdown) { + PeerConnectionCtx::gPeerConnectionCtxShutdown = new PeerConnectionCtxShutdown(); + PeerConnectionCtx::gPeerConnectionCtxShutdown->Init(); + } + } + + EnableWebRtcLog(); + return NS_OK; +} + +PeerConnectionCtx* PeerConnectionCtx::GetInstance() { + MOZ_ASSERT(gInstance); + return gInstance; +} + +bool PeerConnectionCtx::isActive() { + return gInstance; +} + +void PeerConnectionCtx::Destroy() { + CSFLogDebug(logTag, "%s", __FUNCTION__); + + if (gInstance) { + gInstance->Cleanup(); + delete gInstance; + gInstance = nullptr; + } + + StopWebRtcLog(); +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries; + +// Telemetry reporting every second after start of first call. +// The threading model around the media pipelines is weird: +// - The pipelines are containers, +// - containers that are only safe on main thread, with members only safe on STS, +// - hence the there and back again approach. + +static auto +FindId(const Sequence<RTCInboundRTPStreamStats>& aArray, + const nsString &aId) -> decltype(aArray.Length()) { + for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) { + if (aArray[i].mId.Value() == aId) { + return i; + } + } + return aArray.NoIndex; +} + +static auto +FindId(const nsTArray<nsAutoPtr<RTCStatsReportInternal>>& aArray, + const nsString &aId) -> decltype(aArray.Length()) { + for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) { + if (aArray[i]->mPcid == aId) { + return i; + } + } + return aArray.NoIndex; +} + +static void +FreeOnMain_m(nsAutoPtr<RTCStatsQueries> aQueryList) { + MOZ_ASSERT(NS_IsMainThread()); +} + +static void +EverySecondTelemetryCallback_s(nsAutoPtr<RTCStatsQueries> aQueryList) { + using namespace Telemetry; + + if(!PeerConnectionCtx::isActive()) { + return; + } + PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance(); + + for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { + PeerConnectionImpl::ExecuteStatsQuery_s(*q); + auto& r = *(*q)->report; + if (r.mInboundRTPStreamStats.WasPassed()) { + // First, get reports from a second ago, if any, for calculations below + const Sequence<RTCInboundRTPStreamStats> *lastInboundStats = nullptr; + { + auto i = FindId(ctx->mLastReports, r.mPcid); + if (i != ctx->mLastReports.NoIndex) { + lastInboundStats = &ctx->mLastReports[i]->mInboundRTPStreamStats.Value(); + } + } + // Then, look for the things we want telemetry on + auto& array = r.mInboundRTPStreamStats.Value(); + for (decltype(array.Length()) i = 0; i < array.Length(); i++) { + auto& s = array[i]; + bool isAudio = (s.mId.Value().Find("audio") != -1); + if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed() && + (s.mPacketsLost.Value() + s.mPacketsReceived.Value()) != 0) { + ID id; + if (s.mIsRemote) { + id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE : + WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE; + } else { + id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE : + WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE; + } + // *1000 so we can read in 10's of a percent (permille) + Accumulate(id, + (s.mPacketsLost.Value() * 1000) / + (s.mPacketsLost.Value() + s.mPacketsReceived.Value())); + } + if (s.mJitter.WasPassed()) { + ID id; + if (s.mIsRemote) { + id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER : + WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER; + } else { + id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER : + WEBRTC_VIDEO_QUALITY_INBOUND_JITTER; + } + Accumulate(id, s.mJitter.Value()); + } + if (s.mMozRtt.WasPassed()) { + MOZ_ASSERT(s.mIsRemote); + ID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT : + WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT; + Accumulate(id, s.mMozRtt.Value()); + } + if (lastInboundStats && s.mBytesReceived.WasPassed()) { + auto& laststats = *lastInboundStats; + auto i = FindId(laststats, s.mId.Value()); + if (i != laststats.NoIndex) { + auto& lasts = laststats[i]; + if (lasts.mBytesReceived.WasPassed()) { + auto delta_ms = int32_t(s.mTimestamp.Value() - + lasts.mTimestamp.Value()); + // In theory we're called every second, so delta *should* be in that range. + // Small deltas could cause errors due to division + if (delta_ms > 500 && delta_ms < 60000) { + ID id; + if (s.mIsRemote) { + id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS : + WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS; + } else { + id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS : + WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS; + } + Accumulate(id, ((s.mBytesReceived.Value() - + lasts.mBytesReceived.Value()) * 8) / delta_ms); + } + // We could accumulate values until enough time has passed + // and then Accumulate() but this isn't that important. + } + } + } + } + } + } + // Steal and hang on to reports for the next second + ctx->mLastReports.Clear(); + for (auto q = aQueryList->begin(); q != aQueryList->end(); ++q) { + ctx->mLastReports.AppendElement((*q)->report.forget()); // steal avoids copy + } + // Container must be freed back on main thread + NS_DispatchToMainThread(WrapRunnableNM(&FreeOnMain_m, aQueryList), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer, void *closure) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(PeerConnectionCtx::isActive()); + auto ctx = static_cast<PeerConnectionCtx*>(closure); + if (ctx->mPeerConnections.empty()) { + return; + } + nsresult rv; + nsCOMPtr<nsIEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return; + } + MOZ_ASSERT(stsThread); + + nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries); + for (auto p = ctx->mPeerConnections.begin(); + p != ctx->mPeerConnections.end(); ++p) { + if (p->second->HasMedia()) { + if (!queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true)))) { + return; + } + if (NS_WARN_IF(NS_FAILED(p->second->BuildStatsQuery_m(nullptr, // all tracks + queries->back())))) { + queries->popBack(); + } else { + MOZ_ASSERT(queries->back()->report); + } + } + } + if (!queries->empty()) { + rv = RUN_ON_THREAD(stsThread, + WrapRunnableNM(&EverySecondTelemetryCallback_s, queries), + NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); + } +} +#endif + +nsresult PeerConnectionCtx::Initialize() { + initGMP(); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + mTelemetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + MOZ_ASSERT(mTelemetryTimer); + nsresult rv = mTelemetryTimer->SetTarget(gMainThread); + NS_ENSURE_SUCCESS(rv, rv); + mTelemetryTimer->InitWithFuncCallback(EverySecondTelemetryCallback_m, this, 1000, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + + if (XRE_IsContentProcess()) { + WebrtcGlobalChild::Create(); + } +#endif // MOZILLA_INTERNAL_API + + return NS_OK; +} + +static void GMPReady_m() { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->onGMPReady(); + } +}; + +static void GMPReady() { + PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(&GMPReady_m), + NS_DISPATCH_NORMAL); +}; + +void PeerConnectionCtx::initGMP() +{ + mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + + if (!mGMPService) { + CSFLogError(logTag, "%s failed to get the gecko-media-plugin-service", + __FUNCTION__); + return; + } + + nsCOMPtr<nsIThread> thread; + nsresult rv = mGMPService->GetThread(getter_AddRefs(thread)); + + if (NS_FAILED(rv)) { + mGMPService = nullptr; + CSFLogError(logTag, + "%s failed to get the gecko-media-plugin thread, err=%u", + __FUNCTION__, + static_cast<unsigned>(rv)); + return; + } + + // presumes that all GMP dir scans have been queued for the GMPThread + thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL); +} + +nsresult PeerConnectionCtx::Cleanup() { + CSFLogDebug(logTag, "%s", __FUNCTION__); + + mQueuedJSEPOperations.Clear(); + mGMPService = nullptr; + return NS_OK; +} + +PeerConnectionCtx::~PeerConnectionCtx() { + // ensure mTelemetryTimer ends on main thread + MOZ_ASSERT(NS_IsMainThread()); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (mTelemetryTimer) { + mTelemetryTimer->Cancel(); + } +#endif +}; + +void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) { + mQueuedJSEPOperations.AppendElement(aOperation); +} + +void PeerConnectionCtx::onGMPReady() { + mGMPReady = true; + for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) { + mQueuedJSEPOperations[i]->Run(); + } + mQueuedJSEPOperations.Clear(); +} + +bool PeerConnectionCtx::gmpHasH264() { + if (!mGMPService) { + return false; + } + + // XXX I'd prefer if this was all known ahead of time... + + nsTArray<nsCString> tags; + tags.AppendElement(NS_LITERAL_CSTRING("h264")); + + bool has_gmp; + nsresult rv; + rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER), + &tags, + &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + &tags, + &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + return true; +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h new file mode 100644 index 000000000..3f7d6250b --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h @@ -0,0 +1,109 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef peerconnectionctx_h___h__ +#define peerconnectionctx_h___h__ + +#include <string> + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "WebrtcGlobalChild.h" +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "PeerConnectionImpl.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsIRunnable.h" + +namespace mozilla { +class PeerConnectionCtxShutdown; + +namespace dom { +class WebrtcGlobalInformation; +} + +// A class to hold some of the singleton objects we need: +// * The global PeerConnectionImpl table and its associated lock. +// * Stats report objects for PCs that are gone +// * GMP related state +class PeerConnectionCtx { + public: + static nsresult InitializeGlobal(nsIThread *mainThread, nsIEventTarget *stsThread); + static PeerConnectionCtx* GetInstance(); + static bool isActive(); + static void Destroy(); + + bool isReady() { + // If mGMPService is not set, we aren't using GMP. + if (mGMPService) { + return mGMPReady; + } + return true; + } + + void queueJSEPOperation(nsIRunnable* aJSEPOperation); + void onGMPReady(); + + bool gmpHasH264(); + + // Make these classes friend so that they can access mPeerconnections. + friend class PeerConnectionImpl; + friend class PeerConnectionWrapper; + friend class mozilla::dom::WebrtcGlobalInformation; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // WebrtcGlobalInformation uses this; we put it here so we don't need to + // create another shutdown observer class. + mozilla::dom::Sequence<mozilla::dom::RTCStatsReportInternal> + mStatsForClosedPeerConnections; +#endif + + const std::map<const std::string, PeerConnectionImpl *>& mGetPeerConnections(); + private: + // We could make these available only via accessors but it's too much trouble. + std::map<const std::string, PeerConnectionImpl *> mPeerConnections; + + PeerConnectionCtx() : mGMPReady(false) {} + // This is a singleton, so don't copy construct it, etc. + PeerConnectionCtx(const PeerConnectionCtx& other) = delete; + void operator=(const PeerConnectionCtx& other) = delete; + virtual ~PeerConnectionCtx(); + + nsresult Initialize(); + nsresult Cleanup(); + + void initGMP(); + + static void + EverySecondTelemetryCallback_m(nsITimer* timer, void *); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsCOMPtr<nsITimer> mTelemetryTimer; + +public: + // TODO(jib): If we ever enable move semantics on std::map... + //std::map<nsString,nsAutoPtr<mozilla::dom::RTCStatsReportInternal>> mLastReports; + nsTArray<nsAutoPtr<mozilla::dom::RTCStatsReportInternal>> mLastReports; +private: +#endif + + // We cannot form offers/answers properly until the Gecko Media Plugin stuff + // has been initted, which is a complicated mess of thread dispatches, + // including sync dispatches to main. So, we need to be able to queue up + // offer creation (or SetRemote, when we're the answerer) until all of this is + // ready to go, since blocking on this init is just begging for deadlock. + nsCOMPtr<mozIGeckoMediaPluginService> mGMPService; + bool mGMPReady; + nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations; + + static PeerConnectionCtx *gInstance; +public: + static nsIThread *gMainThread; + static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxShutdown> gPeerConnectionCtxShutdown; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp new file mode 100644 index 000000000..33422ed7a --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -0,0 +1,4176 @@ +/* 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 <cstdlib> +#include <cerrno> +#include <deque> +#include <set> +#include <sstream> +#include <vector> + +#include "CSFLog.h" +#include "timecard.h" + +#include "jsapi.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" + +#include "nsNetCID.h" +#include "nsIProperty.h" +#include "nsIPropertyBag2.h" +#include "nsIServiceManager.h" +#include "nsISimpleEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "nsISocketTransportService.h" +#include "nsIConsoleService.h" +#include "nsThreadUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsProxyRelease.h" +#include "nsQueryObject.h" +#include "prtime.h" + +#include "AudioConduit.h" +#include "VideoConduit.h" +#include "runnable_utils.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.h" +#include "nsDOMDataChannelDeclarations.h" +#include "dtlsidentity.h" +#include "signaling/src/sdp/SdpAttribute.h" + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepSession.h" +#include "signaling/src/jsep/JsepSessionImpl.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#ifdef XP_WIN +// We need to undef the MS macro for nsIDocument::CreateEvent +#ifdef CreateEvent +#undef CreateEvent +#endif +#endif // XP_WIN + +#include "nsIDocument.h" +#include "nsGlobalWindow.h" +#include "nsDOMDataChannel.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "mozilla/PublicSSL.h" +#include "nsXULAppAPI.h" +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "nsIScriptError.h" +#include "nsPrintfCString.h" +#include "nsURLHelper.h" +#include "nsNetUtil.h" +#include "nsIURLParser.h" +#include "nsIDOMDataChannel.h" +#include "nsIDOMLocation.h" +#include "nsNullPrincipal.h" +#include "mozilla/PeerIdentity.h" +#include "mozilla/dom/RTCCertificate.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "mozilla/dom/RTCDTMFSenderBinding.h" +#include "mozilla/dom/RTCDTMFToneChangeEvent.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/dom/PeerConnectionImplBinding.h" +#include "mozilla/dom/DataChannelBinding.h" +#include "mozilla/dom/PerformanceTiming.h" +#include "mozilla/dom/PluginCrashedEvent.h" +#include "MediaStreamList.h" +#include "MediaStreamTrack.h" +#include "AudioStreamTrack.h" +#include "VideoStreamTrack.h" +#include "nsIScriptGlobalObject.h" +#include "MediaStreamGraph.h" +#include "DOMMediaStream.h" +#include "rlogconnector.h" +#include "WebrtcGlobalInformation.h" +#include "mozilla/dom/Event.h" +#include "nsIDOMCustomEvent.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/net/DataChannelProtocol.h" +#endif + +#ifndef USE_FAKE_MEDIA_STREAMS +#include "MediaStreamGraphImpl.h" +#endif + +#ifdef XP_WIN +// We need to undef the MS macro again in case the windows include file +// got imported after we included nsIDocument.h +#ifdef CreateEvent +#undef CreateEvent +#endif +#endif // XP_WIN + +#ifndef USE_FAKE_MEDIA_STREAMS +#include "MediaSegment.h" +#endif + +#ifdef USE_FAKE_PCOBSERVER +#include "FakePCObserver.h" +#else +#include "mozilla/dom/PeerConnectionObserverBinding.h" +#endif +#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" + +#ifdef MOZ_WEBRTC_OMX +#include "OMXVideoCodec.h" +#include "OMXCodecWrapper.h" +#endif + +#define ICE_PARSING "In RTCConfiguration passed to RTCPeerConnection constructor" + +using namespace mozilla; +using namespace mozilla::dom; + +typedef PCObserverString ObString; + +static const char* logTag = "PeerConnectionImpl"; + +// Getting exceptions back down from PCObserver is generally not harmful. +namespace { +// This is a terrible hack. The problem is that SuppressException is not +// inline, and we link this file without libxul in some cases (e.g. for our test +// setup). So we can't use ErrorResult or IgnoredErrorResult because those call +// SuppressException... And we can't use FastErrorResult because we can't +// include BindingUtils.h, because our linking is completely fucked up. Use +// BaseErrorResult directly. Please do not let me see _anyone_ doing this +// without really careful review from someone who knows what they are doing. +class JSErrorResult : + public binding_danger::TErrorResult<binding_danger::JustAssertCleanupPolicy> +{ +public: + ~JSErrorResult() + { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + SuppressException(); +#endif + } +}; + +// The WrapRunnable() macros copy passed-in args and passes them to the function +// later on the other thread. ErrorResult cannot be passed like this because it +// disallows copy-semantics. +// +// This WrappableJSErrorResult hack solves this by not actually copying the +// ErrorResult, but creating a new one instead, which works because we don't +// care about the result. +// +// Since this is for JS-calls, these can only be dispatched to the main thread. + +class WrappableJSErrorResult { +public: + WrappableJSErrorResult() + : mRv(MakeUnique<JSErrorResult>()), + isCopy(false) {} + WrappableJSErrorResult(const WrappableJSErrorResult &other) + : mRv(MakeUnique<JSErrorResult>()), + isCopy(true) {} + ~WrappableJSErrorResult() { + if (isCopy) { + MOZ_ASSERT(NS_IsMainThread()); + } + } + operator JSErrorResult &() { return *mRv; } + operator ErrorResult &() { return *mRv; } +private: + mozilla::UniquePtr<JSErrorResult> mRv; + bool isCopy; +}; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +static nsresult InitNSSInContent() +{ + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); + + if (!XRE_IsContentProcess()) { + MOZ_ASSERT_UNREACHABLE("Must be called in content process"); + return NS_ERROR_FAILURE; + } + + static bool nssStarted = false; + if (nssStarted) { + return NS_OK; + } + + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + CSFLogError(logTag, "NSS_NoDB_Init failed."); + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + CSFLogError(logTag, "Fail to set up nss cipher suite."); + return NS_ERROR_FAILURE; + } + + mozilla::psm::DisableMD5(); + + nssStarted = true; + + return NS_OK; +} +#endif // MOZILLA_INTERNAL_API + +namespace mozilla { + class DataChannel; +} + +class nsIDOMDataChannel; + +PRLogModuleInfo *signalingLogInfo() { + static PRLogModuleInfo *logModuleInfo = nullptr; + if (!logModuleInfo) { + logModuleInfo = PR_NewLogModule("signaling"); + } + return logModuleInfo; +} + +// XXX Workaround for bug 998092 to maintain the existing broken semantics +template<> +struct nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void> { + static const nsIID kIID; +}; +const nsIID nsISupportsWeakReference::COMTypeInfo<nsSupportsWeakReference, void>::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<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) +{ + return PeerConnectionImplBinding::Wrap(aCx, this, aGivenProto, aReflector); +} +#endif + +bool PCUuidGenerator::Generate(std::string* idp) { + nsresult rv; + + if(!mGenerator) { + mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + if (!mGenerator) { + return false; + } + } + + nsID id; + rv = mGenerator->GenerateUUIDInPlace(&id); + if (NS_FAILED(rv)) { + return false; + } + char buffer[NSID_LENGTH]; + id.ToProvidedString(buffer); + idp->assign(buffer); + + return true; +} + +bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) +{ +#if defined(MOZILLA_EXTERNAL_LINKAGE) + return false; +#else + if (!aWindow) { + return false; + } + + nsIDocument *doc = aWindow->GetExtantDoc(); + if (!doc) { + return false; + } + + nsILoadContext *loadContext = doc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); +#endif +} + +PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) +: mTimeCard(MOZ_LOG_TEST(signalingLogInfo(),LogLevel::Error) ? + create_timecard() : nullptr) + , mSignalingState(PCImplSignalingState::SignalingStable) + , mIceConnectionState(PCImplIceConnectionState::New) + , mIceGatheringState(PCImplIceGatheringState::New) + , mDtlsConnected(false) + , mWindow(nullptr) +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + , mCertificate(nullptr) +#else + , mIdentity(nullptr) +#endif + , mPrivacyRequested(false) + , mSTSThread(nullptr) + , mAllowIceLoopback(false) + , mAllowIceLinkLocal(false) + , mMedia(nullptr) + , mUuidGen(MakeUnique<PCUuidGenerator>()) + , mNumAudioStreams(0) + , mNumVideoStreams(0) + , mHaveConfiguredCodecs(false) + , mHaveDataStream(false) + , mAddCandidateErrorCount(0) + , mTrickle(true) // TODO(ekr@rtfm.com): Use pref + , mNegotiationNeeded(false) + , mPrivateWindow(false) +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + MOZ_ASSERT(NS_IsMainThread()); + auto log = RLogConnector::CreateInstance(); + if (aGlobal) { + mWindow = do_QueryInterface(aGlobal->GetAsSupports()); + if (IsPrivateBrowsing(mWindow)) { + mPrivateWindow = true; + log->EnterPrivateMode(); + } + } +#endif + CSFLogInfo(logTag, "%s: PeerConnectionImpl constructor for %s", + __FUNCTION__, mHandle.c_str()); + STAMP_TIMECARD(mTimeCard, "Constructor Completed"); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + mAllowIceLoopback = Preferences::GetBool( + "media.peerconnection.ice.loopback", false); + mAllowIceLinkLocal = Preferences::GetBool( + "media.peerconnection.ice.link_local", false); +#endif + memset(mMaxReceiving, 0, sizeof(mMaxReceiving)); + memset(mMaxSending, 0, sizeof(mMaxSending)); +} + +PeerConnectionImpl::~PeerConnectionImpl() +{ + if (mTimeCard) { + STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); + print_timecard(mTimeCard); + destroy_timecard(mTimeCard); + mTimeCard = nullptr; + } + // This aborts if not on main thread (in Debug builds) + PC_AUTO_ENTER_API_CALL_NO_CHECK(); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (mPrivateWindow) { + auto * log = RLogConnector::GetInstance(); + if (log) { + log->ExitPrivateMode(); + } + mPrivateWindow = false; + } +#endif + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle); + } else { + CSFLogError(logTag, "PeerConnectionCtx is already gone. Ignoring..."); + } + + CSFLogInfo(logTag, "%s: PeerConnectionImpl destructor invoked for %s", + __FUNCTION__, mHandle.c_str()); + + Close(); + + // Since this and Initialize() occur on MainThread, they can't both be + // running at once + + // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we + // probably want to shut it down more aggressively to save memory. We + // could shut down here when there are no uses. It might be more optimal + // to release off a timer (and XPCOM Shutdown) to avoid churn +} + +already_AddRefed<DOMMediaStream> +PeerConnectionImpl::MakeMediaStream() +{ + MediaStreamGraph* graph = + MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER, + AudioChannel::Normal); + + RefPtr<DOMMediaStream> stream = + DOMMediaStream::CreateSourceStreamAsInput(GetWindow(), graph); + + CSFLogDebug(logTag, "Created media stream %p, inner: %p", stream.get(), stream->GetInputStream()); + + return stream.forget(); +} + +nsresult +PeerConnectionImpl::CreateRemoteSourceStreamInfo(RefPtr<RemoteSourceStreamInfo>* + aInfo, + const std::string& aStreamID) +{ + MOZ_ASSERT(aInfo); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + RefPtr<DOMMediaStream> stream = MakeMediaStream(); + if (!stream) { + return NS_ERROR_FAILURE; + } + + RefPtr<RemoteSourceStreamInfo> remote; + remote = new RemoteSourceStreamInfo(stream.forget(), mMedia, aStreamID); + *aInfo = remote; + + return NS_OK; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +/** + * In JS, an RTCConfiguration looks like this: + * + * { "iceServers": [ { url:"stun:stun.example.org" }, + * { url:"turn:turn.example.org?transport=udp", + * username: "jib", credential:"mypass"} ] } + * + * This function converts that into an internal PeerConnectionConfiguration + * object. + */ +nsresult +PeerConnectionConfiguration::Init(const RTCConfiguration& aSrc) +{ + if (aSrc.mIceServers.WasPassed()) { + for (size_t i = 0; i < aSrc.mIceServers.Value().Length(); i++) { + nsresult rv = AddIceServer(aSrc.mIceServers.Value()[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + switch (aSrc.mBundlePolicy) { + case dom::RTCBundlePolicy::Balanced: + setBundlePolicy(kBundleBalanced); + break; + case dom::RTCBundlePolicy::Max_compat: + setBundlePolicy(kBundleMaxCompat); + break; + case dom::RTCBundlePolicy::Max_bundle: + setBundlePolicy(kBundleMaxBundle); + break; + default: + MOZ_CRASH(); + } + + switch (aSrc.mIceTransportPolicy) { + case dom::RTCIceTransportPolicy::Relay: + setIceTransportPolicy(NrIceCtx::ICE_POLICY_RELAY); + break; + case dom::RTCIceTransportPolicy::All: + if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) { + setIceTransportPolicy(NrIceCtx::ICE_POLICY_NO_HOST); + } else { + setIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL); + } + break; + default: + MOZ_CRASH(); + } + return NS_OK; +} + +nsresult +PeerConnectionConfiguration::AddIceServer(const RTCIceServer &aServer) +{ + NS_ENSURE_STATE(aServer.mUrls.WasPassed()); + NS_ENSURE_STATE(aServer.mUrls.Value().IsStringSequence()); + auto &urls = aServer.mUrls.Value().GetAsStringSequence(); + for (size_t i = 0; i < urls.Length(); i++) { + // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than + // nsStandardURL. To parse STUN/TURN URI's to spec + // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3 + // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3 + // we parse out the query-string, and use ParseAuthority() on the rest + RefPtr<nsIURI> url; + nsresult rv = NS_NewURI(getter_AddRefs(url), urls[i]); + NS_ENSURE_SUCCESS(rv, rv); + bool isStun = false, isStuns = false, isTurn = false, isTurns = false; + url->SchemeIs("stun", &isStun); + url->SchemeIs("stuns", &isStuns); + url->SchemeIs("turn", &isTurn); + url->SchemeIs("turns", &isTurns); + if (!(isStun || isStuns || isTurn || isTurns)) { + return NS_ERROR_FAILURE; + } + if (isTurns || isStuns) { + continue; // TODO: Support TURNS and STUNS (Bug 1056934) + } + nsAutoCString spec; + rv = url->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509) + int32_t port; + nsAutoCString host; + nsAutoCString transport; + { + uint32_t hostPos; + int32_t hostLen; + nsAutoCString path; + rv = url->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Tolerate query-string + parse 'transport=[udp|tcp]' by hand. + int32_t questionmark = path.FindChar('?'); + if (questionmark >= 0) { + const nsCString match = NS_LITERAL_CSTRING("transport="); + + for (int32_t i = questionmark, endPos; i >= 0; i = endPos) { + endPos = path.FindCharInSet("&", i + 1); + const nsDependentCSubstring fieldvaluepair = Substring(path, i + 1, + endPos); + if (StringBeginsWith(fieldvaluepair, match)) { + transport = Substring(fieldvaluepair, match.Length()); + ToLowerCase(transport); + } + } + path.SetLength(questionmark); + } + + rv = net_GetAuthURLParser()->ParseAuthority(path.get(), path.Length(), + nullptr, nullptr, + nullptr, nullptr, + &hostPos, &hostLen, &port); + NS_ENSURE_SUCCESS(rv, rv); + if (!hostLen) { + return NS_ERROR_FAILURE; + } + if (hostPos > 1) /* The username was removed */ + return NS_ERROR_FAILURE; + path.Mid(host, hostPos, hostLen); + } + if (port == -1) + port = (isStuns || isTurns)? 5349 : 3478; + + if (isTurn || isTurns) { + NS_ConvertUTF16toUTF8 credential(aServer.mCredential.Value()); + NS_ConvertUTF16toUTF8 username(aServer.mUsername.Value()); + + if (!addTurnServer(host.get(), port, + username.get(), + credential.get(), + (transport.IsEmpty() ? + kNrIceTransportUdp : transport.get()))) { + return NS_ERROR_FAILURE; + } + } else { + if (!addStunServer(host.get(), port, (transport.IsEmpty() ? + kNrIceTransportUdp : transport.get()))) { + return NS_ERROR_FAILURE; + } + } + } + return NS_OK; +} +#endif + +nsresult +PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindow* aWindow, + const PeerConnectionConfiguration& aConfiguration, + nsISupports* aThread) +{ + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aThread); + if (!mThread) { + mThread = do_QueryInterface(aThread); + MOZ_ASSERT(mThread); + } + CheckThread(); + + mPCObserver = do_GetWeakReference(&aObserver); + + // Find the STS thread + + mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_ASSERT(mSTSThread); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + + // Initialize NSS if we are in content process. For chrome process, NSS should already + // been initialized. + if (XRE_IsParentProcess()) { + // This code interferes with the C++ unit test startup code. + nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res); + NS_ENSURE_SUCCESS(res, res); + } else { + NS_ENSURE_SUCCESS(res = InitNSSInContent(), res); + } + + // Currently no standalone unit tests for DataChannel, + // which is the user of mWindow + MOZ_ASSERT(aWindow); + mWindow = aWindow->AsInner(); + NS_ENSURE_STATE(mWindow); +#endif // MOZILLA_INTERNAL_API + + PRTime timestamp = PR_Now(); + // Ok if we truncate this. + char temp[128]; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsAutoCString locationCStr; + + if (nsCOMPtr<nsIDOMLocation> location = mWindow->GetLocation()) { + nsAutoString locationAStr; + location->ToString(locationAStr); + + CopyUTF16toUTF8(locationAStr, locationCStr); + } + + SprintfLiteral(temp, + "%" PRIu64 " (id=%" PRIu64 " url=%s)", + static_cast<uint64_t>(timestamp), + static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0), + locationCStr.get() ? locationCStr.get() : "NULL"); + +#else + SprintfLiteral(temp, "%" PRIu64, static_cast<uint64_t>(timestamp)); +#endif // MOZILLA_INTERNAL_API + + mName = temp; + + // Generate a random handle + unsigned char handle_bin[8]; + SECStatus rv; + rv = PK11_GenerateRandom(handle_bin, sizeof(handle_bin)); + if (rv != SECSuccess) { + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + char hex[17]; + SprintfLiteral(hex, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", + handle_bin[0], + handle_bin[1], + handle_bin[2], + handle_bin[3], + handle_bin[4], + handle_bin[5], + handle_bin[6], + handle_bin[7]); + + mHandle = hex; + + STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx"); + res = PeerConnectionCtx::InitializeGlobal(mThread, mSTSThread); + NS_ENSURE_SUCCESS(res, res); + + mMedia = new PeerConnectionMedia(this); + + // Connect ICE slots. + mMedia->SignalIceGatheringStateChange.connect( + this, + &PeerConnectionImpl::IceGatheringStateChange); + mMedia->SignalUpdateDefaultCandidate.connect( + this, + &PeerConnectionImpl::UpdateDefaultCandidate); + mMedia->SignalEndOfLocalCandidates.connect( + this, + &PeerConnectionImpl::EndOfLocalCandidates); + mMedia->SignalIceConnectionStateChange.connect( + this, + &PeerConnectionImpl::IceConnectionStateChange); + + mMedia->SignalCandidate.connect(this, &PeerConnectionImpl::CandidateReady); + + // Initialize the media object. + res = mMedia->Init(aConfiguration.getStunServers(), + aConfiguration.getTurnServers(), + aConfiguration.getIceTransportPolicy()); + if (NS_FAILED(res)) { + CSFLogError(logTag, "%s: Couldn't initialize media object", __FUNCTION__); + return res; + } + + PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this; + + mJsepSession = MakeUnique<JsepSessionImpl>(mName, + MakeUnique<PCUuidGenerator>()); + + res = mJsepSession->Init(); + if (NS_FAILED(res)) { + CSFLogError(logTag, "%s: Couldn't init JSEP Session, res=%u", + __FUNCTION__, + static_cast<unsigned>(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<unsigned>(res)); + return res; + } + +#if defined(MOZILLA_EXTERNAL_LINKAGE) + { + mIdentity = DtlsIdentity::Generate(); + if (!mIdentity) { + return NS_ERROR_FAILURE; + } + + std::vector<uint8_t> 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<unsigned>(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<uint8_t> 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<unsigned>(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<unsigned>(rv)); + mCertificate = nullptr; + } +} + +const RefPtr<mozilla::dom::RTCCertificate>& +PeerConnectionImpl::Certificate() const +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mCertificate; +} +#endif + +RefPtr<DtlsIdentity> +PeerConnectionImpl::Identity() const +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + MOZ_ASSERT(mCertificate); + return mCertificate->CreateDtlsIdentity(); +#else + RefPtr<DtlsIdentity> id = mIdentity; + return id; +#endif +} + +class CompareCodecPriority { + public: + void SetPreferredCodec(int32_t preferredCodec) { + // This pref really ought to be a string, preferably something like + // "H264" or "VP8" instead of a payload type. + // Bug 1101259. + std::ostringstream os; + os << preferredCodec; + mPreferredCodec = os.str(); + } + + bool operator()(JsepCodecDescription* lhs, + JsepCodecDescription* rhs) const { + if (!mPreferredCodec.empty() && + lhs->mDefaultPt == mPreferredCodec && + rhs->mDefaultPt != mPreferredCodec) { + return true; + } + + if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) { + return true; + } + + return false; + } + + private: + std::string mPreferredCodec; +}; + +class ConfigureCodec { + public: + explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch) : + mHardwareH264Enabled(false), + mHardwareH264Supported(false), + mSoftwareH264Enabled(false), + mH264Enabled(false), + mVP9Enabled(false), + mH264Level(13), // minimum suggested for WebRTC spec + mH264MaxBr(0), // Unlimited + mH264MaxMbps(0), // Unlimited + mVP8MaxFs(0), + mVP8MaxFr(0), + mUseTmmbr(false), + mUseRemb(false), + mUseAudioFec(false), + mRedUlpfecEnabled(false), + mDtmfEnabled(false) + { +#ifdef MOZ_WEBRTC_OMX + // Check to see if what HW codecs are available (not in use) at this moment. + // Note that streaming video decode can reserve a decoder + + // XXX See bug 1018791 Implement W3 codec reservation policy + // Note that currently, OMXCodecReservation needs to be held by an sp<> because it puts + // 'this' into an sp<EventListener> to talk to the resource reservation code + + // This pref is a misnomer; it is solely for h264 _hardware_ support. + branch->GetBoolPref("media.peerconnection.video.h264_enabled", + &mHardwareH264Enabled); + + if (mHardwareH264Enabled) { + // Ok, it is preffed on. Can we actually do it? + android::sp<android::OMXCodecReservation> encode = new android::OMXCodecReservation(true); + android::sp<android::OMXCodecReservation> decode = new android::OMXCodecReservation(false); + + // Currently we just check if they're available right now, which will fail if we're + // trying to call ourself, for example. It will work for most real-world cases, like + // if we try to add a person to a 2-way call to make a 3-way mesh call + if (encode->ReserveOMXCodec() && decode->ReserveOMXCodec()) { + CSFLogDebug( logTag, "%s: H264 hardware codec available", __FUNCTION__); + mHardwareH264Supported = true; + } + } + +#endif // MOZ_WEBRTC_OMX + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264(); +#else + // For unit-tests + mSoftwareH264Enabled = true; +#endif + + mH264Enabled = mHardwareH264Supported || mSoftwareH264Enabled; + + branch->GetIntPref("media.navigator.video.h264.level", &mH264Level); + mH264Level &= 0xFF; + + branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr); + +#ifdef MOZ_WEBRTC_OMX + // Level 1.2; but let's allow CIF@30 or QVGA@30+ by default + mH264MaxMbps = 11880; +#endif + + branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps); + + branch->GetBoolPref("media.peerconnection.video.vp9_enabled", + &mVP9Enabled); + + branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs); + if (mVP8MaxFs <= 0) { + mVP8MaxFs = 12288; // We must specify something other than 0 + } + + branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr); + if (mVP8MaxFr <= 0) { + mVP8MaxFr = 60; // We must specify something other than 0 + } + + // TMMBR is enabled from a pref in about:config + branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr); + + // REMB is enabled by default, but can be disabled from about:config + branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb); + + branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec); + + branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled", + &mRedUlpfecEnabled); + + // media.peerconnection.dtmf.enabled controls both sdp generation for + // DTMF support as well as DTMF exposure to DOM + branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled); + } + + void operator()(JsepCodecDescription* codec) const + { + switch (codec->mType) { + case SdpMediaSection::kAudio: + { + JsepAudioCodecDescription& audioCodec = + static_cast<JsepAudioCodecDescription&>(*codec); + if (audioCodec.mName == "opus") { + audioCodec.mFECEnabled = mUseAudioFec; + } else if (audioCodec.mName == "telephone-event") { + audioCodec.mEnabled = mDtmfEnabled; + } + } + break; + case SdpMediaSection::kVideo: + { + JsepVideoCodecDescription& videoCodec = + static_cast<JsepVideoCodecDescription&>(*codec); + + if (videoCodec.mName == "H264") { + // Override level + videoCodec.mProfileLevelId &= 0xFFFF00; + videoCodec.mProfileLevelId |= mH264Level; + + videoCodec.mConstraints.maxBr = mH264MaxBr; + + videoCodec.mConstraints.maxMbps = mH264MaxMbps; + + // Might disable it, but we set up other params anyway + videoCodec.mEnabled = mH264Enabled; + + if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) { + // We're assuming packetization mode 0 is unsupported by + // hardware. + videoCodec.mEnabled = false; + } + + if (mHardwareH264Supported) { + videoCodec.mStronglyPreferred = true; + } + } else if (videoCodec.mName == "red") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "ulpfec") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") { + if (videoCodec.mName == "VP9" && !mVP9Enabled) { + videoCodec.mEnabled = false; + break; + } + videoCodec.mConstraints.maxFs = mVP8MaxFs; + videoCodec.mConstraints.maxFps = mVP8MaxFr; + } + + if (mUseTmmbr) { + videoCodec.EnableTmmbr(); + } + if (mUseRemb) { + videoCodec.EnableRemb(); + } + } + break; + case SdpMediaSection::kText: + case SdpMediaSection::kApplication: + case SdpMediaSection::kMessage: + {} // Nothing to configure for these. + } + } + + private: + bool mHardwareH264Enabled; + bool mHardwareH264Supported; + bool mSoftwareH264Enabled; + bool mH264Enabled; + bool mVP9Enabled; + int32_t mH264Level; + int32_t mH264MaxBr; + int32_t mH264MaxMbps; + int32_t mVP8MaxFs; + int32_t mVP8MaxFr; + bool mUseTmmbr; + bool mUseRemb; + bool mUseAudioFec; + bool mRedUlpfecEnabled; + bool mDtmfEnabled; +}; + +class ConfigureRedCodec { + public: + explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch, + std::vector<uint8_t>* redundantEncodings) : + mRedundantEncodings(redundantEncodings) + { + // if we wanted to override or modify which encodings are considered + // for redundant encodings, we'd probably want to handle it here by + // checking prefs modifying the operator() code below + } + + void operator()(JsepCodecDescription* codec) const + { + if (codec->mType == SdpMediaSection::kVideo && + codec->mEnabled == false) { + uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); + // don't search for the codec payload type unless we have a valid + // conversion (non-zero) + if (pt != 0) { + std::vector<uint8_t>::iterator it = + std::find(mRedundantEncodings->begin(), + mRedundantEncodings->end(), + pt); + if (it != mRedundantEncodings->end()) { + mRedundantEncodings->erase(it); + } + } + } + } + + private: + std::vector<uint8_t>* mRedundantEncodings; +}; + +nsresult +PeerConnectionImpl::ConfigureJsepSessionCodecs() { + nsresult res; + nsCOMPtr<nsIPrefService> 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<unsigned>(res)); + return res; + } + + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); + if (!branch) { + CSFLogError(logTag, "%s: Couldn't get prefs branch", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + ConfigureCodec configurer(branch); + mJsepSession->ForEachCodec(configurer); + + // first find the red codec description + std::vector<JsepCodecDescription*>& codecs = mJsepSession->Codecs(); + JsepVideoCodecDescription* redCodec = nullptr; + for (auto codec : codecs) { + // we only really care about finding the RED codec if it is + // enabled + if (codec->mName == "red" && codec->mEnabled) { + redCodec = static_cast<JsepVideoCodecDescription*>(codec); + break; + } + } + // if red codec was found, configure it for the other enabled codecs + if (redCodec) { + ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings)); + mJsepSession->ForEachCodec(configureRed); + } + + // We use this to sort the list of codecs once everything is configured + CompareCodecPriority comparator; + + // Sort by priority + int32_t preferredCodec = 0; + branch->GetIntPref("media.navigator.video.preferred_codec", + &preferredCodec); + + if (preferredCodec) { + comparator.SetPreferredCodec(preferredCodec); + } + + mJsepSession->SortCodecs(comparator); + return NS_OK; +} + +// Data channels won't work without a window, so in order for the C++ unit +// tests to work (it doesn't have a window available) we ifdef the following +// two implementations. +NS_IMETHODIMP +PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams) +{ + PC_AUTO_ENTER_API_CALL(false); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (mDataConnection) { + CSFLogDebug(logTag,"%s DataConnection already connected",__FUNCTION__); + // Ignore the request to connect when already connected. This entire + // implementation is temporary. Ignore aNumstreams as it's merely advisory + // and we increase the number of streams dynamically as needed. + return NS_OK; + } + mDataConnection = new DataChannelConnection(this); + if (!mDataConnection->Init(5000, aNumstreams, true)) { + CSFLogError(logTag,"%s DataConnection Init Failed",__FUNCTION__); + return NS_ERROR_FAILURE; + } + CSFLogDebug(logTag,"%s DataChannelConnection %p attached to %s", + __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str()); +#endif + return NS_OK; +} + +nsresult +PeerConnectionImpl::GetDatachannelParameters( + const mozilla::JsepApplicationCodecDescription** datachannelCodec, + uint16_t* level) const { + + auto trackPairs = mJsepSession->GetNegotiatedTrackPairs(); + for (auto j = trackPairs.begin(); j != trackPairs.end(); ++j) { + JsepTrackPair& trackPair = *j; + + bool sendDataChannel = + trackPair.mSending && + trackPair.mSending->GetMediaType() == SdpMediaSection::kApplication; + bool recvDataChannel = + trackPair.mReceiving && + trackPair.mReceiving->GetMediaType() == SdpMediaSection::kApplication; + (void)recvDataChannel; + MOZ_ASSERT(sendDataChannel == recvDataChannel); + + if (sendDataChannel) { + // This will release assert if there is no such index, and that's ok + const JsepTrackEncoding& encoding = + trackPair.mSending->GetNegotiatedDetails()->GetEncoding(0); + + if (encoding.GetCodecs().empty()) { + CSFLogError(logTag, "%s: Negotiated m=application with no codec. " + "This is likely to be broken.", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + + for (const JsepCodecDescription* codec : encoding.GetCodecs()) { + if (codec->mType != SdpMediaSection::kApplication) { + CSFLogError(logTag, "%s: Codec type for m=application was %u, this " + "is a bug.", + __FUNCTION__, + static_cast<unsigned>(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<const JsepApplicationCodecDescription*>(codec); + if (trackPair.mBundleLevel.isSome()) { + *level = static_cast<uint16_t>(*trackPair.mBundleLevel); + } else { + *level = static_cast<uint16_t>(trackPair.mLevel); + } + return NS_OK; + } + } + } + + *datachannelCodec = nullptr; + *level = 0; + return NS_OK; +} + +/* static */ +void +PeerConnectionImpl::DeferredAddTrackToJsepSession( + const std::string& pcHandle, + SdpMediaSection::MediaType type, + const std::string& streamId, + const std::string& trackId) +{ + PeerConnectionWrapper wrapper(pcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH("Why is DeferredAddTrackToJsepSession being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->AddTrackToJsepSession(type, streamId, trackId); + } +} + +nsresult +PeerConnectionImpl::AddTrackToJsepSession(SdpMediaSection::MediaType type, + const std::string& streamId, + const std::string& trackId) +{ + nsresult res = ConfigureJsepSessionCodecs(); + if (NS_FAILED(res)) { + CSFLogError(logTag, "Failed to configure codecs"); + return res; + } + + res = mJsepSession->AddTrack( + new JsepTrack(type, streamId, trackId, sdp::kSend)); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + CSFLogError(logTag, "%s (%s) : pc = %s, error = %s", + __FUNCTION__, + type == SdpMediaSection::kAudio ? "audio" : "video", + mHandle.c_str(), + errorString.c_str()); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +PeerConnectionImpl::InitializeDataChannel() +{ + PC_AUTO_ENTER_API_CALL(false); + CSFLogDebug(logTag, "%s", __FUNCTION__); + + const JsepApplicationCodecDescription* codec; + uint16_t level; + nsresult rv = GetDatachannelParameters(&codec, &level); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!codec) { + CSFLogDebug(logTag, "%s: We did not negotiate datachannel", __FUNCTION__); + return NS_OK; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + uint32_t channels = codec->mChannels; + if (channels > MAX_NUM_STREAMS) { + channels = MAX_NUM_STREAMS; + } + + rv = EnsureDataConnection(channels); + if (NS_SUCCEEDED(rv)) { + uint16_t localport = 5000; + uint16_t remoteport = 0; + // The logic that reflects the remote payload type is what sets the remote + // port here. + if (!codec->GetPtAsInt(&remoteport)) { + return NS_ERROR_FAILURE; + } + + // use the specified TransportFlow + RefPtr<TransportFlow> flow = mMedia->GetTransportFlow(level, false).get(); + CSFLogDebug(logTag, "Transportflow[%u] = %p", + static_cast<unsigned>(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<nsDOMDataChannel> +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<nsDOMDataChannel> 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> dataChannel; + DataChannelConnection::Type theType = + static_cast<DataChannelConnection::Type>(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<JsepTrack> 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<nsDOMDataChannel*>(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<nsISupportsWeakReference> tmp = do_QueryReferent(mPCObserver); +// if (!tmp) { +// return; +// } +// RefPtr<nsSupportsWeakReference> tmp2 = do_QueryObject(tmp); +// RefPtr<PeerConnectionObserver> pco = static_cast<PeerConnectionObserver*>(&*tmp2); + +static already_AddRefed<PeerConnectionObserver> +do_QueryObjectReferent(nsIWeakReference* aRawPtr) { + nsCOMPtr<nsISupportsWeakReference> tmp = do_QueryReferent(aRawPtr); + if (!tmp) { + return nullptr; + } + RefPtr<nsSupportsWeakReference> tmp2 = do_QueryObject(tmp); + RefPtr<PeerConnectionObserver> tmp3 = static_cast<PeerConnectionObserver*>(&*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<nsIDOMDataChannel> aChannel, + RefPtr<PeerConnectionObserver> aObserver) +{ + MOZ_ASSERT(NS_IsMainThread()); + JSErrorResult rv; + RefPtr<nsDOMDataChannel> channel = static_cast<nsDOMDataChannel*>(&*aChannel); + aObserver->NotifyDataChannel(*channel, rv); + NS_DataChannelAppReady(aChannel); +} +#endif + +void +PeerConnectionImpl::NotifyDataChannel(already_AddRefed<DataChannel> aChannel) +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + // XXXkhuey this is completely fucked up. We can't use RefPtr<DataChannel> + // 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<nsIDOMDataChannel> domchannel; + nsresult rv = NS_NewDOMDataChannel(already_AddRefed<DataChannel>(channel), + mWindow, getter_AddRefs(domchannel)); + NS_ENSURE_SUCCESS_VOID(rv); + + mHaveDataStream = true; + + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return; + } + + RUN_ON_THREAD(mThread, + WrapRunnableNM(NotifyDataChannel_m, + domchannel.get(), + pco), + NS_DISPATCH_NORMAL); +#endif +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) +{ + JsepOfferOptions options; +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // convert the RTCOfferOptions to JsepOfferOptions + if (aOptions.mOfferToReceiveAudio.WasPassed()) { + options.mOfferToReceiveAudio = + mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value())); + } + + if (aOptions.mOfferToReceiveVideo.WasPassed()) { + options.mOfferToReceiveVideo = + mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value())); + } + + options.mIceRestart = mozilla::Some(aOptions.mIceRestart); + + if (aOptions.mMozDontOfferDataChannel.WasPassed()) { + options.mDontOfferDataChannel = + mozilla::Some(aOptions.mMozDontOfferDataChannel.Value()); + } +#endif + return CreateOffer(options); +} + +static void DeferredCreateOffer(const std::string& aPcHandle, + const JsepOfferOptions& aOptions) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH("Why is DeferredCreateOffer being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->CreateOffer(aOptions); + } +} + +// Used by unit tests and the IDL CreateOffer. +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) +{ + PC_AUTO_ENTER_API_CALL(true); + bool restartIce = aOptions.mIceRestart.isSome() && *(aOptions.mIceRestart); + if (!restartIce && + mMedia->GetIceRestartState() == + PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { + RollbackIceRestart(); + } + + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return NS_OK; + } + + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. + PeerConnectionCtx::GetInstance()->queueJSEPOperation( + WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions)); + STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)"); + return NS_OK; + } + + CSFLogDebug(logTag, "CreateOffer()"); + + nsresult nrv; + if (restartIce && !mJsepSession->GetLocalDescription().empty()) { + // If restart is requested and a restart is already in progress, we + // need to make room for the restart request so we either rollback + // or finalize to "clear" the previous restart. + if (mMedia->GetIceRestartState() == + PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { + // we're mid-restart and can rollback + RollbackIceRestart(); + } else if (mMedia->GetIceRestartState() == + PeerConnectionMedia::ICE_RESTART_COMMITTED) { + // we're mid-restart and can't rollback, finalize restart even + // though we're not really ready yet + FinalizeIceRestart(); + } + + CSFLogInfo(logTag, "Offerer restarting ice"); + nrv = SetupIceRestart(); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u", + __FUNCTION__, + static_cast<unsigned>(nrv)); + return nrv; + } + } + + nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "Failed to configure codecs"); + return nrv; + } + + STAMP_TIMECARD(mTimeCard, "Create Offer"); + + std::string offer; + + nrv = mJsepSession->CreateOffer(aOptions, &offer); + JSErrorResult rv; + if (NS_FAILED(nrv)) { + Error error; + switch (nrv) { + case NS_ERROR_UNEXPECTED: + error = kInvalidState; + break; + default: + error = kInternalError; + } + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(logTag, "%s: pc = %s, error = %s", + __FUNCTION__, mHandle.c_str(), errorString.c_str()); + pco->OnCreateOfferError(error, ObString(errorString.c_str()), rv); + } else { + pco->OnCreateOfferSuccess(ObString(offer.c_str()), rv); + } + + UpdateSignalingState(); + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateAnswer() +{ + PC_AUTO_ENTER_API_CALL(true); + + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return NS_OK; + } + + CSFLogDebug(logTag, "CreateAnswer()"); + + nsresult nrv; + if (mJsepSession->RemoteIceIsRestarting()) { + if (mMedia->GetIceRestartState() == + PeerConnectionMedia::ICE_RESTART_COMMITTED) { + FinalizeIceRestart(); + } else if (!mMedia->IsIceRestarting()) { + CSFLogInfo(logTag, "Answerer restarting ice"); + nrv = SetupIceRestart(); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "%s: SetupIceRestart failed, res=%u", + __FUNCTION__, + static_cast<unsigned>(nrv)); + return nrv; + } + } + } + + STAMP_TIMECARD(mTimeCard, "Create Answer"); + // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to + // add it as a param to CreateAnswer, and convert it here. + JsepAnswerOptions options; + std::string answer; + + nrv = mJsepSession->CreateAnswer(options, &answer); + JSErrorResult rv; + if (NS_FAILED(nrv)) { + Error error; + switch (nrv) { + case NS_ERROR_UNEXPECTED: + error = kInvalidState; + break; + default: + error = kInternalError; + } + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(logTag, "%s: pc = %s, error = %s", + __FUNCTION__, mHandle.c_str(), errorString.c_str()); + pco->OnCreateAnswerError(error, ObString(errorString.c_str()), rv); + } else { + pco->OnCreateAnswerSuccess(ObString(answer.c_str()), rv); + } + + UpdateSignalingState(); + + return NS_OK; +} + +nsresult +PeerConnectionImpl::SetupIceRestart() +{ + if (mMedia->IsIceRestarting()) { + CSFLogError(logTag, "%s: ICE already restarting", + __FUNCTION__); + return NS_ERROR_UNEXPECTED; + } + + std::string ufrag = mMedia->ice_ctx()->GetNewUfrag(); + std::string pwd = mMedia->ice_ctx()->GetNewPwd(); + if (ufrag.empty() || pwd.empty()) { + CSFLogError(logTag, "%s: Bad ICE credentials (ufrag:'%s'/pwd:'%s')", + __FUNCTION__, + ufrag.c_str(), pwd.c_str()); + return NS_ERROR_UNEXPECTED; + } + + // hold on to the current ice creds in case of rollback + mPreviousIceUfrag = mJsepSession->GetUfrag(); + mPreviousIcePwd = mJsepSession->GetPwd(); + mMedia->BeginIceRestart(ufrag, pwd); + + nsresult nrv = mJsepSession->SetIceCredentials(ufrag, pwd); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u", + __FUNCTION__, + static_cast<unsigned>(nrv)); + return nrv; + } + + return NS_OK; +} + +nsresult +PeerConnectionImpl::RollbackIceRestart() +{ + mMedia->RollbackIceRestart(); + // put back the previous ice creds + nsresult nrv = mJsepSession->SetIceCredentials(mPreviousIceUfrag, + mPreviousIcePwd); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "%s: Couldn't set ICE credentials, res=%u", + __FUNCTION__, + static_cast<unsigned>(nrv)); + return nrv; + } + mPreviousIceUfrag = ""; + mPreviousIcePwd = ""; + + return NS_OK; +} + +void +PeerConnectionImpl::FinalizeIceRestart() +{ + mMedia->FinalizeIceRestart(); + // clear the previous ice creds since they are no longer needed + mPreviousIceUfrag = ""; + mPreviousIcePwd = ""; +} + +NS_IMETHODIMP +PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) +{ + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + JSErrorResult rv; + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return NS_OK; + } + + STAMP_TIMECARD(mTimeCard, "Set Local Description"); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + bool isolated = mMedia->AnyLocalTrackHasPeerIdentity(); + mPrivacyRequested = mPrivacyRequested || isolated; +#endif + + mLocalRequestedSDP = aSDP; + + JsepSdpType sdpType; + switch (aAction) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + + } + nsresult nrv = mJsepSession->SetLocalDescription(sdpType, + mLocalRequestedSDP); + if (NS_FAILED(nrv)) { + Error error; + switch (nrv) { + case NS_ERROR_INVALID_ARG: + error = kInvalidSessionDescription; + break; + case NS_ERROR_UNEXPECTED: + error = kInvalidState; + break; + default: + error = kInternalError; + } + + std::string errorString = mJsepSession->GetLastError(); + CSFLogError(logTag, "%s: pc = %s, error = %s", + __FUNCTION__, mHandle.c_str(), errorString.c_str()); + pco->OnSetLocalDescriptionError(error, ObString(errorString.c_str()), rv); + } else { + pco->OnSetLocalDescriptionSuccess(rv); + } + + UpdateSignalingState(sdpType == mozilla::kJsepSdpRollback); + return NS_OK; +} + +static void DeferredSetRemote(const std::string& aPcHandle, + int32_t aAction, + const std::string& aSdp) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH("Why is DeferredSetRemote being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str()); + } +} + +static void StartTrack(MediaStream* aSource, + TrackID aTrackId, + nsAutoPtr<MediaSegment>&& aSegment) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + class Message : public ControlMessage { + public: + Message(MediaStream* aStream, + TrackID aTrack, + nsAutoPtr<MediaSegment>&& aSegment) + : ControlMessage(aStream), + track_id_(aTrack), + segment_(aSegment) {} + + virtual void Run() override { + TrackRate track_rate = segment_->GetType() == MediaSegment::AUDIO ? + WEBRTC_DEFAULT_SAMPLE_RATE : mStream->GraphRate(); + StreamTime current_end = mStream->GetTracksEnd(); + TrackTicks current_ticks = + mStream->TimeToTicksRoundUp(track_rate, current_end); + + // Add a track 'now' to avoid possible underrun, especially if we add + // a track "later". + + if (current_end != 0L) { + CSFLogDebug(logTag, "added track @ %u -> %f", + static_cast<unsigned>(current_end), + mStream->StreamTimeToSeconds(current_end)); + } + + // To avoid assertions, we need to insert a dummy segment that covers up + // to the "start" time for the track + segment_->AppendNullData(current_ticks); + if (segment_->GetType() == MediaSegment::AUDIO) { + mStream->AsSourceStream()->AddAudioTrack( + track_id_, + WEBRTC_DEFAULT_SAMPLE_RATE, + 0, + static_cast<AudioSegment*>(segment_.forget())); + } else { + mStream->AsSourceStream()->AddTrack(track_id_, 0, segment_.forget()); + } + } + private: + TrackID track_id_; + nsAutoPtr<MediaSegment> segment_; + }; + + aSource->GraphImpl()->AppendMessage( + MakeUnique<Message>(aSource, aTrackId, Move(aSegment))); + CSFLogInfo(logTag, "Dispatched track-add for track id %u on stream %p", + aTrackId, aSource); +#endif +} + + +nsresult +PeerConnectionImpl::CreateNewRemoteTracks(RefPtr<PeerConnectionObserver>& aPco) +{ + JSErrorResult jrv; + + std::vector<RefPtr<JsepTrack>> newTracks = + mJsepSession->GetRemoteTracksAdded(); + + // Group new tracks by stream id + std::map<std::string, std::vector<RefPtr<JsepTrack>>> tracksByStreamId; + for (auto i = newTracks.begin(); i != newTracks.end(); ++i) { + RefPtr<JsepTrack> 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<RefPtr<JsepTrack>>& tracks = i->second; + + bool newStream = false; + RefPtr<RemoteSourceStreamInfo> info = + mMedia->GetRemoteStreamById(streamId); + if (!info) { + newStream = true; + nsresult nrv = CreateRemoteSourceStreamInfo(&info, streamId); + if (NS_FAILED(nrv)) { + aPco->OnSetRemoteDescriptionError( + kInternalError, + ObString("CreateRemoteSourceStreamInfo failed"), + jrv); + return nrv; + } + + nrv = mMedia->AddRemoteStream(info); + if (NS_FAILED(nrv)) { + aPco->OnSetRemoteDescriptionError( + kInternalError, + ObString("AddRemoteStream failed"), + jrv); + return nrv; + } + + CSFLogDebug(logTag, "Added remote stream %s", info->GetId().c_str()); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + info->GetMediaStream()->AssignId(NS_ConvertUTF8toUTF16(streamId.c_str())); + info->GetMediaStream()->SetLogicalStreamStartTime( + info->GetMediaStream()->GetPlaybackStream()->GetCurrentTime()); +#else + info->GetMediaStream()->AssignId((streamId)); +#endif + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + Sequence<OwningNonNull<DOMMediaStream>> streams; + if (!streams.AppendElement(OwningNonNull<DOMMediaStream>( + *info->GetMediaStream()), + fallible)) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + // Set the principal used for creating the tracks. This makes the stream + // data (audio/video samples) accessible to the receiving page. We're + // only certain that privacy hasn't been requested if we're connected. + nsCOMPtr<nsIPrincipal> principal; + nsIDocument* doc = GetWindow()->GetExtantDoc(); + MOZ_ASSERT(doc); + if (mDtlsConnected && !PrivacyRequested()) { + principal = doc->NodePrincipal(); + } else { + // we're either certain that we need isolation for the streams, OR + // we're not sure and we can fix the stream in SetDtlsConnected + principal = nsNullPrincipal::CreateWithInheritedAttributes(doc->NodePrincipal()); + } +#endif + + // We need to select unique ids, just use max + 1 + TrackID maxTrackId = 0; + { + nsTArray<RefPtr<dom::MediaStreamTrack>> domTracks; + info->GetMediaStream()->GetTracks(domTracks); + for (auto& track : domTracks) { + maxTrackId = std::max(maxTrackId, track->mTrackID); + } + } + + for (RefPtr<JsepTrack>& track : tracks) { + std::string webrtcTrackId(track->GetTrackId()); + if (!info->HasTrack(webrtcTrackId)) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + RefPtr<RemoteTrackSource> source = + new RemoteTrackSource(principal, nsString()); +#else + RefPtr<MediaStreamTrackSource> source = new MediaStreamTrackSource(); +#endif + TrackID trackID = ++maxTrackId; + RefPtr<MediaStreamTrack> domTrack; + nsAutoPtr<MediaSegment> segment; + if (track->GetMediaType() == SdpMediaSection::kAudio) { + domTrack = + info->GetMediaStream()->CreateDOMTrack(trackID, + MediaSegment::AUDIO, + source); + info->GetMediaStream()->AddTrackInternal(domTrack); + segment = new AudioSegment; + } else { + domTrack = + info->GetMediaStream()->CreateDOMTrack(trackID, + MediaSegment::VIDEO, + source); + info->GetMediaStream()->AddTrackInternal(domTrack); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + segment = new VideoSegment; +#endif + } + + StartTrack(info->GetMediaStream()->GetInputStream()->AsSourceStream(), + trackID, Move(segment)); + info->AddTrack(webrtcTrackId, domTrack); + CSFLogDebug(logTag, "Added remote track %s/%s", + info->GetId().c_str(), webrtcTrackId.c_str()); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + domTrack->AssignId(NS_ConvertUTF8toUTF16(webrtcTrackId.c_str())); + aPco->OnAddTrack(*domTrack, streams, jrv); + if (jrv.Failed()) { + CSFLogError(logTag, ": OnAddTrack(%s) failed! Error: %u", + webrtcTrackId.c_str(), + jrv.ErrorCodeAsInt()); + } +#endif + } + } + + if (newStream) { + aPco->OnAddStream(*info->GetMediaStream(), jrv); + if (jrv.Failed()) { + CSFLogError(logTag, ": OnAddStream() failed! Error: %u", + jrv.ErrorCodeAsInt()); + } + } + } + return NS_OK; +} + +void +PeerConnectionImpl::RemoveOldRemoteTracks(RefPtr<PeerConnectionObserver>& aPco) +{ + JSErrorResult jrv; + + std::vector<RefPtr<JsepTrack>> removedTracks = + mJsepSession->GetRemoteTracksRemoved(); + + for (auto i = removedTracks.begin(); i != removedTracks.end(); ++i) { + const std::string& streamId = (*i)->GetStreamId(); + const std::string& trackId = (*i)->GetTrackId(); + + RefPtr<RemoteSourceStreamInfo> info = mMedia->GetRemoteStreamById(streamId); + if (!info) { + MOZ_ASSERT(false, "A stream/track was removed that wasn't in PCMedia. " + "This is a bug."); + continue; + } + + mMedia->RemoveRemoteTrack(streamId, trackId); + + DOMMediaStream* stream = info->GetMediaStream(); + nsTArray<RefPtr<MediaStreamTrack>> tracks; + stream->GetTracks(tracks); + for (auto& track : tracks) { + if (PeerConnectionImpl::GetTrackId(*track) == trackId) { + aPco->OnRemoveTrack(*track, jrv); + break; + } + } + + // We might be holding the last ref, but that's ok. + if (!info->GetTrackCount()) { + aPco->OnRemoveStream(*stream, jrv); + } + } +} + +NS_IMETHODIMP +PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) +{ + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(logTag, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + JSErrorResult jrv; + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return NS_OK; + } + + if (action == IPeerConnection::kActionOffer) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. (This must be a + // remote offer, or else we would not have gotten this far) + PeerConnectionCtx::GetInstance()->queueJSEPOperation( + WrapRunnableNM(DeferredSetRemote, + mHandle, + action, + std::string(aSDP))); + STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)"); + return NS_OK; + } + + nsresult nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(logTag, "Failed to configure codecs"); + return nrv; + } + } + + STAMP_TIMECARD(mTimeCard, "Set Remote Description"); + + mRemoteRequestedSDP = aSDP; + JsepSdpType sdpType; + switch (action) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + nsresult nrv = mJsepSession->SetRemoteDescription(sdpType, + mRemoteRequestedSDP); + if (NS_FAILED(nrv)) { + Error error; + switch (nrv) { + case NS_ERROR_INVALID_ARG: + error = kInvalidSessionDescription; + break; + case NS_ERROR_UNEXPECTED: + error = kInvalidState; + break; + default: + error = kInternalError; + } + + std::string errorString = mJsepSession->GetLastError(); + CSFLogError(logTag, "%s: pc = %s, error = %s", + __FUNCTION__, mHandle.c_str(), errorString.c_str()); + pco->OnSetRemoteDescriptionError(error, ObString(errorString.c_str()), jrv); + } else { + nrv = CreateNewRemoteTracks(pco); + if (NS_FAILED(nrv)) { + // aPco was already notified, just return early. + return NS_OK; + } + + RemoveOldRemoteTracks(pco); + + pco->OnSetRemoteDescriptionSuccess(jrv); +#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()); + Performance *perf = mWindow->GetPerformance(); + NS_ENSURE_TRUE(perf && perf->Timing(), NS_ERROR_UNEXPECTED); + *result = perf->Now() + perf->Timing()->NavigationStart(); + return NS_OK; +} + +class RTCStatsReportInternalConstruct : public RTCStatsReportInternal { +public: + RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) { + mPcid = pcid; + mInboundRTPStreamStats.Construct(); + mOutboundRTPStreamStats.Construct(); + mMediaStreamTrackStats.Construct(); + mMediaStreamStats.Construct(); + mTransportStats.Construct(); + mIceComponentStats.Construct(); + mIceCandidatePairStats.Construct(); + mIceCandidateStats.Construct(); + mCodecStats.Construct(); + mTimestamp.Construct(now); + } +}; +#endif + +NS_IMETHODIMP +PeerConnectionImpl::GetStats(MediaStreamTrack *aSelector) { + PC_AUTO_ENTER_API_CALL(true); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (!mMedia) { + // Since we zero this out before the d'tor, we should check. + return NS_ERROR_UNEXPECTED; + } + + nsAutoPtr<RTCStatsQuery> 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<PeerConnectionObserver> 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<unsigned>(res), + aCandidate, + static_cast<unsigned>(aLevel), + errorString.c_str()); + + pco->OnAddIceCandidateError(error, ObString(errorString.c_str()), rv); + } + + UpdateSignalingState(); + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CloseStreams() { + PC_AUTO_ENTER_API_CALL(false); + + return NS_OK; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +nsresult +PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) +{ + PC_AUTO_ENTER_API_CALL(true); + MOZ_ASSERT(!aPeerIdentity.IsEmpty()); + + // once set, this can't be changed + if (mPeerIdentity) { + if (!mPeerIdentity->Equals(aPeerIdentity)) { + return NS_ERROR_FAILURE; + } + } else { + mPeerIdentity = new PeerIdentity(aPeerIdentity); + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (!doc) { + CSFLogInfo(logTag, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + MediaStreamTrack* allTracks = nullptr; + mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(), mPeerIdentity); + } + return NS_OK; +} +#endif + +nsresult +PeerConnectionImpl::SetDtlsConnected(bool aPrivacyRequested) +{ + PC_AUTO_ENTER_API_CALL(false); + + // For this, as with mPrivacyRequested, once we've connected to a peer, we + // fixate on that peer. Dealing with multiple peers or connections is more + // than this run-down wreck of an object can handle. + // Besides, this is only used to say if we have been connected ever. +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (!mPrivacyRequested && !aPrivacyRequested && !mDtlsConnected) { + // now we know that privacy isn't needed for sure + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (!doc) { + CSFLogInfo(logTag, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + mMedia->UpdateRemoteStreamPrincipals_m(doc->NodePrincipal()); + } +#endif + mDtlsConnected = true; + mPrivacyRequested = mPrivacyRequested || aPrivacyRequested; + return NS_OK; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +void +PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) { + nsIDocument* doc = GetWindow()->GetExtantDoc(); + if (doc) { + mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity); + } else { + CSFLogInfo(logTag, "Can't update sink principal; document gone"); + } +} +#endif + +std::string +PeerConnectionImpl::GetTrackId(const MediaStreamTrack& aTrack) +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsString wideTrackId; + aTrack.GetId(wideTrackId); + return NS_ConvertUTF16toUTF8(wideTrackId).get(); +#else + return aTrack.GetId(); +#endif +} + +std::string +PeerConnectionImpl::GetStreamId(const DOMMediaStream& aStream) +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsString wideStreamId; + aStream.GetId(wideStreamId); + return NS_ConvertUTF16toUTF8(wideStreamId).get(); +#else + return aStream.GetId(); +#endif +} + +void +PeerConnectionImpl::OnMediaError(const std::string& aError) +{ + CSFLogError(logTag, "Encountered media error! %s", aError.c_str()); + // TODO: Let content know about this somehow. +} + +nsresult +PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack, + const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) +{ + PC_AUTO_ENTER_API_CALL(true); + + if (!aStreams.Length()) { + CSFLogError(logTag, "%s: At least one stream arg required", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + return AddTrack(aTrack, aStreams[0]); +} + +nsresult +PeerConnectionImpl::AddTrack(MediaStreamTrack& aTrack, + DOMMediaStream& aMediaStream) +{ + std::string streamId = PeerConnectionImpl::GetStreamId(aMediaStream); + std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); + nsresult res = mMedia->AddTrack(aMediaStream, streamId, aTrack, trackId); + if (NS_FAILED(res)) { + return res; + } + + CSFLogDebug(logTag, "Added track (%s) to stream %s", + trackId.c_str(), streamId.c_str()); + + aTrack.AddPrincipalChangeObserver(this); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + PrincipalChanged(&aTrack); +#endif + + if (aTrack.AsAudioStreamTrack()) { + res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId); + if (NS_FAILED(res)) { + return res; + } + mNumAudioStreams++; + } + + if (aTrack.AsVideoStreamTrack()) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) { + // Before this code was moved, this would silently ignore just like it + // does now. Is this actually what we want to do? + return NS_OK; + } +#endif + + res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId); + if (NS_FAILED(res)) { + return res; + } + mNumVideoStreams++; + } + OnNegotiationNeeded(); + return NS_OK; +} + +nsresult +PeerConnectionImpl::SelectSsrc(MediaStreamTrack& aRecvTrack, + unsigned short aSsrcIndex) +{ + for (size_t i = 0; i < mMedia->RemoteStreamsLength(); ++i) { + if (mMedia->GetRemoteStreamByIndex(i)->GetMediaStream()-> + HasTrack(aRecvTrack)) { + auto& pipelines = mMedia->GetRemoteStreamByIndex(i)->GetPipelines(); + std::string trackId = PeerConnectionImpl::GetTrackId(aRecvTrack); + auto it = pipelines.find(trackId); + if (it != pipelines.end()) { + it->second->SelectSsrc_m(aSsrcIndex); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::RemoveTrack(MediaStreamTrack& aTrack) { + PC_AUTO_ENTER_API_CALL(true); + + std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsString wideTrackId; + aTrack.GetId(wideTrackId); + for (size_t i = 0; i < mDTMFStates.Length(); ++i) { + if (mDTMFStates[i]->mTrackId == wideTrackId) { + mDTMFStates[i]->mSendTimer->Cancel(); + mDTMFStates.RemoveElementAt(i); + break; + } + } +#endif + + RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId); + + if (!info) { + CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = + mJsepSession->RemoveTrack(info->GetId(), trackId); + + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Unknown stream/track ids %s %s", + __FUNCTION__, + info->GetId().c_str(), + trackId.c_str()); + return rv; + } + + media()->RemoveLocalTrack(info->GetId(), trackId); + + aTrack.RemovePrincipalChangeObserver(this); + + OnNegotiationNeeded(); + + return NS_OK; +} + +static int GetDTMFToneCode(uint16_t c) +{ + const char* DTMF_TONECODES = "0123456789*#ABCD"; + + if (c == ',') { + // , is a special character indicating a 2 second delay + return -1; + } + + const char* i = strchr(DTMF_TONECODES, c); + MOZ_ASSERT(i); + return i - DTMF_TONECODES; +} + +NS_IMETHODIMP +PeerConnectionImpl::InsertDTMF(mozilla::dom::RTCRtpSender& sender, + const nsAString& tones, uint32_t duration, + uint32_t interToneGap) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + PC_AUTO_ENTER_API_CALL(false); + + // Check values passed in from PeerConnection.js + MOZ_ASSERT(duration >= 40, "duration must be at least 40"); + MOZ_ASSERT(duration <= 6000, "duration must be at most 6000"); + MOZ_ASSERT(interToneGap >= 30, "interToneGap must be at least 30"); + + JSErrorResult jrv; + + // Retrieve track + RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv); + if (jrv.Failed()) { + NS_WARNING("Failed to retrieve track for RTCRtpSender!"); + return jrv.StealNSResult(); + } + + nsString senderTrackId; + mst->GetId(senderTrackId); + + // Attempt to locate state for the DTMFSender + RefPtr<DTMFState> state; + for (auto& dtmfState : mDTMFStates) { + if (dtmfState->mTrackId == senderTrackId) { + state = dtmfState; + break; + } + } + + // No state yet, create a new one + if (!state) { + state = *mDTMFStates.AppendElement(new DTMFState); + state->mPeerConnectionImpl = this; + state->mTrackId = senderTrackId; + state->mSendTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + MOZ_ASSERT(state->mSendTimer); + } + MOZ_ASSERT(state); + + auto trackPairs = mJsepSession->GetNegotiatedTrackPairs(); + state->mLevel = -1; + for (auto& trackPair : trackPairs) { + if (state->mTrackId.EqualsASCII(trackPair.mSending->GetTrackId().c_str())) { + if (trackPair.mBundleLevel.isSome()) { + state->mLevel = *trackPair.mBundleLevel; + } else { + state->mLevel = trackPair.mLevel; + } + break; + } + } + + state->mTones = tones; + state->mDuration = duration; + state->mInterToneGap = interToneGap; + if (!state->mTones.IsEmpty()) { + state->mSendTimer->InitWithCallback(state, 0, nsITimer::TYPE_ONE_SHOT); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::GetDTMFToneBuffer(mozilla::dom::RTCRtpSender& sender, + nsAString& outToneBuffer) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + PC_AUTO_ENTER_API_CALL(false); + + JSErrorResult jrv; + + // Retrieve track + RefPtr<MediaStreamTrack> mst = sender.GetTrack(jrv); + if (jrv.Failed()) { + NS_WARNING("Failed to retrieve track for RTCRtpSender!"); + return jrv.StealNSResult(); + } + + nsString senderTrackId; + mst->GetId(senderTrackId); + + // Attempt to locate state for the DTMFSender + for (auto& dtmfState : mDTMFStates) { + if (dtmfState->mTrackId == senderTrackId) { + outToneBuffer = dtmfState->mTones; + break; + } + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::ReplaceTrack(MediaStreamTrack& aThisTrack, + MediaStreamTrack& aWithTrack) { + PC_AUTO_ENTER_API_CALL(true); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsString trackId; + aThisTrack.GetId(trackId); + + for (size_t i = 0; i < mDTMFStates.Length(); ++i) { + if (mDTMFStates[i]->mTrackId == trackId) { + mDTMFStates[i]->mSendTimer->Cancel(); + mDTMFStates.RemoveElementAt(i); + break; + } + } +#endif + + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return NS_ERROR_UNEXPECTED; + } + JSErrorResult jrv; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (&aThisTrack == &aWithTrack) { + pco->OnReplaceTrackSuccess(jrv); + if (jrv.Failed()) { + CSFLogError(logTag, "Error firing replaceTrack success callback"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + } + + nsString thisKind; + aThisTrack.GetKind(thisKind); + nsString withKind; + aWithTrack.GetKind(withKind); + + if (thisKind != withKind) { + pco->OnReplaceTrackError(kIncompatibleMediaStreamTrack, + ObString(mJsepSession->GetLastError().c_str()), + jrv); + if (jrv.Failed()) { + CSFLogError(logTag, "Error firing replaceTrack success callback"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + } +#endif + std::string origTrackId = PeerConnectionImpl::GetTrackId(aThisTrack); + std::string newTrackId = PeerConnectionImpl::GetTrackId(aWithTrack); + + RefPtr<LocalSourceStreamInfo> info = + media()->GetLocalStreamByTrackId(origTrackId); + if (!info) { + CSFLogError(logTag, "Could not find stream from trackId"); + return NS_ERROR_UNEXPECTED; + } + + std::string origStreamId = info->GetId(); + std::string newStreamId = + PeerConnectionImpl::GetStreamId(*aWithTrack.mOwningStream); + + nsresult rv = mJsepSession->ReplaceTrack(origStreamId, + origTrackId, + newStreamId, + newTrackId); + if (NS_FAILED(rv)) { + pco->OnReplaceTrackError(kInvalidMediastreamTrack, + ObString(mJsepSession->GetLastError().c_str()), + jrv); + if (jrv.Failed()) { + CSFLogError(logTag, "Error firing replaceTrack error callback"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + } + + rv = media()->ReplaceTrack(origStreamId, + origTrackId, + aWithTrack, + newStreamId, + newTrackId); + + if (NS_FAILED(rv)) { + CSFLogError(logTag, "Unexpected error in ReplaceTrack: %d", + static_cast<int>(rv)); + pco->OnReplaceTrackError(kInvalidMediastreamTrack, + ObString("Failed to replace track"), + jrv); + if (jrv.Failed()) { + CSFLogError(logTag, "Error firing replaceTrack error callback"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; + } + aThisTrack.RemovePrincipalChangeObserver(this); + aWithTrack.AddPrincipalChangeObserver(this); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + PrincipalChanged(&aWithTrack); +#endif + + // We update the media pipelines here so we can apply different codec + // settings for different sources (e.g. screensharing as opposed to camera.) + // TODO: We should probably only do this if the source has in fact changed. + mMedia->UpdateMediaPipelines(*mJsepSession); + + pco->OnReplaceTrackSuccess(jrv); + if (jrv.Failed()) { + CSFLogError(logTag, "Error firing replaceTrack success callback"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +NS_IMETHODIMP +PeerConnectionImpl::SetParameters(MediaStreamTrack& aTrack, + const RTCRtpParameters& aParameters) { + PC_AUTO_ENTER_API_CALL(true); + + std::vector<JsepTrack::JsConstraints> constraints; + if (aParameters.mEncodings.WasPassed()) { + for (auto& encoding : aParameters.mEncodings.Value()) { + JsepTrack::JsConstraints constraint; + if (encoding.mRid.WasPassed()) { + constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); + } + if (encoding.mMaxBitrate.WasPassed()) { + constraint.constraints.maxBr = encoding.mMaxBitrate.Value(); + } + constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy; + constraints.push_back(constraint); + } + } + return SetParameters(aTrack, constraints); +} +#endif + +nsresult +PeerConnectionImpl::SetParameters( + MediaStreamTrack& aTrack, + const std::vector<JsepTrack::JsConstraints>& aConstraints) +{ + std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); + RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId); + if (!info) { + CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); + return NS_ERROR_INVALID_ARG; + } + std::string streamId = info->GetId(); + + return mJsepSession->SetParameters(streamId, trackId, aConstraints); +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +NS_IMETHODIMP +PeerConnectionImpl::GetParameters(MediaStreamTrack& aTrack, + RTCRtpParameters& aOutParameters) { + PC_AUTO_ENTER_API_CALL(true); + + std::vector<JsepTrack::JsConstraints> constraints; + nsresult rv = GetParameters(aTrack, &constraints); + if (NS_FAILED(rv)) { + return rv; + } + aOutParameters.mEncodings.Construct(); + for (auto& constraint : constraints) { + RTCRtpEncodingParameters encoding; + encoding.mRid.Construct(NS_ConvertASCIItoUTF16(constraint.rid.c_str())); + encoding.mMaxBitrate.Construct(constraint.constraints.maxBr); + encoding.mScaleResolutionDownBy = constraint.constraints.scaleDownBy; + aOutParameters.mEncodings.Value().AppendElement(Move(encoding), fallible); + } + return NS_OK; +} +#endif + +nsresult +PeerConnectionImpl::GetParameters( + MediaStreamTrack& aTrack, + std::vector<JsepTrack::JsConstraints>* aOutConstraints) +{ + std::string trackId = PeerConnectionImpl::GetTrackId(aTrack); + RefPtr<LocalSourceStreamInfo> info = media()->GetLocalStreamByTrackId(trackId); + if (!info) { + CSFLogError(logTag, "%s: Unknown stream", __FUNCTION__); + return NS_ERROR_INVALID_ARG; + } + std::string streamId = info->GetId(); + + return mJsepSession->GetParameters(streamId, trackId, aOutConstraints); +} + +nsresult +PeerConnectionImpl::CalculateFingerprint( + const std::string& algorithm, + std::vector<uint8_t>* fingerprint) const { + uint8_t buf[DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH]; + size_t len = 0; + + MOZ_ASSERT(fingerprint); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + const UniqueCERTCertificate& cert = mCertificate->Certificate(); +#else + const UniqueCERTCertificate& cert = mIdentity->cert(); +#endif + nsresult rv = DtlsIdentity::ComputeFingerprint(cert, algorithm, + &buf[0], sizeof(buf), + &len); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "Unable to calculate certificate fingerprint, rv=%u", + static_cast<unsigned>(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<uint8_t> 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<unsigned long long>(aPluginID)); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsCOMPtr<nsIDocument> 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<PluginCrashedEvent> event = + PluginCrashedEvent::Constructor(doc, NS_LITERAL_STRING("PluginCrashed"), init); + + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + EventDispatcher::DispatchDOMEvent(mWindow, nullptr, event, nullptr, nullptr); +#endif + + return true; +} + +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(Telemetry::WEBRTC_RENEGOTIATIONS, + mJsepSession->GetNegotiations()-1); + } + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kAudio]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kAudio]); + // DataChannels appear in both Sending and Receiving + Telemetry::Accumulate(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(Telemetry::WEBRTC_CALL_TYPE, + type); +#endif +} + +nsresult +PeerConnectionImpl::CloseInt() +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + for (auto& dtmfState : mDTMFStates) { + dtmfState->mSendTimer->Cancel(); + } + + // We do this at the end of the call because we want to make sure we've waited + // for all trickle ICE candidates to come in; this can happen well after we've + // transitioned to connected. As a bonus, this allows us to detect race + // conditions where a stats dispatch happens right as the PC closes. + if (!mPrivateWindow) { + RecordLongtermICEStatistics(); + } + 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() +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + if (!mMedia) + return; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // before we destroy references to local tracks, detach from them + for(uint32_t i = 0; i < media()->LocalStreamsLength(); ++i) { + LocalSourceStreamInfo *info = media()->GetLocalStreamByIndex(i); + for (const auto& pair : info->GetMediaStreamTracks()) { + pair.second->RemovePrincipalChangeObserver(this); + } + } + + // End of call to be recorded in Telemetry + if (!mStartTime.IsNull()){ + TimeDuration timeDelta = TimeStamp::Now() - mStartTime; + Telemetry::Accumulate(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) { + if (mMedia->GetIceRestartState() == + PeerConnectionMedia::ICE_RESTART_PROVISIONAL) { + if (rollback) { + RollbackIceRestart(); + } else { + mMedia->CommitIceRestart(); + } + } + + // Either negotiation is done, or we've rolled back. In either case, we + // need to re-evaluate whether further negotiation is required. + mNegotiationNeeded = false; + // If we're rolling back a local offer, we might need to remove some + // transports, but nothing further needs to be done. + mMedia->ActivateOrRemoveTransports(*mJsepSession); + if (!rollback) { + mMedia->UpdateMediaPipelines(*mJsepSession); + InitializeDataChannel(); + mMedia->StartIceChecks(*mJsepSession); + } + + if (!mJsepSession->AllLocalTracksAreAssigned()) { + CSFLogInfo(logTag, "Not all local tracks were assigned to an " + "m-section, either because the offerer did not offer" + " to receive enough tracks, or because tracks were " + "added after CreateOffer/Answer, but before " + "offer/answer completed. This requires " + "renegotiation."); + fireNegotiationNeeded = true; + } + + // 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<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return; + } + JSErrorResult rv; + pco->OnStateChange(PCObserverStateType::SignalingState, rv); + + if (fireNegotiationNeeded) { + // We don't use MaybeFireNegotiationNeeded here, since content might have + // already cased a transition from stable. + OnNegotiationNeeded(); + } +} + +void +PeerConnectionImpl::UpdateSignalingState(bool rollback) { + mozilla::JsepSignalingState state = + mJsepSession->GetState(); + + PCImplSignalingState newState; + + switch(state) { + case kJsepStateStable: + newState = PCImplSignalingState::SignalingStable; + break; + case kJsepStateHaveLocalOffer: + newState = PCImplSignalingState::SignalingHaveLocalOffer; + break; + case kJsepStateHaveRemoteOffer: + newState = PCImplSignalingState::SignalingHaveRemoteOffer; + break; + case kJsepStateHaveLocalPranswer: + newState = PCImplSignalingState::SignalingHaveLocalPranswer; + break; + case kJsepStateHaveRemotePranswer: + newState = PCImplSignalingState::SignalingHaveRemotePranswer; + break; + case kJsepStateClosed: + newState = PCImplSignalingState::SignalingClosed; + break; + default: + MOZ_CRASH(); + } + + SetSignalingState_m(newState, rollback); +} + +bool +PeerConnectionImpl::IsClosed() const +{ + return mSignalingState == PCImplSignalingState::SignalingClosed; +} + +bool +PeerConnectionImpl::HasMedia() const +{ + return mMedia; +} + +PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) + : impl_(nullptr) { + if (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) == + PeerConnectionCtx::GetInstance()->mPeerConnections.end()) { + return; + } + + PeerConnectionImpl *impl = PeerConnectionCtx::GetInstance()->mPeerConnections[handle]; + + if (!impl->media()) + return; + + impl_ = impl; +} + +const std::string& +PeerConnectionImpl::GetHandle() +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mHandle; +} + +const std::string& +PeerConnectionImpl::GetName() +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mName; +} + +static mozilla::dom::PCImplIceConnectionState +toDomIceConnectionState(NrIceCtx::ConnectionState state) { + switch (state) { + case NrIceCtx::ICE_CTX_INIT: + return PCImplIceConnectionState::New; + case NrIceCtx::ICE_CTX_CHECKING: + return PCImplIceConnectionState::Checking; + case NrIceCtx::ICE_CTX_CONNECTED: + return PCImplIceConnectionState::Connected; + case NrIceCtx::ICE_CTX_COMPLETED: + return PCImplIceConnectionState::Completed; + case NrIceCtx::ICE_CTX_FAILED: + return PCImplIceConnectionState::Failed; + case NrIceCtx::ICE_CTX_DISCONNECTED: + return PCImplIceConnectionState::Disconnected; + case NrIceCtx::ICE_CTX_CLOSED: + return PCImplIceConnectionState::Closed; + } + MOZ_CRASH(); +} + +static mozilla::dom::PCImplIceGatheringState +toDomIceGatheringState(NrIceCtx::GatheringState state) { + switch (state) { + case NrIceCtx::ICE_CTX_GATHER_INIT: + return PCImplIceGatheringState::New; + case NrIceCtx::ICE_CTX_GATHER_STARTED: + return PCImplIceGatheringState::Gathering; + case NrIceCtx::ICE_CTX_GATHER_COMPLETE: + return PCImplIceGatheringState::Complete; + } + MOZ_CRASH(); +} + +void +PeerConnectionImpl::CandidateReady(const std::string& candidate, + uint16_t level) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + std::string mid; + bool skipped = false; + nsresult res = mJsepSession->AddLocalIceCandidate(candidate, + level, + &mid, + &skipped); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(logTag, "Failed to incorporate local candidate into SDP:" + " res = %u, candidate = %s, level = %u, error = %s", + static_cast<unsigned>(res), + candidate.c_str(), + static_cast<unsigned>(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<unsigned>(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<PeerConnectionObserver> pco = do_QueryObjectReferent(weakPCObserver); + if (!pco) { + return; + } + + JSErrorResult rv; + pco->OnIceCandidate(level, + ObString(mid.c_str()), + ObString(candidate.c_str()), + rv); +} + +void +PeerConnectionImpl::SendLocalIceCandidateToContent( + uint16_t level, + const std::string& mid, + const std::string& candidate) { + // We dispatch this because OnSetLocalDescriptionSuccess does a setTimeout(0) + // to unwind the stack, but the event handlers don't. We need to ensure that + // the candidates do not skip ahead of the callback. + NS_DispatchToMainThread( + WrapRunnableNM(&SendLocalIceCandidateToContentImpl, + mPCObserver, + level, + mid, + candidate), + NS_DISPATCH_NORMAL); +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +static bool isDone(PCImplIceConnectionState state) { + return state != PCImplIceConnectionState::Checking && + state != PCImplIceConnectionState::New; +} + +static bool isSucceeded(PCImplIceConnectionState state) { + return state == PCImplIceConnectionState::Connected || + state == PCImplIceConnectionState::Completed; +} + +static bool isFailed(PCImplIceConnectionState state) { + return state == PCImplIceConnectionState::Failed; +} +#endif + +void PeerConnectionImpl::IceConnectionStateChange( + NrIceCtx* ctx, + NrIceCtx::ConnectionState state) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(logTag, "%s", __FUNCTION__); + + auto domState = toDomIceConnectionState(state); + if (domState == mIceConnectionState) { + // no work to be done since the states are the same. + // this can happen during ICE rollback situations. + return; + } + +#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(Telemetry::WEBRTC_ICE_SUCCESS_TIME, + timeDelta.ToMilliseconds()); + } else if (isFailed(domState)) { + Telemetry::Accumulate(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; + + if (mIceConnectionState == PCImplIceConnectionState::Connected || + mIceConnectionState == PCImplIceConnectionState::Completed || + mIceConnectionState == PCImplIceConnectionState::Failed) { + if (mMedia->IsIceRestarting()) { + FinalizeIceRestart(); + } + } + + // Would be nice if we had a means of converting one of these dom enums + // to a string that wasn't almost as much text as this switch statement... + switch (mIceConnectionState) { + case PCImplIceConnectionState::New: + STAMP_TIMECARD(mTimeCard, "Ice state: new"); + break; + case PCImplIceConnectionState::Checking: +#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<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return; + } + WrappableJSErrorResult rv; + RUN_ON_THREAD(mThread, + WrapRunnable(pco, + &PeerConnectionObserver::OnStateChange, + PCObserverStateType::IceConnectionState, + rv, static_cast<JSCompartment*>(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<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver); + if (!pco) { + return; + } + WrappableJSErrorResult rv; + mThread->Dispatch(WrapRunnable(pco, + &PeerConnectionObserver::OnStateChange, + PCObserverStateType::IceGatheringState, + rv, static_cast<JSCompartment*>(nullptr)), + NS_DISPATCH_NORMAL); + + if (mIceGatheringState == PCImplIceGatheringState::Complete) { + SendLocalIceCandidateToContent(0, "", ""); + } +} + +void +PeerConnectionImpl::UpdateDefaultCandidate(const std::string& defaultAddr, + uint16_t defaultPort, + const std::string& defaultRtcpAddr, + uint16_t defaultRtcpPort, + uint16_t level) { + CSFLogDebug(logTag, "%s", __FUNCTION__); + mJsepSession->UpdateDefaultCandidate(defaultAddr, + defaultPort, + defaultRtcpAddr, + defaultRtcpPort, + level); +} + +void +PeerConnectionImpl::EndOfLocalCandidates(uint16_t level) { + CSFLogDebug(logTag, "%s", __FUNCTION__); + mJsepSession->EndOfLocalCandidates(level); +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +nsresult +PeerConnectionImpl::BuildStatsQuery_m( + mozilla::dom::MediaStreamTrack *aSelector, + RTCStatsQuery *query) { + + if (!HasMedia()) { + return NS_ERROR_UNEXPECTED; + } + + if (!mThread) { + CSFLogError(logTag, "Could not build stats query, no MainThread"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = GetTimeSinceEpoch(&(query->now)); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "Could not build stats query, could not get timestamp"); + return rv; + } + + // Note: mMedia->ice_ctx() is deleted on STS thread; so make sure we grab and hold + // a ref instead of making multiple calls. NrIceCtx uses threadsafe refcounting. + // NOTE: Do this after all other failure tests, to ensure we don't + // accidentally release the Ctx on Mainthread. + query->iceCtx = mMedia->ice_ctx(); + if (!query->iceCtx) { + CSFLogError(logTag, "Could not build stats query, no ice_ctx"); + return NS_ERROR_UNEXPECTED; + } + + // We do not use the pcHandle here, since that's risky to expose to content. + query->report = new RTCStatsReportInternalConstruct( + NS_ConvertASCIItoUTF16(mName.c_str()), + query->now); + + query->iceStartTime = mIceStartTime; + query->failed = isFailed(mIceConnectionState); + + // Populate SDP on main + if (query->internalStats) { + if (mJsepSession) { + std::string localDescription = mJsepSession->GetLocalDescription(); + std::string remoteDescription = mJsepSession->GetRemoteDescription(); + query->report->mLocalSdp.Construct( + NS_ConvertASCIItoUTF16(localDescription.c_str())); + query->report->mRemoteSdp.Construct( + NS_ConvertASCIItoUTF16(remoteDescription.c_str())); + } + } + + // Gather up pipelines from mMedia so they may be inspected on STS + + std::string trackId; + if (aSelector) { + trackId = PeerConnectionImpl::GetTrackId(*aSelector); + } + + for (int i = 0, len = mMedia->LocalStreamsLength(); i < len; i++) { + for (auto pipeline : mMedia->GetLocalStreamByIndex(i)->GetPipelines()) { + if (!aSelector || pipeline.second->trackid() == trackId) { + query->pipelines.AppendElement(pipeline.second); + } + } + } + for (int i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) { + for (auto pipeline : mMedia->GetRemoteStreamByIndex(i)->GetPipelines()) { + if (!aSelector || pipeline.second->trackid() == trackId) { + query->pipelines.AppendElement(pipeline.second); + } + } + } + + if (!aSelector) { + query->grabAllLevels = true; + } + + return rv; +} + +static void ToRTCIceCandidateStats( + const std::vector<NrIceCandidate>& 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<NrIceCandidatePair> 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<NrIceCandidate> 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<RTCStatsQuery> 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<RTCStatsQuery> query) { + + // Is the PeerConnectionImpl still around? + PeerConnectionWrapper pcw(pcHandle); + if (pcw.impl()) { + RefPtr<PeerConnectionObserver> 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<PeerConnectionObserver> 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 histograms. + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_2, 1); +} +#endif + +NS_IMETHODIMP +PeerConnectionImpl::GetLocalStreams(nsTArray<RefPtr<DOMMediaStream > >& 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<RefPtr<DOMMediaStream > >& result) +{ + PC_AUTO_ENTER_API_CALL_NO_CHECK(); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + for(uint32_t i=0; i < media()->RemoteStreamsLength(); i++) { + RemoteSourceStreamInfo *info = media()->GetRemoteStreamByIndex(i); + NS_ENSURE_TRUE(info, NS_ERROR_UNEXPECTED); + result.AppendElement(info->GetMediaStream()); + } + return NS_OK; +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult +PeerConnectionImpl::DTMFState::Notify(nsITimer* timer) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsString eventTone; + if (!mTones.IsEmpty()) { + uint16_t toneChar = mTones.CharAt(0); + int tone = GetDTMFToneCode(toneChar); + + eventTone.Assign(toneChar); + + mTones.Cut(0, 1); + + if (tone == -1) { + mSendTimer->InitWithCallback(this, 2000, nsITimer::TYPE_ONE_SHOT); + } else { + // Reset delay if necessary + mSendTimer->InitWithCallback(this, + mDuration + mInterToneGap, + nsITimer::TYPE_ONE_SHOT); + + RefPtr<AudioSessionConduit> conduit = + mPeerConnectionImpl->mMedia->GetAudioConduit(mLevel); + + if (conduit) { + uint32_t duration = mDuration; + mPeerConnectionImpl->mSTSThread->Dispatch(WrapRunnableNM([conduit, tone, duration] () { + //Note: We default to channel 0, not inband, and 6dB attenuation. + // here. We might want to revisit these choices in the future. + conduit->InsertDTMFTone(0, tone, true, duration, 6); + }), NS_DISPATCH_NORMAL); + } + } + } else { + mSendTimer->Cancel(); + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + RefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPeerConnectionImpl->mPCObserver); + if (!pco) { + NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!"); + return NS_OK; // Return is ignored anyhow + } + + JSErrorResult jrv; + pco->OnDTMFToneChange(mTrackId, eventTone, jrv); + + if (jrv.Failed()) { + NS_WARNING("Failed to dispatch the RTCDTMFToneChange event!"); + } +#endif + + return NS_OK; +} + +PeerConnectionImpl::DTMFState::DTMFState() = default; +PeerConnectionImpl::DTMFState::~DTMFState() = default; + +NS_IMPL_ISUPPORTS(PeerConnectionImpl::DTMFState, nsITimerCallback) + +} // end mozilla namespace diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h new file mode 100644 index 000000000..c29d08180 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h @@ -0,0 +1,894 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PEER_CONNECTION_IMPL_H_ +#define _PEER_CONNECTION_IMPL_H_ + +#include <deque> +#include <string> +#include <vector> +#include <map> +#include <cmath> + +#include "prlock.h" +#include "mozilla/RefPtr.h" +#include "nsWeakPtr.h" +#include "nsAutoPtr.h" +#include "nsIWeakReferenceUtils.h" // for the definition of nsWeakPtr +#include "IPeerConnection.h" +#include "sigslot.h" +#include "nricectx.h" +#include "nricemediastream.h" +#include "nsComponentManagerUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIUUIDGenerator.h" +#include "nsIThread.h" + +#include "signaling/src/jsep/JsepSession.h" +#include "signaling/src/jsep/JsepSessionImpl.h" +#include "signaling/src/sdp/SdpMediaSection.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/PeerConnectionImplEnumsBinding.h" +#include "PrincipalChangeObserver.h" +#include "StreamTracks.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "mozilla/TimeStamp.h" +#include "mozilla/net/DataChannel.h" +#include "VideoUtils.h" +#include "VideoSegment.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsIPrincipal.h" +#include "mozilla/PeerIdentity.h" +#endif + +namespace test { +#ifdef USE_FAKE_PCOBSERVER +class AFakePCObserver; +#endif +} + +#ifdef USE_FAKE_MEDIA_STREAMS +class Fake_DOMMediaStream; +class Fake_MediaStreamTrack; +#endif + +class nsGlobalWindow; +class nsDOMDataChannel; + +namespace mozilla { +class DataChannel; +class DtlsIdentity; +class NrIceCtx; +class NrIceMediaStream; +class NrIceStunServer; +class NrIceTurnServer; +class MediaPipeline; + +#ifdef USE_FAKE_MEDIA_STREAMS +typedef Fake_DOMMediaStream DOMMediaStream; +#else +class DOMMediaStream; +#endif + +namespace dom { +class RTCCertificate; +struct RTCConfiguration; +class RTCDTMFSender; +struct RTCIceServer; +struct RTCOfferOptions; +struct RTCRtpParameters; +class RTCRtpSender; +#ifdef USE_FAKE_MEDIA_STREAMS +typedef Fake_MediaStreamTrack MediaStreamTrack; +#else +class MediaStreamTrack; +#endif + +#ifdef USE_FAKE_PCOBSERVER +typedef test::AFakePCObserver PeerConnectionObserver; +typedef const char *PCObserverString; +#else +class PeerConnectionObserver; +typedef NS_ConvertUTF8toUTF16 PCObserverString; +#endif +} +} + +#if defined(__cplusplus) && __cplusplus >= 201103L +typedef struct Timecard Timecard; +#else +#include "timecard.h" +#endif + +// To preserve blame, convert nsresult to ErrorResult with wrappers. These macros +// help declare wrappers w/function being wrapped when there are no differences. + +#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \ +NS_IMETHODIMP func(__VA_ARGS__); \ +void func (__VA_ARGS__, rv) + +#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \ +NS_IMETHODIMP func(__VA_ARGS__, resulttype **result); \ +already_AddRefed<resulttype> func (__VA_ARGS__, rv) + +struct MediaStreamTable; + +namespace mozilla { + +using mozilla::dom::PeerConnectionObserver; +using mozilla::dom::RTCConfiguration; +using mozilla::dom::RTCIceServer; +using mozilla::dom::RTCOfferOptions; +using mozilla::DOMMediaStream; +using mozilla::NrIceCtx; +using mozilla::NrIceMediaStream; +using mozilla::DtlsIdentity; +using mozilla::ErrorResult; +using mozilla::NrIceStunServer; +using mozilla::NrIceTurnServer; +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +using mozilla::PeerIdentity; +#endif + +class PeerConnectionWrapper; +class PeerConnectionMedia; +class RemoteSourceStreamInfo; + +// Uuid Generator +class PCUuidGenerator : public mozilla::JsepUuidGenerator { + public: + virtual bool Generate(std::string* idp) override; + + private: + nsCOMPtr<nsIUUIDGenerator> mGenerator; +}; + +class PeerConnectionConfiguration +{ +public: + PeerConnectionConfiguration() + : mBundlePolicy(kBundleBalanced), + mIceTransportPolicy(NrIceCtx::ICE_POLICY_ALL) {} + + bool addStunServer(const std::string& addr, uint16_t port, + const char* transport) + { + UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(addr, port, transport)); + if (!server) { + return false; + } + addStunServer(*server); + return true; + } + bool addTurnServer(const std::string& addr, uint16_t port, + const std::string& username, + const std::string& pwd, + const char* transport) + { + // TODO(ekr@rtfm.com): Need support for SASLprep for + // username and password. Bug # ??? + std::vector<unsigned char> password(pwd.begin(), pwd.end()); + + UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(addr, port, username, password, + transport)); + if (!server) { + return false; + } + addTurnServer(*server); + return true; + } + void addStunServer(const NrIceStunServer& server) { mStunServers.push_back (server); } + void addTurnServer(const NrIceTurnServer& server) { mTurnServers.push_back (server); } + const std::vector<NrIceStunServer>& getStunServers() const { return mStunServers; } + const std::vector<NrIceTurnServer>& getTurnServers() const { return mTurnServers; } + void setBundlePolicy(JsepBundlePolicy policy) { mBundlePolicy = policy;} + JsepBundlePolicy getBundlePolicy() const { return mBundlePolicy; } + void setIceTransportPolicy(NrIceCtx::Policy policy) { mIceTransportPolicy = policy;} + NrIceCtx::Policy getIceTransportPolicy() const { return mIceTransportPolicy; } + +#ifndef MOZILLA_EXTERNAL_LINKAGE + nsresult Init(const RTCConfiguration& aSrc); + nsresult AddIceServer(const RTCIceServer& aServer); +#endif + +private: + std::vector<NrIceStunServer> mStunServers; + std::vector<NrIceTurnServer> mTurnServers; + JsepBundlePolicy mBundlePolicy; + NrIceCtx::Policy mIceTransportPolicy; +}; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +// Not an inner class so we can forward declare. +class RTCStatsQuery { + public: + explicit RTCStatsQuery(bool internalStats); + ~RTCStatsQuery(); + + nsAutoPtr<mozilla::dom::RTCStatsReportInternal> report; + std::string error; + // A timestamp to help with telemetry. + mozilla::TimeStamp iceStartTime; + // Just for convenience, maybe integrate into the report later + bool failed; + + private: + friend class PeerConnectionImpl; + std::string pcName; + bool internalStats; + nsTArray<RefPtr<mozilla::MediaPipeline>> pipelines; + RefPtr<NrIceCtx> iceCtx; + bool grabAllLevels; + DOMHighResTimeStamp now; +}; +#endif // MOZILLA_INTERNAL_API + +// Enter an API call and check that the state is OK, +// the PC isn't closed, etc. +#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return res; \ + } while(0) +#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return; \ + } while(0) +#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread() + +class PeerConnectionImpl final : public nsISupports, +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + public mozilla::DataChannelConnection::DataConnectionListener, + public dom::PrincipalChangeObserver<dom::MediaStreamTrack>, +#endif + public sigslot::has_slots<> +{ + struct Internal; // Avoid exposing c includes to bindings + +public: + explicit PeerConnectionImpl(const mozilla::dom::GlobalObject* aGlobal = nullptr); + + enum Error { + kNoError = 0, + kInvalidCandidate = 2, + kInvalidMediastreamTrack = 3, + kInvalidState = 4, + kInvalidSessionDescription = 5, + kIncompatibleSessionDescription = 6, + kIncompatibleMediaStreamTrack = 8, + kInternalError = 9 + }; + + NS_DECL_THREADSAFE_ISUPPORTS + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector); +#endif + + static already_AddRefed<PeerConnectionImpl> + Constructor(const mozilla::dom::GlobalObject& aGlobal, ErrorResult& rv); + static PeerConnectionImpl* CreatePeerConnection(); + already_AddRefed<DOMMediaStream> MakeMediaStream(); + + nsresult CreateRemoteSourceStreamInfo(RefPtr<RemoteSourceStreamInfo>* aInfo, + const std::string& aId); + + // DataConnection observers + void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel) +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // PeerConnectionImpl only inherits from mozilla::DataChannelConnection + // inside libxul. + override +#endif + ; + + // Get the media object + const RefPtr<PeerConnectionMedia>& media() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mMedia; + } + + // Configure the ability to use localhost. + void SetAllowIceLoopback(bool val) { mAllowIceLoopback = val; } + bool GetAllowIceLoopback() const { return mAllowIceLoopback; } + + // Configure the ability to use IPV6 link-local addresses. + void SetAllowIceLinkLocal(bool val) { mAllowIceLinkLocal = val; } + bool GetAllowIceLinkLocal() const { return mAllowIceLinkLocal; } + + // Handle system to allow weak references to be passed through C code + virtual const std::string& GetHandle(); + + // Name suitable for exposing to content + virtual const std::string& GetName(); + + // ICE events + void IceConnectionStateChange(NrIceCtx* ctx, + NrIceCtx::ConnectionState state); + void IceGatheringStateChange(NrIceCtx* ctx, + NrIceCtx::GatheringState state); + void UpdateDefaultCandidate(const std::string& defaultAddr, + uint16_t defaultPort, + const std::string& defaultRtcpAddr, + uint16_t defaultRtcpPort, + uint16_t level); + void EndOfLocalCandidates(uint16_t level); + void IceStreamReady(NrIceMediaStream *aStream); + + static void ListenThread(void *aData); + static void ConnectThread(void *aData); + + // Get the main thread + nsCOMPtr<nsIThread> GetMainThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mThread; + } + + // Get the STS thread + nsIEventTarget* GetSTSThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mSTSThread; + } + + nsPIDOMWindowInner* GetWindow() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mWindow; + } + + // Initialize PeerConnection from a PeerConnectionConfiguration object + // (used directly by unit-tests, and indirectly by the JS entry point) + // This is necessary because RTCConfiguration can't be used by unit-tests + nsresult Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindow* aWindow, + const PeerConnectionConfiguration& aConfiguration, + nsISupports* aThread); + +#ifndef MOZILLA_EXTERNAL_LINKAGE + // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint) + void Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindow& aWindow, + const RTCConfiguration& aConfiguration, + nsISupports* aThread, + ErrorResult &rv); +#endif + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + void SetCertificate(mozilla::dom::RTCCertificate& aCertificate); + const RefPtr<mozilla::dom::RTCCertificate>& Certificate() const; +#endif + // This is a hack to support external linkage. + RefPtr<DtlsIdentity> Identity() const; + + NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult &rv, + const RTCOfferOptions& aOptions) + { + rv = CreateOffer(aOptions); + } + + NS_IMETHODIMP CreateAnswer(); + void CreateAnswer(ErrorResult &rv) + { + rv = CreateAnswer(); + } + + NS_IMETHODIMP CreateOffer( + const mozilla::JsepOfferOptions& aConstraints); + + NS_IMETHODIMP SetLocalDescription (int32_t aAction, const char* aSDP); + + void SetLocalDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv) + { + rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + nsresult CreateNewRemoteTracks(RefPtr<PeerConnectionObserver>& aPco); + + void RemoveOldRemoteTracks(RefPtr<PeerConnectionObserver>& aPco); + + NS_IMETHODIMP SetRemoteDescription (int32_t aAction, const char* aSDP); + + void SetRemoteDescription (int32_t aAction, const nsAString& aSDP, ErrorResult &rv) + { + rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + NS_IMETHODIMP_TO_ERRORRESULT(GetStats, ErrorResult &rv, + mozilla::dom::MediaStreamTrack *aSelector) + { + rv = GetStats(aSelector); + } + + NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid, + unsigned short aLevel); + + void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid, + unsigned short aLevel, ErrorResult &rv) + { + rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(), + NS_ConvertUTF16toUTF8(aMid).get(), aLevel); + } + + NS_IMETHODIMP CloseStreams(); + + void CloseStreams(ErrorResult &rv) + { + rv = CloseStreams(); + } + + NS_IMETHODIMP_TO_ERRORRESULT(AddTrack, ErrorResult &rv, + mozilla::dom::MediaStreamTrack& aTrack, + const mozilla::dom::Sequence<mozilla::OwningNonNull<DOMMediaStream>>& aStreams) + { + rv = AddTrack(aTrack, aStreams); + } + + NS_IMETHODIMP_TO_ERRORRESULT(RemoveTrack, ErrorResult &rv, + mozilla::dom::MediaStreamTrack& aTrack) + { + rv = RemoveTrack(aTrack); + } + + nsresult + AddTrack(mozilla::dom::MediaStreamTrack& aTrack, DOMMediaStream& aStream); + + NS_IMETHODIMP_TO_ERRORRESULT(InsertDTMF, ErrorResult &rv, + dom::RTCRtpSender& sender, + const nsAString& tones, + uint32_t duration, uint32_t interToneGap) { + rv = InsertDTMF(sender, tones, duration, interToneGap); + } + + NS_IMETHODIMP_TO_ERRORRESULT(GetDTMFToneBuffer, ErrorResult &rv, + dom::RTCRtpSender& sender, + nsAString& outToneBuffer) { + rv = GetDTMFToneBuffer(sender, outToneBuffer); + } + + NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrack, ErrorResult &rv, + mozilla::dom::MediaStreamTrack& aThisTrack, + mozilla::dom::MediaStreamTrack& aWithTrack) + { + rv = ReplaceTrack(aThisTrack, aWithTrack); + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + NS_IMETHODIMP_TO_ERRORRESULT(SetParameters, ErrorResult &rv, + dom::MediaStreamTrack& aTrack, + const dom::RTCRtpParameters& aParameters) + { + rv = SetParameters(aTrack, aParameters); + } + + NS_IMETHODIMP_TO_ERRORRESULT(GetParameters, ErrorResult &rv, + dom::MediaStreamTrack& aTrack, + dom::RTCRtpParameters& aOutParameters) + { + rv = GetParameters(aTrack, aOutParameters); + } +#endif + + nsresult + SetParameters(dom::MediaStreamTrack& aTrack, + const std::vector<JsepTrack::JsConstraints>& aConstraints); + + nsresult + GetParameters(dom::MediaStreamTrack& aTrack, + std::vector<JsepTrack::JsConstraints>* aOutConstraints); + + NS_IMETHODIMP_TO_ERRORRESULT(SelectSsrc, ErrorResult &rv, + dom::MediaStreamTrack& aRecvTrack, + unsigned short aSsrcIndex) + { + rv = SelectSsrc(aRecvTrack, aSsrcIndex); + } + + nsresult GetPeerIdentity(nsAString& peerIdentity) + { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (mPeerIdentity) { + peerIdentity = mPeerIdentity->ToString(); + return NS_OK; + } +#endif + + peerIdentity.SetIsVoid(true); + return NS_OK; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; } + nsresult SetPeerIdentity(const nsAString& peerIdentity); + + const std::string& GetIdAsAscii() const + { + return mName; + } + + nsresult GetId(nsAString& id) + { + id = NS_ConvertASCIItoUTF16(mName.c_str()); + return NS_OK; + } + + nsresult SetId(const nsAString& id) + { + mName = NS_ConvertUTF16toUTF8(id).get(); + return NS_OK; + } +#endif + + // this method checks to see if we've made a promise to protect media. + bool PrivacyRequested() const { return mPrivacyRequested; } + + NS_IMETHODIMP GetFingerprint(char** fingerprint); + void GetFingerprint(nsAString& fingerprint) + { + char *tmp; + GetFingerprint(&tmp); + fingerprint.AssignASCII(tmp); + delete[] tmp; + } + + NS_IMETHODIMP GetLocalDescription(char** aSDP); + + void GetLocalDescription(nsAString& aSDP) + { + char *tmp; + GetLocalDescription(&tmp); + aSDP.AssignASCII(tmp); + delete[] tmp; + } + + NS_IMETHODIMP GetRemoteDescription(char** aSDP); + + void GetRemoteDescription(nsAString& aSDP) + { + char *tmp; + GetRemoteDescription(&tmp); + aSDP.AssignASCII(tmp); + delete[] tmp; + } + + NS_IMETHODIMP SignalingState(mozilla::dom::PCImplSignalingState* aState); + + mozilla::dom::PCImplSignalingState SignalingState() + { + mozilla::dom::PCImplSignalingState state; + SignalingState(&state); + return state; + } + + NS_IMETHODIMP IceConnectionState( + mozilla::dom::PCImplIceConnectionState* aState); + + mozilla::dom::PCImplIceConnectionState IceConnectionState() + { + mozilla::dom::PCImplIceConnectionState state; + IceConnectionState(&state); + return state; + } + + NS_IMETHODIMP IceGatheringState( + mozilla::dom::PCImplIceGatheringState* aState); + + mozilla::dom::PCImplIceGatheringState IceGatheringState() + { + mozilla::dom::PCImplIceGatheringState state; + IceGatheringState(&state); + return state; + } + + NS_IMETHODIMP Close(); + + void Close(ErrorResult &rv) + { + rv = Close(); + } + + bool PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName); + + void RecordEndOfCallTelemetry() const; + + nsresult InitializeDataChannel(); + + NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, + CreateDataChannel, ErrorResult &rv, + const nsAString& aLabel, + const nsAString& aProtocol, + uint16_t aType, + bool outOfOrderAllowed, + uint16_t aMaxTime, + uint16_t aMaxNum, + bool aExternalNegotiated, + uint16_t aStream); + + NS_IMETHODIMP_TO_ERRORRESULT(GetLocalStreams, ErrorResult &rv, + nsTArray<RefPtr<DOMMediaStream > >& result) + { + rv = GetLocalStreams(result); + } + + NS_IMETHODIMP_TO_ERRORRESULT(GetRemoteStreams, ErrorResult &rv, + nsTArray<RefPtr<DOMMediaStream > >& result) + { + rv = GetRemoteStreams(result); + } + + // Called whenever something is unrecognized by the parser + // May be called more than once and does not necessarily mean + // that parsing was stopped, only that something was unrecognized. + void OnSdpParseError(const char* errorMessage); + + // Called when OnLocal/RemoteDescriptionSuccess/Error + // is called to start the list over. + void ClearSdpParseErrorMessages(); + + // Called to retreive the list of parsing errors. + const std::vector<std::string> &GetSdpParseErrors(); + + // Sets the RTC Signaling State + void SetSignalingState_m(mozilla::dom::PCImplSignalingState aSignalingState, + bool rollback = false); + + // Updates the RTC signaling state based on the JsepSession state + void UpdateSignalingState(bool rollback = false); + + bool IsClosed() const; + // called when DTLS connects; we only need this once + nsresult SetDtlsConnected(bool aPrivacyRequested); + + bool HasMedia() const; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // initialize telemetry for when calls start + void startCallTelem(); + + nsresult BuildStatsQuery_m( + mozilla::dom::MediaStreamTrack *aSelector, + RTCStatsQuery *query); + + static nsresult ExecuteStatsQuery_s(RTCStatsQuery *query); + + // for monitoring changes in track ownership + // PeerConnectionMedia can't do it because it doesn't know about principals + virtual void PrincipalChanged(dom::MediaStreamTrack* aTrack) override; + +#endif + + static std::string GetStreamId(const DOMMediaStream& aStream); + static std::string GetTrackId(const dom::MediaStreamTrack& track); + + void OnMediaError(const std::string& aError); + +private: + virtual ~PeerConnectionImpl(); + PeerConnectionImpl(const PeerConnectionImpl&rhs); + PeerConnectionImpl& operator=(PeerConnectionImpl); + nsresult CalculateFingerprint(const std::string& algorithm, + std::vector<uint8_t>* fingerprint) const; + nsresult ConfigureJsepSessionCodecs(); + + NS_IMETHODIMP EnsureDataConnection(uint16_t aNumstreams); + + nsresult CloseInt(); + nsresult CheckApiState(bool assert_ice_ready) const; + void CheckThread() const { + MOZ_ASSERT(CheckThreadInt(), "Wrong thread"); + } + bool CheckThreadInt() const { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // Thread assertions are disabled in the C++ unit tests because those + // make API calls off the main thread. + // This affects the standalone version of WebRTC since it is also used + // for an alternate build of the unit tests. + // TODO(ekr@rtfm.com): Fix the unit tests so they don't do that. + bool on; + NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false); + NS_ENSURE_TRUE(on, false); +#endif + return true; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + nsresult GetTimeSinceEpoch(DOMHighResTimeStamp *result); +#endif + + // Shut down media - called on main thread only + void ShutdownMedia(); + + void CandidateReady(const std::string& candidate, uint16_t level); + void SendLocalIceCandidateToContent(uint16_t level, + const std::string& mid, + const std::string& candidate); + + nsresult GetDatachannelParameters( + const mozilla::JsepApplicationCodecDescription** codec, + uint16_t* level) const; + + static void DeferredAddTrackToJsepSession(const std::string& pcHandle, + SdpMediaSection::MediaType type, + const std::string& streamId, + const std::string& trackId); + + nsresult AddTrackToJsepSession(SdpMediaSection::MediaType type, + const std::string& streamId, + const std::string& trackId); + + nsresult SetupIceRestart(); + nsresult RollbackIceRestart(); + void FinalizeIceRestart(); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + static void GetStatsForPCObserver_s( + const std::string& pcHandle, + nsAutoPtr<RTCStatsQuery> query); + + // Sends an RTCStatsReport to JS. Must run on main thread. + static void DeliverStatsReportToPCObserver_m( + const std::string& pcHandle, + nsresult result, + nsAutoPtr<RTCStatsQuery> query); +#endif + + // When ICE completes, we record a bunch of statistics that outlive the + // PeerConnection. This is just telemetry right now, but this can also + // include things like dumping the RLogConnector somewhere, saving away + // an RTCStatsReport somewhere so it can be inspected after the call is over, + // or other things. + void RecordLongtermICEStatistics(); + + void OnNegotiationNeeded(); + static void MaybeFireNegotiationNeeded_static(const std::string& pcHandle); + void MaybeFireNegotiationNeeded(); + + // Timecard used to measure processing time. This should be the first class + // attribute so that we accurately measure the time required to instantiate + // any other attributes of this class. + Timecard *mTimeCard; + + mozilla::dom::PCImplSignalingState mSignalingState; + + // ICE State + mozilla::dom::PCImplIceConnectionState mIceConnectionState; + mozilla::dom::PCImplIceGatheringState mIceGatheringState; + + // DTLS + // this is true if we have been connected ever, see SetDtlsConnected + bool mDtlsConnected; + + nsCOMPtr<nsIThread> mThread; + // TODO: Remove if we ever properly wire PeerConnection for cycle-collection. + nsWeakPtr mPCObserver; + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + + // The SDP sent in from JS - here for debugging. + std::string mLocalRequestedSDP; + std::string mRemoteRequestedSDP; + + // DTLS fingerprint + std::string mFingerprint; + std::string mRemoteFingerprint; + + // identity-related fields +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // The entity on the other end of the peer-to-peer connection; + // void if they are not yet identified, and no identity setting has been set + RefPtr<PeerIdentity> mPeerIdentity; + // The certificate we are using. + RefPtr<mozilla::dom::RTCCertificate> mCertificate; +#else + RefPtr<DtlsIdentity> mIdentity; +#endif + // Whether an app should be prevented from accessing media produced by the PC + // If this is true, then media will not be sent until mPeerIdentity matches + // local streams PeerIdentity; and remote streams are protected from content + // + // This can be false if mPeerIdentity is set, in the case where identity is + // provided, but the media is not protected from the app on either side + bool mPrivacyRequested; + + // A handle to refer to this PC with + std::string mHandle; + + // A name for this PC that we are willing to expose to content. + std::string mName; + + // The target to run stuff on + nsCOMPtr<nsIEventTarget> mSTSThread; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // DataConnection that's used to get all the DataChannels + RefPtr<mozilla::DataChannelConnection> mDataConnection; +#endif + + bool mAllowIceLoopback; + bool mAllowIceLinkLocal; + RefPtr<PeerConnectionMedia> mMedia; + + // The JSEP negotiation session. + mozilla::UniquePtr<PCUuidGenerator> mUuidGen; + mozilla::UniquePtr<mozilla::JsepSession> mJsepSession; + std::string mPreviousIceUfrag; // used during rollback of ice restart + std::string mPreviousIcePwd; // used during rollback of ice restart + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // Start time of ICE, used for telemetry + mozilla::TimeStamp mIceStartTime; + // Start time of call used for Telemetry + mozilla::TimeStamp mStartTime; +#endif + + // Temporary: used to prevent multiple audio streams or multiple video streams + // in a single PC. This is tied up in the IETF discussion around proper + // representation of multiple streams in SDP, and strongly related to + // Bug 840728. + int mNumAudioStreams; + int mNumVideoStreams; + bool mHaveConfiguredCodecs; + + bool mHaveDataStream; + + unsigned int mAddCandidateErrorCount; + + bool mTrickle; + + bool mNegotiationNeeded; + + bool mPrivateWindow; + + // storage for Telemetry data + uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes]; + uint16_t mMaxSending[SdpMediaSection::kMediaTypes]; + + // DTMF + class DTMFState : public nsITimerCallback { + virtual ~DTMFState(); + public: + DTMFState(); + + NS_DECL_NSITIMERCALLBACK + NS_DECL_THREADSAFE_ISUPPORTS + + PeerConnectionImpl* mPeerConnectionImpl; + nsCOMPtr<nsITimer> mSendTimer; + nsString mTrackId; + nsString mTones; + size_t mLevel; + uint32_t mDuration; + uint32_t mInterToneGap; + }; + + nsTArray<RefPtr<DTMFState>> mDTMFStates; + +public: + //these are temporary until the DataChannel Listen/Connect API is removed + unsigned short listenPort; + unsigned short connectPort; + char *connectStr; // XXX ownership/free +}; + +// This is what is returned when you acquire on a handle +class PeerConnectionWrapper +{ + public: + explicit PeerConnectionWrapper(const std::string& handle); + + PeerConnectionImpl *impl() { return impl_; } + + private: + RefPtr<PeerConnectionImpl> impl_; +}; + +} // end mozilla namespace + +#undef NS_IMETHODIMP_TO_ERRORRESULT +#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF +#endif // _PEER_CONNECTION_IMPL_H_ diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp new file mode 100644 index 000000000..4f42b0bb7 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp @@ -0,0 +1,1672 @@ +/* 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 <ostream> +#include <string> +#include <vector> + +#include "CSFLog.h" + +#include "nspr.h" + +#include "nricectx.h" +#include "nricemediastream.h" +#include "MediaPipelineFactory.h" +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.h" +#include "AudioConduit.h" +#include "VideoConduit.h" +#include "runnable_utils.h" +#include "transportlayerice.h" +#include "transportlayerdtls.h" +#include "signaling/src/jsep/JsepSession.h" +#include "signaling/src/jsep/JsepTransport.h" + +#ifdef USE_FAKE_STREAMS +#include "DOMMediaStream.h" +#include "FakeMediaStreams.h" +#else +#include "MediaSegment.h" +#ifdef MOZILLA_INTERNAL_API +#include "MediaStreamGraph.h" +#endif +#endif + +#ifndef USE_FAKE_MEDIA_STREAMS +#include "MediaStreamGraphImpl.h" +#endif + +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIURI.h" +#include "nsIScriptSecurityManager.h" +#include "nsICancelable.h" +#include "nsILoadInfo.h" +#include "nsIContentPolicy.h" +#include "nsIProxyInfo.h" +#include "nsIProtocolProxyService.h" + +#include "nsProxyRelease.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "MediaStreamList.h" +#include "nsIScriptGlobalObject.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "MediaStreamTrack.h" +#include "VideoStreamTrack.h" +#include "MediaStreamError.h" +#include "MediaManager.h" +#endif + + + +namespace mozilla { +using namespace dom; + +static const char* logTag = "PeerConnectionMedia"; + +nsresult +PeerConnectionMedia::ReplaceTrack(const std::string& aOldStreamId, + const std::string& aOldTrackId, + MediaStreamTrack& aNewTrack, + const std::string& aNewStreamId, + const std::string& aNewTrackId) +{ + RefPtr<LocalSourceStreamInfo> oldInfo(GetLocalStreamById(aOldStreamId)); + + if (!oldInfo) { + CSFLogError(logTag, "Failed to find stream id %s", aOldStreamId.c_str()); + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = AddTrack(*aNewTrack.mOwningStream, aNewStreamId, + aNewTrack, aNewTrackId); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<LocalSourceStreamInfo> newInfo(GetLocalStreamById(aNewStreamId)); + + if (!newInfo) { + CSFLogError(logTag, "Failed to add track id %s", aNewTrackId.c_str()); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + rv = newInfo->TakePipelineFrom(oldInfo, aOldTrackId, aNewTrack, aNewTrackId); + NS_ENSURE_SUCCESS(rv, rv); + + return RemoveLocalTrack(aOldStreamId, aOldTrackId); +} + +static void +PipelineReleaseRef_m(RefPtr<MediaPipeline> pipeline) +{} + +static void +PipelineDetachTransport_s(RefPtr<MediaPipeline> pipeline, + nsCOMPtr<nsIThread> mainThread) +{ + pipeline->DetachTransport_s(); + mainThread->Dispatch( + // Make sure we let go of our reference before dispatching + // If the dispatch fails, well, we're hosed anyway. + WrapRunnableNM(PipelineReleaseRef_m, pipeline.forget()), + NS_DISPATCH_NORMAL); +} + +void +SourceStreamInfo::EndTrack(MediaStream* stream, dom::MediaStreamTrack* track) +{ + if (!stream || !stream->AsSourceStream()) { + return; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + class Message : public ControlMessage { + public: + Message(MediaStream* stream, TrackID track) + : ControlMessage(stream), + track_id_(track) {} + + virtual void Run() override { + mStream->AsSourceStream()->EndTrack(track_id_); + } + private: + TrackID track_id_; + }; + + stream->GraphImpl()->AppendMessage( + MakeUnique<Message>(stream, track->mTrackID)); +#endif + +} + +void +SourceStreamInfo::RemoveTrack(const std::string& trackId) +{ + mTracks.erase(trackId); + + RefPtr<MediaPipeline> pipeline = GetPipelineByTrackId_m(trackId); + if (pipeline) { + mPipelines.erase(trackId); + pipeline->ShutdownMedia_m(); + mParent->GetSTSThread()->Dispatch( + WrapRunnableNM(PipelineDetachTransport_s, + pipeline.forget(), + mParent->GetMainThread()), + NS_DISPATCH_NORMAL); + } +} + +void SourceStreamInfo::DetachTransport_s() +{ + ASSERT_ON_THREAD(mParent->GetSTSThread()); + // walk through all the MediaPipelines and call the shutdown + // transport functions. Must be on the STS thread. + for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) { + it->second->DetachTransport_s(); + } +} + +void SourceStreamInfo::DetachMedia_m() +{ + ASSERT_ON_THREAD(mParent->GetMainThread()); + + // walk through all the MediaPipelines and call the shutdown + // media functions. Must be on the main thread. + for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) { + it->second->ShutdownMedia_m(); + } + mMediaStream = nullptr; +} + +already_AddRefed<PeerConnectionImpl> +PeerConnectionImpl::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& rv) +{ + RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal); + + CSFLogDebug(logTag, "Created PeerConnection: %p", pc.get()); + + return pc.forget(); +} + +PeerConnectionImpl* PeerConnectionImpl::CreatePeerConnection() +{ + PeerConnectionImpl *pc = new PeerConnectionImpl(); + + CSFLogDebug(logTag, "Created PeerConnection: %p", pc); + + return pc; +} + +NS_IMETHODIMP PeerConnectionMedia::ProtocolProxyQueryHandler:: +OnProxyAvailable(nsICancelable *request, + nsIChannel *aChannel, + nsIProxyInfo *proxyinfo, + nsresult result) { + + if (!pcm_->mProxyRequest) { + // PeerConnectionMedia is no longer waiting + return NS_OK; + } + + CSFLogInfo(logTag, "%s: Proxy Available: %d", __FUNCTION__, (int)result); + + if (NS_SUCCEEDED(result) && proxyinfo) { + SetProxyOnPcm(*proxyinfo); + } + + pcm_->mProxyResolveCompleted = true; + pcm_->mProxyRequest = nullptr; + pcm_->FlushIceCtxOperationQueueIfReady(); + + return NS_OK; +} + +void +PeerConnectionMedia::ProtocolProxyQueryHandler::SetProxyOnPcm( + nsIProxyInfo& proxyinfo) +{ + CSFLogInfo(logTag, "%s: Had proxyinfo", __FUNCTION__); + nsresult rv; + nsCString httpsProxyHost; + int32_t httpsProxyPort; + + rv = proxyinfo.GetHost(httpsProxyHost); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get proxy server host", __FUNCTION__); + return; + } + + rv = proxyinfo.GetPort(&httpsProxyPort); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get proxy server port", __FUNCTION__); + return; + } + + if (pcm_->mIceCtxHdlr.get()) { + assert(httpsProxyPort >= 0 && httpsProxyPort < (1 << 16)); + // Note that this could check if PrivacyRequested() is set on the PC and + // remove "webrtc" from the ALPN list. But that would only work if the PC + // was constructed with a peerIdentity constraint, not when isolated + // streams are added. If we ever need to signal to the proxy that the + // media is isolated, then we would need to restructure this code. + pcm_->mProxyServer.reset( + new NrIceProxyServer(httpsProxyHost.get(), + static_cast<uint16_t>(httpsProxyPort), + "webrtc,c-webrtc")); + } else { + CSFLogError(logTag, "%s: Failed to set proxy server (ICE ctx unavailable)", + __FUNCTION__); + } +} + +NS_IMPL_ISUPPORTS(PeerConnectionMedia::ProtocolProxyQueryHandler, nsIProtocolProxyCallback) + +PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl *parent) + : mParent(parent), + mParentHandle(parent->GetHandle()), + mParentName(parent->GetName()), + mIceCtxHdlr(nullptr), + mDNSResolver(new NrIceResolver()), + mUuidGen(MakeUnique<PCUuidGenerator>()), + mMainThread(mParent->GetMainThread()), + mSTSThread(mParent->GetSTSThread()), + mProxyResolveCompleted(false), + mIceRestartState(ICE_RESTART_NONE) { +} + +nsresult +PeerConnectionMedia::InitProxy() +{ +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // Allow mochitests to disable this, since mochitest configures a fake proxy + // that serves up content. + bool disable = Preferences::GetBool("media.peerconnection.disable_http_proxy", + false); + if (disable) { + mProxyResolveCompleted = true; + return NS_OK; + } +#endif + + nsresult rv; + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get proxy service: %d", __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + // We use the following URL to find the "default" proxy address for all HTTPS + // connections. We will only attempt one HTTP(S) CONNECT per peer connection. + // "example.com" is guaranteed to be unallocated and should return the best default. + nsCOMPtr<nsIURI> fakeHttpsLocation; + rv = NS_NewURI(getter_AddRefs(fakeHttpsLocation), "https://example.com"); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to set URI: %d", __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIScriptSecurityManager> secMan( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get IOService: %d", + __FUNCTION__, (int)rv); + CSFLogError(logTag, "%s: Failed to get securityManager: %d", __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> systemPrincipal; + rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get systemPrincipal: %d", __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + fakeHttpsLocation, + systemPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to get channel from URI: %d", + __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + RefPtr<ProtocolProxyQueryHandler> handler = new ProtocolProxyQueryHandler(this); + rv = pps->AsyncResolve(channel, + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, + handler, getter_AddRefs(mProxyRequest)); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: Failed to resolve protocol proxy: %d", __FUNCTION__, (int)rv); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult PeerConnectionMedia::Init(const std::vector<NrIceStunServer>& stun_servers, + const std::vector<NrIceTurnServer>& turn_servers, + NrIceCtx::Policy policy) +{ + nsresult rv = InitProxy(); + NS_ENSURE_SUCCESS(rv, rv); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false); +#else + bool ice_tcp = false; +#endif + + // TODO(ekr@rtfm.com): need some way to set not offerer later + // Looks like a bug in the NrIceCtx API. + mIceCtxHdlr = NrIceCtxHandler::Create("PC:" + mParentName, + true, // Offerer + mParent->GetAllowIceLoopback(), + ice_tcp, + mParent->GetAllowIceLinkLocal(), + policy); + if(!mIceCtxHdlr) { + CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetStunServers(stun_servers))) { + CSFLogError(logTag, "%s: Failed to set stun servers", __FUNCTION__); + return rv; + } + // Give us a way to globally turn off TURN support +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + bool disabled = Preferences::GetBool("media.peerconnection.turn.disable", false); +#else + bool disabled = false; +#endif + if (!disabled) { + if (NS_FAILED(rv = mIceCtxHdlr->ctx()->SetTurnServers(turn_servers))) { + CSFLogError(logTag, "%s: Failed to set turn servers", __FUNCTION__); + return rv; + } + } else if (turn_servers.size() != 0) { + CSFLogError(logTag, "%s: Setting turn servers disabled", __FUNCTION__); + } + if (NS_FAILED(rv = mDNSResolver->Init())) { + CSFLogError(logTag, "%s: Failed to initialize dns resolver", __FUNCTION__); + return rv; + } + if (NS_FAILED(rv = + mIceCtxHdlr->ctx()->SetResolver(mDNSResolver->AllocateResolver()))) { + CSFLogError(logTag, "%s: Failed to get dns resolver", __FUNCTION__); + return rv; + } + ConnectSignals(mIceCtxHdlr->ctx().get()); + + return NS_OK; +} + +void +PeerConnectionMedia::EnsureTransports(const JsepSession& aSession) +{ + auto transports = aSession.GetTransports(); + for (size_t i = 0; i < transports.size(); ++i) { + RefPtr<JsepTransport> transport = transports[i]; + RUN_ON_THREAD( + GetSTSThread(), + WrapRunnable(RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::EnsureTransport_s, + i, + transport->mComponents), + NS_DISPATCH_NORMAL); + } + + GatherIfReady(); +} + +void +PeerConnectionMedia::EnsureTransport_s(size_t aLevel, size_t aComponentCount) +{ + RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aLevel)); + if (!stream) { + CSFLogDebug(logTag, "%s: Creating ICE media stream=%u components=%u", + mParentHandle.c_str(), + static_cast<unsigned>(aLevel), + static_cast<unsigned>(aComponentCount)); + + std::ostringstream os; + os << mParentName << " aLevel=" << aLevel; + RefPtr<NrIceMediaStream> stream = + mIceCtxHdlr->CreateStream(os.str().c_str(), + aComponentCount); + + if (!stream) { + CSFLogError(logTag, "Failed to create ICE stream."); + return; + } + + stream->SetLevel(aLevel); + stream->SignalReady.connect(this, &PeerConnectionMedia::IceStreamReady_s); + stream->SignalCandidate.connect(this, + &PeerConnectionMedia::OnCandidateFound_s); + mIceCtxHdlr->ctx()->SetStream(aLevel, stream); + } +} + +void +PeerConnectionMedia::ActivateOrRemoveTransports(const JsepSession& aSession) +{ + auto transports = aSession.GetTransports(); + for (size_t i = 0; i < transports.size(); ++i) { + RefPtr<JsepTransport> transport = transports[i]; + + std::string ufrag; + std::string pwd; + std::vector<std::string> candidates; + + if (transport->mComponents) { + MOZ_ASSERT(transport->mIce); + CSFLogDebug(logTag, "Transport %u is active", static_cast<unsigned>(i)); + ufrag = transport->mIce->GetUfrag(); + pwd = transport->mIce->GetPassword(); + candidates = transport->mIce->GetCandidates(); + } else { + CSFLogDebug(logTag, "Transport %u is disabled", static_cast<unsigned>(i)); + // Make sure the MediaPipelineFactory doesn't try to use these. + RemoveTransportFlow(i, false); + RemoveTransportFlow(i, true); + } + + RUN_ON_THREAD( + GetSTSThread(), + WrapRunnable(RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::ActivateOrRemoveTransport_s, + i, + transport->mComponents, + ufrag, + pwd, + candidates), + NS_DISPATCH_NORMAL); + } + + // We can have more streams than m-lines due to rollback. + RUN_ON_THREAD( + GetSTSThread(), + WrapRunnable(RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::RemoveTransportsAtOrAfter_s, + transports.size()), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::ActivateOrRemoveTransport_s( + size_t aMLine, + size_t aComponentCount, + const std::string& aUfrag, + const std::string& aPassword, + const std::vector<std::string>& aCandidateList) { + + if (!aComponentCount) { + CSFLogDebug(logTag, "%s: Removing ICE media stream=%u", + mParentHandle.c_str(), + static_cast<unsigned>(aMLine)); + mIceCtxHdlr->ctx()->SetStream(aMLine, nullptr); + return; + } + + RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine)); + if (!stream) { + MOZ_ASSERT(false); + return; + } + + if (!stream->HasParsedAttributes()) { + CSFLogDebug(logTag, "%s: Activating ICE media stream=%u components=%u", + mParentHandle.c_str(), + static_cast<unsigned>(aMLine), + static_cast<unsigned>(aComponentCount)); + + std::vector<std::string> attrs; + for (auto i = aCandidateList.begin(); i != aCandidateList.end(); ++i) { + attrs.push_back("candidate:" + *i); + } + attrs.push_back("ice-ufrag:" + aUfrag); + attrs.push_back("ice-pwd:" + aPassword); + + nsresult rv = stream->ParseAttributes(attrs); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "Couldn't parse ICE attributes, rv=%u", + static_cast<unsigned>(rv)); + } + + for (size_t c = aComponentCount; c < stream->components(); ++c) { + // components are 1-indexed + stream->DisableComponent(c + 1); + } + } +} + +void +PeerConnectionMedia::RemoveTransportsAtOrAfter_s(size_t aMLine) +{ + for (size_t i = aMLine; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) { + mIceCtxHdlr->ctx()->SetStream(i, nullptr); + } +} + +nsresult PeerConnectionMedia::UpdateMediaPipelines( + const JsepSession& session) { + auto trackPairs = session.GetNegotiatedTrackPairs(); + MediaPipelineFactory factory(this); + nsresult rv; + + for (auto i = trackPairs.begin(); i != trackPairs.end(); ++i) { + JsepTrackPair pair = *i; + + if (pair.mReceiving) { + rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mReceiving); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (pair.mSending) { + rv = factory.CreateOrUpdateMediaPipeline(pair, *pair.mSending); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + for (auto& stream : mRemoteSourceStreams) { + stream->StartReceiving(); + } + + return NS_OK; +} + +void +PeerConnectionMedia::StartIceChecks(const JsepSession& aSession) +{ + nsCOMPtr<nsIRunnable> runnable( + WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::StartIceChecks_s, + aSession.IsIceControlling(), + aSession.RemoteIsIceLite(), + // Copy, just in case API changes to return a ref + std::vector<std::string>(aSession.GetIceOptions()))); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +void +PeerConnectionMedia::StartIceChecks_s( + bool aIsControlling, + bool aIsIceLite, + const std::vector<std::string>& aIceOptionsList) { + + CSFLogDebug(logTag, "Starting ICE Checking"); + + std::vector<std::string> attributes; + if (aIsIceLite) { + attributes.push_back("ice-lite"); + } + + if (!aIceOptionsList.empty()) { + attributes.push_back("ice-options:"); + for (auto i = aIceOptionsList.begin(); i != aIceOptionsList.end(); ++i) { + attributes.back() += *i + ' '; + } + } + + nsresult rv = mIceCtxHdlr->ctx()->ParseGlobalAttributes(attributes); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "%s: couldn't parse global parameters", __FUNCTION__ ); + } + + mIceCtxHdlr->ctx()->SetControlling(aIsControlling ? + NrIceCtx::ICE_CONTROLLING : + NrIceCtx::ICE_CONTROLLED); + + mIceCtxHdlr->ctx()->StartChecks(); +} + +bool +PeerConnectionMedia::IsIceRestarting() const +{ + ASSERT_ON_THREAD(mMainThread); + + return (mIceRestartState != ICE_RESTART_NONE); +} + +PeerConnectionMedia::IceRestartState +PeerConnectionMedia::GetIceRestartState() const +{ + ASSERT_ON_THREAD(mMainThread); + + return mIceRestartState; +} + +void +PeerConnectionMedia::BeginIceRestart(const std::string& ufrag, + const std::string& pwd) +{ + ASSERT_ON_THREAD(mMainThread); + if (IsIceRestarting()) { + return; + } + + RefPtr<NrIceCtx> new_ctx = mIceCtxHdlr->CreateCtx(ufrag, pwd); + + RUN_ON_THREAD(GetSTSThread(), + WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::BeginIceRestart_s, + new_ctx), + NS_DISPATCH_NORMAL); + + mIceRestartState = ICE_RESTART_PROVISIONAL; +} + +void +PeerConnectionMedia::BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx) +{ + ASSERT_ON_THREAD(mSTSThread); + + // hold the original context so we can disconnect signals if needed + RefPtr<NrIceCtx> originalCtx = mIceCtxHdlr->ctx(); + + if (mIceCtxHdlr->BeginIceRestart(new_ctx)) { + ConnectSignals(mIceCtxHdlr->ctx().get(), originalCtx.get()); + } +} + +void +PeerConnectionMedia::CommitIceRestart() +{ + ASSERT_ON_THREAD(mMainThread); + if (mIceRestartState != ICE_RESTART_PROVISIONAL) { + return; + } + + mIceRestartState = ICE_RESTART_COMMITTED; +} + +void +PeerConnectionMedia::FinalizeIceRestart() +{ + ASSERT_ON_THREAD(mMainThread); + if (!IsIceRestarting()) { + return; + } + + RUN_ON_THREAD(GetSTSThread(), + WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::FinalizeIceRestart_s), + NS_DISPATCH_NORMAL); + + mIceRestartState = ICE_RESTART_NONE; +} + +void +PeerConnectionMedia::FinalizeIceRestart_s() +{ + ASSERT_ON_THREAD(mSTSThread); + + // reset old streams since we don't need them anymore + for (auto i = mTransportFlows.begin(); + i != mTransportFlows.end(); + ++i) { + RefPtr<TransportFlow> aFlow = i->second; + if (!aFlow) continue; + TransportLayerIce* ice = + static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID())); + ice->ResetOldStream(); + } + + mIceCtxHdlr->FinalizeIceRestart(); +} + +void +PeerConnectionMedia::RollbackIceRestart() +{ + ASSERT_ON_THREAD(mMainThread); + if (mIceRestartState != ICE_RESTART_PROVISIONAL) { + return; + } + + RUN_ON_THREAD(GetSTSThread(), + WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::RollbackIceRestart_s), + NS_DISPATCH_NORMAL); + + mIceRestartState = ICE_RESTART_NONE; +} + +void +PeerConnectionMedia::RollbackIceRestart_s() +{ + ASSERT_ON_THREAD(mSTSThread); + + // hold the restart context so we can disconnect signals + RefPtr<NrIceCtx> restartCtx = mIceCtxHdlr->ctx(); + + // restore old streams since we're rolling back + for (auto i = mTransportFlows.begin(); + i != mTransportFlows.end(); + ++i) { + RefPtr<TransportFlow> aFlow = i->second; + if (!aFlow) continue; + TransportLayerIce* ice = + static_cast<TransportLayerIce*>(aFlow->GetLayer(TransportLayerIce::ID())); + ice->RestoreOldStream(); + } + + mIceCtxHdlr->RollbackIceRestart(); + ConnectSignals(mIceCtxHdlr->ctx().get(), restartCtx.get()); +} + +bool +PeerConnectionMedia::GetPrefDefaultAddressOnly() const +{ + ASSERT_ON_THREAD(mMainThread); // will crash on STS thread + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + uint64_t winId = mParent->GetWindow()->WindowID(); + + bool default_address_only = Preferences::GetBool( + "media.peerconnection.ice.default_address_only", false); + default_address_only |= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); +#else + bool default_address_only = true; +#endif + return default_address_only; +} + +bool +PeerConnectionMedia::GetPrefProxyOnly() const +{ + ASSERT_ON_THREAD(mMainThread); // will crash on STS thread + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + return Preferences::GetBool("media.peerconnection.ice.proxy_only", false); +#else + return false; +#endif +} + +void +PeerConnectionMedia::ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx) +{ + aCtx->SignalGatheringStateChange.connect( + this, + &PeerConnectionMedia::IceGatheringStateChange_s); + aCtx->SignalConnectionStateChange.connect( + this, + &PeerConnectionMedia::IceConnectionStateChange_s); + + if (aOldCtx) { + MOZ_ASSERT(aCtx != aOldCtx); + aOldCtx->SignalGatheringStateChange.disconnect(this); + aOldCtx->SignalConnectionStateChange.disconnect(this); + + // if the old and new connection state and/or gathering state is + // different fire the state update. Note: we don't fire the update + // if the state is *INIT since updates for the INIT state aren't + // sent during the normal flow. (mjf) + if (aOldCtx->connection_state() != aCtx->connection_state() && + aCtx->connection_state() != NrIceCtx::ICE_CTX_INIT) { + aCtx->SignalConnectionStateChange(aCtx, aCtx->connection_state()); + } + + if (aOldCtx->gathering_state() != aCtx->gathering_state() && + aCtx->gathering_state() != NrIceCtx::ICE_CTX_GATHER_INIT) { + aCtx->SignalGatheringStateChange(aCtx, aCtx->gathering_state()); + } + } +} + +void +PeerConnectionMedia::AddIceCandidate(const std::string& candidate, + const std::string& mid, + uint32_t aMLine) { + RUN_ON_THREAD(GetSTSThread(), + WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::AddIceCandidate_s, + std::string(candidate), // Make copies. + std::string(mid), + aMLine), + NS_DISPATCH_NORMAL); +} +void +PeerConnectionMedia::AddIceCandidate_s(const std::string& aCandidate, + const std::string& aMid, + uint32_t aMLine) { + RefPtr<NrIceMediaStream> stream(mIceCtxHdlr->ctx()->GetStream(aMLine)); + if (!stream) { + CSFLogError(logTag, "No ICE stream for candidate at level %u: %s", + static_cast<unsigned>(aMLine), aCandidate.c_str()); + return; + } + + nsresult rv = stream->ParseTrickleCandidate(aCandidate); + if (NS_FAILED(rv)) { + CSFLogError(logTag, "Couldn't process ICE candidate at level %u", + static_cast<unsigned>(aMLine)); + return; + } +} + +void +PeerConnectionMedia::FlushIceCtxOperationQueueIfReady() +{ + ASSERT_ON_THREAD(mMainThread); + + if (IsIceCtxReady()) { + for (auto i = mQueuedIceCtxOperations.begin(); + i != mQueuedIceCtxOperations.end(); + ++i) { + GetSTSThread()->Dispatch(*i, NS_DISPATCH_NORMAL); + } + mQueuedIceCtxOperations.clear(); + } +} + +void +PeerConnectionMedia::PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable) +{ + ASSERT_ON_THREAD(mMainThread); + + if (IsIceCtxReady()) { + GetSTSThread()->Dispatch(runnable, NS_DISPATCH_NORMAL); + } else { + mQueuedIceCtxOperations.push_back(runnable); + } +} + +void +PeerConnectionMedia::GatherIfReady() { + ASSERT_ON_THREAD(mMainThread); + + nsCOMPtr<nsIRunnable> runnable(WrapRunnable( + RefPtr<PeerConnectionMedia>(this), + &PeerConnectionMedia::EnsureIceGathering_s, + GetPrefDefaultAddressOnly(), + GetPrefProxyOnly())); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +void +PeerConnectionMedia::EnsureIceGathering_s(bool aDefaultRouteOnly, + bool aProxyOnly) { + if (mProxyServer) { + mIceCtxHdlr->ctx()->SetProxyServer(*mProxyServer); + } else if (aProxyOnly) { + IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(), + NrIceCtx::ICE_CTX_GATHER_COMPLETE); + return; + } + + // Start gathering, but only if there are streams + for (size_t i = 0; i < mIceCtxHdlr->ctx()->GetStreamCount(); ++i) { + if (mIceCtxHdlr->ctx()->GetStream(i)) { + mIceCtxHdlr->ctx()->StartGathering(aDefaultRouteOnly, aProxyOnly); + return; + } + } + + // If there are no streams, we're probably in a situation where we've rolled + // back while still waiting for our proxy configuration to come back. Make + // sure content knows that the rollback has stuck wrt gathering. + IceGatheringStateChange_s(mIceCtxHdlr->ctx().get(), + NrIceCtx::ICE_CTX_GATHER_COMPLETE); +} + +nsresult +PeerConnectionMedia::AddTrack(DOMMediaStream& aMediaStream, + const std::string& streamId, + MediaStreamTrack& aTrack, + const std::string& trackId) +{ + ASSERT_ON_THREAD(mMainThread); + + CSFLogDebug(logTag, "%s: MediaStream: %p", __FUNCTION__, &aMediaStream); + + RefPtr<LocalSourceStreamInfo> localSourceStream = + GetLocalStreamById(streamId); + + if (!localSourceStream) { + localSourceStream = new LocalSourceStreamInfo(&aMediaStream, this, streamId); + mLocalSourceStreams.AppendElement(localSourceStream); + } + + localSourceStream->AddTrack(trackId, &aTrack); + return NS_OK; +} + +nsresult +PeerConnectionMedia::RemoveLocalTrack(const std::string& streamId, + const std::string& trackId) +{ + ASSERT_ON_THREAD(mMainThread); + + CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__, + streamId.c_str(), trackId.c_str()); + + RefPtr<LocalSourceStreamInfo> localSourceStream = + GetLocalStreamById(streamId); + if (!localSourceStream) { + return NS_ERROR_ILLEGAL_VALUE; + } + + localSourceStream->RemoveTrack(trackId); + if (!localSourceStream->GetTrackCount()) { + mLocalSourceStreams.RemoveElement(localSourceStream); + } + return NS_OK; +} + +nsresult +PeerConnectionMedia::RemoveRemoteTrack(const std::string& streamId, + const std::string& trackId) +{ + ASSERT_ON_THREAD(mMainThread); + + CSFLogDebug(logTag, "%s: stream: %s track: %s", __FUNCTION__, + streamId.c_str(), trackId.c_str()); + + RefPtr<RemoteSourceStreamInfo> remoteSourceStream = + GetRemoteStreamById(streamId); + if (!remoteSourceStream) { + return NS_ERROR_ILLEGAL_VALUE; + } + + remoteSourceStream->RemoveTrack(trackId); + if (!remoteSourceStream->GetTrackCount()) { + mRemoteSourceStreams.RemoveElement(remoteSourceStream); + } + return NS_OK; +} + +void +PeerConnectionMedia::SelfDestruct() +{ + ASSERT_ON_THREAD(mMainThread); + + CSFLogDebug(logTag, "%s: ", __FUNCTION__); + + // Shut down the media + for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) { + mLocalSourceStreams[i]->DetachMedia_m(); + } + + for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) { + mRemoteSourceStreams[i]->DetachMedia_m(); + } + + if (mProxyRequest) { + mProxyRequest->Cancel(NS_ERROR_ABORT); + mProxyRequest = nullptr; + } + + // Shutdown the transport (async) + RUN_ON_THREAD(mSTSThread, WrapRunnable( + this, &PeerConnectionMedia::ShutdownMediaTransport_s), + NS_DISPATCH_NORMAL); + + CSFLogDebug(logTag, "%s: Media shut down", __FUNCTION__); +} + +void +PeerConnectionMedia::SelfDestruct_m() +{ + CSFLogDebug(logTag, "%s: ", __FUNCTION__); + + ASSERT_ON_THREAD(mMainThread); + + mLocalSourceStreams.Clear(); + mRemoteSourceStreams.Clear(); + + mMainThread = nullptr; + + // Final self-destruct. + this->Release(); +} + +void +PeerConnectionMedia::ShutdownMediaTransport_s() +{ + ASSERT_ON_THREAD(mSTSThread); + + CSFLogDebug(logTag, "%s: ", __FUNCTION__); + + // Here we access m{Local|Remote}SourceStreams off the main thread. + // That's OK because by here PeerConnectionImpl has forgotten about us, + // so there is no chance of getting a call in here from outside. + // The dispatches from SelfDestruct() and to SelfDestruct_m() provide + // memory barriers that protect us from badness. + for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) { + mLocalSourceStreams[i]->DetachTransport_s(); + } + + for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) { + mRemoteSourceStreams[i]->DetachTransport_s(); + } + + disconnect_all(); + mTransportFlows.clear(); + mIceCtxHdlr = nullptr; + + mMainThread->Dispatch(WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m), + NS_DISPATCH_NORMAL); +} + +LocalSourceStreamInfo* +PeerConnectionMedia::GetLocalStreamByIndex(int aIndex) +{ + ASSERT_ON_THREAD(mMainThread); + if(aIndex < 0 || aIndex >= (int) mLocalSourceStreams.Length()) { + return nullptr; + } + + MOZ_ASSERT(mLocalSourceStreams[aIndex]); + return mLocalSourceStreams[aIndex]; +} + +LocalSourceStreamInfo* +PeerConnectionMedia::GetLocalStreamById(const std::string& id) +{ + ASSERT_ON_THREAD(mMainThread); + for (size_t i = 0; i < mLocalSourceStreams.Length(); ++i) { + if (id == mLocalSourceStreams[i]->GetId()) { + return mLocalSourceStreams[i]; + } + } + + return nullptr; +} + +LocalSourceStreamInfo* +PeerConnectionMedia::GetLocalStreamByTrackId(const std::string& id) +{ + ASSERT_ON_THREAD(mMainThread); + for (RefPtr<LocalSourceStreamInfo>& info : mLocalSourceStreams) { + if (info->HasTrack(id)) { + return info; + } + } + + return nullptr; +} + +RemoteSourceStreamInfo* +PeerConnectionMedia::GetRemoteStreamByIndex(size_t aIndex) +{ + ASSERT_ON_THREAD(mMainThread); + MOZ_ASSERT(mRemoteSourceStreams.SafeElementAt(aIndex)); + return mRemoteSourceStreams.SafeElementAt(aIndex); +} + +RemoteSourceStreamInfo* +PeerConnectionMedia::GetRemoteStreamById(const std::string& id) +{ + ASSERT_ON_THREAD(mMainThread); + for (size_t i = 0; i < mRemoteSourceStreams.Length(); ++i) { + if (id == mRemoteSourceStreams[i]->GetId()) { + return mRemoteSourceStreams[i]; + } + } + + return nullptr; +} + +RemoteSourceStreamInfo* +PeerConnectionMedia::GetRemoteStreamByTrackId(const std::string& id) +{ + ASSERT_ON_THREAD(mMainThread); + for (RefPtr<RemoteSourceStreamInfo>& info : mRemoteSourceStreams) { + if (info->HasTrack(id)) { + return info; + } + } + + return nullptr; +} + + +nsresult +PeerConnectionMedia::AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo) +{ + ASSERT_ON_THREAD(mMainThread); + + mRemoteSourceStreams.AppendElement(aInfo); + + return NS_OK; +} + +void +PeerConnectionMedia::IceGatheringStateChange_s(NrIceCtx* ctx, + NrIceCtx::GatheringState state) +{ + ASSERT_ON_THREAD(mSTSThread); + + if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) { + // Fire off EndOfLocalCandidates for each stream + for (size_t i = 0; ; ++i) { + RefPtr<NrIceMediaStream> stream(ctx->GetStream(i)); + if (!stream) { + break; + } + + NrIceCandidate candidate; + NrIceCandidate rtcpCandidate; + GetDefaultCandidates(*stream, &candidate, &rtcpCandidate); + EndOfLocalCandidates(candidate.cand_addr.host, + candidate.cand_addr.port, + rtcpCandidate.cand_addr.host, + rtcpCandidate.cand_addr.port, + i); + } + } + + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, + &PeerConnectionMedia::IceGatheringStateChange_m, + ctx, + state), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::IceConnectionStateChange_s(NrIceCtx* ctx, + NrIceCtx::ConnectionState state) +{ + ASSERT_ON_THREAD(mSTSThread); + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, + &PeerConnectionMedia::IceConnectionStateChange_m, + ctx, + state), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::OnCandidateFound_s(NrIceMediaStream *aStream, + const std::string &aCandidateLine) +{ + ASSERT_ON_THREAD(mSTSThread); + MOZ_ASSERT(aStream); + MOZ_RELEASE_ASSERT(mIceCtxHdlr); + + CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str()); + + NrIceCandidate candidate; + NrIceCandidate rtcpCandidate; + GetDefaultCandidates(*aStream, &candidate, &rtcpCandidate); + + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, + &PeerConnectionMedia::OnCandidateFound_m, + aCandidateLine, + candidate.cand_addr.host, + candidate.cand_addr.port, + rtcpCandidate.cand_addr.host, + rtcpCandidate.cand_addr.port, + aStream->GetLevel()), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::EndOfLocalCandidates(const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine) +{ + GetMainThread()->Dispatch( + WrapRunnable(this, + &PeerConnectionMedia::EndOfLocalCandidates_m, + aDefaultAddr, + aDefaultPort, + aDefaultRtcpAddr, + aDefaultRtcpPort, + aMLine), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::GetDefaultCandidates(const NrIceMediaStream& aStream, + NrIceCandidate* aCandidate, + NrIceCandidate* aRtcpCandidate) +{ + nsresult res = aStream.GetDefaultCandidate(1, aCandidate); + // Optional; component won't exist if doing rtcp-mux + if (NS_FAILED(aStream.GetDefaultCandidate(2, aRtcpCandidate))) { + aRtcpCandidate->cand_addr.host.clear(); + aRtcpCandidate->cand_addr.port = 0; + } + if (NS_FAILED(res)) { + aCandidate->cand_addr.host.clear(); + aCandidate->cand_addr.port = 0; + CSFLogError(logTag, "%s: GetDefaultCandidates failed for level %u, " + "res=%u", + __FUNCTION__, + static_cast<unsigned>(aStream.GetLevel()), + static_cast<unsigned>(res)); + } +} + +void +PeerConnectionMedia::IceGatheringStateChange_m(NrIceCtx* ctx, + NrIceCtx::GatheringState state) +{ + ASSERT_ON_THREAD(mMainThread); + SignalIceGatheringStateChange(ctx, state); +} + +void +PeerConnectionMedia::IceConnectionStateChange_m(NrIceCtx* ctx, + NrIceCtx::ConnectionState state) +{ + ASSERT_ON_THREAD(mMainThread); + SignalIceConnectionStateChange(ctx, state); +} + +void +PeerConnectionMedia::IceStreamReady_s(NrIceMediaStream *aStream) +{ + MOZ_ASSERT(aStream); + + CSFLogDebug(logTag, "%s: %s", __FUNCTION__, aStream->name().c_str()); +} + +void +PeerConnectionMedia::OnCandidateFound_m(const std::string& aCandidateLine, + const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine) +{ + ASSERT_ON_THREAD(mMainThread); + if (!aDefaultAddr.empty()) { + SignalUpdateDefaultCandidate(aDefaultAddr, + aDefaultPort, + aDefaultRtcpAddr, + aDefaultRtcpPort, + aMLine); + } + SignalCandidate(aCandidateLine, aMLine); +} + +void +PeerConnectionMedia::EndOfLocalCandidates_m(const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine) { + ASSERT_ON_THREAD(mMainThread); + if (!aDefaultAddr.empty()) { + SignalUpdateDefaultCandidate(aDefaultAddr, + aDefaultPort, + aDefaultRtcpAddr, + aDefaultRtcpPort, + aMLine); + } + SignalEndOfLocalCandidates(aMLine); +} + +void +PeerConnectionMedia::DtlsConnected_s(TransportLayer *layer, + TransportLayer::State state) +{ + MOZ_ASSERT(layer->id() == "dtls"); + TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(layer); + dtlsLayer->SignalStateChange.disconnect(this); + + bool privacyRequested = (dtlsLayer->GetNegotiatedAlpn() == "c-webrtc"); + GetMainThread()->Dispatch( + WrapRunnableNM(&PeerConnectionMedia::DtlsConnected_m, + mParentHandle, privacyRequested), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::DtlsConnected_m(const std::string& aParentHandle, + bool aPrivacyRequested) +{ + PeerConnectionWrapper pcWrapper(aParentHandle); + PeerConnectionImpl* pc = pcWrapper.impl(); + if (pc) { + pc->SetDtlsConnected(aPrivacyRequested); + } +} + +void +PeerConnectionMedia::AddTransportFlow(int aIndex, bool aRtcp, + const RefPtr<TransportFlow> &aFlow) +{ + int index_inner = GetTransportFlowIndex(aIndex, aRtcp); + + MOZ_ASSERT(!mTransportFlows[index_inner]); + mTransportFlows[index_inner] = aFlow; + + GetSTSThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::ConnectDtlsListener_s, aFlow), + NS_DISPATCH_NORMAL); +} + +void +PeerConnectionMedia::RemoveTransportFlow(int aIndex, bool aRtcp) +{ + int index_inner = GetTransportFlowIndex(aIndex, aRtcp); + NS_ProxyRelease(GetSTSThread(), mTransportFlows[index_inner].forget()); +} + +void +PeerConnectionMedia::ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow) +{ + TransportLayer* dtls = aFlow->GetLayer(TransportLayerDtls::ID()); + if (dtls) { + dtls->SignalStateChange.connect(this, &PeerConnectionMedia::DtlsConnected_s); + } +} + +nsresult +LocalSourceStreamInfo::TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info, + const std::string& oldTrackId, + MediaStreamTrack& aNewTrack, + const std::string& newTrackId) +{ + if (mPipelines.count(newTrackId)) { + CSFLogError(logTag, "%s: Pipeline already exists for %s/%s", + __FUNCTION__, mId.c_str(), newTrackId.c_str()); + return NS_ERROR_INVALID_ARG; + } + + RefPtr<MediaPipeline> pipeline(info->ForgetPipelineByTrackId_m(oldTrackId)); + + if (!pipeline) { + // Replacetrack can potentially happen in the middle of offer/answer, before + // the pipeline has been created. + CSFLogInfo(logTag, "%s: Replacing track before the pipeline has been " + "created, nothing to do.", __FUNCTION__); + return NS_OK; + } + + nsresult rv = + static_cast<MediaPipelineTransmit*>(pipeline.get())->ReplaceTrack(aNewTrack); + NS_ENSURE_SUCCESS(rv, rv); + + mPipelines[newTrackId] = pipeline; + + return NS_OK; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +/** + * Tells you if any local track is isolated to a specific peer identity. + * Obviously, we want all the tracks to be isolated equally so that they can + * all be sent or not. We check once when we are setting a local description + * and that determines if we flip the "privacy requested" bit on. Once the bit + * is on, all media originating from this peer connection is isolated. + * + * @returns true if any track has a peerIdentity set on it + */ +bool +PeerConnectionMedia::AnyLocalTrackHasPeerIdentity() const +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { + for (auto pair : mLocalSourceStreams[u]->GetMediaStreamTracks()) { + if (pair.second->GetPeerIdentity() != nullptr) { + return true; + } + } + } + return false; +} + +void +PeerConnectionMedia::UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal) +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mRemoteSourceStreams.Length(); u++) { + mRemoteSourceStreams[u]->UpdatePrincipal_m(aPrincipal); + } +} + +void +PeerConnectionMedia::UpdateSinkIdentity_m(MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) +{ + ASSERT_ON_THREAD(mMainThread); + + for (uint32_t u = 0; u < mLocalSourceStreams.Length(); u++) { + mLocalSourceStreams[u]->UpdateSinkIdentity_m(aTrack, aPrincipal, + aSinkIdentity); + } +} + +void +LocalSourceStreamInfo::UpdateSinkIdentity_m(MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) +{ + for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) { + MediaPipelineTransmit* pipeline = + static_cast<MediaPipelineTransmit*>((*it).second.get()); + pipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity); + } +} + +void RemoteSourceStreamInfo::UpdatePrincipal_m(nsIPrincipal* aPrincipal) +{ + // This blasts away the existing principal. + // We only do this when we become certain that the all tracks are safe to make + // accessible to the script principal. + for (auto& trackPair : mTracks) { + MOZ_RELEASE_ASSERT(trackPair.second); + RemoteTrackSource& source = + static_cast<RemoteTrackSource&>(trackPair.second->GetSource()); + source.SetPrincipal(aPrincipal); + + RefPtr<MediaPipeline> pipeline = GetPipelineByTrackId_m(trackPair.first); + if (pipeline) { + MOZ_ASSERT(pipeline->direction() == MediaPipeline::RECEIVE); + static_cast<MediaPipelineReceive*>(pipeline.get()) + ->SetPrincipalHandle_m(MakePrincipalHandle(aPrincipal)); + } + } +} +#endif // MOZILLA_INTERNAL_API + +bool +PeerConnectionMedia::AnyCodecHasPluginID(uint64_t aPluginID) +{ + for (uint32_t i=0; i < mLocalSourceStreams.Length(); ++i) { + if (mLocalSourceStreams[i]->AnyCodecHasPluginID(aPluginID)) { + return true; + } + } + for (uint32_t i=0; i < mRemoteSourceStreams.Length(); ++i) { + if (mRemoteSourceStreams[i]->AnyCodecHasPluginID(aPluginID)) { + return true; + } + } + return false; +} + +bool +SourceStreamInfo::AnyCodecHasPluginID(uint64_t aPluginID) +{ + // Scan the videoConduits for this plugin ID + for (auto it = mPipelines.begin(); it != mPipelines.end(); ++it) { + if (it->second->Conduit()->CodecPluginID() == aPluginID) { + return true; + } + } + return false; +} + +nsresult +SourceStreamInfo::StorePipeline( + const std::string& trackId, + const RefPtr<mozilla::MediaPipeline>& aPipeline) +{ + MOZ_ASSERT(mPipelines.find(trackId) == mPipelines.end()); + if (mPipelines.find(trackId) != mPipelines.end()) { + CSFLogError(logTag, "%s: Storing duplicate track", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + mPipelines[trackId] = aPipeline; + return NS_OK; +} + +void +RemoteSourceStreamInfo::DetachMedia_m() +{ + for (auto& webrtcIdAndTrack : mTracks) { + EndTrack(mMediaStream->GetInputStream(), webrtcIdAndTrack.second); + } + SourceStreamInfo::DetachMedia_m(); +} + +void +RemoteSourceStreamInfo::RemoveTrack(const std::string& trackId) +{ + auto it = mTracks.find(trackId); + if (it != mTracks.end()) { + EndTrack(mMediaStream->GetInputStream(), it->second); + } + + SourceStreamInfo::RemoveTrack(trackId); +} + +void +RemoteSourceStreamInfo::SyncPipeline( + RefPtr<MediaPipelineReceive> aPipeline) +{ + // See if we have both audio and video here, and if so cross the streams and + // sync them + // TODO: Do we need to prevent multiple syncs if there is more than one audio + // or video track in a single media stream? What are we supposed to do in this + // case? + for (auto i = mPipelines.begin(); i != mPipelines.end(); ++i) { + if (i->second->IsVideo() != aPipeline->IsVideo()) { + // Ok, we have one video, one non-video - cross the streams! + WebrtcAudioConduit *audio_conduit = + static_cast<WebrtcAudioConduit*>(aPipeline->IsVideo() ? + i->second->Conduit() : + aPipeline->Conduit()); + WebrtcVideoConduit *video_conduit = + static_cast<WebrtcVideoConduit*>(aPipeline->IsVideo() ? + aPipeline->Conduit() : + i->second->Conduit()); + video_conduit->SyncTo(audio_conduit); + CSFLogDebug(logTag, "Syncing %p to %p, %s to %s", + video_conduit, audio_conduit, + i->first.c_str(), aPipeline->trackid().c_str()); + } + } +} + +void +RemoteSourceStreamInfo::StartReceiving() +{ + if (mReceiving || mPipelines.empty()) { + return; + } + + mReceiving = true; + + SourceMediaStream* source = GetMediaStream()->GetInputStream()->AsSourceStream(); + source->SetPullEnabled(true); + // AdvanceKnownTracksTicksTime(HEAT_DEATH_OF_UNIVERSE) means that in + // theory per the API, we can't add more tracks before that + // time. However, the impl actually allows it, and it avoids a whole + // bunch of locking that would be required (and potential blocking) + // if we used smaller values and updated them on each NotifyPull. + source->AdvanceKnownTracksTime(STREAM_TIME_MAX); + CSFLogDebug(logTag, "Finished adding tracks to MediaStream %p", source); +} + +RefPtr<MediaPipeline> SourceStreamInfo::GetPipelineByTrackId_m( + const std::string& trackId) { + ASSERT_ON_THREAD(mParent->GetMainThread()); + + // Refuse to hand out references if we're tearing down. + // (Since teardown involves a dispatch to and from STS before MediaPipelines + // are released, it is safe to start other dispatches to and from STS with a + // RefPtr<MediaPipeline>, since that reference won't be the last one + // standing) + if (mMediaStream) { + if (mPipelines.count(trackId)) { + return mPipelines[trackId]; + } + } + + return nullptr; +} + +already_AddRefed<MediaPipeline> +LocalSourceStreamInfo::ForgetPipelineByTrackId_m(const std::string& trackId) +{ + ASSERT_ON_THREAD(mParent->GetMainThread()); + + // Refuse to hand out references if we're tearing down. + // (Since teardown involves a dispatch to and from STS before MediaPipelines + // are released, it is safe to start other dispatches to and from STS with a + // RefPtr<MediaPipeline>, since that reference won't be the last one + // standing) + if (mMediaStream) { + if (mPipelines.count(trackId)) { + RefPtr<MediaPipeline> pipeline(mPipelines[trackId]); + mPipelines.erase(trackId); + return pipeline.forget(); + } + } + + return nullptr; +} + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +auto +RemoteTrackSource::ApplyConstraints( + nsPIDOMWindowInner* aWindow, + const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid> +{ + RefPtr<PledgeVoid> p = new PledgeVoid(); + p->Reject(new dom::MediaStreamError(aWindow, + NS_LITERAL_STRING("OverconstrainedError"), + NS_LITERAL_STRING(""))); + return p.forget(); +} +#endif + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h new file mode 100644 index 000000000..c0001a5e5 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h @@ -0,0 +1,586 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PEER_CONNECTION_MEDIA_H_ +#define _PEER_CONNECTION_MEDIA_H_ + +#include <string> +#include <vector> +#include <map> + +#include "nspr.h" +#include "prlock.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIProtocolProxyCallback.h" + +#include "signaling/src/jsep/JsepSession.h" +#include "AudioSegment.h" + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "Layers.h" +#include "VideoUtils.h" +#include "ImageLayers.h" +#include "VideoSegment.h" +#include "MediaStreamTrack.h" +#endif + +class nsIPrincipal; + +namespace mozilla { +class DataChannel; +class PeerIdentity; +class MediaPipelineFactory; +namespace dom { +struct RTCInboundRTPStreamStats; +struct RTCOutboundRTPStreamStats; +} +} + +#include "nricectxhandler.h" +#include "nriceresolver.h" +#include "nricemediastream.h" +#include "MediaPipeline.h" + +namespace mozilla { + +class PeerConnectionImpl; +class PeerConnectionMedia; +class PCUuidGenerator; + +class SourceStreamInfo { +public: + SourceStreamInfo(DOMMediaStream* aMediaStream, + PeerConnectionMedia *aParent, + const std::string& aId) + : mMediaStream(aMediaStream), + mParent(aParent), + mId(aId) { + MOZ_ASSERT(mMediaStream); + } + + SourceStreamInfo(already_AddRefed<DOMMediaStream>& aMediaStream, + PeerConnectionMedia *aParent, + const std::string& aId) + : mMediaStream(aMediaStream), + mParent(aParent), + mId(aId) { + MOZ_ASSERT(mMediaStream); + } + + virtual ~SourceStreamInfo() {} + + DOMMediaStream* GetMediaStream() const { + return mMediaStream; + } + + nsresult StorePipeline(const std::string& trackId, + const RefPtr<MediaPipeline>& aPipeline); + + virtual void AddTrack(const std::string& trackId, + const RefPtr<dom::MediaStreamTrack>& aTrack) + { + mTracks.insert(std::make_pair(trackId, aTrack)); + } + virtual void RemoveTrack(const std::string& trackId); + bool HasTrack(const std::string& trackId) const + { + return !!mTracks.count(trackId); + } + size_t GetTrackCount() const { return mTracks.size(); } + + // This method exists for stats and the unittests. + // It allows visibility into the pipelines and flows. + const std::map<std::string, RefPtr<MediaPipeline>>& + GetPipelines() const { return mPipelines; } + RefPtr<MediaPipeline> GetPipelineByTrackId_m(const std::string& trackId); + // This is needed so PeerConnectionImpl can unregister itself as + // PrincipalChangeObserver from each track. + const std::map<std::string, RefPtr<dom::MediaStreamTrack>>& + GetMediaStreamTracks() const { return mTracks; } + dom::MediaStreamTrack* GetTrackById(const std::string& trackId) const + { + auto it = mTracks.find(trackId); + if (it == mTracks.end()) { + return nullptr; + } + + return it->second; + } + const std::string& GetId() const { return mId; } + + void DetachTransport_s(); + virtual void DetachMedia_m(); + bool AnyCodecHasPluginID(uint64_t aPluginID); +protected: + void EndTrack(MediaStream* stream, dom::MediaStreamTrack* track); + RefPtr<DOMMediaStream> mMediaStream; + PeerConnectionMedia *mParent; + const std::string mId; + // These get set up before we generate our local description, the pipelines + // and conduits are set up once offer/answer completes. + std::map<std::string, RefPtr<dom::MediaStreamTrack>> mTracks; + std::map<std::string, RefPtr<MediaPipeline>> mPipelines; +}; + +// TODO(ekr@rtfm.com): Refactor {Local,Remote}SourceStreamInfo +// bug 837539. +class LocalSourceStreamInfo : public SourceStreamInfo { + ~LocalSourceStreamInfo() { + mMediaStream = nullptr; + } +public: + LocalSourceStreamInfo(DOMMediaStream *aMediaStream, + PeerConnectionMedia *aParent, + const std::string& aId) + : SourceStreamInfo(aMediaStream, aParent, aId) {} + + nsresult TakePipelineFrom(RefPtr<LocalSourceStreamInfo>& info, + const std::string& oldTrackId, + dom::MediaStreamTrack& aNewTrack, + const std::string& newTrackId); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + void UpdateSinkIdentity_m(dom::MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity); +#endif + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LocalSourceStreamInfo) + +private: + already_AddRefed<MediaPipeline> ForgetPipelineByTrackId_m( + const std::string& trackId); +}; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +class RemoteTrackSource : public dom::MediaStreamTrackSource +{ +public: + explicit RemoteTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel) + : dom::MediaStreamTrackSource(aPrincipal, aLabel) {} + + dom::MediaSourceEnum GetMediaSource() const override + { + return dom::MediaSourceEnum::Other; + } + + already_AddRefed<PledgeVoid> + ApplyConstraints(nsPIDOMWindowInner* aWindow, + const dom::MediaTrackConstraints& aConstraints) override; + + void Stop() override + { + // XXX (Bug 1314270): Implement rejection logic if necessary when we have + // clarity in the spec. + } + + void SetPrincipal(nsIPrincipal* aPrincipal) + { + mPrincipal = aPrincipal; + PrincipalChanged(); + } + +protected: + virtual ~RemoteTrackSource() {} +}; +#endif + +class RemoteSourceStreamInfo : public SourceStreamInfo { + ~RemoteSourceStreamInfo() {} + public: + RemoteSourceStreamInfo(already_AddRefed<DOMMediaStream> aMediaStream, + PeerConnectionMedia *aParent, + const std::string& aId) + : SourceStreamInfo(aMediaStream, aParent, aId), + mReceiving(false) + { + } + + void DetachMedia_m() override; + void RemoveTrack(const std::string& trackId) override; + void SyncPipeline(RefPtr<MediaPipelineReceive> aPipeline); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + void UpdatePrincipal_m(nsIPrincipal* aPrincipal); +#endif + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RemoteSourceStreamInfo) + + void AddTrack(const std::string& trackId, + const RefPtr<dom::MediaStreamTrack>& aTrack) override + { + SourceStreamInfo::AddTrack(trackId, aTrack); + } + + TrackID GetNumericTrackId(const std::string& trackId) const + { + dom::MediaStreamTrack* track = GetTrackById(trackId); + if (!track) { + return TRACK_INVALID; + } + return track->mTrackID; + } + + void StartReceiving(); + + private: + // True iff SetPullEnabled(true) has been called on the DOMMediaStream. This + // happens when offer/answer concludes. + bool mReceiving; +}; + +class PeerConnectionMedia : public sigslot::has_slots<> { + ~PeerConnectionMedia() + { + MOZ_RELEASE_ASSERT(!mMainThread); + } + + public: + explicit PeerConnectionMedia(PeerConnectionImpl *parent); + + enum IceRestartState { ICE_RESTART_NONE, + ICE_RESTART_PROVISIONAL, + ICE_RESTART_COMMITTED + }; + + PeerConnectionImpl* GetPC() { return mParent; } + nsresult Init(const std::vector<NrIceStunServer>& stun_servers, + const std::vector<NrIceTurnServer>& turn_servers, + NrIceCtx::Policy policy); + // WARNING: This destroys the object! + void SelfDestruct(); + + RefPtr<NrIceCtxHandler> ice_ctx_hdlr() const { return mIceCtxHdlr; } + RefPtr<NrIceCtx> ice_ctx() const { return mIceCtxHdlr->ctx(); } + + RefPtr<NrIceMediaStream> ice_media_stream(size_t i) const { + return mIceCtxHdlr->ctx()->GetStream(i); + } + + size_t num_ice_media_streams() const { + return mIceCtxHdlr->ctx()->GetStreamCount(); + } + + // Ensure ICE transports exist that we might need when offer/answer concludes + void EnsureTransports(const JsepSession& aSession); + + // Activate or remove ICE transports at the conclusion of offer/answer, + // or when rollback occurs. + void ActivateOrRemoveTransports(const JsepSession& aSession); + + // Start ICE checks. + void StartIceChecks(const JsepSession& session); + + bool IsIceRestarting() const; + IceRestartState GetIceRestartState() const; + + // Begin ICE restart + void BeginIceRestart(const std::string& ufrag, + const std::string& pwd); + // Commit ICE Restart - offer/answer complete, no rollback possible + void CommitIceRestart(); + // Finalize ICE restart + void FinalizeIceRestart(); + // Abort ICE restart + void RollbackIceRestart(); + + // Process a trickle ICE candidate. + void AddIceCandidate(const std::string& candidate, const std::string& mid, + uint32_t aMLine); + + // Handle complete media pipelines. + nsresult UpdateMediaPipelines(const JsepSession& session); + + // Add a track (main thread only) + nsresult AddTrack(DOMMediaStream& aMediaStream, + const std::string& streamId, + dom::MediaStreamTrack& aTrack, + const std::string& trackId); + + nsresult RemoveLocalTrack(const std::string& streamId, + const std::string& trackId); + nsresult RemoveRemoteTrack(const std::string& streamId, + const std::string& trackId); + + // Get a specific local stream + uint32_t LocalStreamsLength() + { + return mLocalSourceStreams.Length(); + } + LocalSourceStreamInfo* GetLocalStreamByIndex(int index); + LocalSourceStreamInfo* GetLocalStreamById(const std::string& id); + LocalSourceStreamInfo* GetLocalStreamByTrackId(const std::string& id); + + // Get a specific remote stream + uint32_t RemoteStreamsLength() + { + return mRemoteSourceStreams.Length(); + } + + RemoteSourceStreamInfo* GetRemoteStreamByIndex(size_t index); + RemoteSourceStreamInfo* GetRemoteStreamById(const std::string& id); + RemoteSourceStreamInfo* GetRemoteStreamByTrackId(const std::string& id); + + // Add a remote stream. + nsresult AddRemoteStream(RefPtr<RemoteSourceStreamInfo> aInfo); + + nsresult ReplaceTrack(const std::string& aOldStreamId, + const std::string& aOldTrackId, + dom::MediaStreamTrack& aNewTrack, + const std::string& aNewStreamId, + const std::string& aNewTrackId); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // In cases where the peer isn't yet identified, we disable the pipeline (not + // the stream, that would potentially affect others), so that it sends + // black/silence. Once the peer is identified, re-enable those streams. + // aTrack will be set if this update came from a principal change on aTrack. + void UpdateSinkIdentity_m(dom::MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity); + // this determines if any track is peerIdentity constrained + bool AnyLocalTrackHasPeerIdentity() const; + // When we finally learn who is on the other end, we need to change the ownership + // on streams + void UpdateRemoteStreamPrincipals_m(nsIPrincipal* aPrincipal); +#endif + + bool AnyCodecHasPluginID(uint64_t aPluginID); + + const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; } + const nsCOMPtr<nsIEventTarget>& GetSTSThread() const { return mSTSThread; } + + static size_t GetTransportFlowIndex(int aStreamIndex, bool aRtcp) + { + return aStreamIndex * 2 + (aRtcp ? 1 : 0); + } + + // Get a transport flow either RTP/RTCP for a particular stream + // A stream can be of audio/video/datachannel/budled(?) types + RefPtr<TransportFlow> GetTransportFlow(int aStreamIndex, bool aIsRtcp) { + int index_inner = GetTransportFlowIndex(aStreamIndex, aIsRtcp); + + if (mTransportFlows.find(index_inner) == mTransportFlows.end()) + return nullptr; + + return mTransportFlows[index_inner]; + } + + // Add a transport flow + void AddTransportFlow(int aIndex, bool aRtcp, + const RefPtr<TransportFlow> &aFlow); + void RemoveTransportFlow(int aIndex, bool aRtcp); + void ConnectDtlsListener_s(const RefPtr<TransportFlow>& aFlow); + void DtlsConnected_s(TransportLayer* aFlow, + TransportLayer::State state); + static void DtlsConnected_m(const std::string& aParentHandle, + bool aPrivacyRequested); + + RefPtr<AudioSessionConduit> GetAudioConduit(size_t level) { + auto it = mConduits.find(level); + if (it == mConduits.end()) { + return nullptr; + } + + if (it->second.first) { + MOZ_ASSERT(false, "In GetAudioConduit, we found a video conduit!"); + return nullptr; + } + + return RefPtr<AudioSessionConduit>( + static_cast<AudioSessionConduit*>(it->second.second.get())); + } + + RefPtr<VideoSessionConduit> GetVideoConduit(size_t level) { + auto it = mConduits.find(level); + if (it == mConduits.end()) { + return nullptr; + } + + if (!it->second.first) { + MOZ_ASSERT(false, "In GetVideoConduit, we found an audio conduit!"); + return nullptr; + } + + return RefPtr<VideoSessionConduit>( + static_cast<VideoSessionConduit*>(it->second.second.get())); + } + + // Add a conduit + void AddAudioConduit(size_t level, const RefPtr<AudioSessionConduit> &aConduit) { + mConduits[level] = std::make_pair(false, aConduit); + } + + void AddVideoConduit(size_t level, const RefPtr<VideoSessionConduit> &aConduit) { + mConduits[level] = std::make_pair(true, aConduit); + } + + // ICE state signals + sigslot::signal2<NrIceCtx*, NrIceCtx::GatheringState> + SignalIceGatheringStateChange; + sigslot::signal2<NrIceCtx*, NrIceCtx::ConnectionState> + SignalIceConnectionStateChange; + // This passes a candidate:... attribute and level + sigslot::signal2<const std::string&, uint16_t> SignalCandidate; + // This passes address, port, level of the default candidate. + sigslot::signal5<const std::string&, uint16_t, + const std::string&, uint16_t, uint16_t> + SignalUpdateDefaultCandidate; + sigslot::signal1<uint16_t> + SignalEndOfLocalCandidates; + + private: + nsresult InitProxy(); + class ProtocolProxyQueryHandler : public nsIProtocolProxyCallback { + public: + explicit ProtocolProxyQueryHandler(PeerConnectionMedia *pcm) : + pcm_(pcm) {} + + NS_IMETHOD OnProxyAvailable(nsICancelable *request, + nsIChannel *aChannel, + nsIProxyInfo *proxyinfo, + nsresult result) override; + NS_DECL_ISUPPORTS + + private: + void SetProxyOnPcm(nsIProxyInfo& proxyinfo); + RefPtr<PeerConnectionMedia> pcm_; + virtual ~ProtocolProxyQueryHandler() {} + }; + + // Shutdown media transport. Must be called on STS thread. + void ShutdownMediaTransport_s(); + + // Final destruction of the media stream. Must be called on the main + // thread. + void SelfDestruct_m(); + + // Manage ICE transports. + void EnsureTransport_s(size_t aLevel, size_t aComponentCount); + void ActivateOrRemoveTransport_s( + size_t aMLine, + size_t aComponentCount, + const std::string& aUfrag, + const std::string& aPassword, + const std::vector<std::string>& aCandidateList); + void RemoveTransportsAtOrAfter_s(size_t aMLine); + + void GatherIfReady(); + void FlushIceCtxOperationQueueIfReady(); + void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable); + void EnsureIceGathering_s(bool aDefaultRouteOnly, bool aProxyOnly); + void StartIceChecks_s(bool aIsControlling, + bool aIsIceLite, + const std::vector<std::string>& aIceOptionsList); + + void BeginIceRestart_s(RefPtr<NrIceCtx> new_ctx); + void FinalizeIceRestart_s(); + void RollbackIceRestart_s(); + bool GetPrefDefaultAddressOnly() const; + bool GetPrefProxyOnly() const; + + void ConnectSignals(NrIceCtx *aCtx, NrIceCtx *aOldCtx=nullptr); + + // Process a trickle ICE candidate. + void AddIceCandidate_s(const std::string& aCandidate, const std::string& aMid, + uint32_t aMLine); + + + // ICE events + void IceGatheringStateChange_s(NrIceCtx* ctx, + NrIceCtx::GatheringState state); + void IceConnectionStateChange_s(NrIceCtx* ctx, + NrIceCtx::ConnectionState state); + void IceStreamReady_s(NrIceMediaStream *aStream); + void OnCandidateFound_s(NrIceMediaStream *aStream, + const std::string& aCandidate); + void EndOfLocalCandidates(const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine); + void GetDefaultCandidates(const NrIceMediaStream& aStream, + NrIceCandidate* aCandidate, + NrIceCandidate* aRtcpCandidate); + + void IceGatheringStateChange_m(NrIceCtx* ctx, + NrIceCtx::GatheringState state); + void IceConnectionStateChange_m(NrIceCtx* ctx, + NrIceCtx::ConnectionState state); + void OnCandidateFound_m(const std::string& aCandidateLine, + const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine); + void EndOfLocalCandidates_m(const std::string& aDefaultAddr, + uint16_t aDefaultPort, + const std::string& aDefaultRtcpAddr, + uint16_t aDefaultRtcpPort, + uint16_t aMLine); + bool IsIceCtxReady() const { + return mProxyResolveCompleted; + } + + // The parent PC + PeerConnectionImpl *mParent; + // and a loose handle on it for event driven stuff + std::string mParentHandle; + std::string mParentName; + + // A list of streams returned from GetUserMedia + // This is only accessed on the main thread (with one special exception) + nsTArray<RefPtr<LocalSourceStreamInfo> > mLocalSourceStreams; + + // A list of streams provided by the other side + // This is only accessed on the main thread (with one special exception) + nsTArray<RefPtr<RemoteSourceStreamInfo> > mRemoteSourceStreams; + + std::map<size_t, std::pair<bool, RefPtr<MediaSessionConduit>>> mConduits; + + // ICE objects + RefPtr<NrIceCtxHandler> mIceCtxHdlr; + + // DNS + RefPtr<NrIceResolver> mDNSResolver; + + // Transport flows: even is RTP, odd is RTCP + std::map<int, RefPtr<TransportFlow> > mTransportFlows; + + // UUID Generator + UniquePtr<PCUuidGenerator> mUuidGen; + + // The main thread. + nsCOMPtr<nsIThread> mMainThread; + + // The STS thread. + nsCOMPtr<nsIEventTarget> mSTSThread; + + // Used whenever we need to dispatch a runnable to STS to tweak something + // on our ICE ctx, but are not ready to do so at the moment (eg; we are + // waiting to get a callback with our http proxy config before we start + // gathering or start checking) + std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations; + + // Used to cancel any ongoing proxy request. + nsCOMPtr<nsICancelable> mProxyRequest; + + // Used to track the state of the request. + bool mProxyResolveCompleted; + + // Used to store the result of the request. + UniquePtr<NrIceProxyServer> mProxyServer; + + // Used to track the state of ice restart + IceRestartState mIceRestartState; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PeerConnectionMedia) +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h new file mode 100644 index 000000000..544315a3e --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_CHILD_H_ +#define _WEBRTC_GLOBAL_CHILD_H_ + +#include "mozilla/dom/PWebrtcGlobalChild.h" + +namespace mozilla { +namespace dom { + +class WebrtcGlobalChild : + public PWebrtcGlobalChild +{ + friend class ContentChild; + + bool mShutdown; + + MOZ_IMPLICIT WebrtcGlobalChild(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool RecvGetStatsRequest(const int& aRequestId, + const nsString& aPcIdFilter) override; + virtual bool RecvClearStatsRequest() override; + virtual bool RecvGetLogRequest(const int& aReqestId, + const nsCString& aPattern) override; + virtual bool RecvClearLogRequest() override; + virtual bool RecvSetAecLogging(const bool& aEnable) override; + virtual bool RecvSetDebugMode(const int& aLevel) override; + +public: + virtual ~WebrtcGlobalChild(); + static WebrtcGlobalChild* Create(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_CHILD_H_ diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp new file mode 100644 index 000000000..96bdd5b70 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp @@ -0,0 +1,1241 @@ +/* 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 "WebrtcGlobalInformation.h" +#include "mozilla/media/webrtc/WebrtcGlobal.h" +#include "WebrtcGlobalChild.h" +#include "WebrtcGlobalParent.h" + +#include <deque> +#include <string> +#include <algorithm> +#include <vector> +#include <map> +#include <queue> + +#include "CSFLog.h" +#include "WebRtcLog.h" +#include "mozilla/dom/WebrtcGlobalInformationBinding.h" +#include "mozilla/dom/ContentChild.h" + +#include "nsAutoPtr.h" +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "mozilla/ErrorResult.h" +#include "mozilla/Vector.h" +#include "nsProxyRelease.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/RefPtr.h" + +#include "rlogconnector.h" +#include "runnable_utils.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" +#include "webrtc/system_wrappers/interface/trace.h" + +static const char* logTag = "WebrtcGlobalInformation"; + +namespace mozilla { +namespace dom { + +typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries; +typedef nsTArray<RTCStatsReportInternal> Stats; + +template<class Request, typename Callback, + typename Result, typename QueryParam> +class RequestManager +{ +public: + + static Request* Create(Callback& aCallback, QueryParam& aParam) + { + mozilla::StaticMutexAutoLock lock(sMutex); + + int id = ++sLastRequestId; + auto result = sRequests.insert( + std::make_pair(id, Request(id, aCallback, aParam))); + + if (!result.second) { + return nullptr; + } + + return &result.first->second; + } + + static void Delete(int aId) + { + mozilla::StaticMutexAutoLock lock(sMutex); + sRequests.erase(aId); + } + + static Request* Get(int aId) + { + mozilla::StaticMutexAutoLock lock(sMutex); + auto r = sRequests.find(aId); + + if (r == sRequests.end()) { + return nullptr; + } + + return &r->second; + } + + Result mResult; + std::queue<RefPtr<WebrtcGlobalParent>> mContactList; + const int mRequestId; + + RefPtr<WebrtcGlobalParent> GetNextParent() + { + while (!mContactList.empty()) { + RefPtr<WebrtcGlobalParent> next = mContactList.front(); + mContactList.pop(); + if (next->IsActive()) { + return next; + } + } + + return nullptr; + } + + void Complete() + { + ErrorResult rv; + mCallback.get()->Call(mResult, rv); + + if (rv.Failed()) { + CSFLogError(logTag, "Error firing stats observer callback"); + } + } + +protected: + // The mutex is used to protect two related operations involving the sRequest map + // and the sLastRequestId. For the map, it prevents more than one thread from + // adding or deleting map entries at the same time. For id generation, + // it creates an atomic allocation and increment. + static mozilla::StaticMutex sMutex; + static std::map<int, Request> sRequests; + static int sLastRequestId; + + Callback mCallback; + + explicit RequestManager(int aId, Callback& aCallback) + : mRequestId(aId) + , mCallback(aCallback) + {} + ~RequestManager() {} +private: + + RequestManager() = delete; + RequestManager& operator=(const RequestManager&) = delete; +}; + +template<class Request, typename Callback, + typename Result, typename QueryParam> +mozilla::StaticMutex RequestManager<Request, Callback, Result, QueryParam>::sMutex; +template<class Request, typename Callback, + typename Result, typename QueryParam> +std::map<int, Request> RequestManager<Request, Callback, Result, QueryParam>::sRequests; +template<class Request, typename Callback, + typename Result, typename QueryParam> +int RequestManager<Request, Callback, Result, QueryParam>::sLastRequestId; + +typedef nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> StatsRequestCallback; + +class StatsRequest + : public RequestManager<StatsRequest, + StatsRequestCallback, + WebrtcGlobalStatisticsReport, + nsAString> +{ +public: + const nsString mPcIdFilter; + explicit StatsRequest(int aId, StatsRequestCallback& aCallback, nsAString& aFilter) + : RequestManager(aId, aCallback) + , mPcIdFilter(aFilter) + { + mResult.mReports.Construct(); + } + +private: + StatsRequest() = delete; + StatsRequest& operator=(const StatsRequest&) = delete; +}; + +typedef nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> LogRequestCallback; + +class LogRequest + : public RequestManager<LogRequest, + LogRequestCallback, + Sequence<nsString>, + const nsACString> +{ +public: + const nsCString mPattern; + explicit LogRequest(int aId, LogRequestCallback& aCallback, const nsACString& aPattern) + : RequestManager(aId, aCallback) + , mPattern(aPattern) + {} + +private: + LogRequest() = delete; + LogRequest& operator=(const LogRequest&) = delete; +}; + +class WebrtcContentParents +{ +public: + static WebrtcGlobalParent* Alloc(); + static void Dealloc(WebrtcGlobalParent* aParent); + static bool Empty() + { + return sContentParents.empty(); + } + static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll() + { + return sContentParents; + } +private: + static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents; + WebrtcContentParents() = delete; + WebrtcContentParents(const WebrtcContentParents&) = delete; + WebrtcContentParents& operator=(const WebrtcContentParents&) = delete; +}; + +std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents; + +WebrtcGlobalParent* WebrtcContentParents::Alloc() +{ + RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent; + sContentParents.push_back(cp); + return cp.get(); +} + +void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) +{ + if (aParent) { + aParent->mShutdown = true; + auto cp = std::find(sContentParents.begin(), sContentParents.end(), aParent); + if (cp != sContentParents.end()) { + sContentParents.erase(cp); + } + } +} + +static PeerConnectionCtx* GetPeerConnectionCtx() +{ + if(PeerConnectionCtx::isActive()) { + MOZ_ASSERT(PeerConnectionCtx::GetInstance()); + return PeerConnectionCtx::GetInstance(); + } + return nullptr; +} + +static void +OnStatsReport_m(WebrtcGlobalChild* aThisChild, + const int aRequestId, + nsAutoPtr<RTCStatsQueries> aQueryList) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aQueryList); + + if (aThisChild) { + Stats stats; + + // Copy stats generated for the currently active PeerConnections + for (auto&& query : *aQueryList) { + stats.AppendElement(*(query->report)); + } + // Reports saved for closed/destroyed PeerConnections + auto ctx = PeerConnectionCtx::GetInstance(); + if (ctx) { + for (auto&& pc : ctx->mStatsForClosedPeerConnections) { + stats.AppendElement(pc); + } + } + + Unused << aThisChild->SendGetStatsResult(aRequestId, stats); + return; + } + + // This is the last stats report to be collected. (Must be the gecko process). + MOZ_ASSERT(XRE_IsParentProcess()); + + StatsRequest* request = StatsRequest::Get(aRequestId); + + if (!request) { + CSFLogError(logTag, "Bad RequestId"); + return; + } + + for (auto&& query : *aQueryList) { + request->mResult.mReports.Value().AppendElement(*(query->report), fallible); + } + + // Reports saved for closed/destroyed PeerConnections + auto ctx = PeerConnectionCtx::GetInstance(); + if (ctx) { + for (auto&& pc : ctx->mStatsForClosedPeerConnections) { + request->mResult.mReports.Value().AppendElement(pc, fallible); + } + } + + request->Complete(); + StatsRequest::Delete(aRequestId); +} + +static void +GetAllStats_s(WebrtcGlobalChild* aThisChild, + const int aRequestId, + nsAutoPtr<RTCStatsQueries> aQueryList) +{ + MOZ_ASSERT(aQueryList); + // The call to PeerConnetionImpl must happen from a runnable + // dispatched on the STS thread. + + // Get stats from active connections. + for (auto&& query : *aQueryList) { + PeerConnectionImpl::ExecuteStatsQuery_s(query); + } + + // After the RTCStatsQueries have been filled in, control must return + // to the main thread before their eventual destruction. + NS_DispatchToMainThread(WrapRunnableNM(&OnStatsReport_m, + aThisChild, + aRequestId, + aQueryList), + NS_DISPATCH_NORMAL); +} + +static void OnGetLogging_m(WebrtcGlobalChild* aThisChild, + const int aRequestId, + nsAutoPtr<std::deque<std::string>> aLogList) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aThisChild) { + // Add this log to the collection of logs and call into + // the next content process. + Sequence<nsString> nsLogs; + + if (!aLogList->empty()) { + for (auto& line : *aLogList) { + nsLogs.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()), fallible); + } + nsLogs.AppendElement(NS_LITERAL_STRING("+++++++ END ++++++++"), fallible); + } + + Unused << aThisChild->SendGetLogResult(aRequestId, nsLogs); + return; + } + + // This is the last log to be collected. (Must be the gecko process). + MOZ_ASSERT(XRE_IsParentProcess()); + + LogRequest* request = LogRequest::Get(aRequestId); + + if (!request) { + CSFLogError(logTag, "Bad RequestId"); + return; + } + + if (!aLogList->empty()) { + for (auto& line : *aLogList) { + request->mResult.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()), + fallible); + } + request->mResult.AppendElement(NS_LITERAL_STRING("+++++++ END ++++++++"), + fallible); + } + + request->Complete(); + LogRequest::Delete(aRequestId); +} + +static void GetLogging_s(WebrtcGlobalChild* aThisChild, + const int aRequestId, + const std::string& aPattern) +{ + // Request log while not on the main thread. + RLogConnector* logs = RLogConnector::GetInstance(); + nsAutoPtr<std::deque<std::string>> result(new std::deque<std::string>); + // Might not exist yet. + if (logs) { + logs->Filter(aPattern, 0, result); + } + // Return to main thread to complete processing. + NS_DispatchToMainThread(WrapRunnableNM(&OnGetLogging_m, + aThisChild, + aRequestId, + result), + NS_DISPATCH_NORMAL); +} + +static nsresult +BuildStatsQueryList( + const std::map<const std::string, PeerConnectionImpl *>& aPeerConnections, + const nsAString& aPcIdFilter, + RTCStatsQueries* queries) +{ + nsresult rv; + + for (auto&& pc : aPeerConnections) { + MOZ_ASSERT(pc.second); + if (aPcIdFilter.IsEmpty() || + aPcIdFilter.EqualsASCII(pc.second->GetIdAsAscii().c_str())) { + if (pc.second->HasMedia()) { + if (!queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true)))) { + return NS_ERROR_OUT_OF_MEMORY; + } + rv = pc.second->BuildStatsQuery_m(nullptr, queries->back()); // all tracks + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(queries->back()->report); + } + } + } + + return NS_OK; +} + +static nsresult +RunStatsQuery( + const std::map<const std::string, PeerConnectionImpl *>& aPeerConnections, + const nsAString& aPcIdFilter, + WebrtcGlobalChild* aThisChild, + const int aRequestId) +{ + nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries); + nsresult rv = BuildStatsQueryList(aPeerConnections, aPcIdFilter, queries); + + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } else if (!stsThread) { + return NS_ERROR_FAILURE; + } + + rv = RUN_ON_THREAD(stsThread, + WrapRunnableNM(&GetAllStats_s, + aThisChild, + aRequestId, + queries), + NS_DISPATCH_NORMAL); + return rv; +} + +void ClearClosedStats() +{ + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + + if (ctx) { + ctx->mStatsForClosedPeerConnections.Clear(); + } +} + +void +WebrtcGlobalInformation::ClearAllStats( + const GlobalObject& aGlobal) +{ + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!WebrtcContentParents::Empty()) { + // Pass on the request to any content process based PeerConnections. + for (auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearStatsRequest(); + } + } + + // Flush the history for the chrome process + ClearClosedStats(); +} + +void +WebrtcGlobalInformation::GetAllStats( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& pcIdFilter, + ErrorResult& aRv) +{ + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + // CallbackObject does not support threadsafe refcounting, and must be + // used and destroyed on main. + StatsRequestCallback callbackHandle( + new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(&aStatsCallback)); + + nsString filter; + if (pcIdFilter.WasPassed()) { + filter = pcIdFilter.Value(); + } + + auto* request = StatsRequest::Create(callbackHandle, filter); + + if (!request) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (!WebrtcContentParents::Empty()) { + // Pass on the request to any content based PeerConnections. + for (auto& cp : WebrtcContentParents::GetAll()) { + request->mContactList.push(cp); + } + + auto next = request->GetNextParent(); + if (next) { + aRv = next->SendGetStatsRequest(request->mRequestId, request->mPcIdFilter) ? + NS_OK : NS_ERROR_FAILURE; + return; + } + } + // No content resident PeerConnectionCtx instances. + // Check this process. + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + nsresult rv; + + if (ctx) { + rv = RunStatsQuery(ctx->mGetPeerConnections(), + filter, nullptr, request->mRequestId); + + if (NS_FAILED(rv)) { + StatsRequest::Delete(request->mRequestId); + } + } else { + // Just send back an empty report. + rv = NS_OK; + request->Complete(); + StatsRequest::Delete(request->mRequestId); + } + + aRv = rv; + return; +} + +static nsresult +RunLogQuery(const nsCString& aPattern, + WebrtcGlobalChild* aThisChild, + const int aRequestId) +{ + nsresult rv; + nsCOMPtr<nsIEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } else if (!stsThread) { + return NS_ERROR_FAILURE; + } + + rv = RUN_ON_THREAD(stsThread, + WrapRunnableNM(&GetLogging_s, + aThisChild, + aRequestId, + aPattern.get()), + NS_DISPATCH_NORMAL); + return rv; +} + +static void ClearLogs_s() +{ + // Make call off main thread. + RLogConnector* logs = RLogConnector::GetInstance(); + if (logs) { + logs->Clear(); + } +} + +static nsresult +RunLogClear() +{ + nsresult rv; + nsCOMPtr<nsIEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } + if (!stsThread) { + return NS_ERROR_FAILURE; + } + + return RUN_ON_THREAD(stsThread, + WrapRunnableNM(&ClearLogs_s), + NS_DISPATCH_NORMAL); +} + +void +WebrtcGlobalInformation::ClearLogging( + const GlobalObject& aGlobal) +{ + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!WebrtcContentParents::Empty()) { + // Clear content process signaling logs + for (auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearLogRequest(); + } + } + + // Clear chrome process signaling logs + Unused << RunLogClear(); +} + +void +WebrtcGlobalInformation::GetLogging( + const GlobalObject& aGlobal, + const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, + ErrorResult& aRv) +{ + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + // CallbackObject does not support threadsafe refcounting, and must be + // destroyed on main. + LogRequestCallback callbackHandle( + new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(&aLoggingCallback)); + + nsAutoCString pattern; + CopyUTF16toUTF8(aPattern, pattern); + + LogRequest* request = LogRequest::Create(callbackHandle, pattern); + + if (!request) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (!WebrtcContentParents::Empty()) { + // Pass on the request to any content based PeerConnections. + for (auto& cp : WebrtcContentParents::GetAll()) { + request->mContactList.push(cp); + } + + auto next = request->GetNextParent(); + if (next) { + aRv = next->SendGetLogRequest(request->mRequestId, request->mPattern) ? + NS_OK : NS_ERROR_FAILURE; + return; + } + } + + nsresult rv = RunLogQuery(request->mPattern, nullptr, request->mRequestId); + + if (NS_FAILED(rv)) { + LogRequest::Delete(request->mRequestId); + } + + aRv = rv; + return; +} + +static int32_t sLastSetLevel = 0; +static bool sLastAECDebug = false; + +void +WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel) +{ + if (aLevel) { + StartWebRtcLog(webrtc::TraceLevel(aLevel)); + } else { + StopWebRtcLog(); + } + sLastSetLevel = aLevel; + + for (auto& cp : WebrtcContentParents::GetAll()){ + Unused << cp->SendSetDebugMode(aLevel); + } +} + +int32_t +WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) +{ + return sLastSetLevel; +} + +void +WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal, bool aEnable) +{ + if (aEnable) { + StartAecLog(); + } else { + StopAecLog(); + } + + sLastAECDebug = aEnable; + + for (auto& cp : WebrtcContentParents::GetAll()){ + Unused << cp->SendSetAecLogging(aEnable); + } +} + +bool +WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) +{ + return sLastAECDebug; +} + +bool +WebrtcGlobalParent::RecvGetStatsResult(const int& aRequestId, + nsTArray<RTCStatsReportInternal>&& Stats) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = NS_OK; + + StatsRequest* request = StatsRequest::Get(aRequestId); + + if (!request) { + CSFLogError(logTag, "Bad RequestId"); + return false; + } + + for (auto&& s : Stats) { + request->mResult.mReports.Value().AppendElement(s, fallible); + } + + auto next = request->GetNextParent(); + if (next) { + // There are more content instances to query. + return next->SendGetStatsRequest(request->mRequestId, request->mPcIdFilter); + } + + // Content queries complete, run chrome instance query if applicable + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + + if (ctx) { + rv = RunStatsQuery(ctx->mGetPeerConnections(), + request->mPcIdFilter, nullptr, aRequestId); + } else { + // No instance in the process, return the collections as is + request->Complete(); + StatsRequest::Delete(aRequestId); + } + + return NS_SUCCEEDED(rv); +} + +bool +WebrtcGlobalParent::RecvGetLogResult(const int& aRequestId, + const WebrtcGlobalLog& aLog) +{ + MOZ_ASSERT(NS_IsMainThread()); + + LogRequest* request = LogRequest::Get(aRequestId); + + if (!request) { + CSFLogError(logTag, "Bad RequestId"); + return false; + } + request->mResult.AppendElements(aLog, fallible); + + auto next = request->GetNextParent(); + if (next) { + // There are more content instances to query. + return next->SendGetLogRequest(request->mRequestId, request->mPattern); + } + + // Content queries complete, run chrome instance query if applicable + nsresult rv = RunLogQuery(request->mPattern, nullptr, aRequestId); + + if (NS_FAILED(rv)) { + //Unable to get gecko process log. Return what has been collected. + CSFLogError(logTag, "Unable to extract chrome process log"); + request->Complete(); + LogRequest::Delete(aRequestId); + } + + return true; +} + +WebrtcGlobalParent* +WebrtcGlobalParent::Alloc() +{ + return WebrtcContentParents::Alloc(); +} + +bool +WebrtcGlobalParent::Dealloc(WebrtcGlobalParent * aActor) +{ + WebrtcContentParents::Dealloc(aActor); + return true; +} + +void +WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) +{ + mShutdown = true; + return; +} + +bool +WebrtcGlobalParent::Recv__delete__() +{ + return true; +} + +MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() + : mShutdown(false) +{ + MOZ_COUNT_CTOR(WebrtcGlobalParent); +} + +MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() +{ + MOZ_COUNT_DTOR(WebrtcGlobalParent); +} + +bool +WebrtcGlobalChild::RecvGetStatsRequest(const int& aRequestId, + const nsString& aPcIdFilter) +{ + if (mShutdown) { + return true; + } + + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + + if (ctx) { + nsresult rv = RunStatsQuery(ctx->mGetPeerConnections(), + aPcIdFilter, this, aRequestId); + return NS_SUCCEEDED(rv); + } + + nsTArray<RTCStatsReportInternal> empty_stats; + SendGetStatsResult(aRequestId, empty_stats); + + return true; +} + +bool +WebrtcGlobalChild::RecvClearStatsRequest() +{ + if (mShutdown) { + return true; + } + + ClearClosedStats(); + return true; +} + +bool +WebrtcGlobalChild::RecvGetLogRequest(const int& aRequestId, + const nsCString& aPattern) +{ + if (mShutdown) { + return true; + } + + nsresult rv; + nsCOMPtr<nsIEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv) && stsThread) { + rv = RUN_ON_THREAD(stsThread, + WrapRunnableNM(&GetLogging_s, this, aRequestId, aPattern.get()), + NS_DISPATCH_NORMAL); + + if (NS_SUCCEEDED(rv)) { + return true; + } + } + + Sequence<nsString> empty_log; + SendGetLogResult(aRequestId, empty_log); + + return true; +} + +bool +WebrtcGlobalChild::RecvClearLogRequest() +{ + if (mShutdown) { + return true; + } + + RunLogClear(); + return true; +} + +bool +WebrtcGlobalChild::RecvSetAecLogging(const bool& aEnable) +{ + if (!mShutdown) { + if (aEnable) { + StartAecLog(); + } else { + StopAecLog(); + } + } + return true; +} + +bool +WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) +{ + if (!mShutdown) { + if (aLevel) { + StartWebRtcLog(webrtc::TraceLevel(aLevel)); + } else { + StopWebRtcLog(); + } + } + return true; +} + +WebrtcGlobalChild* +WebrtcGlobalChild::Create() +{ + WebrtcGlobalChild* child = + static_cast<WebrtcGlobalChild*>( + ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor()); + return child; +} + +void +WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mShutdown = true; +} + +MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() + : mShutdown(false) +{ + MOZ_COUNT_CTOR(WebrtcGlobalChild); +} + +MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() +{ + MOZ_COUNT_DTOR(WebrtcGlobalChild); +} + +struct StreamResult { + StreamResult() : candidateTypeBitpattern(0), streamSucceeded(false) {} + uint32_t candidateTypeBitpattern; + bool streamSucceeded; +}; + +static uint32_t GetCandidateIpAndTransportMask(const RTCIceCandidateStats *cand) { + + enum { + CANDIDATE_BITMASK_UDP = 1, + CANDIDATE_BITMASK_TCP = 1 << 1, + CANDIDATE_BITMASK_IPV6 = 1 << 2, + }; + + uint32_t res = 0; + + nsAutoCString transport; + // prefer local transport for local relay candidates + if (cand->mMozLocalTransport.WasPassed()) { + transport.Assign(NS_ConvertUTF16toUTF8(cand->mMozLocalTransport.Value())); + } else { + transport.Assign(NS_ConvertUTF16toUTF8(cand->mTransport.Value())); + } + if (transport == kNrIceTransportUdp) { + res |= CANDIDATE_BITMASK_UDP; + } else if (transport == kNrIceTransportTcp) { + res |= CANDIDATE_BITMASK_TCP; + } + + if (cand->mIpAddress.Value().FindChar(':') != -1) { + res |= CANDIDATE_BITMASK_IPV6; + } + + return res; +}; + +static void StoreLongTermICEStatisticsImpl_m( + nsresult result, + nsAutoPtr<RTCStatsQuery> query) { + + using namespace Telemetry; + + if (NS_FAILED(result) || + !query->error.empty() || + !query->report->mIceCandidateStats.WasPassed()) { + return; + } + + query->report->mClosed.Construct(true); + + // TODO(bcampen@mozilla.com): Do we need to watch out for cases where the + // components within a stream didn't have the same types of relayed + // candidates? I have a feeling that late trickle could cause this, but right + // now we don't have enough information to detect it (we would need to know + // the ICE component id for each candidate pair and candidate) + + std::map<std::string, StreamResult> streamResults; + + // Build list of streams, and whether or not they failed. + for (size_t i = 0; + i < query->report->mIceCandidatePairStats.Value().Length(); + ++i) { + const RTCIceCandidatePairStats &pair = + query->report->mIceCandidatePairStats.Value()[i]; + + if (!pair.mState.WasPassed() || !pair.mComponentId.WasPassed()) { + MOZ_CRASH(); + continue; + } + + // Note: this is not a "component" in the ICE definition, this is really a + // stream ID. This is just the way the stats API is standardized right now. + // Very confusing. + std::string streamId( + NS_ConvertUTF16toUTF8(pair.mComponentId.Value()).get()); + + streamResults[streamId].streamSucceeded |= + pair.mState.Value() == RTCStatsIceCandidatePairState::Succeeded; + } + + for (size_t i = 0; + i < query->report->mIceCandidateStats.Value().Length(); + ++i) { + const RTCIceCandidateStats &cand = + query->report->mIceCandidateStats.Value()[i]; + + if (!cand.mType.WasPassed() || + !cand.mCandidateType.WasPassed() || + !cand.mTransport.WasPassed() || + !cand.mIpAddress.WasPassed() || + !cand.mComponentId.WasPassed()) { + // Crash on debug, ignore this candidate otherwise. + MOZ_CRASH(); + continue; + } + + /* The bitmask after examaning a candidate should look like this: + * REMOTE_GATHERED_HOST_UDP = 1, + * REMOTE_GATHERED_HOST_TCP = 1 << 1, + * REMOTE_GATHERED_HOST_IPV6 = 1 << 2, + * REMOTE_GATHERED_SERVER_REFLEXIVE_UDP = 1 << 3, + * REMOTE_GATHERED_SERVER_REFLEXIVE_TCP = 1 << 4, + * REMOTE_GATHERED_SERVER_REFLEXIVE_IPV6 = 1 << 5, + * REMOTE_GATHERED_TURN_UDP = 1 << 6, + * REMOTE_GATHERED_TURN_TCP = 1 << 7, // dummy place holder + * REMOTE_GATHERED_TURN_IPV6 = 1 << 8, + * REMOTE_GATHERED_PEER_REFLEXIVE_UDP = 1 << 9, + * REMOTE_GATHERED_PEER_REFLEXIVE_TCP = 1 << 10, + * REMOTE_GATHERED_PEER_REFLEXIVE_IPV6 = 1 << 11, + * LOCAL_GATHERED_HOST_UDP = 1 << 16, + * LOCAL_GATHERED_HOST_TCP = 1 << 17, + * LOCAL_GATHERED_HOST_IPV6 = 1 << 18, + * LOCAL_GATHERED_SERVER_REFLEXIVE_UDP = 1 << 19, + * LOCAL_GATHERED_SERVER_REFLEXIVE_TCP = 1 << 20, + * LOCAL_GATHERED_SERVER_REFLEXIVE_IPV6 = 1 << 21, + * LOCAL_GATHERED_TURN_UDP = 1 << 22, + * LOCAL_GATHERED_TURN_TCP = 1 << 23, + * LOCAL_GATHERED_TURN_IPV6 = 1 << 24, + * LOCAL_GATHERED_PEERREFLEXIVE_UDP = 1 << 25, + * LOCAL_GATHERED_PEERREFLEXIVE_TCP = 1 << 26, + * LOCAL_GATHERED_PEERREFLEXIVE_IPV6 = 1 << 27, + * + * This results in following shift values + */ + static const uint32_t kLocalShift = 16; + static const uint32_t kSrflxShift = 3; + static const uint32_t kRelayShift = 6; + static const uint32_t kPrflxShift = 9; + + uint32_t candBitmask = GetCandidateIpAndTransportMask(&cand); + + // Note: shift values need to result in the above enum table + if (cand.mType.Value() == RTCStatsType::Localcandidate) { + candBitmask <<= kLocalShift; + } + + if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Serverreflexive) { + candBitmask <<= kSrflxShift; + } else if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Relayed) { + candBitmask <<= kRelayShift; + } else if (cand.mCandidateType.Value() == RTCStatsIceCandidateType::Peerreflexive) { + candBitmask <<= kPrflxShift; + } + + // Note: this is not a "component" in the ICE definition, this is really a + // stream ID. This is just the way the stats API is standardized right now. + // Very confusing. + std::string streamId( + NS_ConvertUTF16toUTF8(cand.mComponentId.Value()).get()); + + streamResults[streamId].candidateTypeBitpattern |= candBitmask; + } + + for (auto i = streamResults.begin(); i != streamResults.end(); ++i) { + Telemetry::RecordWebrtcIceCandidates(i->second.candidateTypeBitpattern, + i->second.streamSucceeded); + } + + // Beyond ICE, accumulate telemetry for various PER_CALL settings here. + + if (query->report->mOutboundRTPStreamStats.WasPassed()) { + auto& array = query->report->mOutboundRTPStreamStats.Value(); + for (decltype(array.Length()) i = 0; i < array.Length(); i++) { + auto& s = array[i]; + bool isVideo = (s.mId.Value().Find("video") != -1); + if (!isVideo || s.mIsRemote) { + continue; + } + if (s.mBitrateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS, + uint32_t(s.mBitrateMean.Value() / 1000)); + } + if (s.mBitrateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS, + uint32_t(s.mBitrateStdDev.Value() / 1000)); + } + if (s.mFramerateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL, + uint32_t(s.mFramerateMean.Value())); + } + if (s.mFramerateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL, + uint32_t(s.mFramerateStdDev.Value() * 10)); + } + if (s.mDroppedFrames.WasPassed() && !query->iceStartTime.IsNull()) { + double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60; + if (mins > 0) { + Accumulate(WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM, + uint32_t(double(s.mDroppedFrames.Value()) / mins)); + } + } + } + } + + if (query->report->mInboundRTPStreamStats.WasPassed()) { + auto& array = query->report->mInboundRTPStreamStats.Value(); + for (decltype(array.Length()) i = 0; i < array.Length(); i++) { + auto& s = array[i]; + bool isVideo = (s.mId.Value().Find("video") != -1); + if (!isVideo || s.mIsRemote) { + continue; + } + if (s.mBitrateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS, + uint32_t(s.mBitrateMean.Value() / 1000)); + } + if (s.mBitrateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS, + uint32_t(s.mBitrateStdDev.Value() / 1000)); + } + if (s.mFramerateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL, + uint32_t(s.mFramerateMean.Value())); + } + if (s.mFramerateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL, + uint32_t(s.mFramerateStdDev.Value() * 10)); + } + if (s.mDiscardedPackets.WasPassed() && !query->iceStartTime.IsNull()) { + double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60; + if (mins > 0) { + Accumulate(WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM, + uint32_t(double(s.mDiscardedPackets.Value()) / mins)); + } + } + } + } + + // Finally, store the stats + + PeerConnectionCtx *ctx = GetPeerConnectionCtx(); + if (ctx) { + ctx->mStatsForClosedPeerConnections.AppendElement(*query->report, fallible); + } +} + +static void GetStatsForLongTermStorage_s( + nsAutoPtr<RTCStatsQuery> query) { + + MOZ_ASSERT(query); + + nsresult rv = PeerConnectionImpl::ExecuteStatsQuery_s(query.get()); + + // Check whether packets were dropped due to rate limiting during + // this call. (These calls must be made on STS) + unsigned char rate_limit_bit_pattern = 0; + if (!mozilla::nr_socket_short_term_violation_time().IsNull() && + !query->iceStartTime.IsNull() && + mozilla::nr_socket_short_term_violation_time() >= query->iceStartTime) { + rate_limit_bit_pattern |= 1; + } + if (!mozilla::nr_socket_long_term_violation_time().IsNull() && + !query->iceStartTime.IsNull() && + mozilla::nr_socket_long_term_violation_time() >= query->iceStartTime) { + rate_limit_bit_pattern |= 2; + } + + if (query->failed) { + Telemetry::Accumulate( + Telemetry::WEBRTC_STUN_RATE_LIMIT_EXCEEDED_BY_TYPE_GIVEN_FAILURE, + rate_limit_bit_pattern); + } else { + Telemetry::Accumulate( + Telemetry::WEBRTC_STUN_RATE_LIMIT_EXCEEDED_BY_TYPE_GIVEN_SUCCESS, + rate_limit_bit_pattern); + } + + // Even if Telemetry::Accumulate is threadsafe, we still need to send the + // query back to main, since that is where it must be destroyed. + NS_DispatchToMainThread( + WrapRunnableNM( + &StoreLongTermICEStatisticsImpl_m, + rv, + query), + NS_DISPATCH_NORMAL); +} + +void WebrtcGlobalInformation::StoreLongTermICEStatistics( + PeerConnectionImpl& aPc) { + Telemetry::Accumulate(Telemetry::WEBRTC_ICE_FINAL_CONNECTION_STATE, + static_cast<uint32_t>(aPc.IceConnectionState())); + + if (aPc.IceConnectionState() == PCImplIceConnectionState::New) { + // ICE has not started; we won't have any remote candidates, so recording + // statistics on gathered candidates is pointless. + return; + } + + nsAutoPtr<RTCStatsQuery> query(new RTCStatsQuery(true)); + + nsresult rv = aPc.BuildStatsQuery_m(nullptr, query.get()); + + NS_ENSURE_SUCCESS_VOID(rv); + + RUN_ON_THREAD(aPc.GetSTSThread(), + WrapRunnableNM(&GetStatsForLongTermStorage_s, + query), + NS_DISPATCH_NORMAL); +} + +} // namespace dom +} // namespace mozilla diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h new file mode 100644 index 000000000..fb3789c20 --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_INFORMATION_H_ +#define _WEBRTC_GLOBAL_INFORMATION_H_ + +#include "nsString.h" +#include "mozilla/dom/BindingDeclarations.h" // for Optional + +namespace mozilla { +class PeerConnectionImpl; +class ErrorResult; + +namespace dom { + +class GlobalObject; +class WebrtcGlobalStatisticsCallback; +class WebrtcGlobalLoggingCallback; + +class WebrtcGlobalInformation +{ +public: + static void GetAllStats(const GlobalObject& aGlobal, + WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& pcIdFilter, + ErrorResult& aRv); + + static void ClearAllStats(const GlobalObject& aGlobal); + + static void GetLogging(const GlobalObject& aGlobal, + const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, + ErrorResult& aRv); + + static void ClearLogging(const GlobalObject& aGlobal); + + static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel); + static int32_t DebugLevel(const GlobalObject& aGlobal); + + static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable); + static bool AecDebug(const GlobalObject& aGlobal); + + static void StoreLongTermICEStatistics(PeerConnectionImpl& aPc); + +private: + WebrtcGlobalInformation() = delete; + WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete; + WebrtcGlobalInformation& operator=( + const WebrtcGlobalInformation& aRhs) = delete; +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_INFORMATION_H_ diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h new file mode 100644 index 000000000..4e2d0509f --- /dev/null +++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_PARENT_H_ +#define _WEBRTC_GLOBAL_PARENT_H_ + +#include "mozilla/dom/PWebrtcGlobalParent.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace dom { + +class WebrtcParents; + +class WebrtcGlobalParent + : public PWebrtcGlobalParent +{ + friend class ContentParent; + friend class WebrtcGlobalInformation; + friend class WebrtcContentParents; + + bool mShutdown; + + MOZ_IMPLICIT WebrtcGlobalParent(); + + static WebrtcGlobalParent* Alloc(); + static bool Dealloc(WebrtcGlobalParent* aActor); + + virtual bool RecvGetStatsResult(const int& aRequestId, + nsTArray<RTCStatsReportInternal>&& aStats) override; + virtual bool RecvGetLogResult(const int& aRequestId, + const WebrtcGlobalLog& aLog) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual bool Recv__delete__() override; + + virtual ~WebrtcGlobalParent(); +public: + NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent) + + bool IsActive() + { + return !mShutdown; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_PARENT_H_ |