4895 lines
170 KiB
C++
4895 lines
170 KiB
C++
/* 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 "common/browser_logging/CSFLog.h"
|
|
#include "base/histogram.h"
|
|
#include "common/time_profiling/timecard.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "nspr.h"
|
|
#include "nss.h"
|
|
#include "pk11pub.h"
|
|
|
|
#include "nsFmtString.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsEffectiveTLDService.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsIPrefBranch.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "prtime.h"
|
|
|
|
#include "libwebrtcglue/AudioConduit.h"
|
|
#include "libwebrtcglue/VideoConduit.h"
|
|
#include "libwebrtcglue/WebrtcCallWrapper.h"
|
|
#include "libwebrtcglue/WebrtcEnvironmentWrapper.h"
|
|
#include "MediaTrackGraph.h"
|
|
#include "transport/runnable_utils.h"
|
|
#include "IPeerConnection.h"
|
|
#include "PeerConnectionCtx.h"
|
|
#include "PeerConnectionImpl.h"
|
|
#include "RemoteTrackSource.h"
|
|
#include "nsDOMDataChannelDeclarations.h"
|
|
#include "transport/dtlsidentity.h"
|
|
#include "sdp/SdpAttribute.h"
|
|
|
|
#include "jsep/JsepTrack.h"
|
|
#include "jsep/JsepSession.h"
|
|
#include "jsep/JsepSessionImpl.h"
|
|
|
|
#include "transportbridge/MediaPipeline.h"
|
|
#include "transportbridge/RtpLogger.h"
|
|
#include "jsapi/RTCRtpReceiver.h"
|
|
#include "jsapi/RTCRtpSender.h"
|
|
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/Sprintf.h"
|
|
#include "mozilla/StaticPrefs_media.h"
|
|
#include "mozilla/glean/DomMediaWebrtcMetrics.h"
|
|
#include "mozilla/media/MediaUtils.h"
|
|
|
|
#ifdef XP_WIN
|
|
// We need to undef the MS macro for Document::CreateEvent
|
|
# ifdef CreateEvent
|
|
# undef CreateEvent
|
|
# endif
|
|
#endif // XP_WIN
|
|
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsDOMDataChannel.h"
|
|
#include "mozilla/dom/Location.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/glean/DomMediaWebrtcMetrics.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/PublicSSL.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsURLHelper.h"
|
|
#include "nsNetUtil.h"
|
|
#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
|
|
#include "js/GCAnnotations.h" // JS_HAZ_ROOTED
|
|
#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
|
|
#include "mozilla/PeerIdentity.h"
|
|
#include "mozilla/dom/RTCCertificate.h"
|
|
#include "mozilla/dom/RTCSctpTransportBinding.h" // RTCSctpTransportState
|
|
#include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState
|
|
#include "mozilla/dom/RTCIceTransportBinding.h" // RTCIceTransportState
|
|
#include "mozilla/dom/RTCRtpReceiverBinding.h"
|
|
#include "mozilla/dom/RTCRtpSenderBinding.h"
|
|
#include "mozilla/dom/RTCStatsReportBinding.h"
|
|
#include "mozilla/dom/RTCPeerConnectionBinding.h"
|
|
#include "mozilla/dom/PeerConnectionImplBinding.h"
|
|
#include "mozilla/dom/RTCDataChannelBinding.h"
|
|
#include "mozilla/dom/PluginCrashedEvent.h"
|
|
#include "MediaStreamTrack.h"
|
|
#include "AudioStreamTrack.h"
|
|
#include "VideoStreamTrack.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "DOMMediaStream.h"
|
|
#include "WebrtcGlobalInformation.h"
|
|
#include "mozilla/dom/Event.h"
|
|
#include "mozilla/EventDispatcher.h"
|
|
#include "mozilla/net/DataChannelProtocol.h"
|
|
#include "MediaManager.h"
|
|
|
|
#include "transport/nr_socket_proxy_config.h"
|
|
#include "RTCSctpTransport.h"
|
|
#include "RTCDtlsTransport.h"
|
|
#include "jsep/JsepTransport.h"
|
|
|
|
#include "nsILoadInfo.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "mozilla/LoadInfo.h"
|
|
#include "nsIProxiedChannel.h"
|
|
|
|
#include "mozilla/dom/BrowserChild.h"
|
|
#include "mozilla/net/WebrtcProxyConfig.h"
|
|
|
|
#ifdef XP_WIN
|
|
// We need to undef the MS macro again in case the windows include file
|
|
// got imported after we included mozilla/dom/Document.h
|
|
# ifdef CreateEvent
|
|
# undef CreateEvent
|
|
# endif
|
|
#endif // XP_WIN
|
|
|
|
#include "MediaSegment.h"
|
|
|
|
#ifdef USE_FAKE_PCOBSERVER
|
|
# include "FakePCObserver.h"
|
|
#else
|
|
# include "mozilla/dom/PeerConnectionObserverBinding.h"
|
|
#endif
|
|
#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
|
|
|
|
#define ICE_PARSING \
|
|
"In RTCConfiguration passed to RTCPeerConnection constructor"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using glean::webrtc_signaling::AudioMsectionNegotiatedExtra;
|
|
using glean::webrtc_signaling::SdpNegotiatedExtra;
|
|
using glean::webrtc_signaling::VideoMsectionNegotiatedExtra;
|
|
|
|
typedef PCObserverString ObString;
|
|
|
|
static const char* pciLogTag = "PeerConnectionImpl";
|
|
#ifdef LOGTAG
|
|
# undef LOGTAG
|
|
#endif
|
|
#define LOGTAG pciLogTag
|
|
|
|
static mozilla::LazyLogModule logModuleInfo("signaling");
|
|
|
|
// 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 broken. 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() { SuppressException(); }
|
|
} JS_HAZ_ROOTED;
|
|
|
|
// 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 ErrorResult&() { return *mRv; }
|
|
|
|
private:
|
|
mozilla::UniquePtr<JSErrorResult> mRv;
|
|
bool isCopy;
|
|
} JS_HAZ_ROOTED;
|
|
|
|
} // namespace
|
|
|
|
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;
|
|
}
|
|
|
|
namespace mozilla {
|
|
class DataChannel;
|
|
}
|
|
|
|
namespace mozilla {
|
|
|
|
void PeerConnectionAutoTimer::RegisterConnection() { mRefCnt++; }
|
|
|
|
void PeerConnectionAutoTimer::UnregisterConnection(bool aContainedAV) {
|
|
MOZ_ASSERT(mRefCnt);
|
|
mRefCnt--;
|
|
mUsedAV |= aContainedAV;
|
|
if (mRefCnt == 0) {
|
|
TimeDuration sample = TimeStamp::Now() - mStart;
|
|
if (mUsedAV) {
|
|
glean::webrtc::av_call_duration.AccumulateRawDuration(sample);
|
|
}
|
|
glean::webrtc::call_duration.AccumulateRawDuration(sample);
|
|
}
|
|
}
|
|
|
|
bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; }
|
|
|
|
// There is not presently an implementation of these for nsTHashMap :(
|
|
inline void ImplCycleCollectionUnlink(
|
|
PeerConnectionImpl::RTCDtlsTransportMap& aMap) {
|
|
for (auto& tableEntry : aMap) {
|
|
ImplCycleCollectionUnlink(*tableEntry.GetModifiableData());
|
|
}
|
|
aMap.Clear();
|
|
}
|
|
|
|
inline void ImplCycleCollectionTraverse(
|
|
nsCycleCollectionTraversalCallback& aCallback,
|
|
PeerConnectionImpl::RTCDtlsTransportMap& aMap, const char* aName,
|
|
uint32_t aFlags = 0) {
|
|
for (auto& tableEntry : aMap) {
|
|
ImplCycleCollectionTraverse(aCallback, *tableEntry.GetModifiableData(),
|
|
aName, aFlags);
|
|
}
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl)
|
|
tmp->Close();
|
|
tmp->BreakCycles();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(
|
|
mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams,
|
|
mOperations, mTransportIdToRTCDtlsTransport, mSctpTransport,
|
|
mLastStableSctpTransport, mLastStableSctpDtlsTransport, mKungFuDeathGrip)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
|
|
mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams,
|
|
mOperations, mTransceivers, mTransportIdToRTCDtlsTransport,
|
|
mSctpTransport, mLastStableSctpTransport, mLastStableSctpDtlsTransport,
|
|
mKungFuDeathGrip)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl)
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
already_AddRefed<PeerConnectionImpl> PeerConnectionImpl::Constructor(
|
|
const dom::GlobalObject& aGlobal) {
|
|
RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal);
|
|
|
|
CSFLogDebug(LOGTAG, "Created PeerConnection: %p", pc.get());
|
|
|
|
return pc.forget();
|
|
}
|
|
|
|
JSObject* PeerConnectionImpl::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
nsPIDOMWindowInner* PeerConnectionImpl::GetParentObject() const {
|
|
return mWindow;
|
|
}
|
|
|
|
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 (!aWindow) {
|
|
return false;
|
|
}
|
|
|
|
Document* doc = aWindow->GetExtantDoc();
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
|
|
nsILoadContext* loadContext = doc->GetLoadContext();
|
|
return loadContext && loadContext->UsePrivateBrowsing();
|
|
}
|
|
|
|
PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
|
|
: mTimeCard(MOZ_LOG_TEST(logModuleInfo, LogLevel::Error) ? create_timecard()
|
|
: nullptr),
|
|
mSignalingState(RTCSignalingState::Stable),
|
|
mIceConnectionState(RTCIceConnectionState::New),
|
|
mIceGatheringState(RTCIceGatheringState::New),
|
|
mConnectionState(RTCPeerConnectionState::New),
|
|
mWindow(do_QueryInterface(aGlobal ? aGlobal->GetAsSupports() : nullptr)),
|
|
mCertificate(nullptr),
|
|
mSTSThread(nullptr),
|
|
mForceIceTcp(false),
|
|
mTransportHandler(nullptr),
|
|
mUuidGen(MakeUnique<PCUuidGenerator>()),
|
|
mIceRestartCount(0),
|
|
mIceRollbackCount(0),
|
|
mHaveConfiguredCodecs(false),
|
|
mTrickle(true) // TODO(ekr@rtfm.com): Use pref
|
|
,
|
|
mPrivateWindow(false),
|
|
mActiveOnWindow(false),
|
|
mTimestampMaker(dom::RTCStatsTimestampMaker::Create(mWindow)),
|
|
mIdGenerator(new RTCStatsIdGenerator()),
|
|
listenPort(0),
|
|
connectPort(0),
|
|
connectStr(nullptr) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT_IF(aGlobal, mWindow);
|
|
mKungFuDeathGrip = this;
|
|
if (aGlobal) {
|
|
if (IsPrivateBrowsing(mWindow)) {
|
|
mPrivateWindow = true;
|
|
mDisableLongTermStats = true;
|
|
}
|
|
mWindow->AddPeerConnection();
|
|
mActiveOnWindow = true;
|
|
|
|
if (mWindow->GetDocumentURI()) {
|
|
mWindow->GetDocumentURI()->GetAsciiHost(mHostname);
|
|
nsresult rv;
|
|
nsCOMPtr<nsIEffectiveTLDService> eTLDService(
|
|
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
|
|
if (eTLDService) {
|
|
Unused << eTLDService->GetBaseDomain(mWindow->GetDocumentURI(), 0,
|
|
mEffectiveTLDPlus1);
|
|
}
|
|
|
|
mRtxIsAllowed = !media::HostnameInPref(
|
|
"media.peerconnection.video.use_rtx.blocklist", mHostname);
|
|
mDuplicateFingerprintQuirk = media::HostnameInPref(
|
|
"media.peerconnection.sdp.quirk.duplicate_fingerprint.allowlist",
|
|
mHostname);
|
|
}
|
|
}
|
|
|
|
if (!mUuidGen->Generate(&mHandle)) {
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__,
|
|
mHandle.c_str());
|
|
STAMP_TIMECARD(mTimeCard, "Constructor Completed");
|
|
mForceIceTcp =
|
|
Preferences::GetBool("media.peerconnection.ice.force_ice_tcp", false);
|
|
memset(mMaxReceiving, 0, sizeof(mMaxReceiving));
|
|
memset(mMaxSending, 0, sizeof(mMaxSending));
|
|
mJsConfiguration.mCertificatesProvided = false;
|
|
mJsConfiguration.mPeerIdentityProvided = false;
|
|
}
|
|
|
|
PeerConnectionImpl::~PeerConnectionImpl() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
MOZ_ASSERT(!mTransportHandler,
|
|
"PeerConnection should either be closed, or not initted in the "
|
|
"first place.");
|
|
|
|
if (mTimeCard) {
|
|
STAMP_TIMECARD(mTimeCard, "Destructor Invoked");
|
|
STAMP_TIMECARD(mTimeCard, mHandle.c_str());
|
|
print_timecard(mTimeCard);
|
|
destroy_timecard(mTimeCard);
|
|
mTimeCard = nullptr;
|
|
}
|
|
|
|
CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s",
|
|
__FUNCTION__, mHandle.c_str());
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
|
|
nsGlobalWindowInner* aWindow) {
|
|
nsresult res;
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mPCObserver = &aObserver;
|
|
|
|
// Find the STS thread
|
|
|
|
mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
|
|
MOZ_ASSERT(mSTSThread);
|
|
|
|
// We do callback handling on STS instead of main to avoid media jank.
|
|
// Someday, we may have a dedicated thread for this.
|
|
RefPtr transportHandler = MediaTransportHandler::Create(mSTSThread);
|
|
if (mPrivateWindow) {
|
|
transportHandler->EnterPrivateMode();
|
|
}
|
|
|
|
// 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;
|
|
NS_ENSURE_STATE(mWindow);
|
|
|
|
PRTime timestamp = PR_Now();
|
|
// Ok if we truncate this, but we want it to be large enough to reliably
|
|
// contain the location on the tests we run in CI.
|
|
char temp[256];
|
|
|
|
nsAutoCString locationCStr;
|
|
|
|
RefPtr<Location> location = mWindow->Location();
|
|
res = location->GetHref(locationCStr);
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
SprintfLiteral(temp, "%s %" PRIu64 " (id=%" PRIu64 " url=%s)",
|
|
mHandle.c_str(), static_cast<uint64_t>(timestamp),
|
|
static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0),
|
|
locationCStr.get() ? locationCStr.get() : "NULL");
|
|
|
|
mName = temp;
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx");
|
|
res = PeerConnectionCtx::InitializeGlobal();
|
|
NS_ENSURE_SUCCESS(res, res);
|
|
|
|
// Only set mTransportHandler here, after the NS_ENSURE_ exit guards, to not
|
|
// leave it in an unusable state -- CreateIceCtx must have been called for
|
|
// other calls to work.
|
|
mTransportHandler = std::move(transportHandler);
|
|
mTransportHandler->CreateIceCtx("PC:" + GetName());
|
|
|
|
mJsepSession =
|
|
MakeUnique<JsepSessionImpl>(mName, MakeUnique<PCUuidGenerator>());
|
|
mJsepSession->SetRtxIsAllowed(mRtxIsAllowed);
|
|
|
|
res = mJsepSession->Init();
|
|
if (NS_FAILED(res)) {
|
|
CSFLogError(LOGTAG, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__,
|
|
static_cast<unsigned>(res));
|
|
return res;
|
|
}
|
|
|
|
std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
|
|
SetupPreferredCodecs(preferredCodecs);
|
|
mJsepSession->SetDefaultCodecs(preferredCodecs);
|
|
|
|
std::vector<RtpExtensionHeader> preferredHeaders;
|
|
SetupPreferredRtpExtensions(preferredHeaders);
|
|
|
|
for (const auto& header : preferredHeaders) {
|
|
mJsepSession->AddRtpExtension(header.mMediaType, header.extensionname,
|
|
header.direction);
|
|
}
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
mStunAddrsRequest =
|
|
new net::StunAddrsRequestChild(new StunAddrsHandler(this));
|
|
}
|
|
|
|
// Initialize the media object.
|
|
mForceProxy = ShouldForceProxy();
|
|
|
|
// We put this here, in case we later want to set this based on a non-standard
|
|
// param in RTCConfiguration.
|
|
mAllowOldSetParameters = Preferences::GetBool(
|
|
"media.peerconnection.allow_old_setParameters", false);
|
|
|
|
// setup the stun local addresses IPC async call
|
|
InitLocalAddrs();
|
|
|
|
mSignalHandler = MakeUnique<SignalHandler>(this, mTransportHandler.get());
|
|
|
|
PeerConnectionCtx::GetInstance()->AddPeerConnection(mHandle, this);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
|
|
nsGlobalWindowInner& aWindow,
|
|
ErrorResult& rv) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult res = Initialize(aObserver, &aWindow);
|
|
if (NS_FAILED(res)) {
|
|
rv.Throw(res);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (mUncommittedJsepSession) {
|
|
Unused << mUncommittedJsepSession->AddDtlsFingerprint(
|
|
DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint);
|
|
}
|
|
}
|
|
|
|
const RefPtr<mozilla::dom::RTCCertificate>& PeerConnectionImpl::Certificate()
|
|
const {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
return mCertificate;
|
|
}
|
|
|
|
RefPtr<DtlsIdentity> PeerConnectionImpl::Identity() const {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
MOZ_ASSERT(mCertificate);
|
|
return mCertificate->CreateDtlsIdentity();
|
|
}
|
|
|
|
class CompareCodecPriority {
|
|
public:
|
|
void SetPreferredCodec(const nsCString& preferredCodec) {
|
|
mPreferredCodec = preferredCodec;
|
|
}
|
|
|
|
bool operator()(const UniquePtr<JsepCodecDescription>& lhs,
|
|
const UniquePtr<JsepCodecDescription>& rhs) const {
|
|
// Do we have a preferred codec?
|
|
if (!mPreferredCodec.IsEmpty()) {
|
|
const bool lhsMatches = mPreferredCodec.EqualsIgnoreCase(lhs->mName) ||
|
|
mPreferredCodec.EqualsIgnoreCase(lhs->mDefaultPt);
|
|
const bool rhsMatches = mPreferredCodec.EqualsIgnoreCase(rhs->mName) ||
|
|
mPreferredCodec.EqualsIgnoreCase(rhs->mDefaultPt);
|
|
// If the only the left side matches, prefer it
|
|
if (lhsMatches && !rhsMatches) {
|
|
return true;
|
|
}
|
|
}
|
|
// If only the left side is strongly preferred, prefer it
|
|
return (lhs->mStronglyPreferred && !rhs->mStronglyPreferred);
|
|
}
|
|
|
|
private:
|
|
// The preferred codec name or PT number
|
|
nsCString mPreferredCodec;
|
|
};
|
|
|
|
void RecordCodecTelemetry() {
|
|
const auto prefs = PeerConnectionImpl::GetDefaultCodecPreferences();
|
|
if (WebrtcVideoConduit::HasH264Hardware()) {
|
|
glean::webrtc::has_h264_hardware
|
|
.EnumGet(glean::webrtc::HasH264HardwareLabel::eTrue)
|
|
.Add();
|
|
}
|
|
|
|
glean::webrtc::software_h264_enabled
|
|
.EnumGet(static_cast<glean::webrtc::SoftwareH264EnabledLabel>(
|
|
prefs.SoftwareH264Enabled()))
|
|
.Add();
|
|
glean::webrtc::hardware_h264_enabled
|
|
.EnumGet(static_cast<glean::webrtc::HardwareH264EnabledLabel>(
|
|
prefs.HardwareH264Enabled()))
|
|
.Add();
|
|
glean::webrtc::h264_enabled
|
|
.EnumGet(
|
|
static_cast<glean::webrtc::H264EnabledLabel>(prefs.H264Enabled()))
|
|
.Add();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
RecordCodecTelemetry();
|
|
|
|
// We use this to sort the list of codecs once everything is configured
|
|
CompareCodecPriority comparator;
|
|
if (StaticPrefs::media_webrtc_codec_video_av1_experimental_preferred()) {
|
|
comparator.SetPreferredCodec(nsCString("av1"));
|
|
}
|
|
// Sort by priority
|
|
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.
|
|
//
|
|
// Note: 'media.peerconnection.sctp.force_maximum_message_size' changes
|
|
// behaviour triggered by these parameters.
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::EnsureDataConnection(uint16_t aLocalPort,
|
|
uint16_t aNumstreams,
|
|
uint32_t aMaxMessageSize,
|
|
bool aMMSSet) {
|
|
PC_AUTO_ENTER_API_CALL(false);
|
|
|
|
if (mDataConnection) {
|
|
CSFLogDebug(LOGTAG, "%s DataConnection already connected", __FUNCTION__);
|
|
mDataConnection->SetMaxMessageSize(aMMSSet, aMaxMessageSize);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
|
|
Maybe<uint64_t> mms = aMMSSet ? Some(aMaxMessageSize) : Nothing();
|
|
if (auto res = DataChannelConnection::Create(this, target, mTransportHandler,
|
|
aLocalPort, aNumstreams, mms)) {
|
|
mDataConnection = res.value();
|
|
CSFLogDebug(LOGTAG, "%s DataChannelConnection %p attached to %s",
|
|
__FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str());
|
|
return NS_OK;
|
|
}
|
|
CSFLogError(LOGTAG, "%s DataConnection Create Failed", __FUNCTION__);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::GetDatachannelParameters(
|
|
uint32_t* channels, uint16_t* localport, uint16_t* remoteport,
|
|
uint32_t* remotemaxmessagesize, bool* mmsset, std::string* transportId,
|
|
bool* client) const {
|
|
// Clear, just in case we fail.
|
|
*channels = 0;
|
|
*localport = 0;
|
|
*remoteport = 0;
|
|
*remotemaxmessagesize = 0;
|
|
*mmsset = false;
|
|
transportId->clear();
|
|
|
|
Maybe<const JsepTransceiver> datachannelTransceiver =
|
|
mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) {
|
|
return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
|
|
});
|
|
|
|
if (!datachannelTransceiver ||
|
|
!datachannelTransceiver->mTransport.mComponents ||
|
|
!datachannelTransceiver->mTransport.mDtls ||
|
|
!datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// This will release assert if there is no such index, and that's ok
|
|
const JsepTrackEncoding& encoding =
|
|
datachannelTransceiver->mSendTrack.GetNegotiatedDetails()->GetEncoding(0);
|
|
|
|
if (NS_WARN_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 auto& codec : encoding.GetCodecs()) {
|
|
if (codec->Type() != SdpMediaSection::kApplication) {
|
|
CSFLogError(LOGTAG,
|
|
"%s: Codec type for m=application was %u, this "
|
|
"is a bug.",
|
|
__FUNCTION__, static_cast<unsigned>(codec->Type()));
|
|
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;
|
|
}
|
|
|
|
if (codec->mChannels) {
|
|
*channels = codec->mChannels;
|
|
} else {
|
|
*channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT;
|
|
}
|
|
const JsepApplicationCodecDescription* appCodec =
|
|
static_cast<const JsepApplicationCodecDescription*>(codec.get());
|
|
*localport = appCodec->mLocalPort;
|
|
*remoteport = appCodec->mRemotePort;
|
|
*remotemaxmessagesize = appCodec->mRemoteMaxMessageSize;
|
|
*mmsset = appCodec->mRemoteMMSSet;
|
|
MOZ_ASSERT(!datachannelTransceiver->mTransport.mTransportId.empty());
|
|
*transportId = datachannelTransceiver->mTransport.mTransportId;
|
|
*client = datachannelTransceiver->mTransport.mDtls->GetRole() ==
|
|
JsepDtlsTransport::kJsepDtlsClient;
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession(
|
|
JsepTransceiver& transceiver) {
|
|
nsresult res = ConfigureJsepSessionCodecs();
|
|
if (NS_FAILED(res)) {
|
|
CSFLogError(LOGTAG, "Failed to configure codecs");
|
|
return res;
|
|
}
|
|
|
|
mJsepSession->AddTransceiver(transceiver);
|
|
return NS_OK;
|
|
}
|
|
|
|
static Maybe<SdpMediaSection::MediaType> ToSdpMediaType(
|
|
const nsAString& aKind) {
|
|
if (aKind.EqualsASCII("audio")) {
|
|
return Some(SdpMediaSection::MediaType::kAudio);
|
|
} else if (aKind.EqualsASCII("video")) {
|
|
return Some(SdpMediaSection::MediaType::kVideo);
|
|
}
|
|
return Nothing();
|
|
}
|
|
|
|
already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver(
|
|
const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind,
|
|
dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) {
|
|
// Copy, because we might need to modify
|
|
RTCRtpTransceiverInit init(aInit);
|
|
|
|
Maybe<SdpMediaSection::MediaType> type = ToSdpMediaType(aKind);
|
|
if (NS_WARN_IF(!type.isSome())) {
|
|
MOZ_ASSERT(false, "Invalid media kind");
|
|
aRv = NS_ERROR_INVALID_ARG;
|
|
return nullptr;
|
|
}
|
|
|
|
JsepTransceiver jsepTransceiver(*type, *mUuidGen);
|
|
jsepTransceiver.SetRtxIsAllowed(mRtxIsAllowed);
|
|
|
|
// Do this last, since it is not possible to roll back.
|
|
nsresult rv = AddRtpTransceiverToJsepSession(jsepTransceiver);
|
|
if (NS_FAILED(rv)) {
|
|
CSFLogError(LOGTAG, "%s: AddRtpTransceiverToJsepSession failed, res=%u",
|
|
__FUNCTION__, static_cast<unsigned>(rv));
|
|
aRv = rv;
|
|
return nullptr;
|
|
}
|
|
|
|
auto& sendEncodings = init.mSendEncodings;
|
|
|
|
// CheckAndRectifyEncodings covers these six:
|
|
// If any encoding contains a rid member whose value does not conform to the
|
|
// grammar requirements specified in Section 10 of [RFC8851], throw a
|
|
// TypeError.
|
|
|
|
// If some but not all encodings contain a rid member, throw a TypeError.
|
|
|
|
// If any encoding contains a rid member whose value is the same as that of a
|
|
// rid contained in another encoding in sendEncodings, throw a TypeError.
|
|
|
|
// If kind is "audio", remove the scaleResolutionDownBy member from all
|
|
// encodings that contain one.
|
|
|
|
// If any encoding contains a scaleResolutionDownBy member whose value is
|
|
// less than 1.0, throw a RangeError.
|
|
|
|
// Verify that the value of each maxFramerate member in sendEncodings that is
|
|
// defined is greater than 0.0. If one of the maxFramerate values does not
|
|
// meet this requirement, throw a RangeError.
|
|
RTCRtpSender::CheckAndRectifyEncodings(sendEncodings,
|
|
*type == SdpMediaSection::kVideo, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If any encoding contains a read-only parameter other than rid, throw an
|
|
// InvalidAccessError.
|
|
// NOTE: We don't support any additional read-only params right now. Also,
|
|
// spec shoehorns this in between checks that setParameters also performs
|
|
// (between the rid checks and the scaleResolutionDownBy checks).
|
|
|
|
// If any encoding contains a scaleResolutionDownBy member, then for each
|
|
// encoding without one, add a scaleResolutionDownBy member with the value
|
|
// 1.0.
|
|
for (const auto& constEncoding : sendEncodings) {
|
|
if (constEncoding.mScaleResolutionDownBy.WasPassed()) {
|
|
for (auto& encoding : sendEncodings) {
|
|
if (!encoding.mScaleResolutionDownBy.WasPassed()) {
|
|
encoding.mScaleResolutionDownBy.Construct(1.0f);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Let maxN be the maximum number of total simultaneous encodings the user
|
|
// agent may support for this kind, at minimum 1.This should be an optimistic
|
|
// number since the codec to be used is not known yet.
|
|
size_t maxN =
|
|
(*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1;
|
|
|
|
// If the number of encodings stored in sendEncodings exceeds maxN, then trim
|
|
// sendEncodings from the tail until its length is maxN.
|
|
// NOTE: Spec has this after all validation steps; even if there are elements
|
|
// that we will trim off, we still validate them.
|
|
if (sendEncodings.Length() > maxN) {
|
|
sendEncodings.TruncateLength(maxN);
|
|
}
|
|
|
|
// If kind is "video" and none of the encodings contain a
|
|
// scaleResolutionDownBy member, then for each encoding, add a
|
|
// scaleResolutionDownBy member with the value 2^(length of sendEncodings -
|
|
// encoding index - 1). This results in smaller-to-larger resolutions where
|
|
// the last encoding has no scaling applied to it, e.g. 4:2:1 if the length
|
|
// is 3.
|
|
// NOTE: The code above ensures that these are all set, or all unset, so we
|
|
// can just check the first one.
|
|
if (sendEncodings.Length() && *type == SdpMediaSection::kVideo &&
|
|
!sendEncodings[0].mScaleResolutionDownBy.WasPassed()) {
|
|
double scale = 1.0f;
|
|
for (auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) {
|
|
it->mScaleResolutionDownBy.Construct(scale);
|
|
scale *= 2;
|
|
}
|
|
}
|
|
|
|
// If the number of encodings now stored in sendEncodings is 1, then remove
|
|
// any rid member from the lone entry.
|
|
if (sendEncodings.Length() == 1) {
|
|
sendEncodings[0].mRid.Reset();
|
|
}
|
|
|
|
RefPtr<RTCRtpTransceiver> transceiver = CreateTransceiver(
|
|
jsepTransceiver.GetUuid(),
|
|
jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
|
|
aSendTrack, aAddTrackMagic, aRv);
|
|
|
|
if (aRv.Failed()) {
|
|
// Would be nice if we could peek at the rv without stealing it, so we
|
|
// could log...
|
|
CSFLogError(LOGTAG, "%s: failed", __FUNCTION__);
|
|
return nullptr;
|
|
}
|
|
|
|
mTransceivers.AppendElement(transceiver);
|
|
return transceiver.forget();
|
|
}
|
|
|
|
bool PeerConnectionImpl::CheckNegotiationNeeded() {
|
|
MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable);
|
|
SyncToJsep();
|
|
return !mLocalIceCredentialsToReplace.empty() ||
|
|
mJsepSession->CheckNegotiationNeeded();
|
|
}
|
|
|
|
bool PeerConnectionImpl::CreatedSender(const dom::RTCRtpSender& aSender) const {
|
|
return aSender.IsMyPc(this);
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::MaybeInitializeDataChannel() {
|
|
PC_AUTO_ENTER_API_CALL(false);
|
|
CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
|
|
|
|
uint32_t channels = 0;
|
|
uint16_t localport = 0;
|
|
uint16_t remoteport = 0;
|
|
uint32_t remotemaxmessagesize = 0;
|
|
bool mmsset = false;
|
|
std::string transportId;
|
|
bool client = false;
|
|
nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport,
|
|
&remotemaxmessagesize, &mmsset,
|
|
&transportId, &client);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
CSFLogDebug(LOGTAG, "%s: We did not negotiate datachannel", __FUNCTION__);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (channels > MAX_NUM_STREAMS) {
|
|
channels = MAX_NUM_STREAMS;
|
|
}
|
|
|
|
rv = EnsureDataConnection(localport, channels, remotemaxmessagesize, mmsset);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (mDataConnection->ConnectToTransport(transportId, client, localport,
|
|
remoteport)) {
|
|
return NS_OK;
|
|
}
|
|
// If we inited the DataConnection, call Destroy() before releasing it
|
|
mDataConnection->Destroy();
|
|
}
|
|
mDataConnection = nullptr;
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
already_AddRefed<nsDOMDataChannel> PeerConnectionImpl::CreateDataChannel(
|
|
const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
|
|
bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
|
|
uint16_t aStream, ErrorResult& rv) {
|
|
RefPtr<nsDOMDataChannel> result;
|
|
rv = CreateDataChannel(aLabel, aProtocol, aType, ordered, aMaxTime, aMaxNum,
|
|
aExternalNegotiated, aStream, getter_AddRefs(result));
|
|
return result.forget();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::CreateDataChannel(
|
|
const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
|
|
bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
|
|
uint16_t aStream, nsDOMDataChannel** aRetval) {
|
|
PC_AUTO_ENTER_API_CALL(false);
|
|
MOZ_ASSERT(aRetval);
|
|
|
|
RefPtr<DataChannel> dataChannel;
|
|
DataChannelReliabilityPolicy prPolicy;
|
|
switch (aType) {
|
|
case IPeerConnection::kDataChannelReliable:
|
|
prPolicy = DataChannelReliabilityPolicy::Reliable;
|
|
break;
|
|
case IPeerConnection::kDataChannelPartialReliableRexmit:
|
|
prPolicy = DataChannelReliabilityPolicy::LimitedRetransmissions;
|
|
break;
|
|
case IPeerConnection::kDataChannelPartialReliableTimed:
|
|
prPolicy = DataChannelReliabilityPolicy::LimitedLifetime;
|
|
break;
|
|
default:
|
|
MOZ_ASSERT(false);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv = EnsureDataConnection(
|
|
WEBRTC_DATACHANNEL_PORT_DEFAULT, WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
|
|
WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT, false);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
dataChannel = mDataConnection->Open(
|
|
NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), prPolicy,
|
|
ordered,
|
|
prPolicy == DataChannelReliabilityPolicy::LimitedRetransmissions
|
|
? aMaxNum
|
|
: (prPolicy == DataChannelReliabilityPolicy::LimitedLifetime
|
|
? aMaxTime
|
|
: 0),
|
|
nullptr, nullptr, aExternalNegotiated, aStream);
|
|
NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
CSFLogDebug(LOGTAG, "%s: making DOMDataChannel", __FUNCTION__);
|
|
|
|
Maybe<JsepTransceiver> dcTransceiver =
|
|
mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) {
|
|
return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
|
|
});
|
|
|
|
if (dcTransceiver) {
|
|
dcTransceiver->RestartDatachannelTransceiver();
|
|
mJsepSession->SetTransceiver(*dcTransceiver);
|
|
} else {
|
|
mJsepSession->AddTransceiver(
|
|
JsepTransceiver(SdpMediaSection::MediaType::kApplication, *mUuidGen));
|
|
}
|
|
|
|
RefPtr<nsDOMDataChannel> retval;
|
|
rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow,
|
|
getter_AddRefs(retval));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
retval.forget(aRetval);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(PeerConnectionImpl::Operation, mPromise, mPc)
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::Operation)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl::Operation)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl::Operation)
|
|
|
|
PeerConnectionImpl::Operation::Operation(PeerConnectionImpl* aPc,
|
|
ErrorResult& aError)
|
|
: mPromise(aPc->MakePromise(aError)), mPc(aPc) {}
|
|
|
|
PeerConnectionImpl::Operation::~Operation() = default;
|
|
|
|
void PeerConnectionImpl::Operation::Call(ErrorResult& aError) {
|
|
RefPtr<dom::Promise> opPromise = CallImpl(aError);
|
|
if (aError.Failed()) {
|
|
return;
|
|
}
|
|
// Upon fulfillment or rejection of the promise returned by the operation,
|
|
// run the following steps:
|
|
// (NOTE: mPromise is p from https://w3c.github.io/webrtc-pc/#dfn-chain,
|
|
// and CallImpl() is what returns the promise for the operation itself)
|
|
opPromise->AppendNativeHandler(this);
|
|
}
|
|
|
|
void PeerConnectionImpl::Operation::ResolvedCallback(
|
|
JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
// (the spec wants p to never settle in this event)
|
|
if (!mPc->IsClosed()) {
|
|
// If the promise returned by operation was fulfilled with a
|
|
// value, fulfill p with that value.
|
|
mPromise->MaybeResolveWithClone(aCx, aValue);
|
|
// Upon fulfillment or rejection of p, execute the following
|
|
// steps:
|
|
// (Static analysis forces us to use a temporary)
|
|
RefPtr<PeerConnectionImpl> pc = mPc;
|
|
pc->RunNextOperation(aRv);
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::Operation::RejectedCallback(
|
|
JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
// (the spec wants p to never settle in this event)
|
|
if (!mPc->IsClosed()) {
|
|
// If the promise returned by operation was rejected with a
|
|
// value, reject p with that value.
|
|
mPromise->MaybeRejectWithClone(aCx, aValue);
|
|
// Upon fulfillment or rejection of p, execute the following
|
|
// steps:
|
|
// (Static analysis forces us to use a temporary)
|
|
RefPtr<PeerConnectionImpl> pc = mPc;
|
|
pc->RunNextOperation(aRv);
|
|
}
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(PeerConnectionImpl::JSOperation,
|
|
PeerConnectionImpl::Operation, mOperation)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(PeerConnectionImpl::JSOperation,
|
|
PeerConnectionImpl::Operation)
|
|
NS_IMPL_RELEASE_INHERITED(PeerConnectionImpl::JSOperation,
|
|
PeerConnectionImpl::Operation)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::JSOperation)
|
|
NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
|
|
|
|
PeerConnectionImpl::JSOperation::JSOperation(PeerConnectionImpl* aPc,
|
|
dom::ChainedOperation& aOp,
|
|
ErrorResult& aError)
|
|
: Operation(aPc, aError), mOperation(&aOp) {}
|
|
|
|
RefPtr<dom::Promise> PeerConnectionImpl::JSOperation::CallImpl(
|
|
ErrorResult& aError) {
|
|
// Static analysis will not let us call this without a temporary :(
|
|
RefPtr<dom::ChainedOperation> op = mOperation;
|
|
return op->Call(aError);
|
|
}
|
|
|
|
already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
|
|
dom::ChainedOperation& aOperation, ErrorResult& aError) {
|
|
MOZ_RELEASE_ASSERT(!mChainingOperation);
|
|
mChainingOperation = true;
|
|
RefPtr<Operation> operation = new JSOperation(this, aOperation, aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<Promise> promise = Chain(operation, aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
mChainingOperation = false;
|
|
return promise.forget();
|
|
}
|
|
|
|
// This is kinda complicated, but it is what the spec requires us to do. The
|
|
// core of what makes this complicated is the requirement that |aOperation| be
|
|
// run _immediately_ (without any Promise.Then!) if the operations chain is
|
|
// empty.
|
|
already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
|
|
const RefPtr<Operation>& aOperation, ErrorResult& aError) {
|
|
// If connection.[[IsClosed]] is true, return a promise rejected with a newly
|
|
// created InvalidStateError.
|
|
if (IsClosed()) {
|
|
CSFLogDebug(LOGTAG, "%s:%d: Peer connection is closed", __FILE__, __LINE__);
|
|
RefPtr<dom::Promise> error = MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
error->MaybeRejectWithInvalidStateError("Peer connection is closed");
|
|
return error.forget();
|
|
}
|
|
|
|
// Append operation to [[Operations]].
|
|
mOperations.AppendElement(aOperation);
|
|
|
|
// If the length of [[Operations]] is exactly 1, execute operation.
|
|
if (mOperations.Length() == 1) {
|
|
aOperation->Call(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain
|
|
return do_AddRef(aOperation->GetPromise());
|
|
}
|
|
|
|
void PeerConnectionImpl::RunNextOperation(ErrorResult& aError) {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
if (IsClosed()) {
|
|
return;
|
|
}
|
|
|
|
// Remove the first element of [[Operations]].
|
|
mOperations.RemoveElementAt(0);
|
|
|
|
// If [[Operations]] is non-empty, execute the operation represented by the
|
|
// first element of [[Operations]], and abort these steps.
|
|
if (mOperations.Length()) {
|
|
// Cannot call without a temporary :(
|
|
RefPtr<Operation> op = mOperations[0];
|
|
op->Call(aError);
|
|
return;
|
|
}
|
|
|
|
// If connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] is false, abort
|
|
// these steps.
|
|
if (!mUpdateNegotiationNeededFlagOnEmptyChain) {
|
|
return;
|
|
}
|
|
|
|
// Set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to false.
|
|
mUpdateNegotiationNeededFlagOnEmptyChain = false;
|
|
// Update the negotiation-needed flag for connection.
|
|
UpdateNegotiationNeeded();
|
|
}
|
|
|
|
void PeerConnectionImpl::SyncToJsep() {
|
|
for (const auto& transceiver : mTransceivers) {
|
|
transceiver->SyncToJsep(*mJsepSession);
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::SyncFromJsep() {
|
|
CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
|
|
mJsepSession->ForEachTransceiver(
|
|
[this, self = RefPtr<PeerConnectionImpl>(this)](
|
|
const JsepTransceiver& jsepTransceiver) {
|
|
if (jsepTransceiver.GetMediaType() ==
|
|
SdpMediaSection::MediaType::kApplication) {
|
|
return;
|
|
}
|
|
|
|
CSFLogDebug(LOGTAG, "%s: Looking for match", __FUNCTION__);
|
|
RefPtr<RTCRtpTransceiver> transceiver;
|
|
for (auto& temp : mTransceivers) {
|
|
if (temp->GetJsepTransceiverId() == jsepTransceiver.GetUuid()) {
|
|
CSFLogDebug(LOGTAG, "%s: Found match", __FUNCTION__);
|
|
transceiver = temp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!transceiver) {
|
|
if (jsepTransceiver.IsRemoved()) {
|
|
return;
|
|
}
|
|
CSFLogDebug(LOGTAG, "%s: No match, making new", __FUNCTION__);
|
|
dom::RTCRtpTransceiverInit init;
|
|
init.mDirection = RTCRtpTransceiverDirection::Recvonly;
|
|
IgnoredErrorResult rv;
|
|
transceiver = CreateTransceiver(
|
|
jsepTransceiver.GetUuid(),
|
|
jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
|
|
nullptr, false, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
MOZ_ASSERT(false);
|
|
return;
|
|
}
|
|
mTransceivers.AppendElement(transceiver);
|
|
}
|
|
|
|
CSFLogDebug(LOGTAG, "%s: Syncing transceiver", __FUNCTION__);
|
|
transceiver->SyncFromJsep(*mJsepSession);
|
|
});
|
|
}
|
|
|
|
already_AddRefed<dom::Promise> PeerConnectionImpl::MakePromise(
|
|
ErrorResult& aError) const {
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
|
|
return dom::Promise::Create(global, aError);
|
|
}
|
|
|
|
void PeerConnectionImpl::UpdateNegotiationNeeded() {
|
|
// If the length of connection.[[Operations]] is not 0, then set
|
|
// connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort
|
|
// these steps.
|
|
if (mOperations.Length() != 0) {
|
|
mUpdateNegotiationNeededFlagOnEmptyChain = true;
|
|
return;
|
|
}
|
|
|
|
// Queue a task to run the following steps:
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
if (IsClosed()) {
|
|
return;
|
|
}
|
|
// If the length of connection.[[Operations]] is not 0, then set
|
|
// connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and
|
|
// abort these steps.
|
|
if (mOperations.Length()) {
|
|
mUpdateNegotiationNeededFlagOnEmptyChain = true;
|
|
return;
|
|
}
|
|
// If connection's signaling state is not "stable", abort these steps.
|
|
if (mSignalingState != RTCSignalingState::Stable) {
|
|
return;
|
|
}
|
|
// If the result of checking if negotiation is needed is false, clear
|
|
// the negotiation-needed flag by setting
|
|
// connection.[[NegotiationNeeded]] to false, and abort these steps.
|
|
if (!CheckNegotiationNeeded()) {
|
|
mNegotiationNeeded = false;
|
|
return;
|
|
}
|
|
|
|
// If connection.[[NegotiationNeeded]] is already true, abort these
|
|
// steps.
|
|
if (mNegotiationNeeded) {
|
|
return;
|
|
}
|
|
|
|
// Set connection.[[NegotiationNeeded]] to true.
|
|
mNegotiationNeeded = true;
|
|
|
|
// Fire an event named negotiationneeded at connection.
|
|
ErrorResult rv;
|
|
mPCObserver->FireNegotiationNeededEvent(rv);
|
|
}));
|
|
}
|
|
|
|
RefPtr<dom::RTCRtpTransceiver> PeerConnectionImpl::GetTransceiver(
|
|
const std::string& aTransceiverId) {
|
|
for (const auto& transceiver : mTransceivers) {
|
|
if (transceiver->GetJsepTransceiverId() == aTransceiverId) {
|
|
return transceiver;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void PeerConnectionImpl::NotifyDataChannel(
|
|
already_AddRefed<DataChannel> aChannel) {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
|
|
RefPtr<DataChannel> channel(aChannel);
|
|
MOZ_ASSERT(channel);
|
|
CSFLogDebug(LOGTAG, "%s: channel: %p", __FUNCTION__, channel.get());
|
|
|
|
RefPtr<nsDOMDataChannel> domchannel;
|
|
nsresult rv = NS_NewDOMDataChannel(channel.forget(), mWindow,
|
|
getter_AddRefs(domchannel));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
JSErrorResult jrv;
|
|
mPCObserver->NotifyDataChannel(*domchannel, jrv);
|
|
}
|
|
|
|
void PeerConnectionImpl::NotifyDataChannelOpen(DataChannel*) {
|
|
mDataChannelsOpened++;
|
|
}
|
|
|
|
void PeerConnectionImpl::NotifyDataChannelClosed(DataChannel*) {
|
|
mDataChannelsClosed++;
|
|
}
|
|
|
|
void PeerConnectionImpl::NotifySctpConnected() {
|
|
if (!mSctpTransport) {
|
|
MOZ_ASSERT(false);
|
|
return;
|
|
}
|
|
|
|
mSctpTransport->UpdateState(RTCSctpTransportState::Connected);
|
|
}
|
|
|
|
void PeerConnectionImpl::NotifySctpClosed() {
|
|
if (!mSctpTransport) {
|
|
MOZ_ASSERT(false);
|
|
return;
|
|
}
|
|
|
|
mSctpTransport->UpdateState(RTCSctpTransportState::Closed);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) {
|
|
JsepOfferOptions options;
|
|
// 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 ||
|
|
!mLocalIceCredentialsToReplace.empty());
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Have to use unique_ptr because webidl enums are generated without a
|
|
// copy c'tor.
|
|
static std::unique_ptr<dom::PCErrorData> buildJSErrorData(
|
|
const JsepSession::Result& aResult, const std::string& aMessage) {
|
|
std::unique_ptr<dom::PCErrorData> result(new dom::PCErrorData);
|
|
result->mName = *aResult.mError;
|
|
result->mMessage = NS_ConvertASCIItoUTF16(aMessage.c_str());
|
|
return result;
|
|
}
|
|
|
|
// Used by unit tests and the IDL CreateOffer.
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) {
|
|
PC_AUTO_ENTER_API_CALL(true);
|
|
|
|
if (!PeerConnectionCtx::GetInstance()->isReady()) {
|
|
// Uh oh. We're not ready yet. Enqueue this operation.
|
|
PeerConnectionCtx::GetInstance()->queueJSEPOperation(
|
|
WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
|
|
STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
|
|
return NS_OK;
|
|
}
|
|
|
|
CSFLogDebug(LOGTAG, "CreateOffer()");
|
|
|
|
nsresult nrv = ConfigureJsepSessionCodecs();
|
|
if (NS_FAILED(nrv)) {
|
|
CSFLogError(LOGTAG, "Failed to configure codecs");
|
|
return nrv;
|
|
}
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Create Offer");
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<PeerConnectionImpl>(this), aOptions] {
|
|
std::string offer;
|
|
|
|
SyncToJsep();
|
|
UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
|
|
JsepSession::Result result =
|
|
uncommittedJsepSession->CreateOffer(aOptions, &offer);
|
|
JSErrorResult rv;
|
|
if (result.mError.isSome()) {
|
|
std::string errorString = uncommittedJsepSession->GetLastError();
|
|
|
|
CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
|
|
mHandle.c_str(), errorString.c_str());
|
|
|
|
mPCObserver->OnCreateOfferError(
|
|
*buildJSErrorData(result, errorString), rv);
|
|
} else {
|
|
mJsepSession = std::move(uncommittedJsepSession);
|
|
mPCObserver->OnCreateOfferSuccess(ObString(offer.c_str()), rv);
|
|
}
|
|
}));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::CreateAnswer() {
|
|
PC_AUTO_ENTER_API_CALL(true);
|
|
|
|
CSFLogDebug(LOGTAG, "CreateAnswer()");
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Create Answer");
|
|
// TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
|
|
// add it as a param to CreateAnswer, and convert it here.
|
|
JsepAnswerOptions options;
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<PeerConnectionImpl>(this), options] {
|
|
std::string answer;
|
|
SyncToJsep();
|
|
UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
|
|
JsepSession::Result result =
|
|
uncommittedJsepSession->CreateAnswer(options, &answer);
|
|
JSErrorResult rv;
|
|
if (result.mError.isSome()) {
|
|
std::string errorString = uncommittedJsepSession->GetLastError();
|
|
|
|
CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
|
|
mHandle.c_str(), errorString.c_str());
|
|
|
|
mPCObserver->OnCreateAnswerError(
|
|
*buildJSErrorData(result, errorString), rv);
|
|
} else {
|
|
mJsepSession = std::move(uncommittedJsepSession);
|
|
mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
|
|
}
|
|
}));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
dom::RTCSdpType ToDomSdpType(JsepSdpType aType) {
|
|
switch (aType) {
|
|
case kJsepSdpOffer:
|
|
return dom::RTCSdpType::Offer;
|
|
case kJsepSdpAnswer:
|
|
return dom::RTCSdpType::Answer;
|
|
case kJsepSdpPranswer:
|
|
return dom::RTCSdpType::Pranswer;
|
|
case kJsepSdpRollback:
|
|
return dom::RTCSdpType::Rollback;
|
|
}
|
|
|
|
MOZ_CRASH("Nonexistent JsepSdpType");
|
|
}
|
|
|
|
JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) {
|
|
switch (aType) {
|
|
case dom::RTCSdpType::Offer:
|
|
return kJsepSdpOffer;
|
|
case dom::RTCSdpType::Pranswer:
|
|
return kJsepSdpPranswer;
|
|
case dom::RTCSdpType::Answer:
|
|
return kJsepSdpAnswer;
|
|
case dom::RTCSdpType::Rollback:
|
|
return kJsepSdpRollback;
|
|
}
|
|
|
|
MOZ_CRASH("Nonexistent dom::RTCSdpType");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Set Local Description");
|
|
|
|
if (AnyLocalTrackHasPeerIdentity()) {
|
|
mRequestedPrivacy = Some(PrincipalPrivacy::Private);
|
|
}
|
|
|
|
mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
|
|
sdpEntry.mIsLocal = true;
|
|
sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom();
|
|
sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
|
|
auto appendHistory = [&]() {
|
|
if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
};
|
|
|
|
mLocalRequestedSDP = aSDP;
|
|
|
|
SyncToJsep();
|
|
|
|
bool wasRestartingIce = mJsepSession->IsIceRestarting();
|
|
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);
|
|
appendHistory();
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
MOZ_ASSERT(!mUncommittedJsepSession);
|
|
mUncommittedJsepSession.reset(mJsepSession->Clone());
|
|
JsepSession::Result result =
|
|
mUncommittedJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP);
|
|
JSErrorResult rv;
|
|
if (result.mError.isSome()) {
|
|
std::string errorString = mUncommittedJsepSession->GetLastError();
|
|
mUncommittedJsepSession = nullptr;
|
|
CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
|
|
mHandle.c_str(), errorString.c_str());
|
|
mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
|
|
rv);
|
|
sdpEntry.mErrors = GetLastSdpParsingErrors();
|
|
} else {
|
|
if (wasRestartingIce) {
|
|
RecordIceRestartStatistics(sdpType);
|
|
}
|
|
|
|
mPCObserver->OnSetDescriptionSuccess(rv);
|
|
}
|
|
|
|
appendHistory();
|
|
|
|
if (rv.Failed()) {
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction,
|
|
const std::string& aSdp) {
|
|
PeerConnectionWrapper wrapper(aPcHandle);
|
|
|
|
if (wrapper.impl()) {
|
|
if (!PeerConnectionCtx::GetInstance()->isReady()) {
|
|
MOZ_CRASH(
|
|
"Why is DeferredSetRemote being executed when the "
|
|
"PeerConnectionCtx isn't ready?");
|
|
}
|
|
wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str());
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) {
|
|
PC_AUTO_ENTER_API_CALL(true);
|
|
|
|
if (!aSDP) {
|
|
CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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");
|
|
|
|
mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
|
|
sdpEntry.mIsLocal = false;
|
|
sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom();
|
|
sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
|
|
auto appendHistory = [&]() {
|
|
if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
};
|
|
|
|
SyncToJsep();
|
|
|
|
mRemoteRequestedSDP = aSDP;
|
|
bool wasRestartingIce = mJsepSession->IsIceRestarting();
|
|
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;
|
|
}
|
|
|
|
MOZ_ASSERT(!mUncommittedJsepSession);
|
|
mUncommittedJsepSession.reset(mJsepSession->Clone());
|
|
JsepSession::Result result = mUncommittedJsepSession->SetRemoteDescription(
|
|
sdpType, mRemoteRequestedSDP);
|
|
JSErrorResult jrv;
|
|
if (result.mError.isSome()) {
|
|
std::string errorString = mUncommittedJsepSession->GetLastError();
|
|
mUncommittedJsepSession = nullptr;
|
|
sdpEntry.mErrors = GetLastSdpParsingErrors();
|
|
CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
|
|
mHandle.c_str(), errorString.c_str());
|
|
mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
|
|
jrv);
|
|
} else {
|
|
if (wasRestartingIce) {
|
|
RecordIceRestartStatistics(sdpType);
|
|
}
|
|
|
|
mPCObserver->OnSetDescriptionSuccess(jrv);
|
|
}
|
|
|
|
appendHistory();
|
|
|
|
if (jrv.Failed()) {
|
|
return jrv.StealNSResult();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats(
|
|
MediaStreamTrack* aSelector) {
|
|
if (!mWindow) {
|
|
MOZ_CRASH("Cannot create a promise without a window!");
|
|
}
|
|
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
|
|
ErrorResult rv;
|
|
RefPtr<Promise> promise = Promise::Create(global, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
MOZ_CRASH("Failed to create a promise!");
|
|
}
|
|
|
|
if (!IsClosed()) {
|
|
GetStats(aSelector, false)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[promise, window = mWindow](
|
|
UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
|
|
RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
|
|
report->Incorporate(*aReport);
|
|
promise->MaybeResolve(std::move(report));
|
|
},
|
|
[promise, window = mWindow](nsresult aError) {
|
|
RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
|
|
promise->MaybeResolve(std::move(report));
|
|
});
|
|
} else {
|
|
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
}
|
|
|
|
return promise.forget();
|
|
}
|
|
|
|
void PeerConnectionImpl::GetRemoteStreams(
|
|
nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const {
|
|
aStreamsOut = mReceiveStreams.Clone();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::AddIceCandidate(
|
|
const char* aCandidate, const char* aMid, const char* aUfrag,
|
|
const dom::Nullable<unsigned short>& aLevel) {
|
|
PC_AUTO_ENTER_API_CALL(true);
|
|
|
|
if (mForceIceTcp &&
|
|
std::string::npos != std::string(aCandidate).find(" UDP ")) {
|
|
CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate);
|
|
return NS_OK;
|
|
}
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Add Ice Candidate");
|
|
|
|
CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag);
|
|
|
|
std::string transportId;
|
|
Maybe<unsigned short> level;
|
|
if (!aLevel.IsNull()) {
|
|
level = Some(aLevel.Value());
|
|
}
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
!mUncommittedJsepSession,
|
|
"AddIceCandidate is chained, which means it should never "
|
|
"run while an sRD/sLD is in progress");
|
|
JsepSession::Result result = mJsepSession->AddRemoteIceCandidate(
|
|
aCandidate, aMid, level, aUfrag, &transportId);
|
|
|
|
if (!result.mError.isSome()) {
|
|
// We do not bother the MediaTransportHandler about this before
|
|
// offer/answer concludes. Once offer/answer concludes, we will extract
|
|
// these candidates from the remote SDP.
|
|
if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) {
|
|
AddIceCandidate(aCandidate, transportId, aUfrag);
|
|
mRawTrickledCandidates.push_back(aCandidate);
|
|
}
|
|
// Spec says we queue a task for these updates
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
|
|
if (IsClosed()) {
|
|
return;
|
|
}
|
|
mPendingRemoteDescription =
|
|
mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
|
|
mCurrentRemoteDescription =
|
|
mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
|
|
JSErrorResult rv;
|
|
mPCObserver->OnAddIceCandidateSuccess(rv);
|
|
}));
|
|
} else {
|
|
std::string errorString = mJsepSession->GetLastError();
|
|
|
|
CSFLogError(LOGTAG,
|
|
"Failed to incorporate remote candidate into SDP:"
|
|
" res = %u, candidate = %s, level = %i, error = %s",
|
|
static_cast<unsigned>(*result.mError), aCandidate,
|
|
level.valueOr(-1), errorString.c_str());
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__,
|
|
[this, self = RefPtr<PeerConnectionImpl>(this), errorString, result] {
|
|
if (IsClosed()) {
|
|
return;
|
|
}
|
|
JSErrorResult rv;
|
|
mPCObserver->OnAddIceCandidateError(
|
|
*buildJSErrorData(result, errorString), rv);
|
|
}));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::CloseStreams() {
|
|
PC_AUTO_ENTER_API_CALL(false);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
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);
|
|
Document* doc = mWindow->GetExtantDoc();
|
|
if (!doc) {
|
|
CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
for (const auto& transceiver : mTransceivers) {
|
|
transceiver->Sender()->GetPipeline()->UpdateSinkIdentity(
|
|
doc->NodePrincipal(), mPeerIdentity);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) {
|
|
PC_AUTO_ENTER_API_CALL(false);
|
|
MOZ_DIAGNOSTIC_ASSERT(!mRequestedPrivacy ||
|
|
(*mRequestedPrivacy == PrincipalPrivacy::Private) ==
|
|
aPrivacyRequested);
|
|
|
|
mRequestedPrivacy = Some(aPrivacyRequested ? PrincipalPrivacy::Private
|
|
: PrincipalPrivacy::NonPrivate);
|
|
// This updates the MediaPipelines with a private PrincipalHandle. Note that
|
|
// MediaPipelineReceive has its own AlpnNegotiated handler so it can get
|
|
// signaled off-main to drop data until it receives the new PrincipalHandle
|
|
// from us.
|
|
UpdateMediaPipelines();
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::OnDtlsStateChange(const std::string& aTransportId,
|
|
TransportLayer::State aState) {
|
|
nsCString key(aTransportId.data(), aTransportId.size());
|
|
RefPtr<RTCDtlsTransport> dtlsTransport =
|
|
mTransportIdToRTCDtlsTransport.Get(key);
|
|
if (!dtlsTransport) {
|
|
return;
|
|
}
|
|
|
|
dtlsTransport->UpdateState(aState);
|
|
// Whenever the state of an RTCDtlsTransport changes or when the [[IsClosed]]
|
|
// slot turns true, the user agent MUST update the connection state by
|
|
// queueing a task that runs the following steps:
|
|
// NOTE: The business about [[IsClosed]] here is probably a bug, because the
|
|
// rest of the spec makes it very clear that events should never fire when
|
|
// [[IsClosed]] is true. See https://github.com/w3c/webrtc-pc/issues/2865
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
|
|
// Let connection be this RTCPeerConnection object.
|
|
// Let newState be the value of deriving a new state value as described
|
|
// by the RTCPeerConnectionState enum.
|
|
// If connection.[[ConnectionState]] is equal to newState, abort these
|
|
// steps.
|
|
// Set connection.[[ConnectionState]] to newState.
|
|
if (UpdateConnectionState()) {
|
|
// Fire an event named connectionstatechange at connection.
|
|
JSErrorResult jrv;
|
|
mPCObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv);
|
|
}
|
|
}));
|
|
}
|
|
|
|
RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const {
|
|
// closed The RTCPeerConnection object's [[IsClosed]] slot is true.
|
|
if (IsClosed()) {
|
|
return RTCPeerConnectionState::Closed;
|
|
}
|
|
|
|
// Would use a bitset, but that requires lots of static_cast<size_t>
|
|
// Oh well.
|
|
std::set<RTCDtlsTransportState> statesFound;
|
|
std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports());
|
|
for (const auto& transport : transports) {
|
|
statesFound.insert(transport->State());
|
|
}
|
|
|
|
// failed The previous state doesn't apply, and either
|
|
// [[IceConnectionState]] is "failed" or any RTCDtlsTransports are in the
|
|
// "failed" state.
|
|
if (mIceConnectionState == RTCIceConnectionState::Failed ||
|
|
statesFound.count(RTCDtlsTransportState::Failed)) {
|
|
return RTCPeerConnectionState::Failed;
|
|
}
|
|
|
|
// disconnected None of the previous states apply, and
|
|
// [[IceConnectionState]] is "disconnected".
|
|
if (mIceConnectionState == RTCIceConnectionState::Disconnected) {
|
|
return RTCPeerConnectionState::Disconnected;
|
|
}
|
|
|
|
// new None of the previous states apply, and either
|
|
// [[IceConnectionState]] is "new", and all RTCDtlsTransports are in the
|
|
// "new" or "closed" state...
|
|
if (mIceConnectionState == RTCIceConnectionState::New &&
|
|
!statesFound.count(RTCDtlsTransportState::Connecting) &&
|
|
!statesFound.count(RTCDtlsTransportState::Connected) &&
|
|
!statesFound.count(RTCDtlsTransportState::Failed)) {
|
|
return RTCPeerConnectionState::New;
|
|
}
|
|
|
|
// ...or there are no transports.
|
|
if (statesFound.empty()) {
|
|
return RTCPeerConnectionState::New;
|
|
}
|
|
|
|
// connected None of the previous states apply,
|
|
// [[IceConnectionState]] is "connected", and all RTCDtlsTransports are in
|
|
// the "connected" or "closed" state.
|
|
if (mIceConnectionState == RTCIceConnectionState::Connected &&
|
|
!statesFound.count(RTCDtlsTransportState::New) &&
|
|
!statesFound.count(RTCDtlsTransportState::Failed) &&
|
|
!statesFound.count(RTCDtlsTransportState::Connecting)) {
|
|
return RTCPeerConnectionState::Connected;
|
|
}
|
|
|
|
// connecting None of the previous states apply.
|
|
return RTCPeerConnectionState::Connecting;
|
|
}
|
|
|
|
bool PeerConnectionImpl::UpdateConnectionState() {
|
|
auto newState = GetNewConnectionState();
|
|
if (newState != mConnectionState) {
|
|
CSFLogInfo(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__,
|
|
static_cast<int>(mConnectionState), static_cast<int>(newState),
|
|
this);
|
|
mConnectionState = newState;
|
|
if (mConnectionState != RTCPeerConnectionState::Closed) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PeerConnectionImpl::OnMediaError(const std::string& aError) {
|
|
CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str());
|
|
// TODO: Let content know about this somehow.
|
|
}
|
|
|
|
void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type,
|
|
bool sending,
|
|
UniquePtr<uint8_t[]>& packet,
|
|
size_t size) {
|
|
if (IsClosed()) {
|
|
return;
|
|
}
|
|
|
|
// TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere
|
|
// else?
|
|
AutoJSAPI jsapi;
|
|
if (!jsapi.Init(mWindow)) {
|
|
return;
|
|
}
|
|
|
|
UniquePtr<void, JS::FreePolicy> packetPtr{packet.release()};
|
|
JS::Rooted<JSObject*> jsobj(
|
|
jsapi.cx(),
|
|
JS::NewArrayBufferWithContents(jsapi.cx(), size, std::move(packetPtr)));
|
|
|
|
RootedSpiderMonkeyInterface<ArrayBuffer> arrayBuffer(jsapi.cx());
|
|
if (!arrayBuffer.Init(jsobj)) {
|
|
return;
|
|
}
|
|
|
|
JSErrorResult jrv;
|
|
mPCObserver->OnPacket(level, type, sending, arrayBuffer, jrv);
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::EnablePacketDump(unsigned long level,
|
|
dom::mozPacketDumpType type,
|
|
bool sending) {
|
|
return GetPacketDumper()->EnablePacketDump(level, type, sending);
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level,
|
|
dom::mozPacketDumpType type,
|
|
bool sending) {
|
|
return GetPacketDumper()->DisablePacketDump(level, type, sending);
|
|
}
|
|
|
|
void PeerConnectionImpl::StampTimecard(const char* aEvent) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
STAMP_TIMECARD(mTimeCard, aEvent);
|
|
}
|
|
|
|
void PeerConnectionImpl::SendWarningToConsole(const nsCString& aWarning) {
|
|
nsAutoString msg = NS_ConvertASCIItoUTF16(aWarning);
|
|
nsContentUtils::ReportToConsoleByWindowID(msg, nsIScriptError::warningFlag,
|
|
"WebRTC"_ns, mWindow->WindowID());
|
|
}
|
|
|
|
void PeerConnectionImpl::GetDefaultVideoCodecs(
|
|
std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs,
|
|
const OverrideRtxPreference aOverrideRtxPreference) {
|
|
const auto prefs = GetDefaultCodecPreferences(aOverrideRtxPreference);
|
|
// Supported video codecs.
|
|
// Note: order here implies priority for building offers!
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultVP8(prefs));
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultVP9(prefs));
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultH264_1(prefs));
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultH264_0(prefs));
|
|
|
|
const bool disableBaseline = Preferences::GetBool(
|
|
"media.navigator.video.disable_h264_baseline", false);
|
|
|
|
// Only add Baseline if it hasn't been disabled.
|
|
if (!disableBaseline) {
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultH264Baseline_1(prefs));
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultH264Baseline_0(prefs));
|
|
}
|
|
|
|
if (WebrtcVideoConduit::HasAv1() &&
|
|
StaticPrefs::media_webrtc_codec_video_av1_enabled()) {
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultAV1(prefs));
|
|
}
|
|
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultUlpFec(prefs));
|
|
aSupportedCodecs.emplace_back(
|
|
JsepApplicationCodecDescription::CreateDefault());
|
|
aSupportedCodecs.emplace_back(
|
|
JsepVideoCodecDescription::CreateDefaultRed(prefs));
|
|
|
|
CompareCodecPriority comparator;
|
|
if (StaticPrefs::media_webrtc_codec_video_av1_experimental_preferred()) {
|
|
comparator.SetPreferredCodec(nsCString("av1"));
|
|
}
|
|
std::stable_sort(aSupportedCodecs.begin(), aSupportedCodecs.end(),
|
|
comparator);
|
|
}
|
|
|
|
void PeerConnectionImpl::GetDefaultAudioCodecs(
|
|
std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs) {
|
|
const auto prefs = GetDefaultCodecPreferences();
|
|
aSupportedCodecs.emplace_back(
|
|
JsepAudioCodecDescription::CreateDefaultOpus(prefs));
|
|
aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultG722());
|
|
aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMU());
|
|
aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMA());
|
|
aSupportedCodecs.emplace_back(
|
|
JsepAudioCodecDescription::CreateDefaultTelephoneEvent());
|
|
}
|
|
|
|
void PeerConnectionImpl::GetDefaultRtpExtensions(
|
|
std::vector<RtpExtensionHeader>& aRtpExtensions) {
|
|
RtpExtensionHeader audioLevel = {JsepMediaType::kAudio,
|
|
SdpDirectionAttribute::Direction::kSendrecv,
|
|
webrtc::RtpExtension::kAudioLevelUri};
|
|
aRtpExtensions.push_back(audioLevel);
|
|
|
|
RtpExtensionHeader csrcAudioLevels = {
|
|
JsepMediaType::kAudio, SdpDirectionAttribute::Direction::kRecvonly,
|
|
webrtc::RtpExtension::kCsrcAudioLevelsUri};
|
|
aRtpExtensions.push_back(csrcAudioLevels);
|
|
|
|
RtpExtensionHeader mid = {JsepMediaType::kAudioVideo,
|
|
SdpDirectionAttribute::Direction::kSendrecv,
|
|
webrtc::RtpExtension::kMidUri};
|
|
aRtpExtensions.push_back(mid);
|
|
|
|
RtpExtensionHeader absSendTime = {JsepMediaType::kVideo,
|
|
SdpDirectionAttribute::Direction::kSendrecv,
|
|
webrtc::RtpExtension::kAbsSendTimeUri};
|
|
aRtpExtensions.push_back(absSendTime);
|
|
|
|
RtpExtensionHeader timestampOffset = {
|
|
JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv,
|
|
webrtc::RtpExtension::kTimestampOffsetUri};
|
|
aRtpExtensions.push_back(timestampOffset);
|
|
|
|
RtpExtensionHeader playoutDelay = {
|
|
JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kRecvonly,
|
|
webrtc::RtpExtension::kPlayoutDelayUri};
|
|
aRtpExtensions.push_back(playoutDelay);
|
|
|
|
RtpExtensionHeader transportSequenceNumber = {
|
|
JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv,
|
|
webrtc::RtpExtension::kTransportSequenceNumberUri};
|
|
aRtpExtensions.push_back(transportSequenceNumber);
|
|
}
|
|
|
|
void PeerConnectionImpl::GetCapabilities(
|
|
const nsAString& aKind, dom::Nullable<dom::RTCRtpCapabilities>& aResult,
|
|
sdp::Direction aDirection) {
|
|
std::vector<UniquePtr<JsepCodecDescription>> codecs;
|
|
std::vector<RtpExtensionHeader> headers;
|
|
auto mediaType = JsepMediaType::kNone;
|
|
|
|
if (aKind.EqualsASCII("video")) {
|
|
// Note to reviewers, this forced RTX to true.
|
|
// RTX is supported by default, so I am not sure if that was necessary.
|
|
// When it has been explicitly disabled by pref, is there a point in
|
|
// forcing it here?
|
|
GetDefaultVideoCodecs(codecs, OverrideRtxPreference::NoOverride);
|
|
mediaType = JsepMediaType::kVideo;
|
|
} else if (aKind.EqualsASCII("audio")) {
|
|
GetDefaultAudioCodecs(codecs);
|
|
mediaType = JsepMediaType::kAudio;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
GetDefaultRtpExtensions(headers);
|
|
|
|
const bool redUlpfecEnabled =
|
|
Preferences::GetBool("media.navigator.video.red_ulpfec_enabled", false);
|
|
bool haveAddedRtx = false;
|
|
|
|
// Use the codecs for kind to fill out the RTCRtpCodec
|
|
for (const auto& codec : codecs) {
|
|
// To avoid misleading information on codec capabilities skip those
|
|
// not signaled for audio/video (webrtc-datachannel)
|
|
// and any disabled by pref (ulpfec and red).
|
|
if (codec->mName == "webrtc-datachannel" ||
|
|
(codec->mName == "ulpfec" && !redUlpfecEnabled) ||
|
|
(codec->mName == "red" && !redUlpfecEnabled)) {
|
|
continue;
|
|
}
|
|
|
|
dom::RTCRtpCodec capability;
|
|
RTCRtpTransceiver::ToDomRtpCodec(*codec, &capability);
|
|
|
|
if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
|
|
// We need to manually add rtx for video.
|
|
// Spec says: There will only be a single entry in codecs for
|
|
// retransmission via RTX, with sdpFmtpLine not present.
|
|
if (mediaType == JsepMediaType::kVideo && !haveAddedRtx) {
|
|
const JsepVideoCodecDescription& videoCodec =
|
|
static_cast<JsepVideoCodecDescription&>(*codec);
|
|
if (videoCodec.mRtxEnabled) {
|
|
dom::RTCRtpCodec rtx;
|
|
RTCRtpTransceiver::ToDomRtpCodecRtx(videoCodec, &rtx);
|
|
rtx.mSdpFmtpLine.Reset();
|
|
if (!aResult.SetValue().mCodecs.AppendElement(rtx, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
haveAddedRtx = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add headers that match the direction and media type requested.
|
|
for (const auto& header : headers) {
|
|
if ((header.direction & aDirection) && (header.mMediaType & mediaType)) {
|
|
dom::RTCRtpHeaderExtensionCapability rtpHeader;
|
|
rtpHeader.mUri.AssignASCII(header.extensionname);
|
|
if (!aResult.SetValue().mHeaderExtensions.AppendElement(rtpHeader,
|
|
fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::SetupPreferredCodecs(
|
|
std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) {
|
|
GetDefaultVideoCodecs(aPreferredCodecs, OverrideRtxPreference::NoOverride);
|
|
GetDefaultAudioCodecs(aPreferredCodecs);
|
|
}
|
|
|
|
void PeerConnectionImpl::SetupPreferredRtpExtensions(
|
|
std::vector<RtpExtensionHeader>& aPreferredheaders) {
|
|
GetDefaultRtpExtensions(aPreferredheaders);
|
|
|
|
if (!Preferences::GetBool("media.navigator.video.use_transport_cc", false)) {
|
|
aPreferredheaders.erase(
|
|
std::remove_if(
|
|
aPreferredheaders.begin(), aPreferredheaders.end(),
|
|
[&](const RtpExtensionHeader& header) {
|
|
return header.extensionname ==
|
|
webrtc::RtpExtension::kTransportSequenceNumberUri;
|
|
}),
|
|
aPreferredheaders.end());
|
|
}
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::CalculateFingerprint(
|
|
const nsACString& algorithm, std::vector<uint8_t>* fingerprint) const {
|
|
DtlsDigest digest(algorithm);
|
|
|
|
MOZ_ASSERT(fingerprint);
|
|
const UniqueCERTCertificate& cert = mCertificate->Certificate();
|
|
nsresult rv = DtlsIdentity::ComputeFingerprint(cert, &digest);
|
|
if (NS_FAILED(rv)) {
|
|
CSFLogError(LOGTAG, "Unable to calculate certificate fingerprint, rv=%u",
|
|
static_cast<unsigned>(rv));
|
|
return rv;
|
|
}
|
|
*fingerprint = digest.value_;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::GetFingerprint(char** fingerprint) {
|
|
MOZ_ASSERT(fingerprint);
|
|
MOZ_ASSERT(mCertificate);
|
|
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;
|
|
}
|
|
|
|
void PeerConnectionImpl::GetCurrentLocalDescription(nsAString& aSDP) const {
|
|
aSDP = NS_ConvertASCIItoUTF16(mCurrentLocalDescription.c_str());
|
|
}
|
|
|
|
void PeerConnectionImpl::GetPendingLocalDescription(nsAString& aSDP) const {
|
|
aSDP = NS_ConvertASCIItoUTF16(mPendingLocalDescription.c_str());
|
|
}
|
|
|
|
void PeerConnectionImpl::GetCurrentRemoteDescription(nsAString& aSDP) const {
|
|
aSDP = NS_ConvertASCIItoUTF16(mCurrentRemoteDescription.c_str());
|
|
}
|
|
|
|
void PeerConnectionImpl::GetPendingRemoteDescription(nsAString& aSDP) const {
|
|
aSDP = NS_ConvertASCIItoUTF16(mPendingRemoteDescription.c_str());
|
|
}
|
|
|
|
dom::Nullable<bool> PeerConnectionImpl::GetCurrentOfferer() const {
|
|
dom::Nullable<bool> result;
|
|
if (mCurrentOfferer.isSome()) {
|
|
result.SetValue(*mCurrentOfferer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
dom::Nullable<bool> PeerConnectionImpl::GetPendingOfferer() const {
|
|
dom::Nullable<bool> result;
|
|
if (mPendingOfferer.isSome()) {
|
|
result.SetValue(*mPendingOfferer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::SignalingState(RTCSignalingState* aState) {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
MOZ_ASSERT(aState);
|
|
|
|
*aState = mSignalingState;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::IceConnectionState(RTCIceConnectionState* aState) {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
MOZ_ASSERT(aState);
|
|
|
|
*aState = mIceConnectionState;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::IceGatheringState(RTCIceGatheringState* aState) {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
MOZ_ASSERT(aState);
|
|
|
|
*aState = mIceGatheringState;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::ConnectionState(RTCPeerConnectionState* aState) {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
MOZ_ASSERT(aState);
|
|
|
|
*aState = mConnectionState;
|
|
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 == RTCIceGatheringState::Complete));
|
|
|
|
if (IsClosed()) {
|
|
CSFLogError(LOGTAG, "%s: called API while closed", __FUNCTION__);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::StoreFinalStats(
|
|
UniquePtr<RTCStatsReportInternal>&& report) {
|
|
using namespace Telemetry;
|
|
|
|
report->mClosed = true;
|
|
|
|
for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) {
|
|
bool isVideo = (inboundRtpStats.mId.Value().Find(u"video") != -1);
|
|
if (!isVideo) {
|
|
continue;
|
|
}
|
|
if (inboundRtpStats.mDiscardedPackets.WasPassed() &&
|
|
report->mCallDurationMs.WasPassed()) {
|
|
double mins = report->mCallDurationMs.Value() / (1000 * 60);
|
|
if (mins > 0) {
|
|
glean::webrtc::video_decoder_discarded_packets_per_call_ppm
|
|
.AccumulateSingleSample(uint32_t(
|
|
double(inboundRtpStats.mDiscardedPackets.Value()) / mins));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, store the stats
|
|
mFinalStats = std::move(report);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PeerConnectionImpl::Close() {
|
|
CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str());
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
|
|
if (IsClosed()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Close");
|
|
|
|
// When ICE completes, we record some telemetry. 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.
|
|
RecordEndOfCallTelemetry();
|
|
|
|
CSFLogInfo(LOGTAG,
|
|
"%s: Closing PeerConnectionImpl %s; "
|
|
"ending call",
|
|
__FUNCTION__, mHandle.c_str());
|
|
mRtcpReceiveListener.DisconnectIfExists();
|
|
if (mJsepSession) {
|
|
mJsepSession->Close();
|
|
}
|
|
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
|
|
}
|
|
|
|
if (mStunAddrsRequest) {
|
|
for (const auto& hostname : mRegisteredMDNSHostnames) {
|
|
mStunAddrsRequest->SendUnregisterMDNSHostname(
|
|
nsCString(hostname.c_str()));
|
|
}
|
|
mRegisteredMDNSHostnames.clear();
|
|
mStunAddrsRequest->Cancel();
|
|
mStunAddrsRequest = nullptr;
|
|
}
|
|
|
|
for (auto& transceiver : mTransceivers) {
|
|
transceiver->Close();
|
|
}
|
|
|
|
mTransportIdToRTCDtlsTransport.Clear();
|
|
|
|
mQueuedIceCtxOperations.clear();
|
|
|
|
mOperations.Clear();
|
|
|
|
// Uncount this connection as active on the inner window upon close.
|
|
if (mWindow && mActiveOnWindow) {
|
|
mWindow->RemovePeerConnection();
|
|
mActiveOnWindow = false;
|
|
}
|
|
|
|
mSignalingState = RTCSignalingState::Closed;
|
|
mConnectionState = RTCPeerConnectionState::Closed;
|
|
|
|
if (!mTransportHandler) {
|
|
// We were never initialized, apparently.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Clear any resources held by libwebrtc through our Call instance.
|
|
RefPtr<GenericPromise> callDestroyPromise;
|
|
if (mCall) {
|
|
// Make sure the compiler does not get confused and try to acquire a
|
|
// reference to this thread _after_ we null out mCall.
|
|
auto callThread = mCall->mCallThread;
|
|
callDestroyPromise =
|
|
InvokeAsync(callThread, __func__, [call = std::move(mCall)]() {
|
|
call->Destroy();
|
|
return GenericPromise::CreateAndResolve(
|
|
true, "PCImpl->WebRtcCallWrapper::Destroy");
|
|
});
|
|
} else {
|
|
callDestroyPromise = GenericPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
mFinalStatsQuery =
|
|
GetStats(nullptr, true)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[this, self = RefPtr<PeerConnectionImpl>(this)](
|
|
UniquePtr<dom::RTCStatsReportInternal>&& aReport) mutable {
|
|
StoreFinalStats(std::move(aReport));
|
|
return GenericNonExclusivePromise::CreateAndResolve(true,
|
|
__func__);
|
|
},
|
|
[](nsresult aError) {
|
|
return GenericNonExclusivePromise::CreateAndResolve(true,
|
|
__func__);
|
|
});
|
|
|
|
// 1. Allow final stats query to complete.
|
|
// 2. Tear down call, if necessary. We do this before we shut down the
|
|
// transport handler, so RTCP BYE can be sent.
|
|
// 3. Unhook from the signal handler (sigslot) for transport stuff. This must
|
|
// be done before we tear down the transport handler.
|
|
// 4. Tear down the transport handler, and deregister from PeerConnectionCtx.
|
|
// When we deregister from PeerConnectionCtx, our final stats (if any)
|
|
// will be stored.
|
|
MOZ_RELEASE_ASSERT(mSTSThread);
|
|
mFinalStatsQuery
|
|
->Then(GetMainThreadSerialEventTarget(), __func__,
|
|
[callDestroyPromise]() mutable { return callDestroyPromise; })
|
|
->Then(
|
|
mSTSThread, __func__,
|
|
[signalHandler = std::move(mSignalHandler)]() mutable {
|
|
CSFLogDebug(
|
|
LOGTAG,
|
|
"Destroying PeerConnectionImpl::SignalHandler on STS thread");
|
|
return GenericPromise::CreateAndResolve(
|
|
true, "PeerConnectionImpl::~SignalHandler");
|
|
})
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[this, self = RefPtr<PeerConnectionImpl>(this)]() mutable {
|
|
CSFLogDebug(LOGTAG, "PCImpl->mTransportHandler::RemoveTransports");
|
|
mTransportHandler->RemoveTransportsExcept(std::set<std::string>());
|
|
if (mPrivateWindow) {
|
|
mTransportHandler->ExitPrivateMode();
|
|
}
|
|
mTransportHandler = nullptr;
|
|
if (PeerConnectionCtx::isActive()) {
|
|
// If we're shutting down xpcom, this Instance will be unset
|
|
// before calling Close() on all remaining PCs, to avoid
|
|
// reentrancy.
|
|
PeerConnectionCtx::GetInstance()->RemovePeerConnection(mHandle);
|
|
}
|
|
});
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::BreakCycles() {
|
|
for (auto& transceiver : mTransceivers) {
|
|
transceiver->BreakCycles();
|
|
}
|
|
mTransceivers.Clear();
|
|
}
|
|
|
|
bool PeerConnectionImpl::HasPendingSetParameters() const {
|
|
for (const auto& transceiver : mTransceivers) {
|
|
if (transceiver->Sender()->HasPendingSetParameters()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PeerConnectionImpl::InvalidateLastReturnedParameters() {
|
|
for (const auto& transceiver : mTransceivers) {
|
|
transceiver->Sender()->InvalidateLastReturnedParameters();
|
|
}
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::SetConfiguration(
|
|
const RTCConfiguration& aConfiguration) {
|
|
nsresult rv = mTransportHandler->SetIceConfig(
|
|
aConfiguration.mIceServers, aConfiguration.mIceTransportPolicy);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
JsepBundlePolicy bundlePolicy;
|
|
switch (aConfiguration.mBundlePolicy) {
|
|
case dom::RTCBundlePolicy::Balanced:
|
|
bundlePolicy = kBundleBalanced;
|
|
break;
|
|
case dom::RTCBundlePolicy::Max_compat:
|
|
bundlePolicy = kBundleMaxCompat;
|
|
break;
|
|
case dom::RTCBundlePolicy::Max_bundle:
|
|
bundlePolicy = kBundleMaxBundle;
|
|
break;
|
|
default:
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
// Ignore errors, since those ought to be handled earlier.
|
|
Unused << mJsepSession->SetBundlePolicy(bundlePolicy);
|
|
|
|
if (!aConfiguration.mPeerIdentity.IsEmpty()) {
|
|
mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
|
|
mRequestedPrivacy = Some(PrincipalPrivacy::Private);
|
|
}
|
|
|
|
auto proxyConfig = GetProxyConfig();
|
|
if (proxyConfig) {
|
|
// 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.
|
|
mTransportHandler->SetProxyConfig(std::move(*proxyConfig));
|
|
}
|
|
|
|
// Store the configuration for about:webrtc
|
|
StoreConfigurationForAboutWebrtc(aConfiguration);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
RTCSctpTransport* PeerConnectionImpl::GetSctp() const {
|
|
return mSctpTransport.get();
|
|
}
|
|
|
|
void PeerConnectionImpl::RestartIce() {
|
|
RestartIceNoRenegotiationNeeded();
|
|
// Update the negotiation-needed flag for connection.
|
|
UpdateNegotiationNeeded();
|
|
}
|
|
|
|
// webrtc-pc does not specify any situations where this is done, but the JSEP
|
|
// spec does, in some situations due to setConfiguration.
|
|
void PeerConnectionImpl::RestartIceNoRenegotiationNeeded() {
|
|
// Empty connection.[[LocalIceCredentialsToReplace]], and populate it with
|
|
// all ICE credentials (ice-ufrag and ice-pwd as defined in section 15.4 of
|
|
// [RFC5245]) found in connection.[[CurrentLocalDescription]], as well as all
|
|
// ICE credentials found in connection.[[PendingLocalDescription]].
|
|
mLocalIceCredentialsToReplace = mJsepSession->GetLocalIceCredentials();
|
|
}
|
|
|
|
bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
|
|
const nsAString& aPluginName) {
|
|
// fire an event to the DOM window if this is "ours"
|
|
if (!AnyCodecHasPluginID(aPluginID)) {
|
|
return false;
|
|
}
|
|
|
|
CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__,
|
|
static_cast<unsigned long long>(aPluginID));
|
|
|
|
RefPtr<Document> 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, u"PluginCrashed"_ns, init);
|
|
|
|
event->SetTrusted(true);
|
|
event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> window = mWindow;
|
|
// MOZ_KnownLive due to bug 1506441
|
|
EventDispatcher::DispatchDOMEvent(
|
|
MOZ_KnownLive(nsGlobalWindowInner::Cast(window)), nullptr, event, nullptr,
|
|
nullptr);
|
|
|
|
return true;
|
|
}
|
|
|
|
void PeerConnectionImpl::RecordEndOfCallTelemetry() {
|
|
if (!mCallTelemStarted) {
|
|
return;
|
|
}
|
|
MOZ_RELEASE_ASSERT(!mCallTelemEnded, "Don't end telemetry twice");
|
|
MOZ_RELEASE_ASSERT(mJsepSession,
|
|
"Call telemetry only starts after jsep session start");
|
|
MOZ_RELEASE_ASSERT(mJsepSession->GetNegotiations() > 0,
|
|
"Call telemetry only starts after first connection");
|
|
|
|
// 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
|
|
glean::webrtc::renegotiations.AccumulateSingleSample(
|
|
mJsepSession->GetNegotiations() - 1);
|
|
glean::webrtc::max_video_send_track.AccumulateSingleSample(
|
|
mMaxSending[SdpMediaSection::MediaType::kVideo]);
|
|
glean::webrtc::max_video_receive_track.AccumulateSingleSample(
|
|
mMaxReceiving[SdpMediaSection::MediaType::kVideo]);
|
|
glean::webrtc::max_audio_send_track.AccumulateSingleSample(
|
|
mMaxSending[SdpMediaSection::MediaType::kAudio]);
|
|
glean::webrtc::max_audio_receive_track.AccumulateSingleSample(
|
|
mMaxReceiving[SdpMediaSection::MediaType::kAudio]);
|
|
// DataChannels appear in both Sending and Receiving
|
|
glean::webrtc::datachannel_negotiated
|
|
.EnumGet(static_cast<glean::webrtc::DatachannelNegotiatedLabel>(
|
|
mMaxSending[SdpMediaSection::MediaType::kApplication]))
|
|
.Add();
|
|
// 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;
|
|
}
|
|
glean::webrtc::call_type.AccumulateSingleSample(type);
|
|
|
|
MOZ_RELEASE_ASSERT(mWindow);
|
|
auto found = sCallDurationTimers.find(mWindow->WindowID());
|
|
if (found != sCallDurationTimers.end()) {
|
|
found->second.UnregisterConnection((type & kAudioTypeMask) ||
|
|
(type & kVideoTypeMask));
|
|
if (found->second.IsStopped()) {
|
|
sCallDurationTimers.erase(found);
|
|
}
|
|
}
|
|
mCallTelemEnded = true;
|
|
}
|
|
|
|
void PeerConnectionImpl::RecordSignalingTelemetry() const {
|
|
uint16_t recvonly[SdpMediaSection::kMediaTypes];
|
|
uint16_t sendonly[SdpMediaSection::kMediaTypes];
|
|
uint16_t sendrecv[SdpMediaSection::kMediaTypes];
|
|
mJsepSession->CountTransceivers(recvonly, sendonly, sendrecv);
|
|
|
|
uint32_t numTransports = 0;
|
|
mJsepSession->ForEachTransceiver([&](const auto& aTransceiver) {
|
|
if (aTransceiver.HasOwnTransport()) {
|
|
++numTransports;
|
|
}
|
|
});
|
|
|
|
SdpNegotiatedExtra extra = {
|
|
.bundlePolicy = (mJsConfiguration.mBundlePolicy.WasPassed()
|
|
? Some(mJsConfiguration.mBundlePolicy.Value())
|
|
: Nothing())
|
|
.map([](RTCBundlePolicy aPolicy) {
|
|
return GetEnumString(aPolicy);
|
|
}),
|
|
.iceTransportPolicy =
|
|
(mJsConfiguration.mIceTransportPolicy.WasPassed()
|
|
? Some(mJsConfiguration.mIceTransportPolicy.Value())
|
|
: Nothing())
|
|
.map([](RTCIceTransportPolicy aPolicy) {
|
|
return GetEnumString(aPolicy);
|
|
}),
|
|
.isRemoteIceLite = Some(mJsepSession->RemoteIsIceLite()),
|
|
.negotiationCount = Some(mJsepSession->GetNegotiations()),
|
|
.numMsectionsAudioRecvonly = Some(recvonly[SdpMediaSection::kAudio]),
|
|
.numMsectionsAudioSendonly = Some(sendonly[SdpMediaSection::kAudio]),
|
|
.numMsectionsAudioSendrecv = Some(sendrecv[SdpMediaSection::kAudio]),
|
|
.numMsectionsData = Some(sendrecv[SdpMediaSection::kApplication]),
|
|
.numMsectionsVideoRecvonly = Some(recvonly[SdpMediaSection::kVideo]),
|
|
.numMsectionsVideoSendonly = Some(sendonly[SdpMediaSection::kVideo]),
|
|
.numMsectionsVideoSendrecv = Some(sendrecv[SdpMediaSection::kVideo]),
|
|
.numTransports = Some(numTransports),
|
|
.pcId = Some(nsCString(mHandle.c_str())),
|
|
};
|
|
glean::webrtc_signaling::sdp_negotiated.Record(Some(std::move(extra)));
|
|
|
|
mJsepSession->ForEachTransceiver([&](const JsepTransceiver& aTransceiver) {
|
|
if (const auto type = aTransceiver.GetMediaType();
|
|
type != SdpMediaSection::kAudio && type != SdpMediaSection::kVideo) {
|
|
return;
|
|
}
|
|
if (!aTransceiver.IsNegotiated()) {
|
|
return;
|
|
}
|
|
const bool sending = aTransceiver.mSendTrack.GetActive();
|
|
const bool receiving = aTransceiver.mRecvTrack.GetActive();
|
|
const nsFmtCString codecString =
|
|
([](const JsepTrackNegotiatedDetails* aDetails) {
|
|
std::set<std::string> payload_names;
|
|
const size_t count = aDetails ? aDetails->GetEncodingCount() : 0;
|
|
for (size_t i = 0; i < count; ++i) {
|
|
const auto& encoding = aDetails->GetEncoding(i);
|
|
for (const auto& codec : encoding.GetCodecs()) {
|
|
if (codec->mEnabled) {
|
|
payload_names.insert(codec->mName);
|
|
}
|
|
}
|
|
}
|
|
return nsFmtCString{FMT_STRING("{}"), fmt::join(payload_names, ", ")};
|
|
})((sending ? aTransceiver.mSendTrack : aTransceiver.mRecvTrack)
|
|
.GetNegotiatedDetails());
|
|
const char* direction = ([&]() {
|
|
if (sending && receiving) {
|
|
return "sendrecv";
|
|
}
|
|
if (sending) {
|
|
return "sendonly";
|
|
}
|
|
if (receiving) {
|
|
return "recvonly";
|
|
}
|
|
return "inactive";
|
|
})();
|
|
const bool hasRtcpMux = aTransceiver.mTransport.mComponents == 1;
|
|
if (aTransceiver.GetMediaType() == SdpMediaSection::kVideo) {
|
|
VideoMsectionNegotiatedExtra extraVideo{
|
|
.codecs = Some(codecString),
|
|
.direction = Some(direction),
|
|
.hasRtcpMux = Some(hasRtcpMux),
|
|
.numSendSimulcastLayers =
|
|
sending ? Some(aTransceiver.mSendTrack.GetRids().size())
|
|
: Nothing(),
|
|
.pcId = Some(nsCString(mHandle.c_str())),
|
|
.pcNegotiationCount = Some(mJsepSession->GetNegotiations()),
|
|
.preferredRecvCodec =
|
|
receiving ? Some(nsDependentCString(
|
|
aTransceiver.mRecvTrack.GetVideoPreferredCodec()
|
|
.c_str()))
|
|
: Nothing(),
|
|
.preferredSendCodec =
|
|
sending ? Some(nsDependentCString(
|
|
aTransceiver.mSendTrack.GetVideoPreferredCodec()
|
|
.c_str()))
|
|
: Nothing(),
|
|
};
|
|
glean::webrtc_signaling::video_msection_negotiated.Record(
|
|
Some(extraVideo));
|
|
} else {
|
|
AudioMsectionNegotiatedExtra extraAudio{
|
|
.codecs = Some(codecString),
|
|
.direction = Some(direction),
|
|
.hasRtcpMux = Some(hasRtcpMux),
|
|
.pcId = Some(nsCString(mHandle.c_str())),
|
|
.pcNegotiationCount = Some(mJsepSession->GetNegotiations()),
|
|
.preferredRecvCodec =
|
|
receiving ? Some(nsDependentCString(
|
|
aTransceiver.mRecvTrack.GetAudioPreferredCodec()
|
|
.c_str()))
|
|
: Nothing(),
|
|
.preferredSendCodec =
|
|
sending ? Some(nsDependentCString(
|
|
aTransceiver.mSendTrack.GetAudioPreferredCodec()
|
|
.c_str()))
|
|
: Nothing(),
|
|
};
|
|
glean::webrtc_signaling::audio_msection_negotiated.Record(
|
|
Some(extraAudio));
|
|
}
|
|
});
|
|
}
|
|
|
|
DOMMediaStream* PeerConnectionImpl::GetReceiveStream(
|
|
const std::string& aId) const {
|
|
nsString wanted = NS_ConvertASCIItoUTF16(aId.c_str());
|
|
for (auto& stream : mReceiveStreams) {
|
|
nsString id;
|
|
stream->GetId(id);
|
|
if (id == wanted) {
|
|
return stream;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
DOMMediaStream* PeerConnectionImpl::CreateReceiveStream(
|
|
const std::string& aId) {
|
|
mReceiveStreams.AppendElement(new DOMMediaStream(mWindow));
|
|
mReceiveStreams.LastElement()->AssignId(NS_ConvertASCIItoUTF16(aId.c_str()));
|
|
return mReceiveStreams.LastElement();
|
|
}
|
|
|
|
already_AddRefed<dom::Promise> PeerConnectionImpl::OnSetDescriptionSuccess(
|
|
dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError) {
|
|
CSFLogDebug(LOGTAG, __FUNCTION__);
|
|
|
|
RefPtr<dom::Promise> p = MakePromise(aError);
|
|
if (aError.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, p);
|
|
|
|
return p.forget();
|
|
}
|
|
|
|
void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing(
|
|
dom::RTCSdpType aSdpType, bool aRemote, const RefPtr<dom::Promise>& aP) {
|
|
// Spec says we queue a task for all the stuff that ends up back in JS
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
__func__,
|
|
[this, self = RefPtr<PeerConnectionImpl>(this), aSdpType, aRemote, aP] {
|
|
if (IsClosed()) {
|
|
// Yes, we do not settle the promise here. Yes, this is what the spec
|
|
// wants.
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mUncommittedJsepSession);
|
|
|
|
// sRD/sLD needs to be redone in certain circumstances
|
|
bool needsRedo = HasPendingSetParameters();
|
|
if (!needsRedo && aRemote && (aSdpType == dom::RTCSdpType::Offer)) {
|
|
for (auto& transceiver : mTransceivers) {
|
|
if (!mUncommittedJsepSession->GetTransceiver(
|
|
transceiver->GetJsepTransceiverId())) {
|
|
needsRedo = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsRedo) {
|
|
// Spec says to abort, and re-do the sRD!
|
|
// This happens either when there is a SetParameters call in
|
|
// flight (that will race against the [[SendEncodings]]
|
|
// modification caused by sRD(offer)), or when addTrack has been
|
|
// called while sRD(offer) was in progress.
|
|
mUncommittedJsepSession.reset(mJsepSession->Clone());
|
|
JsepSession::Result result;
|
|
if (aRemote) {
|
|
mUncommittedJsepSession->SetRemoteDescription(
|
|
ToJsepSdpType(aSdpType), mRemoteRequestedSDP);
|
|
} else {
|
|
mUncommittedJsepSession->SetLocalDescription(
|
|
ToJsepSdpType(aSdpType), mLocalRequestedSDP);
|
|
}
|
|
if (result.mError.isSome()) {
|
|
// wat
|
|
nsCString error(
|
|
"When redoing sRD/sLD because it raced against "
|
|
"addTrack or setParameters, we encountered a failure that "
|
|
"did not happen "
|
|
"the first time. This should never happen. The error was: ");
|
|
error += mUncommittedJsepSession->GetLastError().c_str();
|
|
aP->MaybeRejectWithOperationError(error);
|
|
MOZ_ASSERT(false);
|
|
} else {
|
|
DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (auto& transceiver : mTransceivers) {
|
|
if (!mUncommittedJsepSession->GetTransceiver(
|
|
transceiver->GetJsepTransceiverId())) {
|
|
// sLD, or sRD(answer), just make sure the new transceiver is
|
|
// added, no need to re-do anything.
|
|
mUncommittedJsepSession->AddTransceiver(
|
|
transceiver->GetJsepTransceiver());
|
|
}
|
|
}
|
|
|
|
auto oldIceCredentials = mJsepSession->GetLocalIceCredentials();
|
|
auto newIceCredentials =
|
|
mUncommittedJsepSession->GetLocalIceCredentials();
|
|
|
|
bool iceRestartDetected =
|
|
(!oldIceCredentials.empty() && !newIceCredentials.empty() &&
|
|
(oldIceCredentials != newIceCredentials));
|
|
|
|
mJsepSession = std::move(mUncommittedJsepSession);
|
|
|
|
auto newSignalingState = GetSignalingState();
|
|
SyncFromJsep();
|
|
if (aRemote || aSdpType == dom::RTCSdpType::Pranswer ||
|
|
aSdpType == dom::RTCSdpType::Answer) {
|
|
InvalidateLastReturnedParameters();
|
|
}
|
|
|
|
if (aSdpType == dom::RTCSdpType::Offer &&
|
|
mSignalingState == RTCSignalingState::Stable) {
|
|
// If description is of type "offer" and
|
|
// connection.[[SignalingState]] is "stable" then for each
|
|
// transceiver in connection's set of transceivers, run the following
|
|
// steps:
|
|
SaveStateForRollback();
|
|
}
|
|
|
|
// Section 4.4.1.5 Set the RTCSessionDescription:
|
|
if (aSdpType == dom::RTCSdpType::Rollback) {
|
|
// - step 4.5.10, type is rollback
|
|
RestoreStateForRollback();
|
|
} else if (!(aRemote && aSdpType == dom::RTCSdpType::Offer)) {
|
|
// - step 4.5.9 type is not rollback
|
|
// - step 4.5.9.1 when remote is false
|
|
// - step 4.5.9.2.13 when remote is true, type answer or pranswer
|
|
// More simply: not rollback, and not for remote offers.
|
|
UpdateRTCDtlsTransports();
|
|
}
|
|
|
|
// Did we just apply a local description?
|
|
if (!aRemote) {
|
|
// We'd like to handle this in PeerConnectionImpl::UpdateNetworkState.
|
|
// Unfortunately, if the WiFi switch happens quickly, we never see
|
|
// that state change. We need to detect the ice restart here and
|
|
// reset the PeerConnectionImpl's stun addresses so they are
|
|
// regathered when PeerConnectionImpl::GatherIfReady is called.
|
|
if (iceRestartDetected || mJsepSession->IsIceRestarting()) {
|
|
ResetStunAddrsForIceRestart();
|
|
}
|
|
EnsureTransports(*mJsepSession);
|
|
}
|
|
|
|
if (mJsepSession->GetState() == kJsepStateStable) {
|
|
// If we're rolling back a local offer, we might need to remove some
|
|
// transports, and stomp some MediaPipeline setup, but nothing further
|
|
// needs to be done.
|
|
UpdateTransports(*mJsepSession, mForceIceTcp);
|
|
if (NS_FAILED(UpdateMediaPipelines())) {
|
|
CSFLogError(LOGTAG, "Error Updating MediaPipelines");
|
|
NS_ASSERTION(
|
|
false,
|
|
"Error Updating MediaPipelines in OnSetDescriptionSuccess()");
|
|
aP->MaybeRejectWithOperationError("Error Updating MediaPipelines");
|
|
}
|
|
|
|
if (aSdpType != dom::RTCSdpType::Rollback) {
|
|
StartIceChecks(*mJsepSession);
|
|
}
|
|
|
|
// 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->CountTracksAndDatachannels(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];
|
|
}
|
|
}
|
|
} else if (aSdpType == dom::RTCSdpType::Offer && !aRemote) {
|
|
// We do this to ensure the mediaPipelineFilter is ready to receive
|
|
// PTs in our offer. This is mainly used for when bundle is involved
|
|
// but for whatever reason mid or SSRC is not signaled.
|
|
for (const auto& transceiverImpl : mTransceivers) {
|
|
if ((transceiverImpl->Direction() ==
|
|
RTCRtpTransceiverDirection::Sendrecv) ||
|
|
(transceiverImpl->Direction() ==
|
|
RTCRtpTransceiverDirection::Recvonly)) {
|
|
transceiverImpl->Receiver()->UpdateTransport();
|
|
}
|
|
}
|
|
}
|
|
|
|
mPendingRemoteDescription =
|
|
mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
|
|
mCurrentRemoteDescription =
|
|
mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
|
|
mPendingLocalDescription =
|
|
mJsepSession->GetLocalDescription(kJsepDescriptionPending);
|
|
mCurrentLocalDescription =
|
|
mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
|
|
mPendingOfferer = mJsepSession->IsPendingOfferer();
|
|
mCurrentOfferer = mJsepSession->IsCurrentOfferer();
|
|
|
|
if (aSdpType == dom::RTCSdpType::Answer) {
|
|
std::set<std::pair<std::string, std::string>> iceCredentials =
|
|
mJsepSession->GetLocalIceCredentials();
|
|
std::vector<std::pair<std::string, std::string>>
|
|
iceCredentialsNotReplaced;
|
|
std::set_intersection(mLocalIceCredentialsToReplace.begin(),
|
|
mLocalIceCredentialsToReplace.end(),
|
|
iceCredentials.begin(), iceCredentials.end(),
|
|
std::back_inserter(iceCredentialsNotReplaced));
|
|
|
|
if (iceCredentialsNotReplaced.empty()) {
|
|
mLocalIceCredentialsToReplace.clear();
|
|
}
|
|
}
|
|
|
|
if (newSignalingState == RTCSignalingState::Stable) {
|
|
mNegotiationNeeded = false;
|
|
UpdateNegotiationNeeded();
|
|
}
|
|
|
|
bool signalingStateChanged = false;
|
|
if (newSignalingState != mSignalingState) {
|
|
mSignalingState = newSignalingState;
|
|
signalingStateChanged = true;
|
|
}
|
|
|
|
// Spec does not actually tell us to do this, but that is probably a
|
|
// spec bug.
|
|
// https://github.com/w3c/webrtc-pc/issues/2817
|
|
bool gatheringStateChanged = UpdateIceGatheringState();
|
|
|
|
bool iceConnectionStateChanged = UpdateIceConnectionState();
|
|
|
|
bool connectionStateChanged = UpdateConnectionState();
|
|
|
|
// This only gets populated for remote descriptions
|
|
dom::RTCRtpReceiver::StreamAssociationChanges changes;
|
|
if (aRemote) {
|
|
for (const auto& transceiver : mTransceivers) {
|
|
transceiver->Receiver()->UpdateStreams(&changes);
|
|
}
|
|
}
|
|
|
|
// Make sure to wait until after we've calculated track changes before
|
|
// doing this.
|
|
for (size_t i = 0; i < mTransceivers.Length();) {
|
|
auto& transceiver = mTransceivers[i];
|
|
if (transceiver->ShouldRemove()) {
|
|
mTransceivers[i]->Close();
|
|
mTransceivers[i]->SetRemovedFromPc();
|
|
mTransceivers.RemoveElementAt(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// JS callbacks happen below. DO NOT TOUCH STATE AFTER THIS UNLESS SPEC
|
|
// EXPLICITLY SAYS TO, OTHERWISE STATES THAT ARE NOT SUPPOSED TO BE
|
|
// OBSERVABLE TO JS WILL BE!
|
|
|
|
JSErrorResult jrv;
|
|
RefPtr<PeerConnectionObserver> pcObserver(mPCObserver);
|
|
if (signalingStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::SignalingState, jrv);
|
|
}
|
|
|
|
if (gatheringStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::IceGatheringState,
|
|
jrv);
|
|
}
|
|
|
|
if (iceConnectionStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::IceConnectionState,
|
|
jrv);
|
|
}
|
|
|
|
if (connectionStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv);
|
|
}
|
|
|
|
for (const auto& receiver : changes.mReceiversToMute) {
|
|
// This sets the muted state for the recv track and all its clones.
|
|
receiver->SetTrackMuteFromRemoteSdp();
|
|
}
|
|
|
|
for (const auto& association : changes.mStreamAssociationsRemoved) {
|
|
RefPtr<DOMMediaStream> stream =
|
|
GetReceiveStream(association.mStreamId);
|
|
if (stream && stream->HasTrack(*association.mTrack)) {
|
|
stream->RemoveTrackInternal(association.mTrack);
|
|
}
|
|
}
|
|
|
|
// TODO(Bug 1241291): For legacy event, remove eventually
|
|
std::vector<RefPtr<DOMMediaStream>> newStreams;
|
|
|
|
for (const auto& association : changes.mStreamAssociationsAdded) {
|
|
RefPtr<DOMMediaStream> stream =
|
|
GetReceiveStream(association.mStreamId);
|
|
if (!stream) {
|
|
stream = CreateReceiveStream(association.mStreamId);
|
|
newStreams.push_back(stream);
|
|
}
|
|
|
|
if (!stream->HasTrack(*association.mTrack)) {
|
|
stream->AddTrackInternal(association.mTrack);
|
|
}
|
|
}
|
|
|
|
for (const auto& trackEvent : changes.mTrackEvents) {
|
|
dom::Sequence<OwningNonNull<DOMMediaStream>> streams;
|
|
for (const auto& id : trackEvent.mStreamIds) {
|
|
RefPtr<DOMMediaStream> stream = GetReceiveStream(id);
|
|
if (!stream) {
|
|
MOZ_ASSERT(false);
|
|
continue;
|
|
}
|
|
if (!streams.AppendElement(*stream, fallible)) {
|
|
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which
|
|
// might involve multiple reallocations) and potentially
|
|
// crashing here, SetCapacity could be called outside the loop
|
|
// once.
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
pcObserver->FireTrackEvent(*trackEvent.mReceiver, streams, jrv);
|
|
}
|
|
|
|
// TODO(Bug 1241291): Legacy event, remove eventually
|
|
for (const auto& stream : newStreams) {
|
|
pcObserver->FireStreamEvent(*stream, jrv);
|
|
}
|
|
|
|
if (signalingStateChanged &&
|
|
mSignalingState == dom::RTCSignalingState::Stable &&
|
|
aSdpType != RTCSdpType::Rollback) {
|
|
RecordSignalingTelemetry();
|
|
}
|
|
|
|
aP->MaybeResolveWithUndefined();
|
|
}));
|
|
}
|
|
|
|
void PeerConnectionImpl::OnSetDescriptionError() {
|
|
mUncommittedJsepSession = nullptr;
|
|
}
|
|
|
|
RTCSignalingState PeerConnectionImpl::GetSignalingState() const {
|
|
switch (mJsepSession->GetState()) {
|
|
case kJsepStateStable:
|
|
return RTCSignalingState::Stable;
|
|
break;
|
|
case kJsepStateHaveLocalOffer:
|
|
return RTCSignalingState::Have_local_offer;
|
|
break;
|
|
case kJsepStateHaveRemoteOffer:
|
|
return RTCSignalingState::Have_remote_offer;
|
|
break;
|
|
case kJsepStateHaveLocalPranswer:
|
|
return RTCSignalingState::Have_local_pranswer;
|
|
break;
|
|
case kJsepStateHaveRemotePranswer:
|
|
return RTCSignalingState::Have_remote_pranswer;
|
|
break;
|
|
case kJsepStateClosed:
|
|
return RTCSignalingState::Closed;
|
|
break;
|
|
}
|
|
MOZ_CRASH("Invalid JSEP state");
|
|
}
|
|
|
|
bool PeerConnectionImpl::IsClosed() const {
|
|
return mSignalingState == RTCSignalingState::Closed;
|
|
}
|
|
|
|
PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle)
|
|
: impl_(nullptr) {
|
|
if (PeerConnectionCtx::isActive()) {
|
|
impl_ = PeerConnectionCtx::GetInstance()->GetPeerConnection(handle);
|
|
}
|
|
}
|
|
|
|
const RefPtr<MediaTransportHandler> PeerConnectionImpl::GetTransportHandler()
|
|
const {
|
|
return mTransportHandler;
|
|
}
|
|
|
|
const std::string& PeerConnectionImpl::GetHandle() { return mHandle; }
|
|
|
|
const std::string& PeerConnectionImpl::GetName() {
|
|
PC_AUTO_ENTER_API_CALL_NO_CHECK();
|
|
return mName;
|
|
}
|
|
|
|
void PeerConnectionImpl::CandidateReady(const std::string& candidate,
|
|
const std::string& transportId,
|
|
const std::string& ufrag) {
|
|
STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered");
|
|
PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
|
|
|
|
if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) {
|
|
CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str());
|
|
STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked");
|
|
return;
|
|
}
|
|
|
|
// One of the very few places we still use level; required by the JSEP API
|
|
uint16_t level = 0;
|
|
std::string mid;
|
|
bool skipped = false;
|
|
|
|
if (mUncommittedJsepSession) {
|
|
// An sLD or sRD is in progress, and while that is the case, we need to add
|
|
// the candidate to both the current JSEP engine, and the uncommitted JSEP
|
|
// engine. We ignore errors because the spec says to only take into account
|
|
// the current/pending local descriptions when determining whether to
|
|
// surface the candidate to content, which does not take into account any
|
|
// in-progress sRD/sLD.
|
|
Unused << mUncommittedJsepSession->AddLocalIceCandidate(
|
|
candidate, transportId, ufrag, &level, &mid, &skipped);
|
|
}
|
|
|
|
nsresult res = mJsepSession->AddLocalIceCandidate(
|
|
candidate, transportId, ufrag, &level, &mid, &skipped);
|
|
|
|
if (NS_FAILED(res)) {
|
|
std::string errorString = mJsepSession->GetLastError();
|
|
|
|
STAMP_TIMECARD(mTimeCard, "Local Ice Candidate invalid");
|
|
CSFLogError(LOGTAG,
|
|
"Failed to incorporate local candidate into SDP:"
|
|
" res = %u, candidate = %s, transport-id = %s,"
|
|
" error = %s",
|
|
static_cast<unsigned>(res), candidate.c_str(),
|
|
transportId.c_str(), errorString.c_str());
|
|
return;
|
|
}
|
|
|
|
if (skipped) {
|
|
STAMP_TIMECARD(mTimeCard, "Local Ice Candidate skipped");
|
|
CSFLogInfo(LOGTAG,
|
|
"Skipped adding local candidate %s (transport-id %s) "
|
|
"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(), transportId.c_str());
|
|
return;
|
|
}
|
|
|
|
mPendingLocalDescription =
|
|
mJsepSession->GetLocalDescription(kJsepDescriptionPending);
|
|
mCurrentLocalDescription =
|
|
mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
|
|
CSFLogInfo(LOGTAG, "Passing local candidate to content: %s",
|
|
candidate.c_str());
|
|
SendLocalIceCandidateToContent(level, mid, candidate, ufrag);
|
|
}
|
|
|
|
void PeerConnectionImpl::SendLocalIceCandidateToContent(
|
|
uint16_t level, const std::string& mid, const std::string& candidate,
|
|
const std::string& ufrag) {
|
|
STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content");
|
|
JSErrorResult rv;
|
|
mPCObserver->OnIceCandidate(level, ObString(mid.c_str()),
|
|
ObString(candidate.c_str()),
|
|
ObString(ufrag.c_str()), rv);
|
|
}
|
|
|
|
void PeerConnectionImpl::IceConnectionStateChange(
|
|
const std::string& aTransportId, dom::RTCIceTransportState domState) {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
|
|
|
|
CSFLogDebug(LOGTAG, "IceConnectionStateChange: %s %d (%p)",
|
|
aTransportId.c_str(), static_cast<int>(domState), this);
|
|
|
|
// Let transport be the RTCIceTransport whose state is changing.
|
|
nsCString key(aTransportId.data(), aTransportId.size());
|
|
RefPtr<RTCDtlsTransport> dtlsTransport =
|
|
mTransportIdToRTCDtlsTransport.Get(key);
|
|
if (!dtlsTransport) {
|
|
return;
|
|
}
|
|
RefPtr<RTCIceTransport> transport = dtlsTransport->IceTransport();
|
|
|
|
if (domState == RTCIceTransportState::Closed) {
|
|
mTransportIdToRTCDtlsTransport.Remove(key);
|
|
}
|
|
|
|
// Let selectedCandidatePairChanged be false.
|
|
// TODO(bug 1307994)
|
|
|
|
// Let transportIceConnectionStateChanged be false.
|
|
bool transportIceConnectionStateChanged = false;
|
|
|
|
// Let connectionIceConnectionStateChanged be false.
|
|
bool connectionIceConnectionStateChanged = false;
|
|
|
|
// Let connectionStateChanged be false.
|
|
bool connectionStateChanged = false;
|
|
|
|
if (transport->State() == domState) {
|
|
return;
|
|
}
|
|
|
|
// If transport's RTCIceTransportState was changed, run the following steps:
|
|
|
|
// Set transport.[[IceTransportState]] to the new indicated
|
|
// RTCIceTransportState.
|
|
transport->SetState(domState);
|
|
|
|
// Set transportIceConnectionStateChanged to true.
|
|
transportIceConnectionStateChanged = true;
|
|
|
|
// Set connection.[[IceConnectionState]] to the value of deriving a new state
|
|
// value as described by the RTCIceConnectionState enum.
|
|
if (UpdateIceConnectionState()) {
|
|
// If connection.[[IceConnectionState]] changed in the previous step, set
|
|
// connectionIceConnectionStateChanged to true.
|
|
connectionIceConnectionStateChanged = true;
|
|
}
|
|
|
|
// Set connection.[[ConnectionState]] to the value of deriving a new state
|
|
// value as described by the RTCPeerConnectionState enum.
|
|
if (UpdateConnectionState()) {
|
|
// If connection.[[ConnectionState]] changed in the previous step, set
|
|
// connectionStateChanged to true.
|
|
connectionStateChanged = true;
|
|
}
|
|
|
|
// If selectedCandidatePairChanged is true, fire an event named
|
|
// selectedcandidatepairchange at transport.
|
|
// TODO(bug 1307994)
|
|
|
|
// If transportIceConnectionStateChanged is true, fire an event named
|
|
// statechange at transport.
|
|
if (transportIceConnectionStateChanged) {
|
|
transport->FireStateChangeEvent();
|
|
}
|
|
|
|
WrappableJSErrorResult rv;
|
|
RefPtr<PeerConnectionObserver> pcObserver(mPCObserver);
|
|
|
|
// If connectionIceConnectionStateChanged is true, fire an event named
|
|
// iceconnectionstatechange at connection.
|
|
if (connectionIceConnectionStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv);
|
|
}
|
|
|
|
// If connectionStateChanged is true, fire an event named
|
|
// connectionstatechange at connection.
|
|
if (connectionStateChanged) {
|
|
pcObserver->OnStateChange(PCObserverStateType::ConnectionState, rv);
|
|
}
|
|
}
|
|
|
|
RTCIceConnectionState PeerConnectionImpl::GetNewIceConnectionState() const {
|
|
// closed The RTCPeerConnection object's [[IsClosed]] slot is true.
|
|
if (IsClosed()) {
|
|
return RTCIceConnectionState::Closed;
|
|
}
|
|
|
|
// Would use a bitset, but that requires lots of static_cast<size_t>
|
|
// Oh well.
|
|
std::set<RTCIceTransportState> statesFound;
|
|
std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports());
|
|
for (const auto& transport : transports) {
|
|
RefPtr<dom::RTCIceTransport> iceTransport = transport->IceTransport();
|
|
CSFLogWarn(LOGTAG, "GetNewIceConnectionState: %p %d", iceTransport.get(),
|
|
static_cast<int>(iceTransport->State()));
|
|
statesFound.insert(iceTransport->State());
|
|
}
|
|
|
|
// failed None of the previous states apply and any RTCIceTransports are
|
|
// in the "failed" state.
|
|
if (statesFound.count(RTCIceTransportState::Failed)) {
|
|
return RTCIceConnectionState::Failed;
|
|
}
|
|
|
|
// disconnected None of the previous states apply and any
|
|
// RTCIceTransports are in the "disconnected" state.
|
|
if (statesFound.count(RTCIceTransportState::Disconnected)) {
|
|
return RTCIceConnectionState::Disconnected;
|
|
}
|
|
|
|
// new None of the previous states apply and all RTCIceTransports are
|
|
// in the "new" or "closed" state, or there are no transports.
|
|
if (!statesFound.count(RTCIceTransportState::Checking) &&
|
|
!statesFound.count(RTCIceTransportState::Completed) &&
|
|
!statesFound.count(RTCIceTransportState::Connected)) {
|
|
return RTCIceConnectionState::New;
|
|
}
|
|
|
|
// checking None of the previous states apply and any RTCIceTransports are
|
|
// in the "new" or "checking" state.
|
|
if (statesFound.count(RTCIceTransportState::New) ||
|
|
statesFound.count(RTCIceTransportState::Checking)) {
|
|
return RTCIceConnectionState::Checking;
|
|
}
|
|
|
|
// completed None of the previous states apply and all RTCIceTransports are
|
|
// in the "completed" or "closed" state.
|
|
if (!statesFound.count(RTCIceTransportState::Connected)) {
|
|
return RTCIceConnectionState::Completed;
|
|
}
|
|
|
|
// connected None of the previous states apply.
|
|
return RTCIceConnectionState::Connected;
|
|
}
|
|
|
|
bool PeerConnectionImpl::UpdateIceConnectionState() {
|
|
auto newState = GetNewIceConnectionState();
|
|
if (newState != mIceConnectionState) {
|
|
CSFLogInfo(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__,
|
|
static_cast<int>(mIceConnectionState),
|
|
static_cast<int>(newState), this);
|
|
mIceConnectionState = newState;
|
|
// Start call telemtry logging on connected.
|
|
if (mIceConnectionState == RTCIceConnectionState::Connected) {
|
|
StartCallTelem();
|
|
}
|
|
if (mIceConnectionState != RTCIceConnectionState::Closed) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId,
|
|
const CandidateInfo& aCandidateInfo) {
|
|
if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) {
|
|
MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty());
|
|
|
|
if (mCanRegisterMDNSHostnamesDirectly) {
|
|
auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress);
|
|
|
|
// We'll see the address twice if we're generating both UDP and TCP
|
|
// candidates.
|
|
if (itor == mRegisteredMDNSHostnames.end()) {
|
|
mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress);
|
|
mStunAddrsRequest->SendRegisterMDNSHostname(
|
|
nsCString(aCandidateInfo.mMDNSAddress.c_str()),
|
|
nsCString(aCandidateInfo.mActualAddress.c_str()));
|
|
}
|
|
} else {
|
|
mMDNSHostnamesToRegister.emplace(aCandidateInfo.mMDNSAddress,
|
|
aCandidateInfo.mActualAddress);
|
|
}
|
|
}
|
|
|
|
if (!aCandidateInfo.mDefaultHostRtp.empty()) {
|
|
UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp,
|
|
aCandidateInfo.mDefaultPortRtp,
|
|
aCandidateInfo.mDefaultHostRtcp,
|
|
aCandidateInfo.mDefaultPortRtcp, aTransportId);
|
|
}
|
|
CandidateReady(aCandidateInfo.mCandidate, aTransportId,
|
|
aCandidateInfo.mUfrag);
|
|
}
|
|
|
|
void PeerConnectionImpl::IceGatheringStateChange(
|
|
const std::string& aTransportId, dom::RTCIceGathererState state) {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
|
|
|
|
CSFLogWarn(LOGTAG, "IceGatheringStateChange: %s %d (%p)",
|
|
aTransportId.c_str(), static_cast<int>(state), this);
|
|
|
|
// Let transport be the RTCIceTransport for which candidate gathering
|
|
// began/finished.
|
|
nsCString key(aTransportId.data(), aTransportId.size());
|
|
RefPtr<RTCDtlsTransport> dtlsTransport =
|
|
mTransportIdToRTCDtlsTransport.Get(key);
|
|
if (!dtlsTransport) {
|
|
return;
|
|
}
|
|
RefPtr<RTCIceTransport> transport = dtlsTransport->IceTransport();
|
|
|
|
if (transport->GatheringState() == state) {
|
|
return;
|
|
}
|
|
|
|
// Set transport.[[IceGathererState]] to gathering.
|
|
// or
|
|
// Set transport.[[IceGathererState]] to complete.
|
|
transport->SetGatheringState(state);
|
|
|
|
// Set connection.[[IceGatheringState]] to the value of deriving a new state
|
|
// value as described by the RTCIceGatheringState enum.
|
|
//
|
|
// Let connectionIceGatheringStateChanged be true if
|
|
// connection.[[IceGatheringState]] changed in the previous step, otherwise
|
|
// false.
|
|
bool gatheringStateChanged = UpdateIceGatheringState();
|
|
|
|
// Do not read or modify state beyond this point.
|
|
|
|
// Fire an event named gatheringstatechange at transport.
|
|
transport->FireGatheringStateChangeEvent();
|
|
|
|
// If connectionIceGatheringStateChanged is true, fire an event named
|
|
// icegatheringstatechange at connection.
|
|
if (gatheringStateChanged) {
|
|
// NOTE: If we're in the "complete" case, our JS code will fire a null
|
|
// icecandidate event after firing the icegatheringstatechange event.
|
|
// Fire an event named icecandidate using the RTCPeerConnectionIceEvent
|
|
// interface with the candidate attribute set to null at connection.
|
|
JSErrorResult rv;
|
|
mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv);
|
|
}
|
|
}
|
|
|
|
bool PeerConnectionImpl::UpdateIceGatheringState() {
|
|
// If connection.[[IsClosed]] is true, abort these steps.
|
|
if (IsClosed()) {
|
|
return false;
|
|
}
|
|
|
|
// Let newState be the value of deriving a new state value as
|
|
// described by the RTCIceGatheringState enum.
|
|
auto newState = GetNewIceGatheringState();
|
|
|
|
// If connection.[[IceGatheringState]] is equal to newState, abort
|
|
// these steps.
|
|
if (newState == mIceGatheringState) {
|
|
return false;
|
|
}
|
|
|
|
CSFLogInfo(LOGTAG, "UpdateIceGatheringState: %d -> %d (%p)",
|
|
static_cast<int>(mIceGatheringState), static_cast<int>(newState),
|
|
this);
|
|
// Set connection.[[IceGatheringState]] to newState.
|
|
mIceGatheringState = newState;
|
|
|
|
// 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 RTCIceGatheringState::New:
|
|
STAMP_TIMECARD(mTimeCard, "Ice gathering state: new");
|
|
break;
|
|
case RTCIceGatheringState::Gathering:
|
|
STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering");
|
|
break;
|
|
case RTCIceGatheringState::Complete:
|
|
STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete");
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RTCIceGatheringState PeerConnectionImpl::GetNewIceGatheringState() const {
|
|
// new Any of the RTCIceTransports are in the "new" gathering state
|
|
// and none of the transports are in the "gathering" state, or there are no
|
|
// transports.
|
|
|
|
// NOTE! This derives the RTCIce**Gathering**State from the individual
|
|
// RTCIce**Gatherer**State of the transports. These are different enums.
|
|
// But they have exactly the same values, in the same order.
|
|
// ¯\_(ツ)_/¯
|
|
bool foundComplete = false;
|
|
std::set<RefPtr<RTCDtlsTransport>> transports(GetActiveTransports());
|
|
for (const auto& transport : transports) {
|
|
RefPtr<dom::RTCIceTransport> iceTransport = transport->IceTransport();
|
|
switch (iceTransport->GatheringState()) {
|
|
case RTCIceGathererState::New:
|
|
break;
|
|
case RTCIceGathererState::Gathering:
|
|
// gathering Any of the RTCIceTransports are in the "gathering"
|
|
// state.
|
|
return RTCIceGatheringState::Gathering;
|
|
case RTCIceGathererState::Complete:
|
|
foundComplete = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundComplete) {
|
|
return RTCIceGatheringState::New;
|
|
}
|
|
|
|
// This could change depending on the outcome in
|
|
// https://github.com/w3c/webrtc-pc/issues/2914
|
|
return RTCIceGatheringState::Complete;
|
|
}
|
|
|
|
void PeerConnectionImpl::UpdateDefaultCandidate(
|
|
const std::string& defaultAddr, uint16_t defaultPort,
|
|
const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort,
|
|
const std::string& transportId) {
|
|
CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
|
|
mJsepSession->UpdateDefaultCandidate(
|
|
defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, transportId);
|
|
if (mUncommittedJsepSession) {
|
|
mUncommittedJsepSession->UpdateDefaultCandidate(
|
|
defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort,
|
|
transportId);
|
|
}
|
|
}
|
|
|
|
static UniquePtr<dom::RTCStatsCollection> GetDataChannelStats_s(
|
|
const RefPtr<DataChannelConnection>& aDataConnection,
|
|
const DOMHighResTimeStamp aTimestamp) {
|
|
UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection);
|
|
if (aDataConnection) {
|
|
aDataConnection->AppendStatsToReport(report, aTimestamp);
|
|
}
|
|
return report;
|
|
}
|
|
|
|
RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetDataChannelStats(
|
|
const RefPtr<DataChannelConnection>& aDataChannelConnection,
|
|
const DOMHighResTimeStamp aTimestamp) {
|
|
// Gather stats from DataChannels
|
|
return InvokeAsync(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[aDataChannelConnection, aTimestamp]() {
|
|
return dom::RTCStatsPromise::CreateAndResolve(
|
|
GetDataChannelStats_s(aDataChannelConnection, aTimestamp),
|
|
__func__);
|
|
});
|
|
}
|
|
|
|
void PeerConnectionImpl::CollectConduitTelemetryData() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsTArray<RefPtr<VideoSessionConduit>> conduits;
|
|
for (const auto& transceiver : mTransceivers) {
|
|
if (RefPtr<MediaSessionConduit> conduit = transceiver->GetConduit()) {
|
|
conduit->AsVideoSessionConduit().apply(
|
|
[&](const auto& aVideo) { conduits.AppendElement(aVideo); });
|
|
}
|
|
}
|
|
|
|
if (!conduits.IsEmpty() && mCall) {
|
|
mCall->mCallThread->Dispatch(
|
|
NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)] {
|
|
for (const auto& conduit : conduits) {
|
|
conduit->CollectTelemetryData();
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
|
|
nsTArray<dom::RTCCodecStats> PeerConnectionImpl::GetCodecStats(
|
|
DOMHighResTimeStamp aNow) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
nsTArray<dom::RTCCodecStats> result;
|
|
|
|
struct CodecComparator {
|
|
bool operator()(const JsepCodecDescription* aA,
|
|
const JsepCodecDescription* aB) const {
|
|
return aA->StatsId() < aB->StatsId();
|
|
}
|
|
};
|
|
|
|
// transportId -> codec; per direction (whether the codecType
|
|
// shall be "encode", "decode" or absent (if a codec exists in both maps for a
|
|
// transport)). These do the bookkeeping to ensure codec stats get coalesced
|
|
// to transport level.
|
|
std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>>
|
|
sendCodecMap;
|
|
std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>>
|
|
recvCodecMap;
|
|
|
|
// Find all JsepCodecDescription instances we want to turn into codec stats.
|
|
for (const auto& transceiver : mTransceivers) {
|
|
// TODO: Grab these from the JSEP transceivers instead
|
|
auto sendCodecs = transceiver->GetNegotiatedSendCodecs();
|
|
auto recvCodecs = transceiver->GetNegotiatedRecvCodecs();
|
|
|
|
const std::string transportId = transceiver->GetTransportId();
|
|
// This ensures both codec maps have the same size.
|
|
auto& sendMap = sendCodecMap[transportId];
|
|
auto& recvMap = recvCodecMap[transportId];
|
|
|
|
sendCodecs.apply([&](const auto& aCodecs) {
|
|
for (const auto& codec : aCodecs) {
|
|
sendMap.insert(codec.get());
|
|
}
|
|
});
|
|
recvCodecs.apply([&](const auto& aCodecs) {
|
|
for (const auto& codec : aCodecs) {
|
|
recvMap.insert(codec.get());
|
|
}
|
|
});
|
|
}
|
|
|
|
auto createCodecStat = [&](const JsepCodecDescription* aCodec,
|
|
const nsString& aTransportId,
|
|
Maybe<RTCCodecType> aCodecType) {
|
|
uint16_t pt;
|
|
{
|
|
DebugOnly<bool> rv = aCodec->GetPtAsInt(&pt);
|
|
MOZ_ASSERT(rv);
|
|
}
|
|
nsString mimeType;
|
|
mimeType.AppendPrintf(
|
|
"%s/%s", aCodec->Type() == SdpMediaSection::kVideo ? "video" : "audio",
|
|
aCodec->mName.c_str());
|
|
nsString id = aTransportId;
|
|
id.Append(u"_");
|
|
id.Append(aCodec->StatsId());
|
|
|
|
dom::RTCCodecStats codec;
|
|
codec.mId.Construct(std::move(id));
|
|
codec.mTimestamp.Construct(aNow);
|
|
codec.mType.Construct(RTCStatsType::Codec);
|
|
codec.mPayloadType = pt;
|
|
if (aCodecType) {
|
|
codec.mCodecType.Construct(*aCodecType);
|
|
}
|
|
codec.mTransportId = aTransportId;
|
|
codec.mMimeType = std::move(mimeType);
|
|
codec.mClockRate.Construct(aCodec->mClock);
|
|
if (aCodec->Type() == SdpMediaSection::MediaType::kAudio) {
|
|
codec.mChannels.Construct(aCodec->mChannels);
|
|
}
|
|
if (aCodec->mSdpFmtpLine) {
|
|
codec.mSdpFmtpLine.Construct(
|
|
NS_ConvertUTF8toUTF16(aCodec->mSdpFmtpLine->c_str()));
|
|
}
|
|
|
|
result.AppendElement(std::move(codec));
|
|
};
|
|
|
|
// Create codec stats for the gathered codec descriptions, sorted primarily
|
|
// by transportId, secondarily by payload type (from StatsId()).
|
|
for (const auto& [transportId, sendCodecs] : sendCodecMap) {
|
|
const auto& recvCodecs = recvCodecMap[transportId];
|
|
const nsString tid = NS_ConvertASCIItoUTF16(transportId);
|
|
AutoTArray<JsepCodecDescription*, 16> bidirectionalCodecs;
|
|
AutoTArray<JsepCodecDescription*, 16> unidirectionalCodecs;
|
|
std::set_intersection(sendCodecs.cbegin(), sendCodecs.cend(),
|
|
recvCodecs.cbegin(), recvCodecs.cend(),
|
|
MakeBackInserter(bidirectionalCodecs),
|
|
CodecComparator());
|
|
std::set_symmetric_difference(sendCodecs.cbegin(), sendCodecs.cend(),
|
|
recvCodecs.cbegin(), recvCodecs.cend(),
|
|
MakeBackInserter(unidirectionalCodecs),
|
|
CodecComparator());
|
|
for (const auto* codec : bidirectionalCodecs) {
|
|
createCodecStat(codec, tid, Nothing());
|
|
}
|
|
for (const auto* codec : unidirectionalCodecs) {
|
|
createCodecStat(
|
|
codec, tid,
|
|
Some(codec->mDirection == sdp::kSend ? RTCCodecType::Encode
|
|
: RTCCodecType::Decode));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
RefPtr<dom::RTCStatsReportPromise> PeerConnectionImpl::GetStats(
|
|
dom::MediaStreamTrack* aSelector, bool aInternalStats) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mFinalStatsQuery) {
|
|
// This case should be _extremely_ rare; this will basically only happen
|
|
// when WebrtcGlobalInformation tries to get our stats while we are tearing
|
|
// down.
|
|
return mFinalStatsQuery->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[this, self = RefPtr<PeerConnectionImpl>(this)]() {
|
|
UniquePtr<dom::RTCStatsReportInternal> finalStats =
|
|
MakeUnique<dom::RTCStatsReportInternal>();
|
|
// Might not be set if this encountered some error.
|
|
if (mFinalStats) {
|
|
*finalStats = *mFinalStats;
|
|
}
|
|
return RTCStatsReportPromise::CreateAndResolve(std::move(finalStats),
|
|
__func__);
|
|
});
|
|
}
|
|
|
|
nsTArray<RefPtr<dom::RTCStatsPromise>> promises;
|
|
DOMHighResTimeStamp now = mTimestampMaker.GetNow().ToDom();
|
|
|
|
nsTArray<dom::RTCCodecStats> codecStats = GetCodecStats(now);
|
|
std::set<std::string> transportIds;
|
|
|
|
if (!aSelector) {
|
|
// There might not be any senders/receivers if we're DataChannel only, so we
|
|
// don't handle the null selector case in the loop below.
|
|
transportIds.insert("");
|
|
}
|
|
|
|
nsTArray<
|
|
std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>>
|
|
transceiverStatsPromises;
|
|
for (const auto& transceiver : mTransceivers) {
|
|
const bool sendSelected = transceiver->Sender()->HasTrack(aSelector);
|
|
const bool recvSelected = transceiver->Receiver()->HasTrack(aSelector);
|
|
if (!sendSelected && !recvSelected) {
|
|
continue;
|
|
}
|
|
|
|
if (aSelector) {
|
|
transportIds.insert(transceiver->GetTransportId());
|
|
}
|
|
|
|
nsTArray<RefPtr<RTCStatsPromise>> rtpStreamPromises;
|
|
// Get all rtp stream stats for the given selector. Then filter away any
|
|
// codec stat not related to the selector, and assign codec ids to the
|
|
// stream stats.
|
|
// Skips the ICE stats; we do our own queries based on |transportIds| to
|
|
// avoid duplicates
|
|
if (sendSelected) {
|
|
rtpStreamPromises.AppendElements(
|
|
transceiver->Sender()->GetStatsInternal(true));
|
|
}
|
|
if (recvSelected) {
|
|
rtpStreamPromises.AppendElements(
|
|
transceiver->Receiver()->GetStatsInternal(true));
|
|
}
|
|
transceiverStatsPromises.AppendElement(
|
|
std::make_tuple(transceiver.get(),
|
|
RTCStatsPromise::All(GetMainThreadSerialEventTarget(),
|
|
rtpStreamPromises)));
|
|
}
|
|
|
|
promises.AppendElement(RTCRtpTransceiver::ApplyCodecStats(
|
|
std::move(codecStats), std::move(transceiverStatsPromises)));
|
|
|
|
for (const auto& transportId : transportIds) {
|
|
promises.AppendElement(mTransportHandler->GetIceStats(transportId, now));
|
|
}
|
|
|
|
promises.AppendElement(GetDataChannelStats(mDataConnection, now));
|
|
|
|
auto pcStatsCollection = MakeUnique<dom::RTCStatsCollection>();
|
|
RTCPeerConnectionStats pcStats;
|
|
pcStats.mTimestamp.Construct(now);
|
|
pcStats.mType.Construct(RTCStatsType::Peer_connection);
|
|
pcStats.mId.Construct(NS_ConvertUTF8toUTF16(mHandle.c_str()));
|
|
pcStats.mDataChannelsOpened.Construct(mDataChannelsOpened);
|
|
pcStats.mDataChannelsClosed.Construct(mDataChannelsClosed);
|
|
if (!pcStatsCollection->mPeerConnectionStats.AppendElement(std::move(pcStats),
|
|
fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
promises.AppendElement(RTCStatsPromise::CreateAndResolve(
|
|
std::move(pcStatsCollection), __func__));
|
|
|
|
// This is what we're going to return; all the stuff in |promises| will be
|
|
// accumulated here.
|
|
UniquePtr<dom::RTCStatsReportInternal> report(
|
|
new dom::RTCStatsReportInternal);
|
|
report->mPcid = NS_ConvertASCIItoUTF16(mName.c_str());
|
|
if (mWindow && mWindow->GetBrowsingContext()) {
|
|
report->mBrowserId = mWindow->GetBrowsingContext()->BrowserId();
|
|
}
|
|
report->mConfiguration.Construct(mJsConfiguration);
|
|
// TODO(bug 1589416): We need to do better here.
|
|
if (!mIceStartTime.IsNull()) {
|
|
report->mCallDurationMs.Construct(
|
|
(TimeStamp::Now() - mIceStartTime).ToMilliseconds());
|
|
}
|
|
report->mIceRestarts = mIceRestartCount;
|
|
report->mIceRollbacks = mIceRollbackCount;
|
|
report->mClosed = false;
|
|
report->mTimestamp = now;
|
|
|
|
if (aInternalStats && mJsepSession) {
|
|
for (const auto& candidate : mRawTrickledCandidates) {
|
|
if (!report->mRawRemoteCandidates.AppendElement(
|
|
NS_ConvertASCIItoUTF16(candidate.c_str()), fallible)) {
|
|
// XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
|
|
// involve multiple reallocations) and potentially crashing here,
|
|
// SetCapacity could be called outside the loop once.
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
|
|
if (mJsepSession) {
|
|
// TODO we probably should report Current and Pending SDPs here
|
|
// separately. Plus the raw SDP we got from JS (mLocalRequestedSDP).
|
|
// And if it's the offer or answer would also be nice.
|
|
std::string localDescription =
|
|
mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
|
|
std::string remoteDescription =
|
|
mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
|
|
if (!report->mSdpHistory.AppendElements(mSdpHistory, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
if (mJsepSession->IsPendingOfferer().isSome()) {
|
|
report->mOfferer.Construct(*mJsepSession->IsPendingOfferer());
|
|
} else if (mJsepSession->IsCurrentOfferer().isSome()) {
|
|
report->mOfferer.Construct(*mJsepSession->IsCurrentOfferer());
|
|
} else {
|
|
// Silly.
|
|
report->mOfferer.Construct(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dom::RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises)
|
|
->Then(
|
|
GetMainThreadSerialEventTarget(), __func__,
|
|
[report = std::move(report), idGen = mIdGenerator](
|
|
nsTArray<UniquePtr<dom::RTCStatsCollection>> aStats) mutable {
|
|
idGen->RewriteIds(std::move(aStats), report.get());
|
|
return dom::RTCStatsReportPromise::CreateAndResolve(
|
|
std::move(report), __func__);
|
|
},
|
|
[](nsresult rv) {
|
|
return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__);
|
|
});
|
|
}
|
|
|
|
void PeerConnectionImpl::RecordIceRestartStatistics(JsepSdpType type) {
|
|
switch (type) {
|
|
case mozilla::kJsepSdpOffer:
|
|
case mozilla::kJsepSdpPranswer:
|
|
break;
|
|
case mozilla::kJsepSdpAnswer:
|
|
++mIceRestartCount;
|
|
break;
|
|
case mozilla::kJsepSdpRollback:
|
|
++mIceRollbackCount;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::StoreConfigurationForAboutWebrtc(
|
|
const dom::RTCConfiguration& aConfig) {
|
|
// This will only be called once, when the PeerConnection is initially
|
|
// configured, at least until setConfiguration is implemented
|
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1253706
|
|
// @TODO bug 1739451 call this from setConfiguration
|
|
mJsConfiguration.mIceServers.Clear();
|
|
for (const auto& server : aConfig.mIceServers) {
|
|
RTCIceServerInternal internal;
|
|
internal.mCredentialProvided = server.mCredential.WasPassed();
|
|
internal.mUserNameProvided = server.mUsername.WasPassed();
|
|
if (server.mUrl.WasPassed()) {
|
|
if (!internal.mUrls.AppendElement(server.mUrl.Value(), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
if (server.mUrls.WasPassed()) {
|
|
for (const auto& url : server.mUrls.Value().GetAsStringSequence()) {
|
|
if (!internal.mUrls.AppendElement(url, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
}
|
|
if (!mJsConfiguration.mIceServers.AppendElement(internal, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
mJsConfiguration.mSdpSemantics.Reset();
|
|
if (aConfig.mSdpSemantics.WasPassed()) {
|
|
mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value());
|
|
}
|
|
|
|
mJsConfiguration.mIceTransportPolicy.Reset();
|
|
mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy);
|
|
mJsConfiguration.mBundlePolicy.Reset();
|
|
mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy);
|
|
mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty();
|
|
mJsConfiguration.mCertificatesProvided = !aConfig.mCertificates.Length();
|
|
}
|
|
|
|
dom::Sequence<dom::RTCSdpParsingErrorInternal>
|
|
PeerConnectionImpl::GetLastSdpParsingErrors() const {
|
|
const auto& sdpErrors = mJsepSession->GetLastSdpParsingErrors();
|
|
dom::Sequence<dom::RTCSdpParsingErrorInternal> domErrors;
|
|
if (!domErrors.SetCapacity(domErrors.Length(), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
for (const auto& error : sdpErrors) {
|
|
mozilla::dom::RTCSdpParsingErrorInternal internal;
|
|
internal.mLineNumber = error.first;
|
|
if (!AppendASCIItoUTF16(MakeStringSpan(error.second.c_str()),
|
|
internal.mError, fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
if (!domErrors.AppendElement(std::move(internal), fallible)) {
|
|
mozalloc_handle_oom(0);
|
|
}
|
|
}
|
|
return domErrors;
|
|
}
|
|
|
|
// Telemetry for when calls start
|
|
void PeerConnectionImpl::StartCallTelem() {
|
|
if (mCallTelemStarted) {
|
|
return;
|
|
}
|
|
MOZ_RELEASE_ASSERT(mWindow);
|
|
uint64_t windowId = mWindow->WindowID();
|
|
auto found = sCallDurationTimers.find(windowId);
|
|
if (found == sCallDurationTimers.end()) {
|
|
found =
|
|
sCallDurationTimers.emplace(windowId, PeerConnectionAutoTimer()).first;
|
|
}
|
|
found->second.RegisterConnection();
|
|
mCallTelemStarted = true;
|
|
|
|
// Increment session call counter
|
|
// If we want to track Loop calls independently here, we need two
|
|
// histograms.
|
|
//
|
|
// NOTE: As of bug 1654248 landing we are no longer counting renegotiations
|
|
// as separate calls. Expect numbers to drop compared to
|
|
// WEBRTC_CALL_COUNT_2.
|
|
glean::webrtc::call_count_3.Add(1);
|
|
}
|
|
|
|
void PeerConnectionImpl::StunAddrsHandler::OnMDNSQueryComplete(
|
|
const nsCString& hostname, const Maybe<nsCString>& address) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
PeerConnectionWrapper pcw(mPcHandle);
|
|
if (!pcw.impl()) {
|
|
return;
|
|
}
|
|
auto itor = pcw.impl()->mQueriedMDNSHostnames.find(hostname.BeginReading());
|
|
if (itor != pcw.impl()->mQueriedMDNSHostnames.end()) {
|
|
if (address) {
|
|
for (auto& cand : itor->second) {
|
|
// Replace obfuscated address with actual address
|
|
std::string obfuscatedAddr = cand.mTokenizedCandidate[4];
|
|
cand.mTokenizedCandidate[4] = address->BeginReading();
|
|
std::ostringstream o;
|
|
for (size_t i = 0; i < cand.mTokenizedCandidate.size(); ++i) {
|
|
o << cand.mTokenizedCandidate[i];
|
|
if (i + 1 != cand.mTokenizedCandidate.size()) {
|
|
o << " ";
|
|
}
|
|
}
|
|
std::string mungedCandidate = o.str();
|
|
pcw.impl()->StampTimecard("Done looking up mDNS name");
|
|
pcw.impl()->mTransportHandler->AddIceCandidate(
|
|
cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr);
|
|
}
|
|
} else {
|
|
pcw.impl()->StampTimecard("Failed looking up mDNS name");
|
|
}
|
|
pcw.impl()->mQueriedMDNSHostnames.erase(itor);
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::StunAddrsHandler::OnStunAddrsAvailable(
|
|
const mozilla::net::NrIceStunAddrArray& addrs) {
|
|
CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__,
|
|
(int)addrs.Length());
|
|
PeerConnectionWrapper pcw(mPcHandle);
|
|
if (!pcw.impl()) {
|
|
return;
|
|
}
|
|
pcw.impl()->mStunAddrs = addrs.Clone();
|
|
pcw.impl()->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
|
|
pcw.impl()->FlushIceCtxOperationQueueIfReady();
|
|
// If this fails, ICE cannot succeed, but we need to still go through the
|
|
// motions.
|
|
}
|
|
|
|
void PeerConnectionImpl::InitLocalAddrs() {
|
|
if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) {
|
|
return;
|
|
}
|
|
if (mStunAddrsRequest) {
|
|
mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING;
|
|
mStunAddrsRequest->SendGetStunAddrs();
|
|
} else {
|
|
mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
|
|
}
|
|
}
|
|
|
|
bool PeerConnectionImpl::ShouldForceProxy() const {
|
|
if (Preferences::GetBool("media.peerconnection.ice.proxy_only", false)) {
|
|
return true;
|
|
}
|
|
|
|
bool isPBM = false;
|
|
// This complicated null check is being extra conservative to avoid
|
|
// introducing crashes. It may not be needed.
|
|
if (mWindow && mWindow->GetExtantDoc() &&
|
|
mWindow->GetExtantDoc()->GetPrincipal() &&
|
|
mWindow->GetExtantDoc()
|
|
->GetPrincipal()
|
|
->OriginAttributesRef()
|
|
.IsPrivateBrowsing()) {
|
|
isPBM = true;
|
|
}
|
|
|
|
if (isPBM && Preferences::GetBool(
|
|
"media.peerconnection.ice.proxy_only_if_pbmode", false)) {
|
|
return true;
|
|
}
|
|
|
|
if (!Preferences::GetBool(
|
|
"media.peerconnection.ice.proxy_only_if_behind_proxy", false)) {
|
|
return false;
|
|
}
|
|
|
|
// Ok, we're supposed to be proxy_only, but only if a proxy is configured.
|
|
// Let's just see if the document was loaded via a proxy.
|
|
|
|
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
|
|
if (!httpChannelInternal) {
|
|
return false;
|
|
}
|
|
|
|
bool proxyUsed = false;
|
|
Unused << httpChannelInternal->GetIsProxyUsed(&proxyUsed);
|
|
return proxyUsed;
|
|
}
|
|
|
|
void PeerConnectionImpl::EnsureTransports(const JsepSession& aSession) {
|
|
mJsepSession->ForEachTransceiver([this,
|
|
self = RefPtr<PeerConnectionImpl>(this)](
|
|
const JsepTransceiver& transceiver) {
|
|
if (transceiver.HasOwnTransport()) {
|
|
mTransportHandler->EnsureProvisionalTransport(
|
|
transceiver.mTransport.mTransportId,
|
|
transceiver.mTransport.mLocalUfrag, transceiver.mTransport.mLocalPwd,
|
|
transceiver.mTransport.mComponents);
|
|
}
|
|
});
|
|
|
|
GatherIfReady();
|
|
}
|
|
|
|
void PeerConnectionImpl::UpdateRTCDtlsTransports() {
|
|
// We use mDataConnection below, make sure it is initted if necessary
|
|
MaybeInitializeDataChannel();
|
|
|
|
// Make sure that the SCTP transport is unset if we do not see a DataChannel.
|
|
// We'll restore this if we do see a DataChannel.
|
|
RefPtr<dom::RTCSctpTransport> oldSctp = mSctpTransport.forget();
|
|
|
|
mJsepSession->ForEachTransceiver(
|
|
[this, self = RefPtr<PeerConnectionImpl>(this),
|
|
oldSctp](const JsepTransceiver& jsepTransceiver) {
|
|
std::string transportId = jsepTransceiver.mTransport.mTransportId;
|
|
RefPtr<dom::RTCDtlsTransport> dtlsTransport;
|
|
if (!transportId.empty()) {
|
|
nsCString key(transportId.data(), transportId.size());
|
|
dtlsTransport = mTransportIdToRTCDtlsTransport.GetOrInsertNew(
|
|
key, GetParentObject());
|
|
}
|
|
|
|
if (jsepTransceiver.GetMediaType() == SdpMediaSection::kApplication) {
|
|
// Spec says we only update the RTCSctpTransport when negotiation
|
|
// completes. This is probably a spec bug.
|
|
// https://github.com/w3c/webrtc-pc/issues/2898
|
|
if (!dtlsTransport || !mDataConnection) {
|
|
return;
|
|
}
|
|
|
|
// Why on earth does the spec use a floating point for this?
|
|
double maxMessageSize =
|
|
static_cast<double>(mDataConnection->GetMaxMessageSize());
|
|
Nullable<uint16_t> maxChannels;
|
|
|
|
if (!oldSctp) {
|
|
mSctpTransport = new RTCSctpTransport(
|
|
GetParentObject(), *dtlsTransport, maxMessageSize, maxChannels);
|
|
} else {
|
|
// Restore the SCTP transport we had before this function was called
|
|
oldSctp->SetTransport(*dtlsTransport);
|
|
oldSctp->SetMaxMessageSize(maxMessageSize);
|
|
oldSctp->SetMaxChannels(maxChannels);
|
|
mSctpTransport = oldSctp;
|
|
}
|
|
} else {
|
|
RefPtr<dom::RTCRtpTransceiver> domTransceiver =
|
|
GetTransceiver(jsepTransceiver.GetUuid());
|
|
if (domTransceiver) {
|
|
domTransceiver->SetDtlsTransport(dtlsTransport);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void PeerConnectionImpl::SaveStateForRollback() {
|
|
// This could change depending on the outcome in
|
|
// https://github.com/w3c/webrtc-pc/issues/2899
|
|
if (mSctpTransport) {
|
|
// We have to save both of these things, because the DTLS transport could
|
|
// change without the SCTP transport changing.
|
|
mLastStableSctpTransport = mSctpTransport;
|
|
mLastStableSctpDtlsTransport = mSctpTransport->Transport();
|
|
} else {
|
|
mLastStableSctpTransport = nullptr;
|
|
mLastStableSctpDtlsTransport = nullptr;
|
|
}
|
|
|
|
for (auto& transceiver : mTransceivers) {
|
|
transceiver->SaveStateForRollback();
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::RestoreStateForRollback() {
|
|
for (auto& transceiver : mTransceivers) {
|
|
transceiver->RollbackToStableDtlsTransport();
|
|
}
|
|
|
|
mSctpTransport = mLastStableSctpTransport;
|
|
if (mSctpTransport) {
|
|
mSctpTransport->SetTransport(*mLastStableSctpDtlsTransport);
|
|
}
|
|
}
|
|
|
|
std::set<RefPtr<dom::RTCDtlsTransport>>
|
|
PeerConnectionImpl::GetActiveTransports() const {
|
|
std::set<RefPtr<dom::RTCDtlsTransport>> result;
|
|
for (const auto& transceiver : mTransceivers) {
|
|
if (transceiver->GetDtlsTransport()) {
|
|
result.insert(transceiver->GetDtlsTransport());
|
|
}
|
|
}
|
|
|
|
if (mSctpTransport && mSctpTransport->Transport()) {
|
|
result.insert(mSctpTransport->Transport());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::UpdateTransports(const JsepSession& aSession,
|
|
const bool forceIceTcp) {
|
|
std::set<std::string> finalTransports;
|
|
mJsepSession->ForEachTransceiver(
|
|
[&, this, self = RefPtr<PeerConnectionImpl>(this)](
|
|
const JsepTransceiver& transceiver) {
|
|
if (transceiver.HasOwnTransport()) {
|
|
finalTransports.insert(transceiver.mTransport.mTransportId);
|
|
UpdateTransport(transceiver, forceIceTcp);
|
|
}
|
|
});
|
|
|
|
mTransportHandler->RemoveTransportsExcept(finalTransports);
|
|
|
|
for (const auto& transceiverImpl : mTransceivers) {
|
|
transceiverImpl->UpdateTransport();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::UpdateTransport(const JsepTransceiver& aTransceiver,
|
|
bool aForceIceTcp) {
|
|
std::string ufrag;
|
|
std::string pwd;
|
|
std::vector<std::string> candidates;
|
|
size_t components = 0;
|
|
|
|
const JsepTransport& transport = aTransceiver.mTransport;
|
|
unsigned level = aTransceiver.GetLevel();
|
|
|
|
CSFLogDebug(LOGTAG, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u",
|
|
mHandle.c_str(), (unsigned)level,
|
|
(unsigned)transport.mComponents);
|
|
|
|
ufrag = transport.mIce->GetUfrag();
|
|
pwd = transport.mIce->GetPassword();
|
|
candidates = transport.mIce->GetCandidates();
|
|
components = transport.mComponents;
|
|
if (aForceIceTcp) {
|
|
candidates.erase(
|
|
std::remove_if(candidates.begin(), candidates.end(),
|
|
[](const std::string& s) {
|
|
return s.find(" UDP ") != std::string::npos ||
|
|
s.find(" udp ") != std::string::npos;
|
|
}),
|
|
candidates.end());
|
|
}
|
|
|
|
nsTArray<uint8_t> keyDer;
|
|
nsTArray<uint8_t> certDer;
|
|
nsresult rv = Identity()->Serialize(&keyDer, &certDer);
|
|
if (NS_FAILED(rv)) {
|
|
CSFLogError(LOGTAG, "%s: Failed to serialize DTLS identity: %d",
|
|
__FUNCTION__, (int)rv);
|
|
return;
|
|
}
|
|
|
|
DtlsDigestList digests;
|
|
for (const auto& fingerprint :
|
|
transport.mDtls->GetFingerprints().mFingerprints) {
|
|
digests.emplace_back(ToString(fingerprint.hashFunc),
|
|
fingerprint.fingerprint);
|
|
}
|
|
|
|
mTransportHandler->ActivateTransport(
|
|
transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd,
|
|
components, ufrag, pwd, keyDer, certDer, Identity()->auth_type(),
|
|
transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests,
|
|
PrivacyRequested());
|
|
|
|
for (auto& candidate : candidates) {
|
|
AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag);
|
|
}
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::UpdateMediaPipelines() {
|
|
for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
|
|
transceiver->ResetSync();
|
|
}
|
|
|
|
for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
|
|
if (!transceiver->IsVideo()) {
|
|
nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
transceiver->UpdatePrincipalPrivacy(PrivacyRequested()
|
|
? PrincipalPrivacy::Private
|
|
: PrincipalPrivacy::NonPrivate);
|
|
|
|
nsresult rv = transceiver->UpdateConduit();
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::StartIceChecks(const JsepSession& aSession) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mJsepSession->GetState() == kJsepStateStable);
|
|
|
|
auto transports = GetActiveTransports();
|
|
|
|
if (!mCanRegisterMDNSHostnamesDirectly) {
|
|
for (auto& pair : mMDNSHostnamesToRegister) {
|
|
mRegisteredMDNSHostnames.insert(pair.first);
|
|
mStunAddrsRequest->SendRegisterMDNSHostname(
|
|
nsCString(pair.first.c_str()), nsCString(pair.second.c_str()));
|
|
}
|
|
mMDNSHostnamesToRegister.clear();
|
|
mCanRegisterMDNSHostnamesDirectly = true;
|
|
}
|
|
|
|
std::vector<std::string> attributes;
|
|
if (aSession.RemoteIsIceLite()) {
|
|
attributes.push_back("ice-lite");
|
|
}
|
|
|
|
if (!aSession.GetIceOptions().empty()) {
|
|
attributes.push_back("ice-options:");
|
|
for (const auto& option : aSession.GetIceOptions()) {
|
|
attributes.back() += option + ' ';
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIRunnable> runnable(
|
|
WrapRunnable(mTransportHandler, &MediaTransportHandler::StartIceChecks,
|
|
aSession.IsIceControlling(), attributes));
|
|
|
|
PerformOrEnqueueIceCtxOperation(runnable);
|
|
}
|
|
|
|
bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
uint64_t winId = mWindow->WindowID();
|
|
|
|
bool default_address_only = Preferences::GetBool(
|
|
"media.peerconnection.ice.default_address_only", false);
|
|
default_address_only |=
|
|
!MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
|
|
return default_address_only;
|
|
}
|
|
|
|
bool PeerConnectionImpl::GetPrefObfuscateHostAddresses() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
uint64_t winId = mWindow->WindowID();
|
|
|
|
bool obfuscate_host_addresses = Preferences::GetBool(
|
|
"media.peerconnection.ice.obfuscate_host_addresses", false);
|
|
obfuscate_host_addresses &=
|
|
!MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
|
|
obfuscate_host_addresses &= !media::HostnameInPref(
|
|
"media.peerconnection.ice.obfuscate_host_addresses.blocklist", mHostname);
|
|
obfuscate_host_addresses &= XRE_IsContentProcess();
|
|
|
|
return obfuscate_host_addresses;
|
|
}
|
|
|
|
PeerConnectionImpl::SignalHandler::SignalHandler(PeerConnectionImpl* aPc,
|
|
MediaTransportHandler* aSource)
|
|
: mHandle(aPc->GetHandle()),
|
|
mSource(aSource),
|
|
mSTSThread(aPc->GetSTSThread()),
|
|
mPacketDumper(aPc->GetPacketDumper()) {
|
|
ConnectSignals();
|
|
}
|
|
|
|
PeerConnectionImpl::SignalHandler::~SignalHandler() {
|
|
ASSERT_ON_THREAD(mSTSThread);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::ConnectSignals() {
|
|
mSource->SignalGatheringStateChange.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s);
|
|
mSource->SignalConnectionStateChange.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s);
|
|
mSource->SignalCandidate.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::OnCandidateFound_s);
|
|
mSource->SignalAlpnNegotiated.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::AlpnNegotiated_s);
|
|
mSource->SignalStateChange.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s);
|
|
mSource->SignalRtcpStateChange.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s);
|
|
mSource->SignalPacketReceived.connect(
|
|
this, &PeerConnectionImpl::SignalHandler::OnPacketReceived_s);
|
|
}
|
|
|
|
void PeerConnectionImpl::AddIceCandidate(const std::string& aCandidate,
|
|
const std::string& aTransportId,
|
|
const std::string& aUfrag) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!aTransportId.empty());
|
|
|
|
bool obfuscate_host_addresses = Preferences::GetBool(
|
|
"media.peerconnection.ice.obfuscate_host_addresses", false);
|
|
|
|
if (obfuscate_host_addresses && !RelayOnly()) {
|
|
std::vector<std::string> tokens;
|
|
TokenizeCandidate(aCandidate, tokens);
|
|
|
|
if (tokens.size() > 4) {
|
|
std::string addr = tokens[4];
|
|
|
|
// Check for address ending with .local
|
|
size_t nPeriods = std::count(addr.begin(), addr.end(), '.');
|
|
size_t dotLocalLength = 6; // length of ".local"
|
|
|
|
if (nPeriods == 1 &&
|
|
addr.rfind(".local") + dotLocalLength == addr.length()) {
|
|
if (mStunAddrsRequest) {
|
|
PendingIceCandidate cand;
|
|
cand.mTokenizedCandidate = std::move(tokens);
|
|
cand.mTransportId = aTransportId;
|
|
cand.mUfrag = aUfrag;
|
|
mQueriedMDNSHostnames[addr].push_back(cand);
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
|
|
"PeerConnectionImpl::SendQueryMDNSHostname",
|
|
[self = RefPtr<PeerConnectionImpl>(this), addr]() mutable {
|
|
if (self->mStunAddrsRequest) {
|
|
self->StampTimecard("Look up mDNS name");
|
|
self->mStunAddrsRequest->SendQueryMDNSHostname(
|
|
nsCString(nsAutoCString(addr.c_str())));
|
|
}
|
|
NS_ReleaseOnMainThread(
|
|
"PeerConnectionImpl::SendQueryMDNSHostname", self.forget());
|
|
}));
|
|
}
|
|
// TODO: Bug 1535690, we don't want to tell the ICE context that remote
|
|
// trickle is done if we are waiting to resolve a mDNS candidate.
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
mTransportHandler->AddIceCandidate(aTransportId, aCandidate, aUfrag, "");
|
|
}
|
|
|
|
void PeerConnectionImpl::UpdateNetworkState(bool online) {
|
|
if (mTransportHandler) {
|
|
mTransportHandler->UpdateNetworkState(online);
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::FlushIceCtxOperationQueueIfReady() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (IsIceCtxReady()) {
|
|
for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) {
|
|
queuedIceCtxOperation->Run();
|
|
}
|
|
mQueuedIceCtxOperations.clear();
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::PerformOrEnqueueIceCtxOperation(
|
|
nsIRunnable* runnable) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (IsIceCtxReady()) {
|
|
runnable->Run();
|
|
} else {
|
|
mQueuedIceCtxOperations.push_back(runnable);
|
|
}
|
|
}
|
|
|
|
void PeerConnectionImpl::GatherIfReady() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Init local addrs here so that if we re-gather after an ICE restart
|
|
// resulting from changing WiFi networks, we get new local addrs.
|
|
// Otherwise, we would reuse the addrs from the original WiFi network
|
|
// and the ICE restart will fail.
|
|
if (!mStunAddrs.Length()) {
|
|
InitLocalAddrs();
|
|
}
|
|
|
|
// If we had previously queued gathering or ICE start, unqueue them
|
|
mQueuedIceCtxOperations.clear();
|
|
nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
|
|
RefPtr<PeerConnectionImpl>(this), &PeerConnectionImpl::EnsureIceGathering,
|
|
GetPrefDefaultAddressOnly(), GetPrefObfuscateHostAddresses()));
|
|
|
|
PerformOrEnqueueIceCtxOperation(runnable);
|
|
}
|
|
|
|
already_AddRefed<nsIHttpChannelInternal> PeerConnectionImpl::GetChannel()
|
|
const {
|
|
Document* doc = mWindow->GetExtantDoc();
|
|
if (NS_WARN_IF(!doc)) {
|
|
NS_WARNING("Unable to get document from window");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!doc->GetDocumentURI()->SchemeIs("file")) {
|
|
nsIChannel* channel = doc->GetChannel();
|
|
if (!channel) {
|
|
NS_WARNING("Unable to get channel from document");
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
|
|
do_QueryInterface(channel);
|
|
if (NS_WARN_IF(!httpChannelInternal)) {
|
|
CSFLogInfo(LOGTAG, "%s: Document does not have an HTTP channel",
|
|
__FUNCTION__);
|
|
return nullptr;
|
|
}
|
|
return httpChannelInternal.forget();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult PeerConnectionImpl::SetTargetForDefaultLocalAddressLookup() {
|
|
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
|
|
if (!httpChannelInternal) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString remoteIp;
|
|
nsresult rv = httpChannelInternal->GetRemoteAddress(remoteIp);
|
|
if (NS_FAILED(rv) || remoteIp.IsEmpty()) {
|
|
CSFLogError(LOGTAG, "%s: Failed to get remote IP address: %d", __FUNCTION__,
|
|
(int)rv);
|
|
return rv;
|
|
}
|
|
|
|
int32_t remotePort;
|
|
rv = httpChannelInternal->GetRemotePort(&remotePort);
|
|
if (NS_FAILED(rv)) {
|
|
CSFLogError(LOGTAG, "%s: Failed to get remote port number: %d",
|
|
__FUNCTION__, (int)rv);
|
|
return rv;
|
|
}
|
|
|
|
mTransportHandler->SetTargetForDefaultLocalAddressLookup(remoteIp.get(),
|
|
remotePort);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PeerConnectionImpl::EnsureIceGathering(bool aDefaultRouteOnly,
|
|
bool aObfuscateHostAddresses) {
|
|
if (!mTargetForDefaultLocalAddressLookupIsSet) {
|
|
nsresult rv = SetTargetForDefaultLocalAddressLookup();
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Unable to set target for default local address lookup");
|
|
}
|
|
mTargetForDefaultLocalAddressLookupIsSet = true;
|
|
}
|
|
|
|
// Make sure we don't call StartIceGathering if we're in e10s mode
|
|
// and we received no STUN addresses from the parent process. In the
|
|
// absence of previously provided STUN addresses, StartIceGathering will
|
|
// attempt to gather them (as in non-e10s mode), and this will cause a
|
|
// sandboxing exception in e10s mode.
|
|
if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
|
|
CSFLogInfo(LOGTAG, "%s: No STUN addresses returned from parent process",
|
|
__FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
mTransportHandler->StartIceGathering(aDefaultRouteOnly,
|
|
aObfuscateHostAddresses, mStunAddrs);
|
|
}
|
|
|
|
already_AddRefed<dom::RTCRtpTransceiver> PeerConnectionImpl::CreateTransceiver(
|
|
const std::string& aId, bool aIsVideo, const RTCRtpTransceiverInit& aInit,
|
|
dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) {
|
|
PeerConnectionCtx* ctx = PeerConnectionCtx::GetInstance();
|
|
if (!mCall) {
|
|
auto envWrapper = WebrtcEnvironmentWrapper::Create(GetTimestampMaker());
|
|
mCall = WebrtcCallWrapper::Create(
|
|
std::move(envWrapper), GetTimestampMaker(),
|
|
media::ShutdownBlockingTicket::Create(
|
|
u"WebrtcCallWrapper shutdown blocker"_ns,
|
|
NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__),
|
|
ctx->GetSharedWebrtcState());
|
|
mRtcpReceiveListener = mSignalHandler->RtcpReceiveEvent().Connect(
|
|
mCall->mCallThread, [call = mCall](MediaPacket aPacket) {
|
|
// This might not be initted yet, because the task to do that is tail
|
|
// dispatched, and STS might beat it to the punch.
|
|
if (call->Call()) {
|
|
call->Call()->Receiver()->DeliverRtcpPacket(
|
|
rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len()));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (aAddTrackMagic) {
|
|
mJsepSession->ApplyToTransceiver(aId, [](JsepTransceiver& aTransceiver) {
|
|
aTransceiver.SetAddTrackMagic();
|
|
});
|
|
}
|
|
|
|
RefPtr<RTCRtpTransceiver> transceiver = new RTCRtpTransceiver(
|
|
mWindow, PrivacyRequested(), this, mTransportHandler, mJsepSession.get(),
|
|
aId, aIsVideo, mSTSThread.get(), aSendTrack, mCall.get(), mIdGenerator);
|
|
|
|
transceiver->Init(aInit, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (aSendTrack) {
|
|
// implement checking for peerIdentity (where failure == black/silence)
|
|
Document* doc = mWindow->GetExtantDoc();
|
|
if (doc) {
|
|
transceiver->Sender()->GetPipeline()->UpdateSinkIdentity(
|
|
doc->NodePrincipal(), GetPeerIdentity());
|
|
} else {
|
|
MOZ_CRASH();
|
|
aRv = NS_ERROR_FAILURE;
|
|
return nullptr; // Don't remove this till we know it's safe.
|
|
}
|
|
}
|
|
|
|
return transceiver.forget();
|
|
}
|
|
|
|
std::string PeerConnectionImpl::GetTransportIdMatchingSendTrack(
|
|
const dom::MediaStreamTrack& aTrack) const {
|
|
for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
|
|
if (transceiver->Sender()->HasTrack(&aTrack)) {
|
|
return transceiver->GetTransportId();
|
|
}
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s(
|
|
const std::string& aTransportId, dom::RTCIceGathererState aState) {
|
|
ASSERT_ON_THREAD(mSTSThread);
|
|
GetMainThreadSerialEventTarget()->Dispatch(
|
|
NS_NewRunnableFunction(__func__,
|
|
[handle = mHandle, aTransportId, aState] {
|
|
PeerConnectionWrapper wrapper(handle);
|
|
if (wrapper.impl()) {
|
|
wrapper.impl()->IceGatheringStateChange(
|
|
aTransportId, aState);
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s(
|
|
const std::string& aTransportId, dom::RTCIceTransportState aState) {
|
|
ASSERT_ON_THREAD(mSTSThread);
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(
|
|
NS_NewRunnableFunction(__func__,
|
|
[handle = mHandle, aTransportId, aState] {
|
|
PeerConnectionWrapper wrapper(handle);
|
|
if (wrapper.impl()) {
|
|
wrapper.impl()->IceConnectionStateChange(
|
|
aTransportId, aState);
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::OnCandidateFound_s(
|
|
const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
|
|
ASSERT_ON_THREAD(mSTSThread);
|
|
CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str());
|
|
|
|
MOZ_ASSERT(!aCandidateInfo.mUfrag.empty());
|
|
|
|
GetMainThreadSerialEventTarget()->Dispatch(
|
|
NS_NewRunnableFunction(__func__,
|
|
[handle = mHandle, aTransportId, aCandidateInfo] {
|
|
PeerConnectionWrapper wrapper(handle);
|
|
if (wrapper.impl()) {
|
|
wrapper.impl()->OnCandidateFound(
|
|
aTransportId, aCandidateInfo);
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::AlpnNegotiated_s(
|
|
const std::string& aAlpn, bool aPrivacyRequested) {
|
|
MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested);
|
|
GetMainThreadSerialEventTarget()->Dispatch(
|
|
NS_NewRunnableFunction(__func__,
|
|
[handle = mHandle, aPrivacyRequested] {
|
|
PeerConnectionWrapper wrapper(handle);
|
|
if (wrapper.impl()) {
|
|
wrapper.impl()->OnAlpnNegotiated(
|
|
aPrivacyRequested);
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::ConnectionStateChange_s(
|
|
const std::string& aTransportId, TransportLayer::State aState) {
|
|
GetMainThreadSerialEventTarget()->Dispatch(
|
|
NS_NewRunnableFunction(__func__,
|
|
[handle = mHandle, aTransportId, aState] {
|
|
PeerConnectionWrapper wrapper(handle);
|
|
if (wrapper.impl()) {
|
|
wrapper.impl()->OnDtlsStateChange(aTransportId,
|
|
aState);
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void PeerConnectionImpl::SignalHandler::OnPacketReceived_s(
|
|
const std::string& aTransportId, const MediaPacket& aPacket) {
|
|
ASSERT_ON_THREAD(mSTSThread);
|
|
|
|
if (!aPacket.len()) {
|
|
return;
|
|
}
|
|
|
|
if (aPacket.type() != MediaPacket::RTCP) {
|
|
return;
|
|
}
|
|
|
|
CSFLogVerbose(LOGTAG, "%s received RTCP packet.", mHandle.c_str());
|
|
|
|
RtpLogger::LogPacket(aPacket, true, mHandle);
|
|
|
|
// Might be nice to pass ownership of the buffer in this case, but it is a
|
|
// small optimization in a rare case.
|
|
mPacketDumper->Dump(SIZE_MAX, dom::mozPacketDumpType::Srtcp, false,
|
|
aPacket.encrypted_data(), aPacket.encrypted_len());
|
|
|
|
mPacketDumper->Dump(SIZE_MAX, dom::mozPacketDumpType::Rtcp, false,
|
|
aPacket.data(), aPacket.len());
|
|
|
|
if (StaticPrefs::media_webrtc_net_force_disable_rtcp_reception()) {
|
|
CSFLogVerbose(LOGTAG, "%s RTCP packet forced to be dropped",
|
|
mHandle.c_str());
|
|
return;
|
|
}
|
|
|
|
mRtcpReceiveEvent.Notify(aPacket.Clone());
|
|
}
|
|
|
|
/**
|
|
* 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 PeerConnectionImpl::AnyLocalTrackHasPeerIdentity() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
|
|
if (transceiver->Sender()->GetTrack() &&
|
|
transceiver->Sender()->GetTrack()->GetPeerIdentity()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PeerConnectionImpl::AnyCodecHasPluginID(uint64_t aPluginID) {
|
|
for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
|
|
if (transceiver->ConduitHasPluginID(aPluginID)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<NrSocketProxyConfig> PeerConnectionImpl::GetProxyConfig()
|
|
const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mForceProxy &&
|
|
Preferences::GetBool("media.peerconnection.disable_http_proxy", false)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCString alpn = "webrtc,c-webrtc"_ns;
|
|
auto* browserChild = BrowserChild::GetFrom(mWindow);
|
|
if (!browserChild) {
|
|
// Android doesn't have browser child apparently...
|
|
return nullptr;
|
|
}
|
|
|
|
Document* doc = mWindow->GetExtantDoc();
|
|
if (NS_WARN_IF(!doc)) {
|
|
NS_WARNING("Unable to get document from window");
|
|
return nullptr;
|
|
}
|
|
|
|
TabId id = browserChild->GetTabId();
|
|
Result<RefPtr<net::LoadInfo>, nsresult> maybeLoadInfo = net::LoadInfo::Create(
|
|
doc->NodePrincipal(), doc->NodePrincipal(), doc,
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
|
nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA);
|
|
if (NS_WARN_IF(maybeLoadInfo.isErr())) {
|
|
return nullptr;
|
|
}
|
|
RefPtr<net::LoadInfo> loadInfo = maybeLoadInfo.unwrap();
|
|
|
|
net::LoadInfoArgs loadInfoArgs;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
|
|
return std::unique_ptr<NrSocketProxyConfig>(new NrSocketProxyConfig(
|
|
net::WebrtcProxyConfig(id, alpn, loadInfoArgs, mForceProxy)));
|
|
}
|
|
|
|
MOZ_RUNINIT std::map<uint64_t, PeerConnectionAutoTimer>
|
|
PeerConnectionImpl::sCallDurationTimers;
|
|
} // namespace mozilla
|