diff options
Diffstat (limited to 'dom/media/webrtc/jsapi')
31 files changed, 11307 insertions, 0 deletions
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp new file mode 100644 index 0000000000..1360380b87 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp @@ -0,0 +1,1593 @@ +/* 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 "MediaTransportHandler.h" +#include "MediaTransportHandlerIPC.h" +#include "transport/nricemediastream.h" +#include "transport/nriceresolver.h" +#include "transport/transportflow.h" +#include "transport/transportlayerice.h" +#include "transport/transportlayerdtls.h" +#include "transport/transportlayersrtp.h" + +// Config stuff +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "mozilla/Preferences.h" + +// Parsing STUN/TURN URIs +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsURLHelper.h" +#include "nsIURLParser.h" + +// Logging stuff +#include "common/browser_logging/CSFLog.h" + +// For fetching ICE logging +#include "transport/rlogconnector.h" + +// DTLS +#include "sdp/SdpAttribute.h" + +#include "transport/runnable_utils.h" + +#include "mozilla/Algorithm.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/dom/RTCStatsReportBinding.h" + +#include "nss.h" // For NSS_NoDB_Init +#include "mozilla/PublicSSL.h" // For psm::InitializeCipherSuite + +#include "nsISocketTransportService.h" + +#include <string> +#include <vector> +#include <map> + +namespace mozilla { + +static const char* mthLogTag = "MediaTransportHandler"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG mthLogTag + +class MediaTransportHandlerSTS : public MediaTransportHandler, + public sigslot::has_slots<> { + public: + explicit MediaTransportHandlerSTS(nsISerialEventTarget* aCallbackThread); + + RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override; + void ClearIceLog() override; + void EnterPrivateMode() override; + void ExitPrivateMode() override; + + nsresult CreateIceCtx(const std::string& aName, + const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) override; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override; + + void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aUfrag, + const std::string& aPwd, + size_t aComponentCount) override; + + void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp, + uint16_t aTargetPort) override; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // This will go away once mtransport moves to its + // own process, because we won't need to get this + // via IPC anymore + const nsTArray<NrIceStunAddr>& aStunAddrs) override; + + void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) override; + + void RemoveTransportsExcept( + const std::set<std::string>& aTransportIds) override; + + void StartIceChecks(bool aIsControlling, + const std::vector<std::string>& aIceOptions) override; + + void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, const std::string& aUfrag, + const std::string& aObfuscatedAddress) override; + + void UpdateNetworkState(bool aOnline) override; + + void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) override; + + RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId, + DOMHighResTimeStamp aNow) override; + + void Shutdown(); + + private: + void Destroy() override; + void Destroy_s(); + void DestroyFinal(); + void Shutdown_s(); + RefPtr<TransportFlow> CreateTransportFlow(const std::string& aTransportId, + bool aIsRtcp, + RefPtr<DtlsIdentity> aDtlsIdentity, + bool aDtlsClient, + const DtlsDigestList& aDigests, + bool aPrivacyRequested); + + struct Transport { + RefPtr<TransportFlow> mFlow; + RefPtr<TransportFlow> mRtcpFlow; + }; + + using MediaTransportHandler::OnAlpnNegotiated; + using MediaTransportHandler::OnCandidate; + using MediaTransportHandler::OnConnectionStateChange; + using MediaTransportHandler::OnEncryptedSending; + using MediaTransportHandler::OnGatheringStateChange; + using MediaTransportHandler::OnPacketReceived; + using MediaTransportHandler::OnRtcpStateChange; + using MediaTransportHandler::OnStateChange; + + void OnGatheringStateChange(NrIceCtx* aIceCtx, + NrIceCtx::GatheringState aState); + void OnConnectionStateChange(NrIceCtx* aIceCtx, + NrIceCtx::ConnectionState aState); + void OnCandidateFound(NrIceMediaStream* aStream, + const std::string& aCandidate, + const std::string& aUfrag, const std::string& aMDNSAddr, + const std::string& aActualAddr); + void OnStateChange(TransportLayer* aLayer, TransportLayer::State); + void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State); + void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket); + void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket); + RefPtr<TransportFlow> GetTransportFlow(const std::string& aTransportId, + bool aIsRtcp) const; + void GetIceStats(const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow, + dom::RTCStatsCollection* aStats) const; + + virtual ~MediaTransportHandlerSTS() = default; + nsCOMPtr<nsISerialEventTarget> mStsThread; + RefPtr<NrIceCtx> mIceCtx; + RefPtr<NrIceResolver> mDNSResolver; + std::map<std::string, Transport> mTransports; + bool mObfuscateHostAddresses = false; + uint32_t mMinDtlsVersion = 0; + uint32_t mMaxDtlsVersion = 0; + + std::set<std::string> mSignaledAddresses; + + // Init can only be done on main, but we want this to be usable on any thread + typedef MozPromise<bool, std::string, false> InitPromise; + RefPtr<InitPromise> mInitPromise; +}; + +/* static */ +already_AddRefed<MediaTransportHandler> MediaTransportHandler::Create( + nsISerialEventTarget* aCallbackThread) { + RefPtr<MediaTransportHandler> result; + if (XRE_IsContentProcess() && + Preferences::GetBool("media.peerconnection.mtransport_process") && + Preferences::GetBool("network.process.enabled")) { + result = new MediaTransportHandlerIPC(aCallbackThread); + } else { + result = new MediaTransportHandlerSTS(aCallbackThread); + } + return result.forget(); +} + +class STSShutdownHandler : public nsISTSShutdownObserver { + public: + NS_DECL_ISUPPORTS + + // Lazy singleton + static RefPtr<STSShutdownHandler>& Instance() { + MOZ_ASSERT(NS_IsMainThread()); + static RefPtr<STSShutdownHandler> sHandler(new STSShutdownHandler); + return sHandler; + } + + void Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + for (const auto& handler : mHandlers) { + handler->Shutdown(); + } + mHandlers.clear(); + } + + STSShutdownHandler() { + CSFLogDebug(LOGTAG, "%s", __func__); + nsresult res; + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res)); + MOZ_RELEASE_ASSERT(sts); + sts->AddShutdownObserver(this); + } + + NS_IMETHOD Observe() override { + CSFLogDebug(LOGTAG, "%s", __func__); + Shutdown(); + nsresult res; + nsCOMPtr<nsISocketTransportService> sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res)); + MOZ_RELEASE_ASSERT(sts); + sts->RemoveShutdownObserver(this); + Instance() = nullptr; + return NS_OK; + } + + void Register(MediaTransportHandlerSTS* aHandler) { + MOZ_ASSERT(NS_IsMainThread()); + mHandlers.insert(aHandler); + } + + void Deregister(MediaTransportHandlerSTS* aHandler) { + MOZ_ASSERT(NS_IsMainThread()); + mHandlers.erase(aHandler); + } + + private: + virtual ~STSShutdownHandler() {} + + // Raw ptrs, registered on init, deregistered on destruction, all on main + std::set<MediaTransportHandlerSTS*> mHandlers; +}; + +NS_IMPL_ISUPPORTS(STSShutdownHandler, nsISTSShutdownObserver); + +MediaTransportHandlerSTS::MediaTransportHandlerSTS( + nsISerialEventTarget* aCallbackThread) + : MediaTransportHandler(aCallbackThread) { + nsresult rv; + mStsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (!mStsThread) { + MOZ_CRASH(); + } + + RLogConnector::CreateInstance(); + + CSFLogDebug(LOGTAG, "%s done %p", __func__, this); + + // We do not set up mDNSService here, because we are not running on main (we + // use PBackground), and the DNS service asserts. +} + +static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy) { + switch (aPolicy) { + case dom::RTCIceTransportPolicy::Relay: + return NrIceCtx::ICE_POLICY_RELAY; + case dom::RTCIceTransportPolicy::All: + if (Preferences::GetBool("media.peerconnection.ice.no_host", false)) { + return NrIceCtx::ICE_POLICY_NO_HOST; + } else { + return NrIceCtx::ICE_POLICY_ALL; + } + default: + MOZ_CRASH(); + } + return NrIceCtx::ICE_POLICY_ALL; +} + +static nsresult addNrIceServer(const nsString& aIceUrl, + const dom::RTCIceServer& aIceServer, + std::vector<NrIceStunServer>* aStunServersOut, + std::vector<NrIceTurnServer>* aTurnServersOut) { + // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than + // nsStandardURL. To parse STUN/TURN URI's to spec + // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3 + // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3 + // we parse out the query-string, and use ParseAuthority() on the rest + RefPtr<nsIURI> url; + nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl); + NS_ENSURE_SUCCESS(rv, rv); + bool isStun = url->SchemeIs("stun"); + bool isStuns = url->SchemeIs("stuns"); + bool isTurn = url->SchemeIs("turn"); + bool isTurns = url->SchemeIs("turns"); + if (!(isStun || isStuns || isTurn || isTurns)) { + return NS_ERROR_FAILURE; + } + if (isStuns) { + return NS_OK; // TODO: Support STUNS (Bug 1056934) + } + + nsAutoCString spec; + rv = url->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509) + int32_t port; + nsAutoCString host; + nsAutoCString transport; + { + uint32_t hostPos; + int32_t hostLen; + nsAutoCString path; + rv = url->GetPathQueryRef(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Tolerate query-string + parse 'transport=[udp|tcp]' by hand. + int32_t questionmark = path.FindChar('?'); + if (questionmark >= 0) { + const nsCString match = "transport="_ns; + + for (int32_t i = questionmark, endPos; i >= 0; i = endPos) { + endPos = path.FindCharInSet("&", i + 1); + const nsDependentCSubstring fieldvaluepair = + Substring(path, i + 1, endPos); + if (StringBeginsWith(fieldvaluepair, match)) { + transport = Substring(fieldvaluepair, match.Length()); + ToLowerCase(transport); + } + } + path.SetLength(questionmark); + } + + rv = net_GetAuthURLParser()->ParseAuthority( + path.get(), path.Length(), nullptr, nullptr, nullptr, nullptr, &hostPos, + &hostLen, &port); + NS_ENSURE_SUCCESS(rv, rv); + if (!hostLen) { + return NS_ERROR_FAILURE; + } + if (hostPos > 1) /* The username was removed */ + return NS_ERROR_FAILURE; + path.Mid(host, hostPos, hostLen); + } + if (port == -1) port = (isStuns || isTurns) ? 5349 : 3478; + + if (isStuns || isTurns) { + // Should we barf if transport is set to udp or something? + transport = kNrIceTransportTls; + } + + if (transport.IsEmpty()) { + transport = kNrIceTransportUdp; + } + + if (isTurn || isTurns) { + std::string pwd( + NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get()); + std::string username( + NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get()); + + std::vector<unsigned char> password(pwd.begin(), pwd.end()); + + UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create( + host.get(), port, username, password, transport.get())); + if (!server) { + return NS_ERROR_FAILURE; + } + aTurnServersOut->emplace_back(std::move(*server)); + } else { + UniquePtr<NrIceStunServer> server( + NrIceStunServer::Create(host.get(), port, transport.get())); + if (!server) { + return NS_ERROR_FAILURE; + } + aStunServersOut->emplace_back(std::move(*server)); + } + return NS_OK; +} + +/* static */ +nsresult MediaTransportHandler::ConvertIceServers( + const nsTArray<dom::RTCIceServer>& aIceServers, + std::vector<NrIceStunServer>* aStunServers, + std::vector<NrIceTurnServer>* aTurnServers) { + for (const auto& iceServer : aIceServers) { + NS_ENSURE_STATE(iceServer.mUrls.WasPassed()); + NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence()); + for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) { + nsresult rv = + addNrIceServer(iceUrl, iceServer, aStunServers, aTurnServers); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s", __FUNCTION__, + NS_ConvertUTF16toUTF8(iceUrl).get()); + return rv; + } + } + } + + return NS_OK; +} + +static NrIceCtx::GlobalConfig GetGlobalConfig() { + NrIceCtx::GlobalConfig config; + config.mAllowLinkLocal = + Preferences::GetBool("media.peerconnection.ice.link_local", false); + config.mAllowLoopback = + Preferences::GetBool("media.peerconnection.ice.loopback", false); + config.mTcpEnabled = + Preferences::GetBool("media.peerconnection.ice.tcp", false); + config.mStunClientMaxTransmits = Preferences::GetInt( + "media.peerconnection.ice.stun_client_maximum_transmits", + config.mStunClientMaxTransmits); + config.mTrickleIceGracePeriod = + Preferences::GetInt("media.peerconnection.ice.trickle_grace_period", + config.mTrickleIceGracePeriod); + config.mIceTcpSoSockCount = Preferences::GetInt( + "media.peerconnection.ice.tcp_so_sock_count", config.mIceTcpSoSockCount); + config.mIceTcpListenBacklog = + Preferences::GetInt("media.peerconnection.ice.tcp_listen_backlog", + config.mIceTcpListenBacklog); + (void)Preferences::GetCString("media.peerconnection.ice.force_interface", + config.mForceNetInterface); + return config; +} + +static Maybe<NrIceCtx::NatSimulatorConfig> GetNatConfig() { + bool block_tcp = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_tcp", false); + bool block_udp = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_udp", false); + int error_code_for_drop = Preferences::GetInt( + "media.peerconnection.nat_simulator.error_code_for_drop", 0); + nsAutoCString mapping_type; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.mapping_type", mapping_type); + nsAutoCString filtering_type; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.filtering_type", filtering_type); + + if (block_udp || block_tcp || !mapping_type.IsEmpty() || + !filtering_type.IsEmpty()) { + CSFLogDebug(LOGTAG, "NAT filtering type: %s", filtering_type.get()); + CSFLogDebug(LOGTAG, "NAT mapping type: %s", mapping_type.get()); + NrIceCtx::NatSimulatorConfig natConfig; + natConfig.mBlockUdp = block_udp; + natConfig.mBlockTcp = block_tcp; + natConfig.mErrorCodeForDrop = error_code_for_drop; + natConfig.mFilteringType = filtering_type; + natConfig.mMappingType = mapping_type; + return Some(natConfig); + } + return Nothing(); +} + +nsresult MediaTransportHandlerSTS::CreateIceCtx( + const std::string& aName, const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) { + // We rely on getting an error when this happens, so do it up front. + std::vector<NrIceStunServer> stunServers; + std::vector<NrIceTurnServer> turnServers; + nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers); + if (NS_FAILED(rv)) { + return rv; + } + + mInitPromise = InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + CSFLogDebug(LOGTAG, "%s starting", __func__); + if (!NSS_IsInitialized()) { + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + MOZ_CRASH(); + return InitPromise::CreateAndReject("NSS_NoDB_Init failed", + __func__); + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + MOZ_CRASH(); + return InitPromise::CreateAndReject("InitializeCipherSuite failed", + __func__); + } + + mozilla::psm::DisableMD5(); + } + + static bool globalInitDone = false; + if (!globalInitDone) { + mStsThread->Dispatch( + WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()), + NS_DISPATCH_NORMAL); + globalInitDone = true; + } + + // Give us a way to globally turn off TURN support + bool turnDisabled = + Preferences::GetBool("media.peerconnection.turn.disable", false); + // We are reading these here, because when we setup the DTLS transport + // we are on the wrong thread to read prefs + mMinDtlsVersion = + Preferences::GetUint("media.peerconnection.dtls.version.min"); + mMaxDtlsVersion = + Preferences::GetUint("media.peerconnection.dtls.version.max"); + + NrIceCtx::Config config; + config.mPolicy = toNrIcePolicy(aIcePolicy); + config.mNatSimulatorConfig = GetNatConfig(); + + MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance()); + STSShutdownHandler::Instance()->Register(this); + + return InvokeAsync( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + mIceCtx = NrIceCtx::Create(aName, config); + if (!mIceCtx) { + return InitPromise::CreateAndReject("NrIceCtx::Create failed", + __func__); + } + + mIceCtx->SignalGatheringStateChange.connect( + this, &MediaTransportHandlerSTS::OnGatheringStateChange); + mIceCtx->SignalConnectionStateChange.connect( + this, &MediaTransportHandlerSTS::OnConnectionStateChange); + + nsresult rv; + + if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) { + CSFLogError(LOGTAG, "%s: Failed to set stun servers", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to set stun servers", __func__); + } + if (!turnDisabled) { + if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) { + CSFLogError(LOGTAG, "%s: Failed to set turn servers", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to set turn servers", __func__); + } + } else if (!turnServers.empty()) { + CSFLogError(LOGTAG, "%s: Setting turn servers disabled", + __FUNCTION__); + } + + mDNSResolver = new NrIceResolver; + if (NS_FAILED(rv = mDNSResolver->Init())) { + CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to initialize dns resolver", __func__); + } + if (NS_FAILED(rv = mIceCtx->SetResolver( + mDNSResolver->AllocateResolver()))) { + CSFLogError(LOGTAG, "%s: Failed to get dns resolver", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to get dns resolver", __func__); + } + + CSFLogDebug(LOGTAG, "%s done", __func__); + return InitPromise::CreateAndResolve(true, __func__); + }); + }); + return NS_OK; +} + +void MediaTransportHandlerSTS::Shutdown() { + CSFLogDebug(LOGTAG, "%s", __func__); + MOZ_ASSERT(NS_IsMainThread()); + mStsThread->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::Shutdown_s)); +} + +void MediaTransportHandlerSTS::Shutdown_s() { + CSFLogDebug(LOGTAG, "%s", __func__); + disconnect_all(); + // Clear the transports before destroying the ice ctx so that + // the close_notify alerts have a chance to be sent as the + // TransportFlow destructors execute. + mTransports.clear(); + if (mIceCtx) { + NrIceStats stats = mIceCtx->Destroy(); + CSFLogDebug(LOGTAG, + "Ice Telemetry: stun (retransmits: %d)" + " turn (401s: %d 403s: %d 438s: %d)", + stats.stun_retransmits, stats.turn_401s, stats.turn_403s, + stats.turn_438s); + } + mIceCtx = nullptr; + mDNSResolver = nullptr; +} + +void MediaTransportHandlerSTS::Destroy() { + CSFLogDebug(LOGTAG, "%s %p", __func__, this); + // Our "destruction tour" starts on main, because we need to deregister. + if (!NS_IsMainThread()) { + GetMainThreadEventTarget()->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::Destroy)); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + if (!STSShutdownHandler::Instance()) { + CSFLogDebug(LOGTAG, "%s Already shut down. Nothing else to do.", __func__); + delete this; + return; + } + + STSShutdownHandler::Instance()->Deregister(this); + Shutdown(); + + // mIceCtx still has a reference to us via sigslot! We must dispach to STS, + // and clean up there. However, by the time _that_ happens, we may have + // dispatched a signal callback to mCallbackThread, so we have to dispatch + // the final destruction to mCallbackThread. + nsresult rv = mStsThread->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::Destroy_s)); + if (NS_WARN_IF(NS_FAILED(rv))) { + CSFLogError(LOGTAG, + "Unable to dispatch to STS: why has the XPCOM shutdown handler " + "not been invoked?"); + delete this; + } +} + +void MediaTransportHandlerSTS::Destroy_s() { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + nsresult rv = mCallbackThread->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::DestroyFinal)); + if (NS_SUCCEEDED(rv)) { + return; + } + } + + DestroyFinal(); +} + +void MediaTransportHandlerSTS::DestroyFinal() { delete this; } + +void MediaTransportHandlerSTS::SetProxyConfig( + NrSocketProxyConfig&& aProxyConfig) { + mInitPromise->Then( + mStsThread, __func__, + [this, self = RefPtr<MediaTransportHandlerSTS>(this), + aProxyConfig = std::move(aProxyConfig)]() mutable { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->SetProxyConfig(std::move(aProxyConfig)); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::EnsureProvisionalTransport( + const std::string& aTransportId, const std::string& aUfrag, + const std::string& aPwd, size_t aComponentCount) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%u", + mIceCtx->name().c_str(), aTransportId.c_str(), + static_cast<unsigned>(aComponentCount)); + + std::ostringstream os; + os << mIceCtx->name() << " transport-id=" << aTransportId; + stream = + mIceCtx->CreateStream(aTransportId, os.str(), aComponentCount); + + if (!stream) { + CSFLogError(LOGTAG, "Failed to create ICE stream."); + return; + } + + stream->SignalCandidate.connect( + this, &MediaTransportHandlerSTS::OnCandidateFound); + } + + // Begins an ICE restart if this stream has a different ufrag/pwd + stream->SetIceCredentials(aUfrag, aPwd); + + // Make sure there's an entry in mTransports + mTransports[aTransportId]; + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) { + mInitPromise->Then( + mStsThread, __func__, + [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(), + self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + MOZ_ASSERT(aComponentCount); + RefPtr<DtlsIdentity> dtlsIdentity( + DtlsIdentity::Deserialize(keyDer, certDer, aAuthType)); + if (!dtlsIdentity) { + MOZ_ASSERT(false); + return; + } + + RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + MOZ_ASSERT(false); + return; + } + + CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u", + mIceCtx->name().c_str(), aTransportId.c_str(), + static_cast<unsigned>(aComponentCount)); + + std::vector<std::string> attrs; + attrs.reserve(2 /* ufrag + pwd */); + attrs.push_back("ice-ufrag:" + aUfrag); + attrs.push_back("ice-pwd:" + aPassword); + + // If we started an ICE restart in EnsureProvisionalTransport, this is + // where we decide whether to commit or rollback. + nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u", + static_cast<unsigned>(rv)); + MOZ_ASSERT(false); + return; + } + + Transport transport = mTransports[aTransportId]; + if (!transport.mFlow) { + transport.mFlow = + CreateTransportFlow(aTransportId, false, dtlsIdentity, + aDtlsClient, aDigests, aPrivacyRequested); + if (!transport.mFlow) { + return; + } + TransportLayer* dtls = + transport.mFlow->GetLayer(TransportLayerDtls::ID()); + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnStateChange); + if (aComponentCount < 2) { + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnRtcpStateChange); + } + } + + if (aComponentCount == 2) { + if (!transport.mRtcpFlow) { + transport.mRtcpFlow = + CreateTransportFlow(aTransportId, true, dtlsIdentity, + aDtlsClient, aDigests, aPrivacyRequested); + if (!transport.mRtcpFlow) { + return; + } + TransportLayer* dtls = + transport.mRtcpFlow->GetLayer(TransportLayerDtls::ID()); + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnRtcpStateChange); + } + } else { + transport.mRtcpFlow = nullptr; + // components are 1-indexed + stream->DisableComponent(2); + } + + mTransports[aTransportId] = transport; + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->SetTargetForDefaultLocalAddressLookup(aTargetIp, aTargetPort); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::StartIceGathering( + bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + const nsTArray<NrIceStunAddr>& aStunAddrs) { + mInitPromise->Then( + mStsThread, __func__, + [=, stunAddrs = aStunAddrs.Clone(), + self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mObfuscateHostAddresses = aObfuscateHostAddresses; + + // Belt and suspenders - in e10s mode, the call below to SetStunAddrs + // needs to have the proper flags set on ice ctx. For non-e10s, + // setting those flags happens in StartGathering. We could probably + // just set them here, and only do it here. + mIceCtx->SetCtxFlags(aDefaultRouteOnly); + + if (stunAddrs.Length()) { + mIceCtx->SetStunAddrs(stunAddrs); + } + + // Start gathering, but only if there are streams + if (!mIceCtx->GetStreams().empty()) { + mIceCtx->StartGathering(aDefaultRouteOnly, aObfuscateHostAddresses); + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::StartIceChecks( + bool aIsControlling, const std::vector<std::string>& aIceOptions) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't parse global parameters", + __FUNCTION__); + return; + } + + rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING + : NrIceCtx::ICE_CONTROLLED); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't set controlling to %d", + __FUNCTION__, aIsControlling); + return; + } + + rv = mIceCtx->StartChecks(); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__); + return; + } + }, + [](const std::string& aError) {}); +} + +void TokenizeCandidate(const std::string& aCandidate, + std::vector<std::string>& aTokens) { + aTokens.clear(); + + std::istringstream iss(aCandidate); + std::string token; + while (std::getline(iss, token, ' ')) { + aTokens.push_back(token); + } +} + +void MediaTransportHandlerSTS::AddIceCandidate( + const std::string& aTransportId, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aObfuscatedAddress) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + std::vector<std::string> tokens; + TokenizeCandidate(aCandidate, tokens); + + RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + CSFLogError(LOGTAG, + "No ICE stream for candidate with transport id %s: %s", + aTransportId.c_str(), aCandidate.c_str()); + return; + } + + nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag, + aObfuscatedAddress); + if (NS_SUCCEEDED(rv)) { + // If the address is not obfuscated, we want to track it as + // explicitly signaled so that we know it is fine to reveal + // the address later on. + if (mObfuscateHostAddresses && tokens.size() > 4 && + aObfuscatedAddress.empty()) { + mSignaledAddresses.insert(tokens[4]); + } + } else { + CSFLogError(LOGTAG, + "Couldn't process ICE candidate with transport id %s: " + "%s", + aTransportId.c_str(), aCandidate.c_str()); + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::UpdateNetworkState(bool aOnline) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->UpdateNetworkState(aOnline); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::RemoveTransportsExcept( + const std::set<std::string>& aTransportIds) { + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + for (auto it = mTransports.begin(); it != mTransports.end();) { + const std::string transportId(it->first); + if (!aTransportIds.count(transportId)) { + if (it->second.mFlow) { + OnStateChange(transportId, TransportLayer::TS_NONE); + OnRtcpStateChange(transportId, TransportLayer::TS_NONE); + } + // Erase the transport before destroying the ice stream so that + // the close_notify alerts have a chance to be sent as the + // TransportFlow destructors execute. + it = mTransports.erase(it); + // We're already on the STS thread, but the TransportFlow + // destructor executed when mTransports.erase(it) is called + // above dispatches the call to DestroyFinal to the STS thread. If + // we don't also dispatch the call to destroy the NrIceMediaStream + // to the STS thread, it will tear down the NrIceMediaStream + // before the TransportFlow is destroyed. Without a valid + // NrIceMediaStream the close_notify alert cannot be sent. + mStsThread->Dispatch(NS_NewRunnableFunction( + __func__, [iceCtx = RefPtr<NrIceCtx>(mIceCtx), transportId] { + iceCtx->DestroyStream(transportId); + })); + } else { + MOZ_ASSERT(it->second.mFlow); + ++it; + } + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) { + mInitPromise->Then( + mStsThread, __func__, + [this, self = RefPtr<MediaTransportHandlerSTS>(this), aTransportId, + aPacket = std::move(aPacket)]() mutable { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED); + RefPtr<TransportFlow> flow = + GetTransportFlow(aTransportId, aPacket.type() == MediaPacket::RTCP); + + if (!flow) { + CSFLogError(LOGTAG, + "%s: No such transport flow (%s) for outgoing packet", + mIceCtx->name().c_str(), aTransportId.c_str()); + return; + } + + TransportLayer* layer = nullptr; + switch (aPacket.type()) { + case MediaPacket::SCTP: + layer = flow->GetLayer(TransportLayerDtls::ID()); + break; + case MediaPacket::RTP: + case MediaPacket::RTCP: + layer = flow->GetLayer(TransportLayerSrtp::ID()); + break; + default: + // Maybe it would be useful to allow the injection of other packet + // types for testing? + MOZ_ASSERT(false); + return; + } + + MOZ_ASSERT(layer); + + if (layer->SendPacket(aPacket) < 0) { + CSFLogError(LOGTAG, "%s: Transport flow (%s) failed to send packet", + mIceCtx->name().c_str(), aTransportId.c_str()); + } + }, + [](const std::string& aError) {}); +} + +TransportLayer::State MediaTransportHandler::GetState( + const std::string& aTransportId, bool aRtcp) const { + // TODO Bug 1520692: we should allow Datachannel to connect without + // DTLS SRTP keys + if (mCallbackThread) { + MOZ_ASSERT(mCallbackThread->IsOnCurrentThread()); + } + + const std::map<std::string, TransportLayer::State>* cache = nullptr; + if (aRtcp) { + cache = &mRtcpStateCache; + } else { + cache = &mStateCache; + } + + auto it = cache->find(aTransportId); + if (it != cache->end()) { + return it->second; + } + return TransportLayer::TS_NONE; +} + +void MediaTransportHandler::OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnCandidate, aTransportId, + aCandidateInfo), + NS_DISPATCH_NORMAL); + return; + } + + SignalCandidate(aTransportId, aCandidateInfo); +} + +void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnAlpnNegotiated, aAlpn), + NS_DISPATCH_NORMAL); + return; + } + + const bool privacyRequested = aAlpn == "c-webrtc"; + SignalAlpnNegotiated(aAlpn, privacyRequested); +} + +void MediaTransportHandler::OnGatheringStateChange( + dom::RTCIceGatheringState aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnGatheringStateChange, + aState), + NS_DISPATCH_NORMAL); + return; + } + + SignalGatheringStateChange(aState); +} + +void MediaTransportHandler::OnConnectionStateChange( + dom::RTCIceConnectionState aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnConnectionStateChange, + aState), + NS_DISPATCH_NORMAL); + return; + } + + SignalConnectionStateChange(aState); +} + +void MediaTransportHandler::OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnPacketReceived, + aTransportId, const_cast<MediaPacket&>(aPacket)), + NS_DISPATCH_NORMAL); + return; + } + + SignalPacketReceived(aTransportId, aPacket); +} + +void MediaTransportHandler::OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnEncryptedSending, + aTransportId, const_cast<MediaPacket&>(aPacket)), + NS_DISPATCH_NORMAL); + return; + } + + SignalEncryptedSending(aTransportId, aPacket); +} + +void MediaTransportHandler::OnStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnStateChange, aTransportId, + aState), + NS_DISPATCH_NORMAL); + return; + } + + if (aState == TransportLayer::TS_NONE) { + mStateCache.erase(aTransportId); + } else { + mStateCache[aTransportId] = aState; + } + SignalStateChange(aTransportId, aState); +} + +void MediaTransportHandler::OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnRtcpStateChange, + aTransportId, aState), + NS_DISPATCH_NORMAL); + return; + } + + if (aState == TransportLayer::TS_NONE) { + mRtcpStateCache.erase(aTransportId); + } else { + mRtcpStateCache[aTransportId] = aState; + } + SignalRtcpStateChange(aTransportId, aState); +} + +RefPtr<dom::RTCStatsPromise> MediaTransportHandlerSTS::GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) { + return mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + UniquePtr<dom::RTCStatsCollection> stats(new dom::RTCStatsCollection); + if (mIceCtx) { + for (const auto& stream : mIceCtx->GetStreams()) { + if (aTransportId.empty() || aTransportId == stream->GetId()) { + GetIceStats(*stream, aNow, stats.get()); + } + } + } + return dom::RTCStatsPromise::CreateAndResolve(std::move(stats), + __func__); + }); +} + +RefPtr<MediaTransportHandler::IceLogPromise> +MediaTransportHandlerSTS::GetIceLog(const nsCString& aPattern) { + return InvokeAsync( + mStsThread, __func__, [=, self = RefPtr<MediaTransportHandlerSTS>(this)] { + dom::Sequence<nsString> converted; + RLogConnector* logs = RLogConnector::GetInstance(); + std::deque<std::string> result; + // Might not exist yet. + if (logs) { + logs->Filter(aPattern.get(), 0, &result); + } + /// XXX(Bug 1631386) Check if we should reject the promise instead of + /// crashing in an OOM situation. + if (!converted.SetCapacity(result.size(), fallible)) { + mozalloc_handle_oom(sizeof(nsString) * result.size()); + } + for (auto& line : result) { + // Cannot fail, SetCapacity was called before. + (void)converted.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()), + fallible); + } + return IceLogPromise::CreateAndResolve(std::move(converted), __func__); + }); +} + +void MediaTransportHandlerSTS::ClearIceLog() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch(WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this), + &MediaTransportHandlerSTS::ClearIceLog), + NS_DISPATCH_NORMAL); + return; + } + + RLogConnector* logs = RLogConnector::GetInstance(); + if (logs) { + logs->Clear(); + } +} + +void MediaTransportHandlerSTS::EnterPrivateMode() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch( + WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this), + &MediaTransportHandlerSTS::EnterPrivateMode), + NS_DISPATCH_NORMAL); + return; + } + + RLogConnector::GetInstance()->EnterPrivateMode(); +} + +void MediaTransportHandlerSTS::ExitPrivateMode() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch( + WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this), + &MediaTransportHandlerSTS::ExitPrivateMode), + NS_DISPATCH_NORMAL); + return; + } + + auto* log = RLogConnector::GetInstance(); + MOZ_ASSERT(log); + if (log) { + log->ExitPrivateMode(); + } +} + +static void ToRTCIceCandidateStats( + const std::vector<NrIceCandidate>& candidates, + dom::RTCStatsType candidateType, const nsString& transportId, + DOMHighResTimeStamp now, dom::RTCStatsCollection* stats, + bool obfuscateHostAddresses, + const std::set<std::string>& signaledAddresses) { + MOZ_ASSERT(stats); + for (const auto& candidate : candidates) { + dom::RTCIceCandidateStats cand; + cand.mType.Construct(candidateType); + NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str()); + cand.mTransportId.Construct(transportId); + cand.mId.Construct(codeword); + cand.mTimestamp.Construct(now); + cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type)); + cand.mPriority.Construct(candidate.priority); + // https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1 + // This obfuscates the address with the mDNS address if one exists + if (!candidate.mdns_addr.empty()) { + cand.mAddress.Construct( + NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str())); + } else if (obfuscateHostAddresses && + candidate.type == NrIceCandidate::ICE_PEER_REFLEXIVE && + signaledAddresses.find(candidate.cand_addr.host) == + signaledAddresses.end()) { + cand.mAddress.Construct(NS_ConvertASCIItoUTF16("(redacted)")); + } else { + cand.mAddress.Construct( + NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str())); + } + cand.mPort.Construct(candidate.cand_addr.port); + cand.mProtocol.Construct( + NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str())); + if (candidateType == dom::RTCStatsType::Local_candidate && + dom::RTCIceCandidateType(candidate.type) == + dom::RTCIceCandidateType::Relay) { + cand.mRelayProtocol.Construct( + NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str())); + } + cand.mProxied.Construct(NS_ConvertASCIItoUTF16( + candidate.is_proxied ? "proxied" : "non-proxied")); + if (!stats->mIceCandidateStats.AppendElement(cand, 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 (candidate.trickled) { + if (!stats->mTrickledIceCandidateStats.AppendElement(cand, fallible)) { + mozalloc_handle_oom(0); + } + } + } +} + +void MediaTransportHandlerSTS::GetIceStats( + const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow, + dom::RTCStatsCollection* aStats) const { + MOZ_ASSERT(mStsThread->IsOnCurrentThread()); + + NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str()); + + std::vector<NrIceCandidatePair> candPairs; + nsresult res = aStream.GetCandidatePairs(&candPairs); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, + "%s: Error getting candidate pairs for transport id \"%s\"", + __FUNCTION__, aStream.GetId().c_str()); + return; + } + + for (auto& candPair : candPairs) { + NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str()); + NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str()); + NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str()); + // Only expose candidate-pair statistics to chrome, until we've thought + // through the implications of exposing it to content. + + dom::RTCIceCandidatePairStats s; + s.mId.Construct(codeword); + s.mTransportId.Construct(transportId); + s.mTimestamp.Construct(aNow); + s.mType.Construct(dom::RTCStatsType::Candidate_pair); + s.mLocalCandidateId.Construct(localCodeword); + s.mRemoteCandidateId.Construct(remoteCodeword); + s.mNominated.Construct(candPair.nominated); + s.mWritable.Construct(candPair.writable); + s.mReadable.Construct(candPair.readable); + s.mPriority.Construct(candPair.priority); + s.mSelected.Construct(candPair.selected); + s.mBytesSent.Construct(candPair.bytes_sent); + s.mBytesReceived.Construct(candPair.bytes_recvd); + s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send); + s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv); + s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state)); + s.mComponentId.Construct(candPair.component_id); + if (!aStats->mIceCandidatePairStats.AppendElement(s, 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); + } + } + + std::vector<NrIceCandidate> candidates; + if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) { + ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Local_candidate, + transportId, aNow, aStats, mObfuscateHostAddresses, + mSignaledAddresses); + // add the local candidates unparsed string to a sequence + for (const auto& candidate : candidates) { + if (!aStats->mRawLocalCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.label.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); + } + } + } + candidates.clear(); + + if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) { + ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Remote_candidate, + transportId, aNow, aStats, mObfuscateHostAddresses, + mSignaledAddresses); + // add the remote candidates unparsed string to a sequence + for (const auto& candidate : candidates) { + if (!aStats->mRawRemoteCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.label.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); + } + } + } +} + +RefPtr<TransportFlow> MediaTransportHandlerSTS::GetTransportFlow( + const std::string& aTransportId, bool aIsRtcp) const { + auto it = mTransports.find(aTransportId); + if (it == mTransports.end()) { + return nullptr; + } + + if (aIsRtcp) { + return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow; + ; + } + + return it->second.mFlow; +} + +RefPtr<TransportFlow> MediaTransportHandlerSTS::CreateTransportFlow( + const std::string& aTransportId, bool aIsRtcp, + RefPtr<DtlsIdentity> aDtlsIdentity, bool aDtlsClient, + const DtlsDigestList& aDigests, bool aPrivacyRequested) { + nsresult rv; + RefPtr<TransportFlow> flow = new TransportFlow(aTransportId); + + // The media streams are made on STS so we need to defer setup. + auto ice = MakeUnique<TransportLayerIce>(); + auto dtls = MakeUnique<TransportLayerDtls>(); + auto srtp = MakeUnique<TransportLayerSrtp>(*dtls); + dtls->SetRole(aDtlsClient ? TransportLayerDtls::CLIENT + : TransportLayerDtls::SERVER); + + dtls->SetIdentity(aDtlsIdentity); + + dtls->SetMinMaxVersion( + static_cast<TransportLayerDtls::Version>(mMinDtlsVersion), + static_cast<TransportLayerDtls::Version>(mMaxDtlsVersion)); + + for (const auto& digest : aDigests) { + rv = dtls->SetVerificationDigest(digest); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Could not set fingerprint"); + return nullptr; + } + } + + std::vector<uint16_t> srtpCiphers = + TransportLayerDtls::GetDefaultSrtpCiphers(); + + rv = dtls->SetSrtpCiphers(srtpCiphers); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't set SRTP ciphers"); + return nullptr; + } + + // Always permits negotiation of the confidential mode. + // Only allow non-confidential (which is an allowed default), + // if we aren't confidential. + std::set<std::string> alpn = {"c-webrtc"}; + std::string alpnDefault; + if (!aPrivacyRequested) { + alpnDefault = "webrtc"; + alpn.insert(alpnDefault); + } + rv = dtls->SetAlpn(alpn, alpnDefault); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't set ALPN"); + return nullptr; + } + + ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ? 2 : 1); + NS_ENSURE_SUCCESS(ice->Init(), nullptr); + NS_ENSURE_SUCCESS(dtls->Init(), nullptr); + NS_ENSURE_SUCCESS(srtp->Init(), nullptr); + dtls->Chain(ice.get()); + srtp->Chain(ice.get()); + + dtls->SignalPacketReceived.connect(this, + &MediaTransportHandlerSTS::PacketReceived); + srtp->SignalPacketReceived.connect(this, + &MediaTransportHandlerSTS::PacketReceived); + ice->SignalPacketSending.connect( + this, &MediaTransportHandlerSTS::EncryptedPacketSending); + flow->PushLayer(ice.release()); + flow->PushLayer(dtls.release()); + flow->PushLayer(srtp.release()); + return flow; +} + +static mozilla::dom::RTCIceGatheringState toDomIceGatheringState( + NrIceCtx::GatheringState aState) { + switch (aState) { + case NrIceCtx::ICE_CTX_GATHER_INIT: + return dom::RTCIceGatheringState::New; + case NrIceCtx::ICE_CTX_GATHER_STARTED: + return dom::RTCIceGatheringState::Gathering; + case NrIceCtx::ICE_CTX_GATHER_COMPLETE: + return dom::RTCIceGatheringState::Complete; + } + MOZ_CRASH(); +} + +void MediaTransportHandlerSTS::OnGatheringStateChange( + NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) { + OnGatheringStateChange(toDomIceGatheringState(aState)); +} + +static mozilla::dom::RTCIceConnectionState toDomIceConnectionState( + NrIceCtx::ConnectionState aState) { + switch (aState) { + case NrIceCtx::ICE_CTX_INIT: + return dom::RTCIceConnectionState::New; + case NrIceCtx::ICE_CTX_CHECKING: + return dom::RTCIceConnectionState::Checking; + case NrIceCtx::ICE_CTX_CONNECTED: + return dom::RTCIceConnectionState::Connected; + case NrIceCtx::ICE_CTX_COMPLETED: + return dom::RTCIceConnectionState::Completed; + case NrIceCtx::ICE_CTX_FAILED: + return dom::RTCIceConnectionState::Failed; + case NrIceCtx::ICE_CTX_DISCONNECTED: + return dom::RTCIceConnectionState::Disconnected; + case NrIceCtx::ICE_CTX_CLOSED: + return dom::RTCIceConnectionState::Closed; + } + MOZ_CRASH(); +} + +void MediaTransportHandlerSTS::OnConnectionStateChange( + NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) { + OnConnectionStateChange(toDomIceConnectionState(aState)); +} + +// The stuff below here will eventually go into the MediaTransportChild class +void MediaTransportHandlerSTS::OnCandidateFound( + NrIceMediaStream* aStream, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aMDNSAddr, + const std::string& aActualAddr) { + CandidateInfo info; + info.mCandidate = aCandidate; + MOZ_ASSERT(!aUfrag.empty()); + info.mUfrag = aUfrag; + NrIceCandidate defaultRtpCandidate; + NrIceCandidate defaultRtcpCandidate; + nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate); + if (NS_SUCCEEDED(rv)) { + if (!defaultRtpCandidate.mdns_addr.empty()) { + info.mDefaultHostRtp = "0.0.0.0"; + info.mDefaultPortRtp = 9; + } else { + info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host; + info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port; + } + } else { + CSFLogError(LOGTAG, + "%s: GetDefaultCandidates failed for transport id %s, " + "res=%u", + __FUNCTION__, aStream->GetId().c_str(), + static_cast<unsigned>(rv)); + } + + // Optional; component won't exist if doing rtcp-mux + if (NS_SUCCEEDED(aStream->GetDefaultCandidate(2, &defaultRtcpCandidate))) { + if (!defaultRtcpCandidate.mdns_addr.empty()) { + info.mDefaultHostRtcp = defaultRtcpCandidate.mdns_addr; + } else { + info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host; + } + info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port; + } + + info.mMDNSAddress = aMDNSAddr; + info.mActualAddress = aActualAddr; + + OnCandidate(aStream->GetId(), info); +} + +void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer, + TransportLayer::State aState) { + if (aState == TransportLayer::TS_OPEN) { + MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID()); + TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer); + OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn()); + } + + // DTLS state indicates the readiness of the transport as a whole, because + // SRTP uses the keys from the DTLS handshake. + MediaTransportHandler::OnStateChange(aLayer->flow_id(), aState); +} + +void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer, + TransportLayer::State aState) { + MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState); +} + +void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer, + MediaPacket& aPacket) { + OnPacketReceived(aLayer->flow_id(), aPacket); +} + +void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer, + MediaPacket& aPacket) { + OnEncryptedSending(aLayer->flow_id(), aPacket); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h new file mode 100644 index 0000000000..f8f355d665 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h @@ -0,0 +1,163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MTRANSPORTHANDLER_H__ +#define _MTRANSPORTHANDLER_H__ + +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" +#include "transport/sigslot.h" +#include "transport/transportlayer.h" // Need the State enum +#include "transport/dtlsidentity.h" // For DtlsDigest +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "transport/nricectx.h" // Need some enums +#include "common/CandidateInfo.h" +#include "transport/nr_socket_proxy_config.h" +#include "RTCStatsReport.h" + +#include "nsString.h" + +#include <string> +#include <set> +#include <vector> + +namespace mozilla { +class DtlsIdentity; +class NrIceCtx; +class NrIceMediaStream; +class NrIceResolver; +class TransportFlow; +class RTCStatsQuery; + +namespace dom { +struct RTCStatsReportInternal; +} + +class MediaTransportHandler { + public: + // Creates either a MediaTransportHandlerSTS or a MediaTransportHandlerIPC, + // as appropriate. If you want signals to fire on a specific thread, pass + // the event target here, otherwise they will fire on whatever is convenient. + // Note: This also determines what thread the state cache is updated on! + // Don't call GetState on any other thread! + static already_AddRefed<MediaTransportHandler> Create( + nsISerialEventTarget* aCallbackThread); + + explicit MediaTransportHandler(nsISerialEventTarget* aCallbackThread) + : mCallbackThread(aCallbackThread) {} + + static nsresult ConvertIceServers( + const nsTArray<dom::RTCIceServer>& aIceServers, + std::vector<NrIceStunServer>* aStunServers, + std::vector<NrIceTurnServer>* aTurnServers); + + typedef MozPromise<dom::Sequence<nsString>, nsresult, true> IceLogPromise; + + // There's a wrinkle here; the ICE logging is not separated out by + // MediaTransportHandler. These are a little more like static methods, but + // to avoid needing yet another IPC interface, we bolt them on here. + virtual RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) = 0; + virtual void ClearIceLog() = 0; + virtual void EnterPrivateMode() = 0; + virtual void ExitPrivateMode() = 0; + + virtual nsresult CreateIceCtx(const std::string& aName, + const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) = 0; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + virtual void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) = 0; + + virtual void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aLocalUfrag, + const std::string& aLocalPwd, + size_t aComponentCount) = 0; + + virtual void SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) = 0; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + virtual void StartIceGathering(bool aDefaultRouteOnly, + bool aObfuscateHostAddresses, + // TODO: It probably makes sense to look + // this up internally + const nsTArray<NrIceStunAddr>& aStunAddrs) = 0; + + virtual void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) = 0; + + virtual void RemoveTransportsExcept( + const std::set<std::string>& aTransportIds) = 0; + + virtual void StartIceChecks(bool aIsControlling, + const std::vector<std::string>& aIceOptions) = 0; + + virtual void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) = 0; + + virtual void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, + const std::string& aUFrag, + const std::string& aObfuscatedAddress) = 0; + + virtual void UpdateNetworkState(bool aOnline) = 0; + + virtual RefPtr<dom::RTCStatsPromise> GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) = 0; + + sigslot::signal2<const std::string&, const CandidateInfo&> SignalCandidate; + sigslot::signal2<const std::string&, bool> SignalAlpnNegotiated; + sigslot::signal1<dom::RTCIceGatheringState> SignalGatheringStateChange; + sigslot::signal1<dom::RTCIceConnectionState> SignalConnectionStateChange; + + sigslot::signal2<const std::string&, const MediaPacket&> SignalPacketReceived; + sigslot::signal2<const std::string&, const MediaPacket&> + SignalEncryptedSending; + sigslot::signal2<const std::string&, TransportLayer::State> SignalStateChange; + sigslot::signal2<const std::string&, TransportLayer::State> + SignalRtcpStateChange; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTransportHandler, + Destroy()) + + TransportLayer::State GetState(const std::string& aTransportId, + bool aRtcp) const; + + protected: + void OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + void OnAlpnNegotiated(const std::string& aAlpn); + void OnGatheringStateChange(dom::RTCIceGatheringState aState); + void OnConnectionStateChange(dom::RTCIceConnectionState aState); + void OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket); + void OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket); + void OnStateChange(const std::string& aTransportId, + TransportLayer::State aState); + void OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState); + virtual void Destroy() = 0; + virtual ~MediaTransportHandler() = default; + std::map<std::string, TransportLayer::State> mStateCache; + std::map<std::string, TransportLayer::State> mRtcpStateCache; + RefPtr<nsISerialEventTarget> mCallbackThread; +}; + +void TokenizeCandidate(const std::string& aCandidate, + std::vector<std::string>& aTokens); + +} // namespace mozilla + +#endif //_MTRANSPORTHANDLER_H__ diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp new file mode 100644 index 0000000000..70316b51ae --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp @@ -0,0 +1,398 @@ +/* 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 "MediaTransportHandlerIPC.h" +#include "mozilla/dom/MediaTransportChild.h" +#include "nsThreadUtils.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "common/browser_logging/CSFLog.h" + +namespace mozilla { + +static const char* mthipcLogTag = "MediaTransportHandler"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG mthipcLogTag + +MediaTransportHandlerIPC::MediaTransportHandlerIPC( + nsISerialEventTarget* aCallbackThread) + : MediaTransportHandler(aCallbackThread) { + mInitPromise = net::SocketProcessBridgeChild::GetSocketProcessBridge()->Then( + mCallbackThread, __func__, + [this, self = RefPtr<MediaTransportHandlerIPC>(this)]( + const RefPtr<net::SocketProcessBridgeChild>& aBridge) { + ipc::PBackgroundChild* actor = + ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread(); + if (!actor) { + NS_WARNING( + "MediaTransportHandlerIPC async init failed! Webrtc networking " + "will not work!"); + return InitPromise::CreateAndReject( + nsCString("GetOrCreateSocketActorForCurrentThread failed!"), + __func__); + } + MediaTransportChild* child = new MediaTransportChild(this); + actor->SetEventTargetForActor(child, mCallbackThread); + // PBackgroungChild owns mChild! When it is done with it, + // mChild will let us know it it going away. + mChild = actor->SendPMediaTransportConstructor(child); + CSFLogDebug(LOGTAG, "%s Init done", __func__); + return InitPromise::CreateAndResolve(true, __func__); + }, + [=](const nsCString& aError) { + CSFLogError(LOGTAG, + "MediaTransportHandlerIPC async init failed! Webrtc " + "networking will not work! Error was %s", + aError.get()); + NS_WARNING( + "MediaTransportHandlerIPC async init failed! Webrtc networking " + "will not work!"); + return InitPromise::CreateAndReject(aError, __func__); + }); +} + +RefPtr<MediaTransportHandler::IceLogPromise> +MediaTransportHandlerIPC::GetIceLog(const nsCString& aPattern) { + return mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /* dummy */) { + if (!mChild) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + // Compiler has trouble deducing the return type here for some reason, + // so we use a temp variable as a hint. + // SendGetIceLog _almost_ returns an IceLogPromise; the reject value + // differs (ipc::ResponseRejectReason vs nsresult) so we need to + // convert. + RefPtr<IceLogPromise> promise = mChild->SendGetIceLog(aPattern)->Then( + mCallbackThread, __func__, + [](WebrtcGlobalLog&& aLogLines) { + return IceLogPromise::CreateAndResolve(std::move(aLogLines), + __func__); + }, + [](ipc::ResponseRejectReason aReason) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); + return promise; + }, + [](const nsCString& aError) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); +} + +void MediaTransportHandlerIPC::ClearIceLog() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendClearIceLog(); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::EnterPrivateMode() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendEnterPrivateMode(); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::ExitPrivateMode() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendExitPrivateMode(); + } + }, + [](const nsCString& aError) {}); +} + +nsresult MediaTransportHandlerIPC::CreateIceCtx( + const std::string& aName, const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) { + CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start"); + // Run some validation on this side of the IPC boundary so we can return + // errors synchronously. We don't actually use the results. It might make + // sense to move this check to PeerConnection and have this API take the + // converted form, but we would need to write IPC serialization code for + // the NrIce*Server types. + std::vector<NrIceStunServer> stunServers; + std::vector<NrIceTurnServer> turnServers; + nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers); + if (NS_FAILED(rv)) { + return rv; + } + + mInitPromise->Then( + mCallbackThread, __func__, + [=, iceServers = aIceServers.Clone(), + self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + CSFLogDebug(LOGTAG, "%s starting", __func__); + if (!mChild->SendCreateIceCtx(aName, std::move(iceServers), + aIcePolicy)) { + CSFLogError(LOGTAG, "%s failed!", __func__); + } + } + }, + [](const nsCString& aError) {}); + + return NS_OK; +} + +void MediaTransportHandlerIPC::Destroy() { + if (mChild) { + MediaTransportChild::Send__delete__(mChild); + mChild = nullptr; + } + delete this; +} + +// We will probably be able to move the proxy lookup stuff into +// this class once we move mtransport to its own process. +void MediaTransportHandlerIPC::SetProxyConfig( + NrSocketProxyConfig&& aProxyConfig) { + mInitPromise->Then( + mCallbackThread, __func__, + [aProxyConfig = std::move(aProxyConfig), this, + self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) mutable { + if (mChild) { + mChild->SendSetProxyConfig(aProxyConfig.GetConfig()); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::EnsureProvisionalTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendEnsureProvisionalTransport(aTransportId, aLocalUfrag, + aLocalPwd, aComponentCount); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendSetTargetForDefaultLocalAddressLookup(aTargetIp, + aTargetPort); + } + }, + [](const nsCString& aError) {}); +} + +// We set default-route-only as late as possible because it depends on what +// capture permissions have been granted on the window, which could easily +// change between Init (ie; when the PC is created) and StartIceGathering +// (ie; when we set the local description). +void MediaTransportHandlerIPC::StartIceGathering( + bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // TODO(bug 1522205): It probably makes sense to look this up internally + const nsTArray<NrIceStunAddr>& aStunAddrs) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, stunAddrs = aStunAddrs.Clone(), + self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendStartIceGathering(aDefaultRouteOnly, + aObfuscateHostAddresses, stunAddrs); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(), + self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendActivateTransport(aTransportId, aLocalUfrag, aLocalPwd, + aComponentCount, aUfrag, aPassword, + keyDer, certDer, aAuthType, aDtlsClient, + aDigests, aPrivacyRequested); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::RemoveTransportsExcept( + const std::set<std::string>& aTransportIds) { + std::vector<std::string> transportIds(aTransportIds.begin(), + aTransportIds.end()); + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendRemoveTransportsExcept(transportIds); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::StartIceChecks( + bool aIsControlling, const std::vector<std::string>& aIceOptions) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendStartIceChecks(aIsControlling, aIceOptions); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) { + mInitPromise->Then( + mCallbackThread, __func__, + [this, self = RefPtr<MediaTransportHandlerIPC>(this), aTransportId, + aPacket = std::move(aPacket)](bool /*dummy*/) mutable { + if (mChild) { + mChild->SendSendPacket(aTransportId, aPacket); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::AddIceCandidate( + const std::string& aTransportId, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aObfuscatedAddress) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag, + aObfuscatedAddress); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendUpdateNetworkState(aOnline); + } + }, + [](const nsCString& aError) {}); +} + +RefPtr<dom::RTCStatsPromise> MediaTransportHandlerIPC::GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) { + return mInitPromise->Then( + mCallbackThread, __func__, + [aTransportId, aNow, this, + self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (!mChild) { + return dom::RTCStatsPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + RefPtr<dom::RTCStatsPromise> promise = + mChild->SendGetIceStats(aTransportId, aNow) + ->Then( + mCallbackThread, __func__, + [](const dom::RTCStatsCollection& aStats) { + UniquePtr<dom::RTCStatsCollection> stats( + new dom::RTCStatsCollection(aStats)); + return dom::RTCStatsPromise::CreateAndResolve( + std::move(stats), __func__); + }, + [](ipc::ResponseRejectReason aReason) { + return dom::RTCStatsPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + }); + return promise; + }, + [](const nsCString& aError) { + return dom::RTCStatsPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + }); +} + +MediaTransportChild::MediaTransportChild(MediaTransportHandlerIPC* aUser) + : mUser(aUser) {} + +MediaTransportChild::~MediaTransportChild() { mUser->mChild = nullptr; } + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidate( + const string& transportId, const CandidateInfo& candidateInfo) { + mUser->OnCandidate(transportId, candidateInfo); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated( + const string& alpn) { + mUser->OnAlpnNegotiated(alpn); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange( + const int& state) { + mUser->OnGatheringStateChange(static_cast<dom::RTCIceGatheringState>(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange( + const int& state) { + mUser->OnConnectionStateChange( + static_cast<dom::RTCIceConnectionState>(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnPacketReceived( + const string& transportId, const MediaPacket& packet) { + MediaPacket copy(packet); // Laaaaaame! Might be safe to const_cast? + mUser->OnPacketReceived(transportId, copy); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending( + const string& transportId, const MediaPacket& packet) { + MediaPacket copy(packet); // Laaaaaame! Might be safe to const_cast? + mUser->OnEncryptedSending(transportId, copy); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnStateChange( + const string& transportId, const int& state) { + mUser->OnStateChange(transportId, static_cast<TransportLayer::State>(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnRtcpStateChange( + const string& transportId, const int& state) { + mUser->OnRtcpStateChange(transportId, + static_cast<TransportLayer::State>(state)); + return ipc::IPCResult::Ok(); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h new file mode 100644 index 0000000000..05df596ca3 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MTRANSPORTHANDLER_IPC_H__ +#define _MTRANSPORTHANDLER_IPC_H__ + +#include "jsapi/MediaTransportHandler.h" +#include "mozilla/dom/PMediaTransportChild.h" + +namespace mozilla { + +class MediaTransportChild; + +// Implementation of MediaTransportHandler that uses IPC (PMediaTransport) to +// talk to mtransport on another process. +class MediaTransportHandlerIPC : public MediaTransportHandler { + public: + explicit MediaTransportHandlerIPC(nsISerialEventTarget* aCallbackThread); + RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override; + void ClearIceLog() override; + void EnterPrivateMode() override; + void ExitPrivateMode() override; + + nsresult CreateIceCtx(const std::string& aName, + const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) override; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override; + + void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aLocalUfrag, + const std::string& aLocalPwd, + size_t aComponentCount) override; + + void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp, + uint16_t aTargetPort) override; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // TODO: It probably makes sense to look + // this up internally + const nsTArray<NrIceStunAddr>& aStunAddrs) override; + + void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) override; + + void RemoveTransportsExcept( + const std::set<std::string>& aTransportIds) override; + + void StartIceChecks(bool aIsControlling, + const std::vector<std::string>& aIceOptions) override; + + void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) override; + + void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, const std::string& aUfrag, + const std::string& aObfuscatedAddress) override; + + void UpdateNetworkState(bool aOnline) override; + + RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId, + DOMHighResTimeStamp aNow) override; + + private: + friend class MediaTransportChild; + void Destroy() override; + + // We do not own this; it will tell us when it is going away. + dom::PMediaTransportChild* mChild = nullptr; + + // |mChild| can only be initted asynchronously, |mInitPromise| resolves + // when that happens. The |Then| calls make it convenient to dispatch API + // calls to main, which is a bonus. + // Init promise is not exclusive; this lets us call |Then| on it for every + // API call we get, instead of creating another promise each time. + typedef MozPromise<bool, nsCString, false> InitPromise; + RefPtr<InitPromise> mInitPromise; +}; + +} // namespace mozilla + +#endif //_MTRANSPORTHANDLER_IPC_H__ diff --git a/dom/media/webrtc/jsapi/MediaTransportParent.cpp b/dom/media/webrtc/jsapi/MediaTransportParent.cpp new file mode 100644 index 0000000000..5ac9ca3594 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp @@ -0,0 +1,238 @@ +/* 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 "mozilla/dom/MediaTransportParent.h" +#include "jsapi/MediaTransportHandler.h" + +#include "transport/sigslot.h" +#include "common/browser_logging/CSFLog.h" + +namespace mozilla { + +// Deals with the MediaTransportHandler interface, so MediaTransportParent +// doesn't have to.. +class MediaTransportParent::Impl : public sigslot::has_slots<> { + public: + explicit Impl(MediaTransportParent* aParent) + : mHandler(MediaTransportHandler::Create(GetCurrentSerialEventTarget())), + mParent(aParent) { + mHandler->SignalCandidate.connect(this, + &MediaTransportParent::Impl::OnCandidate); + mHandler->SignalAlpnNegotiated.connect( + this, &MediaTransportParent::Impl::OnAlpnNegotiated); + mHandler->SignalGatheringStateChange.connect( + this, &MediaTransportParent::Impl::OnGatheringStateChange); + mHandler->SignalConnectionStateChange.connect( + this, &MediaTransportParent::Impl::OnConnectionStateChange); + mHandler->SignalPacketReceived.connect( + this, &MediaTransportParent::Impl::OnPacketReceived); + mHandler->SignalEncryptedSending.connect( + this, &MediaTransportParent::Impl::OnEncryptedSending); + mHandler->SignalStateChange.connect( + this, &MediaTransportParent::Impl::OnStateChange); + mHandler->SignalRtcpStateChange.connect( + this, &MediaTransportParent::Impl::OnRtcpStateChange); + } + + virtual ~Impl() { + disconnect_all(); + mHandler = nullptr; + } + + void OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + NS_ENSURE_TRUE_VOID(mParent->SendOnCandidate(aTransportId, aCandidateInfo)); + } + + void OnAlpnNegotiated(const std::string& aAlpn, bool aPrivacyRequested) { + NS_ENSURE_TRUE_VOID(mParent->SendOnAlpnNegotiated(aAlpn)); + } + + void OnGatheringStateChange(dom::RTCIceGatheringState aState) { + NS_ENSURE_TRUE_VOID( + mParent->SendOnGatheringStateChange(static_cast<int>(aState))); + } + + void OnConnectionStateChange(dom::RTCIceConnectionState aState) { + NS_ENSURE_TRUE_VOID( + mParent->SendOnConnectionStateChange(static_cast<int>(aState))); + } + + void OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket) { + NS_ENSURE_TRUE_VOID(mParent->SendOnPacketReceived(aTransportId, aPacket)); + } + + void OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket) { + NS_ENSURE_TRUE_VOID(mParent->SendOnEncryptedSending(aTransportId, aPacket)); + } + + void OnStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnStateChange(aTransportId, aState)); + } + + void OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnRtcpStateChange(aTransportId, aState)); + } + + RefPtr<MediaTransportHandler> mHandler; + + private: + MediaTransportParent* mParent; +}; + +MediaTransportParent::MediaTransportParent() : mImpl(new Impl(this)) {} + +MediaTransportParent::~MediaTransportParent() {} + +mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceLog( + const nsCString& pattern, GetIceLogResolver&& aResolve) { + mImpl->mHandler->GetIceLog(pattern)->Then( + GetCurrentSerialEventTarget(), __func__, + // IPDL doesn't give us a reject function, so we cannot reject async, so + // we are forced to resolve with an empty result. Laaaaaaame. + [aResolve = std::move(aResolve)]( + MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& + aResult) mutable { + WebrtcGlobalLog logLines; + if (aResult.IsResolve()) { + logLines = std::move(aResult.ResolveValue()); + } + aResolve(logLines); + }); + + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvClearIceLog() { + mImpl->mHandler->ClearIceLog(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvEnterPrivateMode() { + mImpl->mHandler->EnterPrivateMode(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvExitPrivateMode() { + mImpl->mHandler->ExitPrivateMode(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvCreateIceCtx( + const string& name, nsTArray<RTCIceServer>&& iceServers, + const RTCIceTransportPolicy& icePolicy) { + nsresult rv = mImpl->mHandler->CreateIceCtx(name, iceServers, icePolicy); + if (NS_FAILED(rv)) { + return ipc::IPCResult::Fail(WrapNotNull(this), __func__, + "MediaTransportHandler::Init failed"); + } + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSetProxyConfig( + const net::WebrtcProxyConfig& aProxyConfig) { + mImpl->mHandler->SetProxyConfig(NrSocketProxyConfig(aProxyConfig)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvEnsureProvisionalTransport( + const string& transportId, const string& localUfrag, const string& localPwd, + const int& componentCount) { + mImpl->mHandler->EnsureProvisionalTransport(transportId, localUfrag, localPwd, + componentCount); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult +MediaTransportParent::RecvSetTargetForDefaultLocalAddressLookup( + const std::string& targetIp, uint16_t targetPort) { + mImpl->mHandler->SetTargetForDefaultLocalAddressLookup(targetIp, targetPort); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceGathering( + const bool& defaultRouteOnly, const bool& obfuscateHostAddresses, + const net::NrIceStunAddrArray& stunAddrs) { + mImpl->mHandler->StartIceGathering(defaultRouteOnly, obfuscateHostAddresses, + stunAddrs); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvActivateTransport( + const string& transportId, const string& localUfrag, const string& localPwd, + const int& componentCount, const string& remoteUfrag, + const string& remotePwd, nsTArray<uint8_t>&& keyDer, + nsTArray<uint8_t>&& certDer, const int& authType, const bool& dtlsClient, + const DtlsDigestList& digests, const bool& privacyRequested) { + mImpl->mHandler->ActivateTransport( + transportId, localUfrag, localPwd, componentCount, remoteUfrag, remotePwd, + keyDer, certDer, static_cast<SSLKEAType>(authType), dtlsClient, digests, + privacyRequested); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvRemoveTransportsExcept( + const StringVector& transportIds) { + std::set<std::string> ids(transportIds.begin(), transportIds.end()); + mImpl->mHandler->RemoveTransportsExcept(ids); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceChecks( + const bool& isControlling, const StringVector& iceOptions) { + mImpl->mHandler->StartIceChecks(isControlling, iceOptions); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSendPacket( + const string& transportId, const MediaPacket& packet) { + MediaPacket copy(packet); // Laaaaaaame. + mImpl->mHandler->SendPacket(transportId, std::move(copy)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvAddIceCandidate( + const string& transportId, const string& candidate, const string& ufrag, + const string& obfuscatedAddr) { + mImpl->mHandler->AddIceCandidate(transportId, candidate, ufrag, + obfuscatedAddr); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvUpdateNetworkState( + const bool& online) { + mImpl->mHandler->UpdateNetworkState(online); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceStats( + const string& transportId, const double& now, + GetIceStatsResolver&& aResolve) { + mImpl->mHandler->GetIceStats(transportId, now) + ->Then( + GetCurrentSerialEventTarget(), __func__, + // IPDL doesn't give us a reject function, so we cannot reject async, + // so we are forced to resolve with an unmodified result. Laaaaaaame. + [aResolve = std::move(aResolve)]( + dom::RTCStatsPromise::ResolveOrRejectValue&& aResult) { + if (aResult.IsResolve()) { + aResolve( + dom::NotReallyMovableButLetsPretendItIsRTCStatsCollection( + *aResult.ResolveValue())); + } else { + dom::NotReallyMovableButLetsPretendItIsRTCStatsCollection empty; + aResolve(empty); + } + }); + + return ipc::IPCResult::Ok(); +} + +void MediaTransportParent::ActorDestroy(ActorDestroyReason aWhy) {} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PacketDumper.cpp b/dom/media/webrtc/jsapi/PacketDumper.cpp new file mode 100644 index 0000000000..92bb492c10 --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.cpp @@ -0,0 +1,51 @@ +/* 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 "jsapi/PacketDumper.h" +#include "jsapi/PeerConnectionImpl.h" +#include "mozilla/media/MediaUtils.h" // NewRunnableFrom +#include "nsThreadUtils.h" // NS_DispatchToMainThread + +namespace mozilla { + +PacketDumper::PacketDumper(PeerConnectionImpl* aPc) : mPc(aPc) {} + +PacketDumper::PacketDumper(const std::string& aPcHandle) { + if (!aPcHandle.empty()) { + PeerConnectionWrapper pcw(aPcHandle); + mPc = pcw.impl(); + } +} + +PacketDumper::~PacketDumper() { + RefPtr<Runnable> pcDisposeRunnable = media::NewRunnableFrom(std::bind( + [](RefPtr<PeerConnectionImpl> pc) { return NS_OK; }, mPc.forget())); + NS_DispatchToMainThread(pcDisposeRunnable); +} + +void PacketDumper::Dump(size_t level, dom::mozPacketDumpType type, bool sending, + const void* data, size_t size) { + // Optimization; avoids making a copy of the buffer, but we need to lock a + // mutex and check the flags. Could be optimized further, if we really want to + if (!mPc || !mPc->ShouldDumpPacket(level, type, sending)) { + return; + } + + RefPtr<PeerConnectionImpl> pc = mPc; + + UniquePtr<uint8_t[]> ownedPacket = MakeUnique<uint8_t[]>(size); + memcpy(ownedPacket.get(), data, size); + + RefPtr<Runnable> dumpRunnable = media::NewRunnableFrom(std::bind( + [pc, level, type, sending, + size](UniquePtr<uint8_t[]>& packet) -> nsresult { + pc->DumpPacket_m(level, type, sending, packet, size); + return NS_OK; + }, + std::move(ownedPacket))); + + NS_DispatchToMainThread(dumpRunnable); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PacketDumper.h b/dom/media/webrtc/jsapi/PacketDumper.h new file mode 100644 index 0000000000..fdf1c8900e --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PACKET_DUMPER_H_ +#define _PACKET_DUMPER_H_ + +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" + +namespace mozilla { +class PeerConnectionImpl; + +class PacketDumper { + public: + explicit PacketDumper(PeerConnectionImpl* aPc); + explicit PacketDumper(const std::string& aPcHandle); + PacketDumper(const PacketDumper&) = delete; + ~PacketDumper(); + + PacketDumper& operator=(const PacketDumper&) = delete; + + void Dump(size_t level, dom::mozPacketDumpType type, bool sending, + const void* data, size_t size); + + private: + RefPtr<PeerConnectionImpl> mPc; +}; + +} // namespace mozilla + +#endif // _PACKET_DUMPER_H_ diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp new file mode 100644 index 0000000000..9ae9e83adc --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp @@ -0,0 +1,409 @@ +/* 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 "common/browser_logging/CSFLog.h" + +#include "PeerConnectionImpl.h" +#include "PeerConnectionCtx.h" +#include "transport/runnable_utils.h" +#include "prcvar.h" + +#include "mozilla/Telemetry.h" +#include "common/browser_logging/WebRtcLog.h" + +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/Preferences.h" +#include <mozilla/Types.h> + +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" + +#include "nsCRTGlue.h" +#include "nsIIOService.h" + +#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER +#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER + +static const char* pccLogTag = "PeerConnectionCtx"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pccLogTag + +namespace mozilla { + +using namespace dom; + +class PeerConnectionCtxObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + + PeerConnectionCtxObserver() {} + + void Init() { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (!observerService) return; + + nsresult rv = NS_OK; + + rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + MOZ_ALWAYS_SUCCEEDS(rv); + rv = observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + false); + MOZ_ALWAYS_SUCCEEDS(rv); + (void)rv; + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + CSFLogDebug(LOGTAG, "Shutting down PeerConnectionCtx"); + PeerConnectionCtx::Destroy(); + + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + nsresult rv = observerService->RemoveObserver( + this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + MOZ_ALWAYS_SUCCEEDS(rv); + rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + MOZ_ALWAYS_SUCCEEDS(rv); + + // Make sure we're not deleted while still inside ::Observe() + RefPtr<PeerConnectionCtxObserver> kungFuDeathGrip(this); + PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr; + } + if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) { + if (NS_strcmp(aData, u"" NS_IOSERVICE_OFFLINE) == 0) { + CSFLogDebug(LOGTAG, "Updating network state to offline"); + PeerConnectionCtx::UpdateNetworkState(false); + } else if (NS_strcmp(aData, u"" NS_IOSERVICE_ONLINE) == 0) { + CSFLogDebug(LOGTAG, "Updating network state to online"); + PeerConnectionCtx::UpdateNetworkState(true); + } else { + CSFLogDebug(LOGTAG, "Received unsupported network state event"); + MOZ_CRASH(); + } + } + return NS_OK; + } + + private: + virtual ~PeerConnectionCtxObserver() { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + } +}; + +NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver); +} // namespace mozilla + +namespace mozilla { + +PeerConnectionCtx* PeerConnectionCtx::gInstance; +nsIThread* PeerConnectionCtx::gMainThread; +StaticRefPtr<PeerConnectionCtxObserver> + PeerConnectionCtx::gPeerConnectionCtxObserver; + +const std::map<const std::string, PeerConnectionImpl*>& +PeerConnectionCtx::GetPeerConnections() { + return mPeerConnections; +} + +nsresult PeerConnectionCtx::InitializeGlobal(nsIThread* mainThread, + nsISerialEventTarget* stsThread) { + if (!gMainThread) { + gMainThread = mainThread; + } else { + MOZ_ASSERT(gMainThread == mainThread); + } + + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + + if (!gInstance) { + CSFLogDebug(LOGTAG, "Creating PeerConnectionCtx"); + PeerConnectionCtx* ctx = new PeerConnectionCtx(); + + res = ctx->Initialize(); + PR_ASSERT(NS_SUCCEEDED(res)); + if (!NS_SUCCEEDED(res)) return res; + + gInstance = ctx; + + if (!PeerConnectionCtx::gPeerConnectionCtxObserver) { + PeerConnectionCtx::gPeerConnectionCtxObserver = + new PeerConnectionCtxObserver(); + PeerConnectionCtx::gPeerConnectionCtxObserver->Init(); + } + } + + EnableWebRtcLog(); + return NS_OK; +} + +PeerConnectionCtx* PeerConnectionCtx::GetInstance() { + MOZ_ASSERT(gInstance); + return gInstance; +} + +bool PeerConnectionCtx::isActive() { return gInstance; } + +void PeerConnectionCtx::Destroy() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + + if (gInstance) { + gInstance->Cleanup(); + delete gInstance; + gInstance = nullptr; + } + + StopWebRtcLog(); +} + +template <typename T> +static void RecordCommonRtpTelemetry(const T& list, const T& lastList, + const bool isRemote) { + using namespace Telemetry; + for (const auto& s : list) { + const bool isAudio = s.mKind.Value().Find("audio") != -1; + if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed()) { + if (const uint64_t total = + s.mPacketsLost.Value() + s.mPacketsReceived.Value()) { + HistogramID id = + isRemote ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE + : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE) + : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE + : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE); + Accumulate(id, (s.mPacketsLost.Value() * 1000) / total); + } + } + if (s.mJitter.WasPassed()) { + HistogramID id = isRemote + ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER + : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER) + : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER + : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER); + Accumulate(id, s.mJitter.Value() * 1000); + } + // Record bandwidth telemetry + if (s.mBytesReceived.WasPassed()) { + for (const auto& lastS : lastList) { + if (lastS.mId == s.mId) { + int32_t deltaMs = s.mTimestamp.Value() - lastS.mTimestamp.Value(); + // In theory we're called every second, so delta *should* be in that + // range. Small deltas could cause errors due to division + if (deltaMs < 500 || deltaMs > 60000 || + !lastS.mBytesReceived.WasPassed()) { + break; + } + HistogramID id = + isRemote + ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS + : WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS) + : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS + : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS); + // We could accumulate values until enough time has passed + // and then Accumulate() but this isn't that important + Accumulate( + id, + ((s.mBytesReceived.Value() - lastS.mBytesReceived.Value()) * 8) / + deltaMs); + break; + } + } + } + } +} + +// Telemetry reporting every second after start of first call. +// The threading model around the media pipelines is weird: +// - The pipelines are containers, +// - containers that are only safe on main thread, with members only safe on +// STS, +// - hence the there and back again approach. + +void PeerConnectionCtx::DeliverStats( + UniquePtr<dom::RTCStatsReportInternal>&& aReport) { + using namespace Telemetry; + + // First, get reports from a second ago, if any, for calculations below + UniquePtr<dom::RTCStatsReportInternal> lastReport; + { + auto i = mLastReports.find(aReport->mPcid); + if (i != mLastReports.end()) { + lastReport = std::move(i->second); + } else { + lastReport = MakeUnique<dom::RTCStatsReportInternal>(); + } + } + // Record Telemetery + RecordCommonRtpTelemetry(aReport->mInboundRtpStreamStats, + lastReport->mInboundRtpStreamStats, false); + RecordCommonRtpTelemetry(aReport->mRemoteInboundRtpStreamStats, + lastReport->mRemoteInboundRtpStreamStats, true); + for (const auto& s : aReport->mRemoteInboundRtpStreamStats) { + if (s.mRoundTripTime.WasPassed()) { + const bool isAudio = s.mKind.Value().Find("audio") != -1; + HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT + : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT; + Accumulate(id, s.mRoundTripTime.Value() * 1000); + } + } + + mLastReports[aReport->mPcid] = std::move(aReport); +} + +void PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer, + void* closure) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(PeerConnectionCtx::isActive()); + + for (auto& idAndPc : GetInstance()->mPeerConnections) { + if (idAndPc.second->HasMedia()) { + idAndPc.second->GetStats(nullptr, true) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->DeliverStats( + std::move(aReport)); + } + }, + [=](nsresult aError) {}); + idAndPc.second->RecordConduitTelemetry(); + } + } +} + +void PeerConnectionCtx::UpdateNetworkState(bool online) { + auto ctx = GetInstance(); + if (ctx->mPeerConnections.empty()) { + return; + } + for (auto pc : ctx->mPeerConnections) { + pc.second->UpdateNetworkState(online); + } +} + +nsresult PeerConnectionCtx::Initialize() { + initGMP(); + + nsresult rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(mTelemetryTimer), EverySecondTelemetryCallback_m, this, + 1000, nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "EverySecondTelemetryCallback_m"); + NS_ENSURE_SUCCESS(rv, rv); + + if (XRE_IsContentProcess()) { + WebrtcGlobalChild::Create(); + } + + return NS_OK; +} + +static void GMPReady_m() { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->onGMPReady(); + } +}; + +static void GMPReady() { + PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(&GMPReady_m), + NS_DISPATCH_NORMAL); +}; + +void PeerConnectionCtx::initGMP() { + mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + + if (!mGMPService) { + CSFLogError(LOGTAG, "%s failed to get the gecko-media-plugin-service", + __FUNCTION__); + return; + } + + nsCOMPtr<nsIThread> thread; + nsresult rv = mGMPService->GetThread(getter_AddRefs(thread)); + + if (NS_FAILED(rv)) { + mGMPService = nullptr; + CSFLogError(LOGTAG, + "%s failed to get the gecko-media-plugin thread, err=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + return; + } + + // presumes that all GMP dir scans have been queued for the GMPThread + thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL); +} + +nsresult PeerConnectionCtx::Cleanup() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + + mQueuedJSEPOperations.Clear(); + mGMPService = nullptr; + mTransportHandler = nullptr; + return NS_OK; +} + +PeerConnectionCtx::~PeerConnectionCtx() { + // ensure mTelemetryTimer ends on main thread + MOZ_ASSERT(NS_IsMainThread()); + if (mTelemetryTimer) { + mTelemetryTimer->Cancel(); + } +}; + +void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) { + mQueuedJSEPOperations.AppendElement(aOperation); +} + +void PeerConnectionCtx::onGMPReady() { + mGMPReady = true; + for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) { + mQueuedJSEPOperations[i]->Run(); + } + mQueuedJSEPOperations.Clear(); +} + +bool PeerConnectionCtx::gmpHasH264() { + if (!mGMPService) { + return false; + } + + // XXX I'd prefer if this was all known ahead of time... + + nsTArray<nsCString> tags; + tags.AppendElement("h264"_ns); + + bool has_gmp; + nsresult rv; + rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_ENCODER), + &tags, &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_DECODER), + &tags, &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + return true; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.h b/dom/media/webrtc/jsapi/PeerConnectionCtx.h new file mode 100644 index 0000000000..098b39e1fe --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.h @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef peerconnectionctx_h___h__ +#define peerconnectionctx_h___h__ + +#include <string> +#include <map> + +#include "WebrtcGlobalChild.h" + +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "PeerConnectionImpl.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsIRunnable.h" +#include "MediaTransportHandler.h" // Mostly for IceLogPromise + +namespace mozilla { +class PeerConnectionCtxObserver; + +namespace dom { +class WebrtcGlobalInformation; +} + +// A class to hold some of the singleton objects we need: +// * The global PeerConnectionImpl table and its associated lock. +// * Stats report objects for PCs that are gone +// * GMP related state +class PeerConnectionCtx { + public: + static nsresult InitializeGlobal(nsIThread* mainThread, + nsISerialEventTarget* stsThread); + static PeerConnectionCtx* GetInstance(); + static bool isActive(); + static void Destroy(); + + bool isReady() { + // If mGMPService is not set, we aren't using GMP. + if (mGMPService) { + return mGMPReady; + } + return true; + } + + void queueJSEPOperation(nsIRunnable* aJSEPOperation); + void onGMPReady(); + + bool gmpHasH264(); + + static void UpdateNetworkState(bool online); + + RefPtr<MediaTransportHandler> GetTransportHandler() const { + return mTransportHandler; + } + + // Make these classes friend so that they can access mPeerconnections. + friend class PeerConnectionImpl; + friend class PeerConnectionWrapper; + friend class mozilla::dom::WebrtcGlobalInformation; + + // WebrtcGlobalInformation uses this; we put it here so we don't need to + // create another shutdown observer class. + mozilla::dom::Sequence<mozilla::dom::RTCStatsReportInternal> + mStatsForClosedPeerConnections; + + const std::map<const std::string, PeerConnectionImpl*>& GetPeerConnections(); + + private: + // We could make these available only via accessors but it's too much trouble. + std::map<const std::string, PeerConnectionImpl*> mPeerConnections; + + PeerConnectionCtx() + : mGMPReady(false), + mTransportHandler( + MediaTransportHandler::Create(GetMainThreadSerialEventTarget())) {} + // This is a singleton, so don't copy construct it, etc. + PeerConnectionCtx(const PeerConnectionCtx& other) = delete; + void operator=(const PeerConnectionCtx& other) = delete; + virtual ~PeerConnectionCtx(); + + nsresult Initialize(); + nsresult Cleanup(); + + void initGMP(); + + static void EverySecondTelemetryCallback_m(nsITimer* timer, void*); + + nsCOMPtr<nsITimer> mTelemetryTimer; + + private: + void DeliverStats(UniquePtr<dom::RTCStatsReportInternal>&& aReport); + + std::map<nsString, UniquePtr<dom::RTCStatsReportInternal>> mLastReports; + // We cannot form offers/answers properly until the Gecko Media Plugin stuff + // has been initted, which is a complicated mess of thread dispatches, + // including sync dispatches to main. So, we need to be able to queue up + // offer creation (or SetRemote, when we're the answerer) until all of this is + // ready to go, since blocking on this init is just begging for deadlock. + nsCOMPtr<mozIGeckoMediaPluginService> mGMPService; + bool mGMPReady; + nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations; + + // Not initted, just for ICE logging stuff + RefPtr<MediaTransportHandler> mTransportHandler; + + static PeerConnectionCtx* gInstance; + + public: + static nsIThread* gMainThread; + static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxObserver> + gPeerConnectionCtxObserver; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp new file mode 100644 index 0000000000..318ab64622 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp @@ -0,0 +1,3019 @@ +/* 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 "nsNetCID.h" +#include "nsIIDNService.h" +#include "nsILoadContext.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 "MediaTrackGraph.h" +#include "transport/runnable_utils.h" +#include "IPeerConnection.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.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 "jsapi/RTCRtpReceiver.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.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 "nsGlobalWindow.h" +#include "nsDOMDataChannel.h" +#include "mozilla/dom/Location.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "mozilla/PublicSSL.h" +#include "nsXULAppAPI.h" +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "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/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" + +#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; + +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() { + MOZ_ASSERT(mRefCnt); + mRefCnt--; + if (mRefCnt == 0) { + Telemetry::Accumulate( + Telemetry::WEBRTC_CALL_DURATION, + static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds())); + } +} + +bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; } + +NS_IMPL_ISUPPORTS0(PeerConnectionImpl) + +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(); +} + +bool PeerConnectionImpl::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) { + return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto, aReflector); +} + +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), + mJsConfiguration(), + mSignalingState(RTCSignalingState::Stable), + mIceConnectionState(RTCIceConnectionState::New), + mIceGatheringState(RTCIceGatheringState::New), + mWindow(nullptr), + mCertificate(nullptr), + mSTSThread(nullptr), + mForceIceTcp(false), + mMedia(nullptr), + mTransportHandler(nullptr), + mUuidGen(MakeUnique<PCUuidGenerator>()), + mIceRestartCount(0), + mIceRollbackCount(0), + mHaveConfiguredCodecs(false), + mTrickle(true) // TODO(ekr@rtfm.com): Use pref + , + mPrivateWindow(false), + mActiveOnWindow(false), + mPacketDumpEnabled(false), + mPacketDumpFlagsMutex("Packet dump flags mutex"), + mTimestampMaker(aGlobal), + mIdGenerator(new RTCStatsIdGenerator()), + listenPort(0), + connectPort(0), + connectStr(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + if (aGlobal) { + mWindow = do_QueryInterface(aGlobal->GetAsSupports()); + if (IsPrivateBrowsing(mWindow)) { + mPrivateWindow = true; + } + mWindow->AddPeerConnection(); + mActiveOnWindow = true; + + mRtxIsAllowed = + !HostnameInPref("media.peerconnection.video.use_rtx.blocklist", + mWindow->GetDocumentURI()); + } + 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)); +} + +PeerConnectionImpl::~PeerConnectionImpl() { + if (mTimeCard) { + STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); + print_timecard(mTimeCard); + destroy_timecard(mTimeCard); + mTimeCard = nullptr; + } + // This aborts if not on main thread (in Debug builds) + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + if (mWindow && mActiveOnWindow) { + mWindow->RemovePeerConnection(); + // No code is supposed to observe the assignment below, but + // hopefully it makes looking at this object in a debugger + // make more sense. + mActiveOnWindow = false; + } + + if (mPrivateWindow && mTransportHandler) { + mTransportHandler->ExitPrivateMode(); + } + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->mPeerConnections.erase(mHandle); + } else { + CSFLogError(LOGTAG, "PeerConnectionCtx is already gone. Ignoring..."); + } + + CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s", + __FUNCTION__, mHandle.c_str()); + + // Since this and Initialize() occur on MainThread, they can't both be + // running at once + + // Right now, we delete PeerConnectionCtx at XPCOM shutdown only, but we + // probably want to shut it down more aggressively to save memory. We + // could shut down here when there are no uses. It might be more optimal + // to release off a timer (and XPCOM Shutdown) to avoid churn + ShutdownMedia(); +} + +nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow, + const RTCConfiguration& aConfiguration, + nsISupports* aThread) { + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aThread); + if (!mThread) { + mThread = do_QueryInterface(aThread); + MOZ_ASSERT(mThread); + } + CheckThread(); + + // Store the configuration for about:webrtc + StoreConfigurationForAboutWebrtc(aConfiguration); + + 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. + mTransportHandler = MediaTransportHandler::Create(mSTSThread); + if (mPrivateWindow) { + mTransportHandler->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. + char temp[128]; + + nsAutoCString locationCStr; + + RefPtr<Location> location = mWindow->Location(); + nsAutoString locationAStr; + res = location->ToString(locationAStr); + NS_ENSURE_SUCCESS(res, res); + + CopyUTF16toUTF8(locationAStr, locationCStr); + + if (!mUuidGen->Generate(&mHandle)) { + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + 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(mThread, mSTSThread); + NS_ENSURE_SUCCESS(res, res); + + nsTArray<dom::RTCIceServer> iceServers; + if (aConfiguration.mIceServers.WasPassed()) { + iceServers = aConfiguration.mIceServers.Value(); + } + + res = mTransportHandler->CreateIceCtx("PC:" + GetName(), iceServers, + aConfiguration.mIceTransportPolicy); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Failed to init mtransport", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + 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; + } + + 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(); + } + + res = mJsepSession->SetBundlePolicy(bundlePolicy); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't set bundle policy, res=%u, error=%s", + __FUNCTION__, static_cast<unsigned>(res), + mJsepSession->GetLastError().c_str()); + return res; + } + + mMedia = new PeerConnectionMedia(this); + + // Initialize the media object. + res = mMedia->Init(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't initialize media object", __FUNCTION__); + ShutdownMedia(); + return res; + } + + PeerConnectionCtx::GetInstance()->mPeerConnections[mHandle] = this; + + return NS_OK; +} + +void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, + const RTCConfiguration& aConfiguration, + nsISupports* aThread, ErrorResult& rv) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aThread); + mThread = do_QueryInterface(aThread); + + nsresult res = Initialize(aObserver, &aWindow, aConfiguration, aThread); + if (NS_FAILED(res)) { + rv.Throw(res); + return; + } + + if (!aConfiguration.mPeerIdentity.IsEmpty()) { + mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity); + mPrivacyRequested = Some(true); + } +} + +void PeerConnectionImpl::SetCertificate( + mozilla::dom::RTCCertificate& aCertificate) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(!mCertificate, "This can only be called once"); + mCertificate = &aCertificate; + + std::vector<uint8_t> fingerprint; + nsresult rv = + CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't calculate fingerprint, rv=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + mCertificate = nullptr; + return; + } + rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, + fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't set DTLS credentials, rv=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + mCertificate = nullptr; + } +} + +const RefPtr<mozilla::dom::RTCCertificate>& PeerConnectionImpl::Certificate() + const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mCertificate; +} + +RefPtr<DtlsIdentity> PeerConnectionImpl::Identity() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(mCertificate); + return mCertificate->CreateDtlsIdentity(); +} + +class CompareCodecPriority { + public: + void SetPreferredCodec(int32_t preferredCodec) { + // This pref really ought to be a string, preferably something like + // "H264" or "VP8" instead of a payload type. + // Bug 1101259. + std::ostringstream os; + os << preferredCodec; + mPreferredCodec = os.str(); + } + + bool operator()(const UniquePtr<JsepCodecDescription>& lhs, + const UniquePtr<JsepCodecDescription>& rhs) const { + if (!mPreferredCodec.empty() && lhs->mDefaultPt == mPreferredCodec && + rhs->mDefaultPt != mPreferredCodec) { + return true; + } + + if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) { + return true; + } + + return false; + } + + private: + std::string mPreferredCodec; +}; + +class ConfigureCodec { + public: + explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch) + : mHardwareH264Enabled(false), + mSoftwareH264Enabled(false), + mH264Enabled(false), + mVP9Enabled(true), + mVP9Preferred(false), + mH264Level(13), // minimum suggested for WebRTC spec + mH264MaxBr(0), // Unlimited + mH264MaxMbps(0), // Unlimited + mVP8MaxFs(0), + mVP8MaxFr(0), + mUseTmmbr(false), + mUseRemb(false), + mUseTransportCC(false), + mUseAudioFec(false), + mRedUlpfecEnabled(false), + mDtmfEnabled(false) { + mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264(); + + if (WebrtcVideoConduit::HasH264Hardware()) { + branch->GetBoolPref("media.webrtc.hw.h264.enabled", + &mHardwareH264Enabled); + } + + mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled; + + branch->GetIntPref("media.navigator.video.h264.level", &mH264Level); + mH264Level &= 0xFF; + + branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr); + + branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps); + + branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &mVP9Enabled); + + branch->GetBoolPref("media.peerconnection.video.vp9_preferred", + &mVP9Preferred); + + branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs); + if (mVP8MaxFs <= 0) { + mVP8MaxFs = 12288; // We must specify something other than 0 + } + + branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr); + if (mVP8MaxFr <= 0) { + mVP8MaxFr = 60; // We must specify something other than 0 + } + + // TMMBR is enabled from a pref in about:config + branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr); + + // REMB is enabled by default, but can be disabled from about:config + branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb); + + branch->GetBoolPref("media.navigator.video.use_transport_cc", + &mUseTransportCC); + + branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec); + + branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled", + &mRedUlpfecEnabled); + + // media.peerconnection.dtmf.enabled controls both sdp generation for + // DTMF support as well as DTMF exposure to DOM + branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled); + } + + void operator()(UniquePtr<JsepCodecDescription>& codec) const { + switch (codec->mType) { + case SdpMediaSection::kAudio: { + JsepAudioCodecDescription& audioCodec = + static_cast<JsepAudioCodecDescription&>(*codec); + if (audioCodec.mName == "opus") { + audioCodec.mFECEnabled = mUseAudioFec; + } else if (audioCodec.mName == "telephone-event") { + audioCodec.mEnabled = mDtmfEnabled; + } + } break; + case SdpMediaSection::kVideo: { + JsepVideoCodecDescription& videoCodec = + static_cast<JsepVideoCodecDescription&>(*codec); + + if (videoCodec.mName == "H264") { + // Override level + videoCodec.mProfileLevelId &= 0xFFFF00; + videoCodec.mProfileLevelId |= mH264Level; + + videoCodec.mConstraints.maxBr = mH264MaxBr; + + videoCodec.mConstraints.maxMbps = mH264MaxMbps; + + // Might disable it, but we set up other params anyway + videoCodec.mEnabled = mH264Enabled; + + if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) { + // We're assuming packetization mode 0 is unsupported by + // hardware. + videoCodec.mEnabled = false; + } + + if (mHardwareH264Enabled) { + videoCodec.mStronglyPreferred = true; + } + } else if (videoCodec.mName == "red") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "ulpfec") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") { + if (videoCodec.mName == "VP9") { + if (!mVP9Enabled) { + videoCodec.mEnabled = false; + break; + } + if (mVP9Preferred) { + videoCodec.mStronglyPreferred = true; + } + } + videoCodec.mConstraints.maxFs = mVP8MaxFs; + videoCodec.mConstraints.maxFps = mVP8MaxFr; + } + + if (mUseTmmbr) { + videoCodec.EnableTmmbr(); + } + if (mUseRemb) { + videoCodec.EnableRemb(); + } + if (mUseTransportCC) { + videoCodec.EnableTransportCC(); + } + } break; + case SdpMediaSection::kText: + case SdpMediaSection::kApplication: + case SdpMediaSection::kMessage: { + } // Nothing to configure for these. + } + } + + private: + bool mHardwareH264Enabled; + bool mSoftwareH264Enabled; + bool mH264Enabled; + bool mVP9Enabled; + bool mVP9Preferred; + int32_t mH264Level; + int32_t mH264MaxBr; + int32_t mH264MaxMbps; + int32_t mVP8MaxFs; + int32_t mVP8MaxFr; + bool mUseTmmbr; + bool mUseRemb; + bool mUseTransportCC; + bool mUseAudioFec; + bool mRedUlpfecEnabled; + bool mDtmfEnabled; +}; + +class ConfigureRedCodec { + public: + explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch, + std::vector<uint8_t>* redundantEncodings) + : mRedundantEncodings(redundantEncodings) { + // if we wanted to override or modify which encodings are considered + // for redundant encodings, we'd probably want to handle it here by + // checking prefs modifying the operator() code below + } + + void operator()(UniquePtr<JsepCodecDescription>& codec) const { + if (codec->mType == SdpMediaSection::kVideo && codec->mEnabled == false) { + uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); + // don't search for the codec payload type unless we have a valid + // conversion (non-zero) + if (pt != 0) { + std::vector<uint8_t>::iterator it = std::find( + mRedundantEncodings->begin(), mRedundantEncodings->end(), pt); + if (it != mRedundantEncodings->end()) { + mRedundantEncodings->erase(it); + } + } + } + } + + private: + std::vector<uint8_t>* mRedundantEncodings; +}; + +nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() { + nsresult res; + nsCOMPtr<nsIPrefService> prefs = + do_GetService("@mozilla.org/preferences-service;1", &res); + + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't get prefs service, res=%u", __FUNCTION__, + static_cast<unsigned>(res)); + return res; + } + + nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs); + if (!branch) { + CSFLogError(LOGTAG, "%s: Couldn't get prefs branch", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + ConfigureCodec configurer(branch); + mJsepSession->ForEachCodec(configurer); + + // if red codec is enabled, configure it for the other enabled codecs + for (auto& codec : mJsepSession->Codecs()) { + if (codec->mName == "red" && codec->mEnabled) { + JsepVideoCodecDescription* redCodec = + static_cast<JsepVideoCodecDescription*>(codec.get()); + ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings)); + mJsepSession->ForEachCodec(configureRed); + break; + } + } + + // We use this to sort the list of codecs once everything is configured + CompareCodecPriority comparator; + + // Sort by priority + int32_t preferredCodec = 0; + branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec); + + if (preferredCodec) { + comparator.SetPreferredCodec(preferredCodec); + } + + mJsepSession->SortCodecs(comparator); + return NS_OK; +} + +// Data channels won't work without a window, so in order for the C++ unit +// tests to work (it doesn't have a window available) we ifdef the following +// two implementations. +// +// 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 = + mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr; + 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(); + + RefPtr<JsepTransceiver> datachannelTransceiver; + for (const auto& [id, transceiver] : mJsepSession->GetTransceivers()) { + (void)id; // Lame, but no better way to do this right now. + if ((transceiver->GetMediaType() == SdpMediaSection::kApplication) && + transceiver->mSendTrack.GetNegotiatedDetails()) { + datachannelTransceiver = transceiver; + break; + } + } + + if (!datachannelTransceiver || + !datachannelTransceiver->mTransport.mComponents) { + 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->mType != SdpMediaSection::kApplication) { + CSFLogError(LOGTAG, + "%s: Codec type for m=application was %u, this " + "is a bug.", + __FUNCTION__, static_cast<unsigned>(codec->mType)); + MOZ_ASSERT(false, "Codec for m=application was not \"application\""); + return NS_ERROR_FAILURE; + } + + if (codec->mName != "webrtc-datachannel") { + CSFLogWarn(LOGTAG, + "%s: Codec for m=application was not " + "webrtc-datachannel (was instead %s). ", + __FUNCTION__, codec->mName.c_str()); + continue; + } + + 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( + RefPtr<JsepTransceiver>& transceiver) { + nsresult res = ConfigureJsepSessionCodecs(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return res; + } + + res = mJsepSession->AddTransceiver(transceiver); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + CSFLogError(LOGTAG, "%s (%s) : pc = %s, error = %s", __FUNCTION__, + transceiver->GetMediaType() == SdpMediaSection::kAudio + ? "audio" + : "video", + mHandle.c_str(), errorString.c_str()); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +already_AddRefed<TransceiverImpl> PeerConnectionImpl::CreateTransceiverImpl( + JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack, + ErrorResult& aRv) { + // TODO: Maybe this should be done in PeerConnectionMedia? + if (aSendTrack) { + aSendTrack->AddPrincipalChangeObserver(this); + } + + RefPtr<TransceiverImpl> transceiverImpl; + + aRv = mMedia->AddTransceiver(aJsepTransceiver, aSendTrack, &transceiverImpl); + + return transceiverImpl.forget(); +} + +already_AddRefed<TransceiverImpl> PeerConnectionImpl::CreateTransceiverImpl( + const nsAString& aKind, dom::MediaStreamTrack* aSendTrack, + ErrorResult& jrv) { + SdpMediaSection::MediaType type; + if (aKind.EqualsASCII("audio")) { + type = SdpMediaSection::MediaType::kAudio; + } else if (aKind.EqualsASCII("video")) { + type = SdpMediaSection::MediaType::kVideo; + } else { + MOZ_ASSERT(false); + jrv = NS_ERROR_INVALID_ARG; + return nullptr; + } + + RefPtr<JsepTransceiver> jsepTransceiver = new JsepTransceiver(type); + + RefPtr<TransceiverImpl> transceiverImpl = + CreateTransceiverImpl(jsepTransceiver, aSendTrack, jrv); + + if (jrv.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; + } + + 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)); + jrv = rv; + return nullptr; + } + + return transceiverImpl.forget(); +} + +bool PeerConnectionImpl::CheckNegotiationNeeded(ErrorResult& rv) { + MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable); + return mJsepSession->CheckNegotiationNeeded(); +} + +nsresult PeerConnectionImpl::InitializeDataChannel() { + 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; + DataChannelConnection::Type theType = + static_cast<DataChannelConnection::Type>(aType); + + 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), theType, + ordered, + aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT + ? aMaxNum + : (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime + : 0), + nullptr, nullptr, aExternalNegotiated, aStream); + NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE); + + CSFLogDebug(LOGTAG, "%s: making DOMDataChannel", __FUNCTION__); + + RefPtr<JsepTransceiver> dcTransceiver; + for (auto& [id, transceiver] : mJsepSession->GetTransceivers()) { + (void)id; // Lame, but no better way to do this right now. + if (transceiver->GetMediaType() == SdpMediaSection::kApplication) { + dcTransceiver = transceiver; + break; + } + } + + if (!dcTransceiver) { + dcTransceiver = + new JsepTransceiver(SdpMediaSection::MediaType::kApplication); + mJsepSession->AddTransceiver(dcTransceiver); + } + + dcTransceiver->RestartDatachannelTransceiver(); + + RefPtr<nsDOMDataChannel> retval; + rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, + getter_AddRefs(retval)); + if (NS_FAILED(rv)) { + return rv; + } + retval.forget(aRetval); + return NS_OK; +} + +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); +} + +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); + + 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"); + + mThread->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this), aOptions] { + std::string offer; + + JsepSession::Result result = + mJsepSession->CreateOffer(aOptions, &offer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateOfferError( + *buildJSErrorData(result, errorString), rv); + } else { + 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; + + mThread->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this), options] { + std::string answer; + + JsepSession::Result result = + mJsepSession->CreateAnswer(options, &answer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateAnswerError( + *buildJSErrorData(result, errorString), rv); + } else { + mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv); + } + })); + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + JSErrorResult rv; + STAMP_TIMECARD(mTimeCard, "Set Local Description"); + + if (mMedia->AnyLocalTrackHasPeerIdentity()) { + mPrivacyRequested = Some(true); + } + + mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry; + sdpEntry.mIsLocal = true; + sdpEntry.mTimestamp = mTimestampMaker.GetNow(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + mLocalRequestedSDP = aSDP; + + 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; + } + JsepSession::Result result = + mJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP); + if (result.mError.isSome()) { + std::string errorString = mJsepSession->GetLastError(); + 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); + } + + OnSetDescriptionSuccess(sdpType, false); + } + + appendHistory(); + return NS_OK; +} + +static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction, + const std::string& aSdp) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH( + "Why is DeferredSetRemote being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str()); + } +} + +NS_IMETHODIMP +PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + JSErrorResult jrv; + 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(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + 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; + } + + auto originalTransceivers = mJsepSession->GetTransceivers(); + JsepSession::Result result = + mJsepSession->SetRemoteDescription(sdpType, mRemoteRequestedSDP); + if (result.mError.isSome()) { + std::string errorString = mJsepSession->GetLastError(); + sdpEntry.mErrors = GetLastSdpParsingErrors(); + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString), + jrv); + } else { + for (const auto& [id, jsepTransceiver] : mJsepSession->GetTransceivers()) { + if (jsepTransceiver->GetMediaType() == + SdpMediaSection::MediaType::kApplication) { + continue; + } + + if (originalTransceivers.count(id)) { + continue; + } + + // New audio or video transceiver, need to tell JS about it. + RefPtr<TransceiverImpl> transceiverImpl = + CreateTransceiverImpl(jsepTransceiver, nullptr, jrv); + if (jrv.Failed()) { + appendHistory(); + return NS_ERROR_FAILURE; + } + + const JsepTrack& receiving(jsepTransceiver->mRecvTrack); + CSFLogInfo(LOGTAG, "%s: pc = %s, asking JS to create transceiver", + __FUNCTION__, mHandle.c_str()); + switch (receiving.GetMediaType()) { + case SdpMediaSection::MediaType::kAudio: + mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("audio"), + *transceiverImpl, jrv); + break; + case SdpMediaSection::MediaType::kVideo: + mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("video"), + *transceiverImpl, jrv); + break; + default: + MOZ_RELEASE_ASSERT(false); + } + + if (jrv.Failed()) { + nsresult rv = jrv.StealNSResult(); + CSFLogError(LOGTAG, + "%s: pc = %s, OnTransceiverNeeded failed. " + "This should never happen. rv = %d", + __FUNCTION__, mHandle.c_str(), static_cast<int>(rv)); + MOZ_CRASH(); + return NS_ERROR_FAILURE; + } + } + + if (wasRestartingIce) { + RecordIceRestartStatistics(sdpType); + } + + OnSetDescriptionSuccess(sdpType, true); + } + + appendHistory(); + return NS_OK; +} + +already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats( + MediaStreamTrack* aSelector) { + if (NS_FAILED(CheckApiState(false))) { + return nullptr; + } + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + ErrorResult rv; + RefPtr<Promise> promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.StealNSResult(); + return nullptr; + } + + 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)); + }); + + 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()); + } + JsepSession::Result result = mJsepSession->AddRemoteIceCandidate( + aCandidate, aMid, level, aUfrag, &transportId); + + if (!result.mError.isSome()) { + // We do not bother PCMedia about this before offer/answer concludes. + // Once offer/answer concludes, PCMedia will extract these candidates from + // the remote SDP. + if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) { + mMedia->AddIceCandidate(aCandidate, transportId, aUfrag); + mRawTrickledCandidates.push_back(aCandidate); + } + // Spec says we queue a task for these updates + mThread->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()); + + mThread->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; +} + +void PeerConnectionImpl::UpdateNetworkState(bool online) { + if (!mMedia) { + return; + } + mMedia->UpdateNetworkState(online); +} + +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 = GetWindow()->GetExtantDoc(); + if (!doc) { + CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + MediaStreamTrack* allTracks = nullptr; + mMedia->UpdateSinkIdentity_m(allTracks, doc->NodePrincipal(), + mPeerIdentity); + } + return NS_OK; +} + +nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) { + PC_AUTO_ENTER_API_CALL(false); + if (mPrivacyRequested.isSome()) { + MOZ_DIAGNOSTIC_ASSERT(*mPrivacyRequested == aPrivacyRequested); + return NS_OK; + } + + mPrivacyRequested = Some(aPrivacyRequested); + return NS_OK; +} + +void PeerConnectionImpl::PrincipalChanged(MediaStreamTrack* aTrack) { + Document* doc = GetWindow()->GetExtantDoc(); + if (doc) { + mMedia->UpdateSinkIdentity_m(aTrack, doc->NodePrincipal(), mPeerIdentity); + } else { + CSFLogInfo(LOGTAG, "Can't update sink principal; document gone"); + } +} + +void PeerConnectionImpl::OnMediaError(const std::string& aError) { + CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str()); + // TODO: Let content know about this somehow. +} + +bool PeerConnectionImpl::ShouldDumpPacket(size_t level, + dom::mozPacketDumpType type, + bool sending) const { + if (!mPacketDumpEnabled) { + return false; + } + + MutexAutoLock lock(mPacketDumpFlagsMutex); + + const std::vector<unsigned>* packetDumpFlags; + + if (sending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + if (level < packetDumpFlags->size()) { + unsigned flag = 1 << (unsigned)type; + return flag & packetDumpFlags->at(level); + } + + return false; +} + +void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type, + bool sending, + UniquePtr<uint8_t[]>& packet, + size_t size) { + if (IsClosed()) { + return; + } + + if (!ShouldDumpPacket(level, type, sending)) { + return; + } + + // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere + // else? + AutoJSAPI jsapi; + if (!jsapi.Init(GetWindow())) { + return; + } + + JS::Rooted<JSObject*> jsobj( + jsapi.cx(), + JS::NewArrayBufferWithContents(jsapi.cx(), size, packet.release())); + + RootedSpiderMonkeyInterface<ArrayBuffer> arrayBuffer(jsapi.cx()); + if (!arrayBuffer.Init(jsobj)) { + return; + } + + JSErrorResult jrv; + mPCObserver->OnPacket(level, type, sending, arrayBuffer, jrv); +} + +bool PeerConnectionImpl::HostnameInPref(const char* aPref, nsIURI* aDocURI) { + auto HostInDomain = [](const nsCString& aHost, const nsCString& aPattern) { + int32_t patternOffset = 0; + int32_t hostOffset = 0; + + // Act on '*.' wildcard in the left-most position in a domain pattern. + if (StringBeginsWith(aPattern, nsCString("*."))) { + patternOffset = 2; + + // Ignore the lowest level sub-domain for the hostname. + hostOffset = aHost.FindChar('.') + 1; + + if (hostOffset <= 1) { + // Reject a match between a wildcard and a TLD or '.foo' form. + return false; + } + } + + nsDependentCString hostRoot(aHost, hostOffset); + return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset); + }; + + if (!aDocURI) { + return false; + } + + nsCString hostName; + aDocURI->GetAsciiHost(hostName); // normalize UTF8 to ASCII equivalent + nsCString domainList; + nsresult nr = Preferences::GetCString(aPref, domainList); + + if (NS_FAILED(nr)) { + return false; + } + + domainList.StripWhitespace(); + + if (domainList.IsEmpty() || hostName.IsEmpty()) { + return false; + } + + // Get UTF8 to ASCII domain name normalization service + nsresult rv; + nsCOMPtr<nsIIDNService> idnService = + do_GetService("@mozilla.org/network/idn-service;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + // Test each domain name in the comma separated list + // after converting from UTF8 to ASCII. Each domain + // must match exactly or have a single leading '*.' wildcard. + for (const nsACString& each : domainList.Split(',')) { + nsCString domainName; + rv = idnService->ConvertUTF8toACE(each, domainName); + if (NS_SUCCEEDED(rv)) { + if (HostInDomain(hostName, domainName)) { + return true; + } + } else { + NS_WARNING("Failed to convert UTF-8 host to ASCII"); + } + } + + return false; +} + +nsresult PeerConnectionImpl::EnablePacketDump(unsigned long level, + dom::mozPacketDumpType type, + bool sending) { + mPacketDumpEnabled = true; + std::vector<unsigned>* packetDumpFlags; + if (sending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)type; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (level >= packetDumpFlags->size()) { + packetDumpFlags->resize(level + 1); + } + + (*packetDumpFlags)[level] |= flag; + return NS_OK; +} + +nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level, + dom::mozPacketDumpType type, + bool sending) { + std::vector<unsigned>* packetDumpFlags; + if (sending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)type; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (level < packetDumpFlags->size()) { + (*packetDumpFlags)[level] &= ~flag; + } + + return NS_OK; +} + +void PeerConnectionImpl::StampTimecard(const char* aEvent) { + MOZ_ASSERT(NS_IsMainThread()); + STAMP_TIMECARD(mTimeCard, aEvent); +} + +NS_IMETHODIMP +PeerConnectionImpl::ReplaceTrackNoRenegotiation(TransceiverImpl& aTransceiver, + MediaStreamTrack* aWithTrack) { + PC_AUTO_ENTER_API_CALL(true); + + RefPtr<dom::MediaStreamTrack> oldSendTrack(aTransceiver.GetSendTrack()); + if (oldSendTrack) { + oldSendTrack->RemovePrincipalChangeObserver(this); + } + + nsresult rv = aTransceiver.UpdateSendTrack(aWithTrack); + + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Failed to update transceiver: %d", + static_cast<int>(rv)); + return rv; + } + + if (aWithTrack) { + aWithTrack->AddPrincipalChangeObserver(this); + PrincipalChanged(aWithTrack); + } + + if (aTransceiver.IsVideo()) { + // We update the media pipelines here so we can apply different codec + // settings for different sources (e.g. screensharing as opposed to camera.) + MediaSourceEnum oldSource = oldSendTrack + ? oldSendTrack->GetSource().GetMediaSource() + : MediaSourceEnum::Camera; + MediaSourceEnum newSource = aWithTrack + ? aWithTrack->GetSource().GetMediaSource() + : MediaSourceEnum::Camera; + if (oldSource != newSource) { + if (NS_WARN_IF(NS_FAILED(rv = aTransceiver.UpdateConduit()))) { + CSFLogError(LOGTAG, "Error Updating VideoConduit"); + return rv; + } + } + } + + return NS_OK; +} + +nsresult PeerConnectionImpl::CalculateFingerprint( + const std::string& 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; +} + +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; + } + if (!mMedia) { + CSFLogError(LOGTAG, "%s: called API with disposed mMedia", __FUNCTION__); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::Close() { + CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + CloseInt(); + // Uncount this connection as active on the inner window upon close. + if (mWindow && mActiveOnWindow) { + mWindow->RemovePeerConnection(); + mActiveOnWindow = false; + } + + return NS_OK; +} + +bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName) { + // fire an event to the DOM window if this is "ours" + bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false; + if (!result) { + return false; + } + + CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__, + static_cast<unsigned long long>(aPluginID)); + + 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; + + EventDispatcher::DispatchDOMEvent(mWindow, 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 + Telemetry::Accumulate(Telemetry::WEBRTC_RENEGOTIATIONS, + mJsepSession->GetNegotiations() - 1); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kAudio]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kAudio]); + // DataChannels appear in both Sending and Receiving + Telemetry::Accumulate(Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED, + mMaxSending[SdpMediaSection::MediaType::kApplication]); + // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel + // A/V = 3, A/V/D = 7, etc + uint32_t type = 0; + if (mMaxSending[SdpMediaSection::MediaType::kAudio] || + mMaxReceiving[SdpMediaSection::MediaType::kAudio]) { + type = kAudioTypeMask; + } + if (mMaxSending[SdpMediaSection::MediaType::kVideo] || + mMaxReceiving[SdpMediaSection::MediaType::kVideo]) { + type |= kVideoTypeMask; + } + if (mMaxSending[SdpMediaSection::MediaType::kApplication]) { + type |= kDataChannelTypeMask; + } + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_TYPE, type); + + MOZ_RELEASE_ASSERT(mWindow); + auto found = sCallDurationTimers.find(mWindow->WindowID()); + if (found != sCallDurationTimers.end()) { + found->second.UnregisterConnection(); + if (found->second.IsStopped()) { + sCallDurationTimers.erase(found); + } + } + mCallTelemEnded = true; +} + +nsresult PeerConnectionImpl::CloseInt() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + // We do this at the end of the call because we want to make sure we've waited + // for all trickle ICE candidates to come in; this can happen well after we've + // transitioned to connected. As a bonus, this allows us to detect race + // conditions where a stats dispatch happens right as the PC closes. + if (!mPrivateWindow) { + RecordLongtermICEStatistics(); + } + RecordEndOfCallTelemetry(); + CSFLogInfo(LOGTAG, + "%s: Closing PeerConnectionImpl %s; " + "ending call", + __FUNCTION__, mHandle.c_str()); + 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 + } + ShutdownMedia(); + + // DataConnection will need to stay alive until all threads/runnables exit + + return NS_OK; +} + +void PeerConnectionImpl::ShutdownMedia() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + if (!mMedia) return; + + // before we destroy references to local tracks, detach from them + for (RefPtr<TransceiverImpl>& transceiver : mMedia->GetTransceivers()) { + RefPtr<dom::MediaStreamTrack> track = transceiver->GetSendTrack(); + if (track) { + track->RemovePrincipalChangeObserver(this); + } + } + + // Forget the reference so that we can transfer it to + // SelfDestruct(). + mMedia.forget().take()->SelfDestruct(); +} + +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(); +} + +void PeerConnectionImpl::OnSetDescriptionSuccess(JsepSdpType sdpType, + bool remote) { + // Spec says we queue a task for all the stuff that ends up back in JS + auto newSignalingState = GetSignalingState(); + + mThread->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this), + newSignalingState, sdpType, remote] { + if (IsClosed()) { + return; + } + + if (HasMedia()) { + // Section 4.4.1.5 Set the RTCSessionDescription: + if (sdpType == mozilla::kJsepSdpRollback) { + // - step 4.5.10, type is rollback + mMedia->RollbackRTCDtlsTransports(); + } else if (!(remote && sdpType == mozilla::kJsepSdpOffer)) { + // - 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. + bool markAsStable = sdpType == kJsepSdpOffer && + mSignalingState == RTCSignalingState::Stable; + mMedia->UpdateRTCDtlsTransports(markAsStable); + } + } + + JSErrorResult jrv; + mPCObserver->SyncTransceivers(jrv); + if (NS_WARN_IF(jrv.Failed())) { + return; + } + mPendingRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPending); + mCurrentRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent); + mPendingLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPending); + mCurrentLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionCurrent); + mPendingOfferer = mJsepSession->IsPendingOfferer(); + mCurrentOfferer = mJsepSession->IsCurrentOfferer(); + if (newSignalingState != mSignalingState) { + mSignalingState = newSignalingState; + mPCObserver->OnStateChange(PCObserverStateType::SignalingState, jrv); + } + + // TODO: Spec says that we should do this even if JS closes the PC + // during the signalingstatechange event. We'd need to refactor a + // little here to make this possible. + if (remote && HasMedia()) { + dom::RTCRtpReceiver::StreamAssociationChanges changes; + for (const auto& transceiver : mMedia->GetTransceivers()) { + transceiver->Receiver()->UpdateStreams(&changes); + } + + for (const auto& track : changes.mTracksToMute) { + // This sets the muted state for track and all its clones. + static_cast<RemoteTrackSource&>(track->GetSource()).SetMuted(true); + } + + 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); + } + } + mPCObserver->FireTrackEvent(*trackEvent.mReceiver, streams, jrv); + } + + // TODO(Bug 1241291): Legacy event, remove eventually + for (const auto& stream : newStreams) { + mPCObserver->FireStreamEvent(*stream, jrv); + } + } + + mPCObserver->OnSetDescriptionSuccess(jrv); + })); + + // We do this after queueing the above task, to ensure that ICE state + // changes don't start happening before sRD finishes. + + // Did we just apply a local description? + if (!remote) { + // We'd like to handle this in PeerConnectionMedia::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 PeerConnectionMedia's stun addresses so they are + // regathered when PeerConnectionMedia::GatherIfReady is called. + if (mJsepSession->IsIceRestarting()) { + mMedia->ResetStunAddrsForIceRestart(); + } + mMedia->EnsureTransports(*mJsepSession); + } + + if (mJsepSession->GetState() != kJsepStateStable) { + return; // The rest of this stuff is done only when offer/answer is done + } + + // 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. + mMedia->UpdateTransports(*mJsepSession, mForceIceTcp); + if (NS_FAILED(mMedia->UpdateMediaPipelines())) { + CSFLogError(LOGTAG, "Error Updating MediaPipelines"); + NS_ASSERTION(false, + "Error Updating MediaPipelines in OnSetDescriptionSuccess()"); + // XXX what now? Not much we can do but keep going, without major + // restructuring + } + + if (sdpType != kJsepSdpRollback) { + InitializeDataChannel(); + mMedia->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]; + } + } +} + +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; +} + +bool PeerConnectionImpl::HasMedia() const { return mMedia; } + +PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) + : impl_(nullptr) { + if (!PeerConnectionCtx::isActive() || + (PeerConnectionCtx::GetInstance()->mPeerConnections.find(handle) == + PeerConnectionCtx::GetInstance()->mPeerConnections.end())) { + return; + } + + PeerConnectionImpl* impl = + PeerConnectionCtx::GetInstance()->mPeerConnections[handle]; + + if (!impl->media()) return; + + impl_ = impl; +} + +const RefPtr<MediaTransportHandler> PeerConnectionImpl::GetTransportHandler() + const { + return mTransportHandler; +} + +const std::string& PeerConnectionImpl::GetHandle() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mHandle; +} + +const std::string& PeerConnectionImpl::GetName() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mName; +} + +void PeerConnectionImpl::CandidateReady(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag) { + 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()); + 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; + nsresult res = mJsepSession->AddLocalIceCandidate( + candidate, transportId, ufrag, &level, &mid, &skipped); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(LOGTAG, + "Failed to incorporate local candidate into SDP:" + " res = %u, candidate = %s, transport-id = %s," + " error = %s", + static_cast<unsigned>(res), candidate.c_str(), + transportId.c_str(), errorString.c_str()); + return; + } + + if (skipped) { + CSFLogDebug(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); + CSFLogDebug(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) { + JSErrorResult rv; + mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), + ObString(candidate.c_str()), + ObString(ufrag.c_str()), rv); +} + +void PeerConnectionImpl::IceConnectionStateChange( + dom::RTCIceConnectionState domState) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(LOGTAG, "%s: %d", __FUNCTION__, static_cast<int>(domState)); + + if (domState == mIceConnectionState) { + // no work to be done since the states are the same. + // this can happen during ICE rollback situations. + return; + } + + mIceConnectionState = domState; + + // Uncount this connection as active on the inner window upon close. + if (mWindow && mActiveOnWindow && + mIceConnectionState == RTCIceConnectionState::Closed) { + mWindow->RemovePeerConnection(); + mActiveOnWindow = false; + } + + // Would be nice if we had a means of converting one of these dom enums + // to a string that wasn't almost as much text as this switch statement... + switch (mIceConnectionState) { + case RTCIceConnectionState::New: + STAMP_TIMECARD(mTimeCard, "Ice state: new"); + break; + case RTCIceConnectionState::Checking: + // For telemetry + mIceStartTime = TimeStamp::Now(); + STAMP_TIMECARD(mTimeCard, "Ice state: checking"); + break; + case RTCIceConnectionState::Connected: + STAMP_TIMECARD(mTimeCard, "Ice state: connected"); + StartCallTelem(); + break; + case RTCIceConnectionState::Completed: + STAMP_TIMECARD(mTimeCard, "Ice state: completed"); + break; + case RTCIceConnectionState::Failed: + STAMP_TIMECARD(mTimeCard, "Ice state: failed"); + break; + case RTCIceConnectionState::Disconnected: + STAMP_TIMECARD(mTimeCard, "Ice state: disconnected"); + break; + case RTCIceConnectionState::Closed: + STAMP_TIMECARD(mTimeCard, "Ice state: closed"); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!"); + } + + WrappableJSErrorResult rv; + mPCObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv); +} + +void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + if (!aCandidateInfo.mDefaultHostRtp.empty()) { + UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp, + aCandidateInfo.mDefaultPortRtp, + aCandidateInfo.mDefaultHostRtcp, + aCandidateInfo.mDefaultPortRtcp, aTransportId); + } + CandidateReady(aCandidateInfo.mCandidate, aTransportId, + aCandidateInfo.mUfrag); +} + +void PeerConnectionImpl::IceGatheringStateChange( + dom::RTCIceGatheringState state) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(LOGTAG, "%s %d", __FUNCTION__, static_cast<int>(state)); + if (mIceGatheringState == state) { + return; + } + + mIceGatheringState = state; + + // Would be nice if we had a means of converting one of these dom enums + // to a string that wasn't almost as much text as this switch statement... + switch (mIceGatheringState) { + case 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!"); + } + + JSErrorResult rv; + mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv); +} + +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); +} + +// TODO(bug 1616937): Move this to RTCRtpSender. +static UniquePtr<dom::RTCStatsCollection> GetSenderStats_s( + const RefPtr<MediaPipelineTransmit>& aPipeline, + const nsAutoString& aTrackName) { + UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection); + + // Add bandwidth estimation stats + aPipeline->Conduit()->GetBandwidthEstimation().apply([&](auto& bw) { + bw.mTrackIdentifier = aTrackName; + if (!report->mBandwidthEstimations.AppendElement(bw, fallible)) { + mozalloc_handle_oom(0); + } + }); + + auto asVideo = aPipeline->Conduit()->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast<uint32_t>(aPipeline->Level())); + + // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level + // This is pending spec work + // Gather pipeline stats. + nsString localId = u"outbound_rtp_"_ns + idstr; + nsString remoteId; + Maybe<uint32_t> ssrc; + std::vector<unsigned int> ssrcvals = aPipeline->Conduit()->GetLocalSSRCs(); + if (!ssrcvals.empty()) { + ssrc = Some(ssrcvals[0]); + } + { + // First, fill in remote stat with rtcp receiver data, if present. + // ReceiverReports have less information than SenderReports, + // so fill in what we can. + uint32_t jitterMs; + uint32_t packetsReceived; + uint64_t bytesReceived; + uint32_t packetsLost; + Maybe<double> rtt; + Maybe<DOMHighResTimeStamp> timestamp = + aPipeline->Conduit()->LastRtcpReceived(); + if (timestamp.isSome() && + aPipeline->Conduit()->GetRTCPReceiverReport( + &jitterMs, &packetsReceived, &bytesReceived, &packetsLost, &rtt)) { + remoteId = u"outbound_rtcp_"_ns + idstr; + RTCRemoteInboundRtpStreamStats s; + s.mTimestamp.Construct(*timestamp); + s.mId.Construct(remoteId); + s.mType.Construct(RTCStatsType::Remote_inbound_rtp); + ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); }); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); + s.mJitter.Construct(double(jitterMs) / 1000); + s.mLocalId.Construct(localId); + s.mPacketsReceived.Construct(packetsReceived); + s.mBytesReceived.Construct(bytesReceived); + s.mPacketsLost.Construct(packetsLost); + rtt.apply([&s](auto r) { s.mRoundTripTime.Construct(r); }); + if (!report->mRemoteInboundRtpStreamStats.AppendElement(s, fallible)) { + mozalloc_handle_oom(0); + } + } + } + // Then, fill in local side (with cross-link to remote only if present) + RTCOutboundRtpStreamStats s; + s.mTimestamp.Construct(aPipeline->GetNow()); + s.mId.Construct(localId); + s.mType.Construct(RTCStatsType::Outbound_rtp); + ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); }); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); + if (remoteId.Length()) { + s.mRemoteId.Construct(remoteId); + } + s.mPacketsSent.Construct(aPipeline->RtpPacketsSent()); + s.mBytesSent.Construct(aPipeline->RtpBytesSent()); + + // Fill in packet type statistics + webrtc::RtcpPacketTypeCounter counters; + if (aPipeline->Conduit()->GetSendPacketTypeStats(&counters)) { + s.mNackCount.Construct(counters.nack_packets); + // Fill in video only packet type stats + if (asVideo) { + s.mFirCount.Construct(counters.fir_packets); + s.mPliCount.Construct(counters.pli_packets); + } + } + + // Lastly, fill in video encoder stats, and bandwidth estimation if this is + // video + asVideo.apply([&s](auto conduit) { + double framerateMean; + double framerateStdDev; + double bitrateMean; + double bitrateStdDev; + uint32_t droppedFrames; + uint32_t framesEncoded; + Maybe<uint64_t> qpSum; + if (conduit->GetVideoEncoderStats(&framerateMean, &framerateStdDev, + &bitrateMean, &bitrateStdDev, + &droppedFrames, &framesEncoded, &qpSum)) { + s.mFramerateMean.Construct(framerateMean); + s.mFramerateStdDev.Construct(framerateStdDev); + s.mBitrateMean.Construct(bitrateMean); + s.mBitrateStdDev.Construct(bitrateStdDev); + s.mDroppedFrames.Construct(droppedFrames); + s.mFramesEncoded.Construct(framesEncoded); + qpSum.apply([&s](uint64_t aQp) { s.mQpSum.Construct(aQp); }); + } + }); + if (!report->mOutboundRtpStreamStats.AppendElement(s, fallible)) { + mozalloc_handle_oom(0); + } + return report; +} + +RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetSenderStats( + const RefPtr<MediaPipelineTransmit>& aPipeline) { + nsAutoString trackName; + auto track = aPipeline->GetTrack(); + if (track) { + track->GetId(trackName); + } + return InvokeAsync(mSTSThread, __func__, [aPipeline, trackName]() { + return dom::RTCStatsPromise::CreateAndResolve( + GetSenderStats_s(aPipeline, trackName), __func__); + }); +} + +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::RecordConduitTelemetry() { + if (!mMedia) { + return; + } + + nsTArray<RefPtr<VideoSessionConduit>> conduits; + for (const auto& transceiver : mMedia->GetTransceivers()) { + RefPtr<MediaSessionConduit> conduit = transceiver->GetConduit(); + if (conduit) { + auto asVideo = conduit->AsVideoSessionConduit(); + if (asVideo) { + conduits.AppendElement(asVideo.value()); + } + } + } + + mSTSThread->Dispatch( + NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)]() { + for (const auto& conduit : conduits) { + conduit->RecordTelemetry(); + } + })); +} + +template <class T> +void AssignWithOpaqueIds(dom::Sequence<T>& aSource, dom::Sequence<T>& aDest, + RefPtr<RTCStatsIdGenerator>& aGenerator) { + for (auto& stat : aSource) { + stat.mId.Value() = aGenerator->Id(stat.mId.Value()); + } + if (!aDest.AppendElements(aSource, fallible)) { + mozalloc_handle_oom(0); + } +} + +template <class T> +void RewriteRemoteIds(dom::Sequence<T>& aList, + RefPtr<RTCStatsIdGenerator>& aGenerator) { + for (auto& stat : aList) { + if (stat.mRemoteId.WasPassed()) { + stat.mRemoteId.Value() = aGenerator->Id(stat.mRemoteId.Value()); + } + } +} + +template <class T> +void RewriteLocalIds(dom::Sequence<T>& aList, + RefPtr<RTCStatsIdGenerator>& aGenerator) { + for (auto& stat : aList) { + if (stat.mLocalId.WasPassed()) { + stat.mLocalId.Value() = aGenerator->Id(stat.mLocalId.Value()); + } + } +} + +RefPtr<dom::RTCStatsReportPromise> PeerConnectionImpl::GetStats( + dom::MediaStreamTrack* aSelector, bool aInternalStats) { + nsTArray<RefPtr<dom::RTCStatsPromise>> promises; + DOMHighResTimeStamp now = mTimestampMaker.GetNow(); + + if (mMedia) { + nsTArray<RefPtr<MediaPipelineTransmit>> sendPipelines; + // Gather up pipelines from mMedia so they may be inspected on STS + // TODO(bug 1616937): Use RTCRtpSender for these instead. + mMedia->GetTransmitPipelinesMatching(aSelector, &sendPipelines); + if (!sendPipelines.Length()) { + CSFLogError(LOGTAG, "%s: Found no pipelines matching selector.", + __FUNCTION__); + } + + for (const auto& pipeline : sendPipelines) { + promises.AppendElement(GetSenderStats(pipeline)); + } + + for (const auto& transceiver : mMedia->GetTransceivers()) { + if (transceiver->Receiver()->HasTrack(aSelector)) { + // Right now, returns two promises; one for RTP/RTCP stats, and another + // for ICE stats. + promises.AppendElements(transceiver->Receiver()->GetStatsInternal()); + } + } + + // TODO(bug 1616937): We need to move this is RTCRtpSender, to make + // getStats on those objects work properly. It might be worth optimizing the + // null selector case, so we don't end up with bunches of copies of the same + // transport information in the final report. + if (aSelector) { + std::string transportId = + mMedia->GetTransportIdMatchingSendTrack(*aSelector); + if (!transportId.empty()) { + promises.AppendElement( + mTransportHandler->GetIceStats(transportId, now)); + } + } else { + promises.AppendElement(mTransportHandler->GetIceStats("", now)); + } + + promises.AppendElement(GetDataChannelStats(mDataConnection, now)); + } + + // 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()); + 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); + report->mLocalSdp.Construct( + NS_ConvertASCIItoUTF16(localDescription.c_str())); + report->mRemoteSdp.Construct( + NS_ConvertASCIItoUTF16(remoteDescription.c_str())); + 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(mThread, promises) + ->Then( + mThread, __func__, + [report = std::move(report), idGen = mIdGenerator]( + const nsTArray<UniquePtr<dom::RTCStatsCollection>>& + aStats) mutable { + // Rewrite an Optional id + auto rewriteId = [&idGen](Optional<nsString>& id) { + if (id.WasPassed()) { + id.Value() = idGen->Id(id.Value()); + } + }; + + // Involves a lot of copying, since webidl dictionaries don't have + // move semantics. Oh well. + for (const auto& stats : aStats) { + for (auto& stat : stats->mIceCandidatePairStats) { + rewriteId(stat.mLocalCandidateId); + rewriteId(stat.mRemoteCandidateId); + }; + AssignWithOpaqueIds(stats->mIceCandidatePairStats, + report->mIceCandidatePairStats, idGen); + + AssignWithOpaqueIds(stats->mIceCandidateStats, + report->mIceCandidateStats, idGen); + + RewriteRemoteIds(stats->mInboundRtpStreamStats, idGen); + AssignWithOpaqueIds(stats->mInboundRtpStreamStats, + report->mInboundRtpStreamStats, idGen); + + RewriteRemoteIds(stats->mOutboundRtpStreamStats, idGen); + AssignWithOpaqueIds(stats->mOutboundRtpStreamStats, + report->mOutboundRtpStreamStats, idGen); + + RewriteLocalIds(stats->mRemoteInboundRtpStreamStats, idGen); + AssignWithOpaqueIds(stats->mRemoteInboundRtpStreamStats, + report->mRemoteInboundRtpStreamStats, idGen); + + RewriteLocalIds(stats->mRemoteOutboundRtpStreamStats, idGen); + AssignWithOpaqueIds(stats->mRemoteOutboundRtpStreamStats, + report->mRemoteOutboundRtpStreamStats, idGen); + + AssignWithOpaqueIds(stats->mRtpContributingSourceStats, + report->mRtpContributingSourceStats, idGen); + AssignWithOpaqueIds(stats->mTrickledIceCandidateStats, + report->mTrickledIceCandidateStats, idGen); + AssignWithOpaqueIds(stats->mDataChannelStats, + report->mDataChannelStats, idGen); + if (!report->mRawLocalCandidates.AppendElements( + stats->mRawLocalCandidates, fallible) || + !report->mRawRemoteCandidates.AppendElements( + stats->mRawRemoteCandidates, fallible) || + !report->mVideoFrameHistories.AppendElements( + stats->mVideoFrameHistories, fallible) || + !report->mBandwidthEstimations.AppendElements( + stats->mBandwidthEstimations, 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); + } + } + return dom::RTCStatsReportPromise::CreateAndResolve( + std::move(report), __func__); + }, + [](nsresult rv) { + return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__); + }); +} + +void PeerConnectionImpl::RecordLongtermICEStatistics() { + WebrtcGlobalInformation::StoreLongTermICEStatistics(*this); +} + +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 call this from setConfiguration + if (aConfig.mIceServers.WasPassed()) { + for (const auto& server : aConfig.mIceServers.Value()) { + 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); + } + if (aConfig.mSdpSemantics.WasPassed()) { + mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value()); + } + } + } + mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy); + mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy); + mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty(); + mJsConfiguration.mCertificatesProvided = + aConfig.mCertificates.WasPassed() && + !aConfig.mCertificates.Value().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. + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_3, 1); +} + +std::map<uint64_t, PeerConnectionAutoTimer> + PeerConnectionImpl::sCallDurationTimers; +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h new file mode 100644 index 0000000000..39aaff2055 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h @@ -0,0 +1,642 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PEER_CONNECTION_IMPL_H_ +#define _PEER_CONNECTION_IMPL_H_ + +#include <string> +#include <vector> +#include <map> +#include <cmath> + +#include "prlock.h" +#include "mozilla/RefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIUUIDGenerator.h" +#include "nsIThread.h" +#include "mozilla/Mutex.h" + +// Work around nasty macro in webrtc/voice_engine/voice_engine_defines.h +#ifdef GetLastError +# undef GetLastError +#endif + +#include "jsep/JsepSession.h" +#include "jsep/JsepSessionImpl.h" +#include "sdp/SdpMediaSection.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move? +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "PrincipalChangeObserver.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/net/DataChannel.h" +#include "VideoUtils.h" +#include "VideoSegment.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/PeerIdentity.h" +#include "RTCStatsIdGenerator.h" +#include "RTCStatsReport.h" + +namespace test { +#ifdef USE_FAKE_PCOBSERVER +class AFakePCObserver; +#endif +} // namespace test + +class nsDOMDataChannel; + +namespace mozilla { +struct CandidateInfo; +class DataChannel; +class DtlsIdentity; +class MediaPipeline; +class MediaPipelineReceive; +class MediaPipelineTransmit; +class TransceiverImpl; + +namespace dom { +class RTCCertificate; +struct RTCConfiguration; +struct RTCRtpSourceEntry; +struct RTCIceServer; +struct RTCOfferOptions; +struct RTCRtpParameters; +class RTCRtpSender; +class MediaStreamTrack; + +#ifdef USE_FAKE_PCOBSERVER +typedef test::AFakePCObserver PeerConnectionObserver; +typedef const char* PCObserverString; +#else +class PeerConnectionObserver; +typedef NS_ConvertUTF8toUTF16 PCObserverString; +#endif +} // namespace dom +} // namespace mozilla + +#if defined(__cplusplus) && __cplusplus >= 201103L +typedef struct Timecard Timecard; +#else +# include "common/time_profiling/timecard.h" +#endif + +// To preserve blame, convert nsresult to ErrorResult with wrappers. These +// macros help declare wrappers w/function being wrapped when there are no +// differences. + +#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \ + NS_IMETHODIMP func(__VA_ARGS__); \ + void func(__VA_ARGS__, rv) + +#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \ + NS_IMETHODIMP func(__VA_ARGS__, resulttype** result); \ + already_AddRefed<resulttype> func(__VA_ARGS__, rv) + +namespace mozilla { + +using mozilla::DtlsIdentity; +using mozilla::ErrorResult; +using mozilla::PeerIdentity; +using mozilla::dom::PeerConnectionObserver; +using mozilla::dom::RTCConfiguration; +using mozilla::dom::RTCIceServer; +using mozilla::dom::RTCOfferOptions; + +class PeerConnectionWrapper; +class PeerConnectionMedia; +class RemoteSourceStreamInfo; + +// Uuid Generator +class PCUuidGenerator : public mozilla::JsepUuidGenerator { + public: + virtual bool Generate(std::string* idp) override; + + private: + nsCOMPtr<nsIUUIDGenerator> mGenerator; +}; + +// This is a variation of Telemetry::AutoTimer that keeps a reference +// count and records the elapsed time when the count falls to zero. The +// elapsed time is recorded in seconds. +struct PeerConnectionAutoTimer { + PeerConnectionAutoTimer() : mRefCnt(0), mStart(TimeStamp::Now()){}; + void RegisterConnection(); + void UnregisterConnection(); + bool IsStopped(); + + private: + int64_t mRefCnt; + TimeStamp mStart; +}; + +// Enter an API call and check that the state is OK, +// the PC isn't closed, etc. +#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return res; \ + } while (0) +#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return; \ + } while (0) +#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread() + +class PeerConnectionImpl final + : public nsISupports, + public mozilla::DataChannelConnection::DataConnectionListener, + public dom::PrincipalChangeObserver<dom::MediaStreamTrack> { + struct Internal; // Avoid exposing c includes to bindings + + public: + explicit PeerConnectionImpl( + const mozilla::dom::GlobalObject* aGlobal = nullptr); + + NS_DECL_THREADSAFE_ISUPPORTS + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector); + + static already_AddRefed<PeerConnectionImpl> Constructor( + const mozilla::dom::GlobalObject& aGlobal); + + // DataConnection observers + void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel) + // PeerConnectionImpl only inherits from mozilla::DataChannelConnection + // inside libxul. + override; + + const RefPtr<MediaTransportHandler> GetTransportHandler() const; + + // Get the media object + const RefPtr<PeerConnectionMedia>& media() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mMedia; + } + + // Handle system to allow weak references to be passed through C code + virtual const std::string& GetHandle(); + + // Name suitable for exposing to content + virtual const std::string& GetName(); + + // ICE events + void IceConnectionStateChange(dom::RTCIceConnectionState state); + void IceGatheringStateChange(dom::RTCIceGatheringState state); + void OnCandidateFound(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + void UpdateDefaultCandidate(const std::string& defaultAddr, + uint16_t defaultPort, + const std::string& defaultRtcpAddr, + uint16_t defaultRtcpPort, + const std::string& transportId); + + static void ListenThread(void* aData); + static void ConnectThread(void* aData); + + // Get the main thread + nsCOMPtr<nsIThread> GetMainThread() { return mThread; } + + // Get the STS thread + nsISerialEventTarget* GetSTSThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mSTSThread; + } + + nsPIDOMWindowInner* GetWindow() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mWindow; + } + + nsresult Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow, + const RTCConfiguration& aConfiguration, + nsISupports* aThread); + + // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint) + void Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, + const RTCConfiguration& aConfiguration, nsISupports* aThread, + ErrorResult& rv); + + void SetCertificate(mozilla::dom::RTCCertificate& aCertificate); + const RefPtr<mozilla::dom::RTCCertificate>& Certificate() const; + // This is a hack to support external linkage. + RefPtr<DtlsIdentity> Identity() const; + + NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult& rv, + const RTCOfferOptions& aOptions) { + rv = CreateOffer(aOptions); + } + + NS_IMETHODIMP CreateAnswer(); + void CreateAnswer(ErrorResult& rv) { rv = CreateAnswer(); } + + NS_IMETHODIMP CreateOffer(const mozilla::JsepOfferOptions& aConstraints); + + NS_IMETHODIMP SetLocalDescription(int32_t aAction, const char* aSDP); + + void SetLocalDescription(int32_t aAction, const nsAString& aSDP, + ErrorResult& rv) { + rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + NS_IMETHODIMP SetRemoteDescription(int32_t aAction, const char* aSDP); + + void SetRemoteDescription(int32_t aAction, const nsAString& aSDP, + ErrorResult& rv) { + rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + already_AddRefed<dom::Promise> GetStats(dom::MediaStreamTrack* aSelector); + + void GetRemoteStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const; + + NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid, + const char* aUfrag, + const dom::Nullable<unsigned short>& aLevel); + + void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid, + const nsAString& aUfrag, + const dom::Nullable<unsigned short>& aLevel, + ErrorResult& rv) { + rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(), + NS_ConvertUTF16toUTF8(aMid).get(), + NS_ConvertUTF16toUTF8(aUfrag).get(), aLevel); + } + + void UpdateNetworkState(bool online); + + NS_IMETHODIMP CloseStreams(); + + void CloseStreams(ErrorResult& rv) { rv = CloseStreams(); } + + already_AddRefed<TransceiverImpl> CreateTransceiverImpl( + const nsAString& aKind, dom::MediaStreamTrack* aSendTrack, + ErrorResult& rv); + + bool CheckNegotiationNeeded(ErrorResult& rv); + + NS_IMETHODIMP_TO_ERRORRESULT(ReplaceTrackNoRenegotiation, ErrorResult& rv, + TransceiverImpl& aTransceiver, + mozilla::dom::MediaStreamTrack* aWithTrack) { + rv = ReplaceTrackNoRenegotiation(aTransceiver, aWithTrack); + } + + // test-only + NS_IMETHODIMP_TO_ERRORRESULT(EnablePacketDump, ErrorResult& rv, + unsigned long level, dom::mozPacketDumpType type, + bool sending) { + rv = EnablePacketDump(level, type, sending); + } + + // test-only + NS_IMETHODIMP_TO_ERRORRESULT(DisablePacketDump, ErrorResult& rv, + unsigned long level, dom::mozPacketDumpType type, + bool sending) { + rv = DisablePacketDump(level, type, sending); + } + + void GetPeerIdentity(nsAString& peerIdentity) { + if (mPeerIdentity) { + peerIdentity = mPeerIdentity->ToString(); + return; + } + + peerIdentity.SetIsVoid(true); + } + + const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; } + NS_IMETHODIMP_TO_ERRORRESULT(SetPeerIdentity, ErrorResult& rv, + const nsAString& peerIdentity) { + rv = SetPeerIdentity(peerIdentity); + } + + const std::string& GetIdAsAscii() const { return mName; } + + void GetId(nsAString& id) { id = NS_ConvertASCIItoUTF16(mName.c_str()); } + + void SetId(const nsAString& id) { mName = NS_ConvertUTF16toUTF8(id).get(); } + + // this method checks to see if we've made a promise to protect media. + bool PrivacyRequested() const { + return mPrivacyRequested.isSome() && *mPrivacyRequested; + } + + bool PrivacyNeeded() const { + return mPrivacyRequested.isSome() && *mPrivacyRequested; + } + + NS_IMETHODIMP GetFingerprint(char** fingerprint); + void GetFingerprint(nsAString& fingerprint) { + char* tmp; + nsresult rv = GetFingerprint(&tmp); + NS_ENSURE_SUCCESS_VOID(rv); + fingerprint.AssignASCII(tmp); + delete[] tmp; + } + + void GetCurrentLocalDescription(nsAString& aSDP) const; + void GetPendingLocalDescription(nsAString& aSDP) const; + + void GetCurrentRemoteDescription(nsAString& aSDP) const; + void GetPendingRemoteDescription(nsAString& aSDP) const; + + dom::Nullable<bool> GetCurrentOfferer() const; + dom::Nullable<bool> GetPendingOfferer() const; + + NS_IMETHODIMP SignalingState(mozilla::dom::RTCSignalingState* aState); + + mozilla::dom::RTCSignalingState SignalingState() { + mozilla::dom::RTCSignalingState state; + SignalingState(&state); + return state; + } + + NS_IMETHODIMP IceConnectionState(mozilla::dom::RTCIceConnectionState* aState); + + mozilla::dom::RTCIceConnectionState IceConnectionState() { + mozilla::dom::RTCIceConnectionState state; + IceConnectionState(&state); + return state; + } + + NS_IMETHODIMP IceGatheringState(mozilla::dom::RTCIceGatheringState* aState); + + mozilla::dom::RTCIceGatheringState IceGatheringState() { + return mIceGatheringState; + } + + NS_IMETHODIMP Close(); + + void Close(ErrorResult& rv) { rv = Close(); } + + bool PluginCrash(uint32_t aPluginID, const nsAString& aPluginName); + + void RecordEndOfCallTelemetry(); + + nsresult InitializeDataChannel(); + + NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, CreateDataChannel, + ErrorResult& rv, const nsAString& aLabel, + const nsAString& aProtocol, + uint16_t aType, bool outOfOrderAllowed, + uint16_t aMaxTime, uint16_t aMaxNum, + bool aExternalNegotiated, + uint16_t aStream); + + // Gets the RTC Signaling State of the JSEP session + dom::RTCSignalingState GetSignalingState() const; + + void OnSetDescriptionSuccess(JsepSdpType sdpType, bool remote); + + bool IsClosed() const; + // called when DTLS connects; we only need this once + nsresult OnAlpnNegotiated(bool aPrivacyRequested); + + bool HasMedia() const; + + // initialize telemetry for when calls start + void StartCallTelem(); + + RefPtr<dom::RTCStatsReportPromise> GetStats(dom::MediaStreamTrack* aSelector, + bool aInternalStats); + + void RecordConduitTelemetry(); + + // for monitoring changes in track ownership + // PeerConnectionMedia can't do it because it doesn't know about principals + virtual void PrincipalChanged(dom::MediaStreamTrack* aTrack) override; + + void OnMediaError(const std::string& aError); + + bool ShouldDumpPacket(size_t level, dom::mozPacketDumpType type, + bool sending) const; + + void DumpPacket_m(size_t level, dom::mozPacketDumpType type, bool sending, + UniquePtr<uint8_t[]>& packet, size_t size); + + const dom::RTCStatsTimestampMaker& GetTimestampMaker() const { + return mTimestampMaker; + } + + // Utility function, given a string pref and an URI, returns whether or not + // the URI occurs in the pref. Wildcards are supported (e.g. *.example.com) + // and multiple hostnames can be present, separated by commas. + static bool HostnameInPref(const char* aPrefList, nsIURI* aDocURI); + + void StampTimecard(const char* aEvent); + + private: + virtual ~PeerConnectionImpl(); + PeerConnectionImpl(const PeerConnectionImpl& rhs); + PeerConnectionImpl& operator=(PeerConnectionImpl); + + RefPtr<dom::RTCStatsPromise> GetReceiverStats( + const RefPtr<MediaPipelineReceive>& aPipeline); + RefPtr<dom::RTCStatsPromise> GetSenderStats( + const RefPtr<MediaPipelineTransmit>& aPipeline); + RefPtr<dom::RTCStatsPromise> GetDataChannelStats( + const RefPtr<DataChannelConnection>& aDataChannelConnection, + const DOMHighResTimeStamp aTimestamp); + nsresult CalculateFingerprint(const std::string& algorithm, + std::vector<uint8_t>* fingerprint) const; + nsresult ConfigureJsepSessionCodecs(); + + NS_IMETHODIMP EnsureDataConnection(uint16_t aLocalPort, uint16_t aNumstreams, + uint32_t aMaxMessageSize, bool aMMSSet); + + nsresult CloseInt(); + nsresult CheckApiState(bool assert_ice_ready) const; + void CheckThread() const { MOZ_ASSERT(CheckThreadInt(), "Wrong thread"); } + bool CheckThreadInt() const { + bool on; + NS_ENSURE_SUCCESS(mThread->IsOnCurrentThread(&on), false); + NS_ENSURE_TRUE(on, false); + return true; + } + + // test-only: called from AddRIDExtension and AddRIDFilter + // for simulcast mochitests. + RefPtr<MediaPipeline> GetMediaPipelineForTrack( + dom::MediaStreamTrack& aRecvTrack); + + // Shut down media - called on main thread only + void ShutdownMedia(); + + void CandidateReady(const std::string& candidate, + const std::string& transportId, const std::string& ufrag); + void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid, + const std::string& candidate, + const std::string& ufrag); + + nsresult GetDatachannelParameters(uint32_t* channels, uint16_t* localport, + uint16_t* remoteport, + uint32_t* maxmessagesize, bool* mmsset, + std::string* transportId, + bool* client) const; + + nsresult AddRtpTransceiverToJsepSession(RefPtr<JsepTransceiver>& transceiver); + already_AddRefed<TransceiverImpl> CreateTransceiverImpl( + JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack, + ErrorResult& aRv); + + // When ICE completes, we record a bunch of statistics that outlive the + // PeerConnection. This is just telemetry right now, but this can also + // include things like dumping the RLogConnector somewhere, saving away + // an RTCStatsReport somewhere so it can be inspected after the call is over, + // or other things. + void RecordLongtermICEStatistics(); + + void RecordIceRestartStatistics(JsepSdpType type); + + void StoreConfigurationForAboutWebrtc(const RTCConfiguration& aConfig); + + dom::Sequence<dom::RTCSdpParsingErrorInternal> GetLastSdpParsingErrors() + const; + // Timecard used to measure processing time. This should be the first class + // attribute so that we accurately measure the time required to instantiate + // any other attributes of this class. + Timecard* mTimeCard; + + // Configuration used to initialize the PeerConnection + dom::RTCConfigurationInternal mJsConfiguration; + + mozilla::dom::RTCSignalingState mSignalingState; + + // ICE State + mozilla::dom::RTCIceConnectionState mIceConnectionState; + mozilla::dom::RTCIceGatheringState mIceGatheringState; + + nsCOMPtr<nsIThread> mThread; + RefPtr<PeerConnectionObserver> mPCObserver; + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + + // The SDP sent in from JS + std::string mLocalRequestedSDP; + std::string mRemoteRequestedSDP; + // Only accessed from main + mozilla::dom::Sequence<mozilla::dom::RTCSdpHistoryEntryInternal> mSdpHistory; + std::string mPendingLocalDescription; + std::string mPendingRemoteDescription; + std::string mCurrentLocalDescription; + std::string mCurrentRemoteDescription; + Maybe<bool> mPendingOfferer; + Maybe<bool> mCurrentOfferer; + + // DTLS fingerprint + std::string mFingerprint; + std::string mRemoteFingerprint; + + // identity-related fields + // The entity on the other end of the peer-to-peer connection; + // void if they are not yet identified, and no identity setting has been set + RefPtr<PeerIdentity> mPeerIdentity; + // The certificate we are using. + RefPtr<mozilla::dom::RTCCertificate> mCertificate; + // Whether an app should be prevented from accessing media produced by the PC + // If this is true, then media will not be sent until mPeerIdentity matches + // local streams PeerIdentity; and remote streams are protected from content + // + // This can be false if mPeerIdentity is set, in the case where identity is + // provided, but the media is not protected from the app on either side + Maybe<bool> mPrivacyRequested; + + // A handle to refer to this PC with + std::string mHandle; + + // A name for this PC that we are willing to expose to content. + std::string mName; + + // The target to run stuff on + nsCOMPtr<nsISerialEventTarget> mSTSThread; + + // DataConnection that's used to get all the DataChannels + RefPtr<mozilla::DataChannelConnection> mDataConnection; + + bool mForceIceTcp; + RefPtr<PeerConnectionMedia> mMedia; + RefPtr<MediaTransportHandler> mTransportHandler; + + // The JSEP negotiation session. + mozilla::UniquePtr<PCUuidGenerator> mUuidGen; + mozilla::UniquePtr<mozilla::JsepSession> mJsepSession; + unsigned long mIceRestartCount; + unsigned long mIceRollbackCount; + + // The following are used for Telemetry: + bool mCallTelemStarted = false; + bool mCallTelemEnded = false; + + // Start time of ICE. + mozilla::TimeStamp mIceStartTime; + // Hold PeerConnectionAutoTimer instances for each window. + static std::map<uint64_t, PeerConnectionAutoTimer> sCallDurationTimers; + + bool mHaveConfiguredCodecs; + + bool mTrickle; + + bool mPrivateWindow; + + // Whether this PeerConnection is being counted as active by mWindow + bool mActiveOnWindow; + + // storage for Telemetry data + uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes]; + uint16_t mMaxSending[SdpMediaSection::kMediaTypes]; + + std::vector<unsigned> mSendPacketDumpFlags; + std::vector<unsigned> mRecvPacketDumpFlags; + Atomic<bool> mPacketDumpEnabled; + mutable Mutex mPacketDumpFlagsMutex; + + // used to store the raw trickle candidate string for display + // on the about:webrtc raw candidates table. + std::vector<std::string> mRawTrickledCandidates; + + dom::RTCStatsTimestampMaker mTimestampMaker; + + RefPtr<RTCStatsIdGenerator> mIdGenerator; + // Ordinarily, I would use a std::map here, but this used to be a JS Map + // which iterates in insertion order, and I want to avoid changing this. + nsTArray<RefPtr<DOMMediaStream>> mReceiveStreams; + + DOMMediaStream* GetReceiveStream(const std::string& aId) const; + DOMMediaStream* CreateReceiveStream(const std::string& aId); + + // See Bug 1642419, this can be removed when all sites are working with RTX. + bool mRtxIsAllowed = true; + + public: + // these are temporary until the DataChannel Listen/Connect API is removed + unsigned short listenPort; + unsigned short connectPort; + char* connectStr; // XXX ownership/free +}; + +// This is what is returned when you acquire on a handle +class PeerConnectionWrapper { + public: + explicit PeerConnectionWrapper(const std::string& handle); + + PeerConnectionImpl* impl() { return impl_; } + + private: + RefPtr<PeerConnectionImpl> impl_; +}; + +} // namespace mozilla + +#undef NS_IMETHODIMP_TO_ERRORRESULT +#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF +#endif // _PEER_CONNECTION_IMPL_H_ diff --git a/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp b/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp new file mode 100644 index 0000000000..eb3b6df173 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionMedia.cpp @@ -0,0 +1,891 @@ +/* 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 "common/browser_logging/CSFLog.h" + +#include "transport/nr_socket_proxy_config.h" +#include "transportbridge/MediaPipelineFilter.h" +#include "transportbridge/MediaPipeline.h" +#include "PeerConnectionImpl.h" +#include "PeerConnectionMedia.h" +#include "RTCDtlsTransport.h" +#include "transport/runnable_utils.h" +#include "jsep/JsepSession.h" +#include "jsep/JsepTransport.h" + +#include "nsContentUtils.h" +#include "nsIIDNService.h" +#include "nsILoadInfo.h" +#include "nsIProxyInfo.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "nsIProxiedChannel.h" + +#include "nsIScriptGlobalObject.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/Document.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/WebrtcProxyConfig.h" +#include "MediaManager.h" +#include "libwebrtcglue/WebrtcGmpVideoCodec.h" + +namespace mozilla { +using namespace dom; + +static const char* pcmLogTag = "PeerConnectionMedia"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pcmLogTag + +void PeerConnectionMedia::StunAddrsHandler::OnMDNSQueryComplete( + const nsCString& hostname, const Maybe<nsCString>& address) { + ASSERT_ON_THREAD(pcm_->mMainThread); + auto itor = pcm_->mQueriedMDNSHostnames.find(hostname.BeginReading()); + if (itor != pcm_->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(); + pcm_->mParent->StampTimecard("Done looking up mDNS name"); + pcm_->mTransportHandler->AddIceCandidate( + cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr); + } + } else { + pcm_->mParent->StampTimecard("Failed looking up mDNS name"); + } + pcm_->mQueriedMDNSHostnames.erase(itor); + } +} + +void PeerConnectionMedia::StunAddrsHandler::OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) { + CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__, + (int)addrs.Length()); + if (pcm_) { + pcm_->mStunAddrs = addrs.Clone(); + pcm_->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + pcm_->FlushIceCtxOperationQueueIfReady(); + // If parent process returns 0 STUN addresses, change ICE connection + // state to failed. + if (!pcm_->mStunAddrs.Length()) { + pcm_->IceConnectionStateChange_m(dom::RTCIceConnectionState::Failed); + } + } +} + +PeerConnectionMedia::PeerConnectionMedia(PeerConnectionImpl* parent) + : mTransportHandler(parent->GetTransportHandler()), + mParent(parent), + mParentHandle(parent->GetHandle()), + mParentName(parent->GetName()), + mMainThread(mParent->GetMainThread()), + mSTSThread(mParent->GetSTSThread()), + mForceProxy(false), + mStunAddrsRequest(nullptr), + mLocalAddrsRequestState(STUN_ADDR_REQUEST_NONE), + mTargetForDefaultLocalAddressLookupIsSet(false), + mDestroyed(false) { + if (XRE_IsContentProcess()) { + nsCOMPtr<nsISerialEventTarget> target = + mParent->GetWindow() + ? mParent->GetWindow()->EventTargetFor(TaskCategory::Other) + : nullptr; + + mStunAddrsRequest = + new net::StunAddrsRequestChild(new StunAddrsHandler(this), target); + } +} + +PeerConnectionMedia::~PeerConnectionMedia() { + MOZ_RELEASE_ASSERT(!mMainThread); +} + +void PeerConnectionMedia::InitLocalAddrs() { + if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) { + return; + } + if (mStunAddrsRequest) { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING; + mStunAddrsRequest->SendGetStunAddrs(); + } else { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + } +} + +bool PeerConnectionMedia::ShouldForceProxy() const { + if (Preferences::GetBool("media.peerconnection.ice.proxy_only", 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; + } + + nsCOMPtr<nsIProxiedChannel> proxiedChannel = + do_QueryInterface(httpChannelInternal); + if (!proxiedChannel) { + return false; + } + + nsCOMPtr<nsIProxyInfo> proxyInfo; + proxiedChannel->GetProxyInfo(getter_AddRefs(proxyInfo)); + if (!proxyInfo) { + return false; + } + + nsCString proxyType; + proxyInfo->GetType(proxyType); + + return !proxyType.IsEmpty() && !proxyType.EqualsLiteral("direct"); +} + +nsresult PeerConnectionMedia::Init() { + mForceProxy = ShouldForceProxy(); + + // setup the stun local addresses IPC async call + InitLocalAddrs(); + + ConnectSignals(); + return NS_OK; +} + +void PeerConnectionMedia::EnsureTransports(const JsepSession& aSession) { + for (const auto& [id, transceiver] : aSession.GetTransceivers()) { + (void)id; // Lame, but no better way to do this right now. + if (transceiver->HasOwnTransport()) { + mTransportHandler->EnsureProvisionalTransport( + transceiver->mTransport.mTransportId, + transceiver->mTransport.mLocalUfrag, + transceiver->mTransport.mLocalPwd, + transceiver->mTransport.mComponents); + } + } + + GatherIfReady(); +} + +void PeerConnectionMedia::UpdateRTCDtlsTransports(bool aMarkAsStable) { + for (auto& transceiver : mTransceivers) { + std::string transportId = transceiver->GetTransportId(); + if (transportId.empty()) { + continue; + } + if (!mTransportIdToRTCDtlsTransport.count(transportId)) { + mTransportIdToRTCDtlsTransport.emplace( + transportId, new RTCDtlsTransport(transceiver->GetParentObject())); + } + + transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId], + aMarkAsStable); + } +} + +void PeerConnectionMedia::RollbackRTCDtlsTransports() { + for (auto& transceiver : mTransceivers) { + transceiver->RollbackToStableDtlsTransport(); + } +} + +void PeerConnectionMedia::RemoveRTCDtlsTransportsExcept( + const std::set<std::string>& aTransportIds) { + for (auto iter = mTransportIdToRTCDtlsTransport.begin(); + iter != mTransportIdToRTCDtlsTransport.end();) { + if (!aTransportIds.count(iter->first)) { + iter = mTransportIdToRTCDtlsTransport.erase(iter); + } else { + ++iter; + } + } +} + +nsresult PeerConnectionMedia::UpdateTransports(const JsepSession& aSession, + const bool forceIceTcp) { + std::set<std::string> finalTransports; + for (const auto& [id, transceiver] : aSession.GetTransceivers()) { + (void)id; // Lame, but no better way to do this right now. + if (transceiver->HasOwnTransport()) { + finalTransports.insert(transceiver->mTransport.mTransportId); + UpdateTransport(*transceiver, forceIceTcp); + } + } + + // clean up the unused RTCDtlsTransports + RemoveRTCDtlsTransportsExcept(finalTransports); + + mTransportHandler->RemoveTransportsExcept(finalTransports); + + for (const auto& transceiverImpl : mTransceivers) { + transceiverImpl->UpdateTransport(); + } + + return NS_OK; +} + +void PeerConnectionMedia::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", + mParentHandle.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 = mParent->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) { + std::ostringstream ss; + ss << fingerprint.hashFunc; + digests.emplace_back(ss.str(), fingerprint.fingerprint); + } + + mTransportHandler->ActivateTransport( + transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd, + components, ufrag, pwd, keyDer, certDer, mParent->Identity()->auth_type(), + transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests, + mParent->PrivacyRequested()); + + for (auto& candidate : candidates) { + AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag); + } +} + +nsresult PeerConnectionMedia::UpdateMediaPipelines() { + // The GMP code is all the way on the other side of webrtc.org, and it is not + // feasible to plumb error information all the way back. So, we set up a + // handle to the PC (for the duration of this call) in a global variable. + // This allows the GMP code to report errors to the PC. + WebrtcGmpPCHandleSetter setter(mParentHandle); + + for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + transceiver->ResetSync(); + } + + for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + if (!transceiver->IsVideo()) { + nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsresult rv = transceiver->UpdateConduit(); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +void PeerConnectionMedia::StartIceChecks(const JsepSession& aSession) { + ASSERT_ON_THREAD(mMainThread); + + 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 PeerConnectionMedia::GetPrefDefaultAddressOnly() const { + ASSERT_ON_THREAD(mMainThread); // will crash on STS thread + + uint64_t winId = mParent->GetWindow()->WindowID(); + + bool default_address_only = Preferences::GetBool( + "media.peerconnection.ice.default_address_only", false); + default_address_only |= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + return default_address_only; +} + +bool PeerConnectionMedia::GetPrefObfuscateHostAddresses() const { + ASSERT_ON_THREAD(mMainThread); // will crash on STS thread + + uint64_t winId = mParent->GetWindow()->WindowID(); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + obfuscate_host_addresses &= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + obfuscate_host_addresses &= !PeerConnectionImpl::HostnameInPref( + "media.peerconnection.ice.obfuscate_host_addresses.blocklist", + mParent->GetWindow()->GetDocumentURI()); + obfuscate_host_addresses &= XRE_IsContentProcess(); + + return obfuscate_host_addresses; +} + +void PeerConnectionMedia::ConnectSignals() { + mTransportHandler->SignalGatheringStateChange.connect( + this, &PeerConnectionMedia::IceGatheringStateChange_s); + mTransportHandler->SignalConnectionStateChange.connect( + this, &PeerConnectionMedia::IceConnectionStateChange_s); + mTransportHandler->SignalCandidate.connect( + this, &PeerConnectionMedia::OnCandidateFound_s); + mTransportHandler->SignalAlpnNegotiated.connect( + this, &PeerConnectionMedia::AlpnNegotiated_s); +} + +void PeerConnectionMedia::AddIceCandidate(const std::string& aCandidate, + const std::string& aTransportId, + const std::string& aUfrag) { + ASSERT_ON_THREAD(mMainThread); + MOZ_ASSERT(!aTransportId.empty()); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + + if (obfuscate_host_addresses) { + 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); + + mMainThread->Dispatch(NS_NewRunnableFunction( + "PeerConnectionMedia::SendQueryMDNSHostname", + [self = RefPtr<PeerConnectionMedia>(this), addr]() mutable { + if (self->mStunAddrsRequest) { + self->mParent->StampTimecard("Look up mDNS name"); + self->mStunAddrsRequest->SendQueryMDNSHostname( + nsCString(nsAutoCString(addr.c_str()))); + } + NS_ReleaseOnMainThread( + "PeerConnectionMedia::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 PeerConnectionMedia::UpdateNetworkState(bool online) { + mTransportHandler->UpdateNetworkState(online); +} + +void PeerConnectionMedia::FlushIceCtxOperationQueueIfReady() { + ASSERT_ON_THREAD(mMainThread); + + if (IsIceCtxReady()) { + for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) { + queuedIceCtxOperation->Run(); + } + mQueuedIceCtxOperations.clear(); + } +} + +void PeerConnectionMedia::PerformOrEnqueueIceCtxOperation( + nsIRunnable* runnable) { + ASSERT_ON_THREAD(mMainThread); + + if (IsIceCtxReady()) { + runnable->Run(); + } else { + mQueuedIceCtxOperations.push_back(runnable); + } +} + +void PeerConnectionMedia::GatherIfReady() { + ASSERT_ON_THREAD(mMainThread); + + // 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<PeerConnectionMedia>(this), + &PeerConnectionMedia::EnsureIceGathering, GetPrefDefaultAddressOnly(), + GetPrefObfuscateHostAddresses())); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +already_AddRefed<nsIHttpChannelInternal> PeerConnectionMedia::GetChannel() + const { + Document* doc = mParent->GetWindow()->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 PeerConnectionMedia::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 PeerConnectionMedia::EnsureIceGathering(bool aDefaultRouteOnly, + bool aObfuscateHostAddresses) { + 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)); + } + + 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); +} + +void PeerConnectionMedia::SelfDestruct() { + ASSERT_ON_THREAD(mMainThread); + + CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__); + + mDestroyed = true; + + if (mStunAddrsRequest) { + for (auto& hostname : mRegisteredMDNSHostnames) { + mStunAddrsRequest->SendUnregisterMDNSHostname( + nsCString(hostname.c_str())); + } + mRegisteredMDNSHostnames.clear(); + mStunAddrsRequest->Cancel(); + mStunAddrsRequest = nullptr; + } + + for (auto& transceiver : mTransceivers) { + // transceivers are garbage-collected, so we need to poke them to perform + // cleanup right now so the appropriate events fire. + transceiver->Shutdown_m(); + } + + mTransceivers.clear(); + + mQueuedIceCtxOperations.clear(); + + // Shutdown the transport (async) + RUN_ON_THREAD( + mSTSThread, + WrapRunnable(this, &PeerConnectionMedia::ShutdownMediaTransport_s), + NS_DISPATCH_NORMAL); + mParent = nullptr; + + CSFLogDebug(LOGTAG, "%s: Media shut down", __FUNCTION__); +} + +void PeerConnectionMedia::SelfDestruct_m() { + CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__); + + ASSERT_ON_THREAD(mMainThread); + + mTransportHandler->RemoveTransportsExcept(std::set<std::string>()); + mTransportHandler = nullptr; + + mMainThread = nullptr; + + // Final self-destruct. + this->Release(); +} + +void PeerConnectionMedia::ShutdownMediaTransport_s() { + ASSERT_ON_THREAD(mSTSThread); + + CSFLogDebug(LOGTAG, "%s: ", __FUNCTION__); + + disconnect_all(); + + // we're holding a ref to 'this' that's released by SelfDestruct_m + mMainThread->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::SelfDestruct_m), + NS_DISPATCH_NORMAL); +} + +nsresult PeerConnectionMedia::AddTransceiver( + JsepTransceiver* aJsepTransceiver, dom::MediaStreamTrack* aSendTrack, + RefPtr<TransceiverImpl>* aTransceiverImpl) { + if (!mCall) { + mCall = WebRtcCallWrapper::Create(mParent->GetTimestampMaker()); + } + + RefPtr<TransceiverImpl> transceiver = new TransceiverImpl( + mParent->GetWindow(), mParent->PrivacyNeeded(), mParent->GetHandle(), + mTransportHandler, aJsepTransceiver, mMainThread.get(), mSTSThread.get(), + aSendTrack, mCall.get()); + + if (!transceiver->IsValid()) { + return NS_ERROR_FAILURE; + } + + if (aSendTrack) { + // implement checking for peerIdentity (where failure == black/silence) + Document* doc = mParent->GetWindow()->GetExtantDoc(); + if (doc) { + transceiver->UpdateSinkIdentity(nullptr, doc->NodePrincipal(), + mParent->GetPeerIdentity()); + } else { + MOZ_CRASH(); + return NS_ERROR_FAILURE; // Don't remove this till we know it's safe. + } + } + + mTransceivers.push_back(transceiver); + *aTransceiverImpl = transceiver; + + return NS_OK; +} + +void PeerConnectionMedia::GetTransmitPipelinesMatching( + const MediaStreamTrack* aTrack, + nsTArray<RefPtr<MediaPipelineTransmit>>* aPipelines) { + for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + if (transceiver->HasSendTrack(aTrack)) { + aPipelines->AppendElement(transceiver->GetSendPipeline()); + } + } +} + +std::string PeerConnectionMedia::GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const { + for (const RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + if (transceiver->HasSendTrack(&aTrack)) { + return transceiver->GetTransportId(); + } + } + return std::string(); +} + +void PeerConnectionMedia::IceGatheringStateChange_s( + dom::RTCIceGatheringState aState) { + ASSERT_ON_THREAD(mSTSThread); + + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::IceGatheringStateChange_m, + aState), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionMedia::IceConnectionStateChange_s( + dom::RTCIceConnectionState aState) { + ASSERT_ON_THREAD(mSTSThread); + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::IceConnectionStateChange_m, + aState), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionMedia::OnCandidateFound_s( + const std::string& aTransportId, const CandidateInfo& aCandidateInfo) { + ASSERT_ON_THREAD(mSTSThread); + MOZ_RELEASE_ASSERT(mTransportHandler); + + CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str()); + + MOZ_ASSERT(!aCandidateInfo.mUfrag.empty()); + + // ShutdownMediaTransport_s has not run yet because it unhooks this function + // from its signal, which means that SelfDestruct_m has not been dispatched + // yet either, so this PCMedia will still be around when this dispatch reaches + // main. + GetMainThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::OnCandidateFound_m, aTransportId, + aCandidateInfo), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionMedia::IceGatheringStateChange_m( + dom::RTCIceGatheringState aState) { + ASSERT_ON_THREAD(mMainThread); + if (mParent) { + mParent->IceGatheringStateChange(aState); + } +} + +void PeerConnectionMedia::IceConnectionStateChange_m( + dom::RTCIceConnectionState aState) { + ASSERT_ON_THREAD(mMainThread); + if (mParent) { + mParent->IceConnectionStateChange(aState); + } +} + +void PeerConnectionMedia::OnCandidateFound_m( + const std::string& aTransportId, const CandidateInfo& aCandidateInfo) { + ASSERT_ON_THREAD(mMainThread); + 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 (mParent) { + mParent->OnCandidateFound(aTransportId, aCandidateInfo); + } +} + +void PeerConnectionMedia::AlpnNegotiated_s(const std::string& aAlpn, + bool aPrivacyRequested) { + MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested); + GetMainThread()->Dispatch( + WrapRunnable(this, &PeerConnectionMedia::AlpnNegotiated_m, + aPrivacyRequested), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionMedia::AlpnNegotiated_m(bool aPrivacyRequested) { + if (mParent) { + mParent->OnAlpnNegotiated(aPrivacyRequested); + } +} + +/** + * Tells you if any local track is isolated to a specific peer identity. + * Obviously, we want all the tracks to be isolated equally so that they can + * all be sent or not. We check once when we are setting a local description + * and that determines if we flip the "privacy requested" bit on. Once the bit + * is on, all media originating from this peer connection is isolated. + * + * @returns true if any track has a peerIdentity set on it + */ +bool PeerConnectionMedia::AnyLocalTrackHasPeerIdentity() const { + ASSERT_ON_THREAD(mMainThread); + + for (const RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + if (transceiver->GetSendTrack() && + transceiver->GetSendTrack()->GetPeerIdentity()) { + return true; + } + } + return false; +} + +void PeerConnectionMedia::UpdateSinkIdentity_m( + const MediaStreamTrack* aTrack, nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) { + ASSERT_ON_THREAD(mMainThread); + + for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + transceiver->UpdateSinkIdentity(aTrack, aPrincipal, aSinkIdentity); + } +} + +bool PeerConnectionMedia::AnyCodecHasPluginID(uint64_t aPluginID) { + for (RefPtr<TransceiverImpl>& transceiver : mTransceivers) { + if (transceiver->ConduitHasPluginID(aPluginID)) { + return true; + } + } + return false; +} + +nsPIDOMWindowInner* PeerConnectionMedia::GetWindow() const { + return mParent->GetWindow(); +} + +std::unique_ptr<NrSocketProxyConfig> PeerConnectionMedia::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(GetWindow()); + if (!browserChild) { + // Android doesn't have browser child apparently... + return nullptr; + } + + Document* doc = mParent->GetWindow()->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + NS_WARNING("Unable to get document from window"); + return nullptr; + } + + TabId id = browserChild->GetTabId(); + nsCOMPtr<nsILoadInfo> loadInfo = + new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc, 0, + nsIContentPolicy::TYPE_INVALID); + + Maybe<net::LoadInfoArgs> loadInfoArgs; + MOZ_ALWAYS_SUCCEEDS( + mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs)); + return std::unique_ptr<NrSocketProxyConfig>(new NrSocketProxyConfig( + net::WebrtcProxyConfig(id, alpn, *loadInfoArgs, mForceProxy))); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PeerConnectionMedia.h b/dom/media/webrtc/jsapi/PeerConnectionMedia.h new file mode 100644 index 0000000000..bc89029545 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionMedia.h @@ -0,0 +1,262 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _PEER_CONNECTION_MEDIA_H_ +#define _PEER_CONNECTION_MEDIA_H_ + +#include <string> +#include <vector> +#include <map> + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/net/StunAddrsRequestChild.h" +#include "MediaTransportHandler.h" +#include "nsIHttpChannelInternal.h" +#include "RTCDtlsTransport.h" + +#include "TransceiverImpl.h" + +class nsIPrincipal; + +namespace mozilla { +class DataChannel; +class PeerIdentity; +namespace dom { +class MediaStreamTrack; +} // namespace dom +} // namespace mozilla + +#include "transport/nriceresolver.h" +#include "transport/nricemediastream.h" + +namespace mozilla { + +class PeerConnectionImpl; +class PeerConnectionMedia; +class PCUuidGenerator; +class MediaPipeline; +class MediaPipelineReceive; +class MediaPipelineTransmit; +class MediaPipelineFilter; +class JsepSession; + +// TODO(bug 1402997): If we move the TransceiverImpl stuff out of here, this +// will be a class that handles just the transport stuff, and we can rename it +// to something more explanatory (say, PeerConnectionTransportManager). +class PeerConnectionMedia : public sigslot::has_slots<> { + ~PeerConnectionMedia(); + + public: + explicit PeerConnectionMedia(PeerConnectionImpl* parent); + + nsresult Init(); + // WARNING: This destroys the object! + void SelfDestruct(); + + // Ensure ICE transports exist that we might need when offer/answer concludes + void EnsureTransports(const JsepSession& aSession); + + void UpdateRTCDtlsTransports(bool aMarkAsStable); + void RollbackRTCDtlsTransports(); + void RemoveRTCDtlsTransportsExcept( + const std::set<std::string>& aTransportIds); + + // Activate ICE transports at the conclusion of offer/answer, + // or when rollback occurs. + nsresult UpdateTransports(const JsepSession& aSession, + const bool forceIceTcp); + + void ResetStunAddrsForIceRestart() { mStunAddrs.Clear(); } + + // Start ICE checks. + void StartIceChecks(const JsepSession& session); + + // Process a trickle ICE candidate. + void AddIceCandidate(const std::string& candidate, + const std::string& aTransportId, + const std::string& aUFrag); + + // Handle notifications of network online/offline events. + void UpdateNetworkState(bool online); + + // Handle complete media pipelines. + // This updates codec parameters, starts/stops send/receive, and other + // stuff that doesn't necessarily require negotiation. This can be called at + // any time, not just when an offer/answer exchange completes. + // TODO: Let's move this to PeerConnectionImpl + nsresult UpdateMediaPipelines(); + + // TODO: Let's move the TransceiverImpl stuff to PeerConnectionImpl. + nsresult AddTransceiver(JsepTransceiver* aJsepTransceiver, + dom::MediaStreamTrack* aSendTrack, + RefPtr<TransceiverImpl>* aTransceiverImpl); + + void GetTransmitPipelinesMatching( + const dom::MediaStreamTrack* aTrack, + nsTArray<RefPtr<MediaPipelineTransmit>>* aPipelines); + + std::string GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const; + + // In cases where the peer isn't yet identified, we disable the pipeline (not + // the stream, that would potentially affect others), so that it sends + // black/silence. Once the peer is identified, re-enable those streams. + // aTrack will be set if this update came from a principal change on aTrack. + // TODO: Move to PeerConnectionImpl + void UpdateSinkIdentity_m(const dom::MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity); + // this determines if any track is peerIdentity constrained + bool AnyLocalTrackHasPeerIdentity() const; + + bool AnyCodecHasPluginID(uint64_t aPluginID); + + const nsCOMPtr<nsIThread>& GetMainThread() const { return mMainThread; } + const nsCOMPtr<nsISerialEventTarget>& GetSTSThread() const { + return mSTSThread; + } + + // Used by PCImpl in a couple of places. Might be good to move that code in + // here. + std::vector<RefPtr<TransceiverImpl>>& GetTransceivers() { + return mTransceivers; + } + + nsPIDOMWindowInner* GetWindow() const; + already_AddRefed<nsIHttpChannelInternal> GetChannel() const; + + void AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested); + void AlpnNegotiated_m(bool aPrivacyRequested); + + // TODO: Move to PeerConnectionImpl + RefPtr<WebRtcCallWrapper> mCall; + + // mtransport objects + RefPtr<MediaTransportHandler> mTransportHandler; + + private: + void InitLocalAddrs(); // for stun local address IPC request + bool ShouldForceProxy() const; + std::unique_ptr<NrSocketProxyConfig> GetProxyConfig() const; + + class StunAddrsHandler : public net::StunAddrsListener { + public: + explicit StunAddrsHandler(PeerConnectionMedia* pcm) : pcm_(pcm) {} + + void OnMDNSQueryComplete(const nsCString& hostname, + const Maybe<nsCString>& address) override; + + void OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) override; + + private: + RefPtr<PeerConnectionMedia> pcm_; + virtual ~StunAddrsHandler() {} + }; + + // Shutdown media transport. Must be called on STS thread. + void ShutdownMediaTransport_s(); + + // Final destruction of the media stream. Must be called on the main + // thread. + void SelfDestruct_m(); + + // Manage ICE transports. + void UpdateTransport(const JsepTransceiver& aTransceiver, bool aForceIceTcp); + + void GatherIfReady(); + void FlushIceCtxOperationQueueIfReady(); + void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable); + nsresult SetTargetForDefaultLocalAddressLookup(); + void EnsureIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses); + + bool GetPrefDefaultAddressOnly() const; + bool GetPrefObfuscateHostAddresses() const; + + void ConnectSignals(); + + // ICE events + void IceGatheringStateChange_s(dom::RTCIceGatheringState aState); + void IceConnectionStateChange_s(dom::RTCIceConnectionState aState); + void OnCandidateFound_s(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + + void IceGatheringStateChange_m(dom::RTCIceGatheringState aState); + void IceConnectionStateChange_m(dom::RTCIceConnectionState aState); + void OnCandidateFound_m(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + + bool IsIceCtxReady() const { + return mLocalAddrsRequestState == STUN_ADDR_REQUEST_COMPLETE; + } + + // The parent PC + PeerConnectionImpl* mParent; + // and a loose handle on it for event driven stuff + std::string mParentHandle; + std::string mParentName; + + std::vector<RefPtr<TransceiverImpl>> mTransceivers; + std::map<std::string, RefPtr<dom::RTCDtlsTransport>> + mTransportIdToRTCDtlsTransport; + + // The main thread. + nsCOMPtr<nsIThread> mMainThread; + + // The STS thread. + nsCOMPtr<nsISerialEventTarget> mSTSThread; + + // Used whenever we need to dispatch a runnable to STS to tweak something + // on our ICE ctx, but are not ready to do so at the moment (eg; we are + // waiting to get a callback with our http proxy config before we start + // gathering or start checking) + std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations; + + // Set if prefs dictate that we should force the use of a web proxy. + bool mForceProxy; + + // Used to cancel incoming stun addrs response + RefPtr<net::StunAddrsRequestChild> mStunAddrsRequest; + + enum StunAddrRequestState { + STUN_ADDR_REQUEST_NONE, + STUN_ADDR_REQUEST_PENDING, + STUN_ADDR_REQUEST_COMPLETE + }; + // Used to track the state of the stun addr IPC request + StunAddrRequestState mLocalAddrsRequestState; + + // Used to store the result of the stun addr IPC request + nsTArray<NrIceStunAddr> mStunAddrs; + + // Used to ensure the target for default local address lookup is only set + // once. + bool mTargetForDefaultLocalAddressLookupIsSet; + + // Set to true when the object is going to be released. + bool mDestroyed; + + // Keep track of local hostnames to register. Registration is deferred + // until StartIceChecks has run. Accessed on main thread only. + std::map<std::string, std::string> mMDNSHostnamesToRegister; + bool mCanRegisterMDNSHostnamesDirectly = false; + + // Used to store the mDNS hostnames that we have registered + std::set<std::string> mRegisteredMDNSHostnames; + + // Used to store the mDNS hostnames that we have queried + struct PendingIceCandidate { + std::vector<std::string> mTokenizedCandidate; + std::string mTransportId; + std::string mUfrag; + }; + std::map<std::string, std::list<PendingIceCandidate>> mQueriedMDNSHostnames; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PeerConnectionMedia) +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.cpp b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp new file mode 100644 index 0000000000..0939399c55 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp @@ -0,0 +1,150 @@ +/* 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 "RTCDTMFSender.h" +#include "libwebrtcglue/MediaConduitInterface.h" +#include "transport/logging.h" +#include "TransceiverImpl.h" +#include "nsITimer.h" +#include "mozilla/dom/RTCDTMFSenderBinding.h" +#include "mozilla/dom/RTCDTMFToneChangeEvent.h" +#include <algorithm> +#include <bitset> + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDTMFSender, DOMEventTargetHelper, + mTransceiver, mSendTimer) + +NS_IMPL_ADDREF_INHERITED(RTCDTMFSender, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCDTMFSender, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDTMFSender) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +LazyLogModule gDtmfLog("RTCDTMFSender"); + +RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow, + TransceiverImpl* aTransceiver, + AudioSessionConduit* aConduit) + : DOMEventTargetHelper(aWindow), + mTransceiver(aTransceiver), + mConduit(aConduit) {} + +JSObject* RTCDTMFSender::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCDTMFSender_Binding::Wrap(aCx, this, aGivenProto); +} + +static int GetDTMFToneCode(uint16_t c) { + const char* DTMF_TONECODES = "0123456789*#ABCD"; + + if (c == ',') { + // , is a special character indicating a 2 second delay + return -1; + } + + const char* i = strchr(DTMF_TONECODES, c); + MOZ_ASSERT(i); + return static_cast<int>(i - DTMF_TONECODES); +} + +static std::bitset<256> GetCharacterBitset(const std::string& aCharsInSet) { + std::bitset<256> result; + for (auto c : aCharsInSet) { + result[c] = true; + } + return result; +} + +static bool IsUnrecognizedChar(const char c) { + static const std::bitset<256> recognized = + GetCharacterBitset("0123456789ABCD#*,"); + return !recognized[c]; +} + +void RTCDTMFSender::InsertDTMF(const nsAString& aTones, uint32_t aDuration, + uint32_t aInterToneGap, ErrorResult& aRv) { + if (!mTransceiver->CanSendDTMF()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + std::string utf8Tones = NS_ConvertUTF16toUTF8(aTones).get(); + + std::transform(utf8Tones.begin(), utf8Tones.end(), utf8Tones.begin(), + [](const char c) { return std::toupper(c); }); + + if (std::any_of(utf8Tones.begin(), utf8Tones.end(), IsUnrecognizedChar)) { + aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); + return; + } + + CopyUTF8toUTF16(utf8Tones, mToneBuffer); + mDuration = std::clamp(aDuration, 40U, 6000U); + mInterToneGap = std::clamp(aInterToneGap, 30U, 6000U); + + if (mToneBuffer.Length()) { + StartPlayout(0); + } +} + +void RTCDTMFSender::StopPlayout() { + if (mSendTimer) { + mSendTimer->Cancel(); + mSendTimer = nullptr; + } +} + +void RTCDTMFSender::StartPlayout(uint32_t aDelay) { + if (!mSendTimer) { + mSendTimer = NS_NewTimer(); + mSendTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); + } +} + +nsresult RTCDTMFSender::Notify(nsITimer* timer) { + MOZ_ASSERT(NS_IsMainThread()); + StopPlayout(); + + if (!mTransceiver->IsSending()) { + return NS_OK; + } + + RTCDTMFToneChangeEventInit init; + if (!mToneBuffer.IsEmpty()) { + uint16_t toneChar = mToneBuffer.CharAt(0); + int tone = GetDTMFToneCode(toneChar); + + init.mTone.Assign(toneChar); + + mToneBuffer.Cut(0, 1); + + if (tone == -1) { + StartPlayout(2000); + } else { + // Reset delay if necessary + StartPlayout(mDuration + mInterToneGap); + // Note: We default to channel 0, not inband, and 6dB attenuation. + // here. We might want to revisit these choices in the future. + mConduit->InsertDTMFTone(0, tone, true, mDuration, 6); + } + } + + RefPtr<RTCDTMFToneChangeEvent> event = + RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init); + DispatchTrustedEvent(event); + + return NS_OK; +} + +void RTCDTMFSender::GetToneBuffer(nsAString& aOutToneBuffer) { + aOutToneBuffer = mToneBuffer; +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.h b/dom/media/webrtc/jsapi/RTCDTMFSender.h new file mode 100644 index 0000000000..8ca07debbe --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.h @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _RTCDTMFSender_h_ +#define _RTCDTMFSender_h_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "nsITimer.h" + +class nsPIDOMWindowInner; +class nsITimer; + +namespace mozilla { +class AudioSessionConduit; +class TransceiverImpl; + +namespace dom { + +class RTCDTMFSender : public DOMEventTargetHelper, public nsITimerCallback { + public: + explicit RTCDTMFSender(nsPIDOMWindowInner* aWindow, + TransceiverImpl* aTransceiver, + AudioSessionConduit* aConduit); + + // nsISupports + NS_DECL_NSITIMERCALLBACK + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDTMFSender, DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + void InsertDTMF(const nsAString& aTones, uint32_t aDuration, + uint32_t aInterToneGap, ErrorResult& aRv); + void GetToneBuffer(nsAString& aOutToneBuffer); + IMPL_EVENT_HANDLER(tonechange) + + void StopPlayout(); + + private: + virtual ~RTCDTMFSender() = default; + + void StartPlayout(uint32_t aDelay); + + RefPtr<TransceiverImpl> mTransceiver; + RefPtr<AudioSessionConduit> mConduit; + nsString mToneBuffer; + uint32_t mDuration = 0; + uint32_t mInterToneGap = 0; + nsCOMPtr<nsITimer> mSendTimer; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCDTMFSender_h_ diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp new file mode 100644 index 0000000000..442e9c7a17 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp @@ -0,0 +1,69 @@ +/* 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 "RTCDtlsTransport.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/RTCDtlsTransportBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDtlsTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +RTCDtlsTransport::RTCDtlsTransport(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow), mState(RTCDtlsTransportState::New) {} + +JSObject* RTCDtlsTransport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCDtlsTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCDtlsTransport::UpdateState(TransportLayer::State aState) { + if (mState == RTCDtlsTransportState::Closed) { + return; + } + + RTCDtlsTransportState newState = mState; + switch (aState) { + case TransportLayer::TS_NONE: + break; + case TransportLayer::TS_INIT: + break; + case TransportLayer::TS_CONNECTING: + newState = RTCDtlsTransportState::Connecting; + break; + case TransportLayer::TS_OPEN: + newState = RTCDtlsTransportState::Connected; + break; + case TransportLayer::TS_CLOSED: + newState = RTCDtlsTransportState::Closed; + break; + case TransportLayer::TS_ERROR: + newState = RTCDtlsTransportState::Failed; + break; + } + + if (newState == mState) { + return; + } + + mState = newState; + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init); + + DispatchTrustedEvent(event); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.h b/dom/media/webrtc/jsapi/RTCDtlsTransport.h new file mode 100644 index 0000000000..4318e20c61 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.h @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _RTCDtlsTransport_h_ +#define _RTCDtlsTransport_h_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "transport/transportlayer.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +namespace dom { + +enum class RTCDtlsTransportState : uint8_t; + +class RTCDtlsTransport : public DOMEventTargetHelper { + public: + explicit RTCDtlsTransport(nsPIDOMWindowInner* aWindow); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDtlsTransport, + DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + IMPL_EVENT_HANDLER(statechange) + RTCDtlsTransportState State() const { return mState; } + + void UpdateState(TransportLayer::State aState); + + private: + virtual ~RTCDtlsTransport() = default; + + RTCDtlsTransportState mState; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCDtlsTransport_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp new file mode 100644 index 0000000000..972b81adba --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp @@ -0,0 +1,619 @@ +/* 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 "RTCRtpReceiver.h" +#include "transport/logging.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "mozilla/dom/Promise.h" +#include "transportbridge/MediaPipeline.h" +#include "nsPIDOMWindow.h" +#include "PrincipalHandle.h" +#include "nsIPrincipal.h" +#include "mozilla/dom/Document.h" +#include "mozilla/NullPrincipal.h" +#include "MediaTrackGraph.h" +#include "RemoteTrackSource.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsString.h" +#include "mozilla/dom/AudioStreamTrack.h" +#include "mozilla/dom/VideoStreamTrack.h" +#include "MediaTransportHandler.h" +#include "jsep/JsepTransceiver.h" +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSourcesBinding.h" +#include "RTCStatsReport.h" +#include "mozilla/Preferences.h" +#include "TransceiverImpl.h" +#include "libwebrtcglue/AudioConduit.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCRtpReceiver, mWindow, mTrack) +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpReceiver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpReceiver) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpReceiver) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +LazyLogModule gReceiverLog("RTCRtpReceiver"); + +static PrincipalHandle GetPrincipalHandle(nsPIDOMWindowInner* aWindow, + bool aPrivacyNeeded) { + // Set the principal used for creating the tracks. This makes the track + // data (audio/video samples) accessible to the receiving page. We're + // only certain that privacy hasn't been requested if we're connected. + nsCOMPtr<nsIScriptObjectPrincipal> winPrincipal = do_QueryInterface(aWindow); + RefPtr<nsIPrincipal> principal = winPrincipal->GetPrincipal(); + if (NS_WARN_IF(!principal)) { + principal = NullPrincipal::CreateWithoutOriginAttributes(); + } else if (aPrivacyNeeded) { + principal = NullPrincipal::CreateWithInheritedAttributes(principal); + } + return MakePrincipalHandle(principal); +} + +static already_AddRefed<dom::MediaStreamTrack> CreateTrack( + nsPIDOMWindowInner* aWindow, bool aAudio, + const nsCOMPtr<nsIPrincipal>& aPrincipal) { + MediaTrackGraph* graph = MediaTrackGraph::GetInstance( + aAudio ? MediaTrackGraph::AUDIO_THREAD_DRIVER + : MediaTrackGraph::SYSTEM_THREAD_DRIVER, + aWindow, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, + MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); + + RefPtr<MediaStreamTrack> track; + RefPtr<RemoteTrackSource> trackSource; + if (aAudio) { + RefPtr<SourceMediaTrack> source = + graph->CreateSourceTrack(MediaSegment::AUDIO); + trackSource = new RemoteTrackSource(source, aPrincipal, u"remote audio"_ns); + track = new AudioStreamTrack(aWindow, source, trackSource); + } else { + RefPtr<SourceMediaTrack> source = + graph->CreateSourceTrack(MediaSegment::VIDEO); + trackSource = new RemoteTrackSource(source, aPrincipal, u"remote video"_ns); + track = new VideoStreamTrack(aWindow, source, trackSource); + } + + // Spec says remote tracks start out muted. + trackSource->SetMuted(true); + + return track.forget(); +} + +RTCRtpReceiver::RTCRtpReceiver(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, + const std::string& aPCHandle, + MediaTransportHandler* aTransportHandler, + JsepTransceiver* aJsepTransceiver, + nsISerialEventTarget* aMainThread, + nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, + TransceiverImpl* aTransceiverImpl) + : mWindow(aWindow), + mPCHandle(aPCHandle), + mJsepTransceiver(aJsepTransceiver), + mMainThread(aMainThread), + mStsThread(aStsThread), + mTransportHandler(aTransportHandler), + mTransceiverImpl(aTransceiverImpl) { + PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacyNeeded); + mTrack = CreateTrack(aWindow, aConduit->type() == MediaSessionConduit::AUDIO, + principalHandle.get()); + // Until Bug 1232234 is fixed, we'll get extra RTCP BYES during renegotiation, + // so we'll disable muting on RTCP BYE and timeout for now. + if (Preferences::GetBool("media.peerconnection.mute_on_bye_or_timeout", + false)) { + aConduit->SetRtcpEventObserver(this); + } + if (aConduit->type() == MediaSessionConduit::AUDIO) { + mPipeline = new MediaPipelineReceiveAudio( + mPCHandle, aTransportHandler, mMainThread.get(), mStsThread.get(), + static_cast<AudioSessionConduit*>(aConduit), mTrack, principalHandle); + } else { + mPipeline = new MediaPipelineReceiveVideo( + mPCHandle, aTransportHandler, mMainThread.get(), mStsThread.get(), + static_cast<VideoSessionConduit*>(aConduit), mTrack, principalHandle); + } +} + +RTCRtpReceiver::~RTCRtpReceiver() { MOZ_ASSERT(!mPipeline); } + +JSObject* RTCRtpReceiver::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCRtpReceiver_Binding::Wrap(aCx, this, aGivenProto); +} + +RTCDtlsTransport* RTCRtpReceiver::GetTransport() const { + if (!mTransceiverImpl) { + return nullptr; + } + return mTransceiverImpl->GetDtlsTransport(); +} + +already_AddRefed<Promise> RTCRtpReceiver::GetStats() { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + ErrorResult rv; + RefPtr<Promise> promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.StealNSResult(); + return nullptr; + } + + // Two promises; one for RTP/RTCP stats, another for ICE stats. + auto promises = GetStatsInternal(); + RTCStatsPromise::All(mMainThread, promises) + ->Then( + mMainThread, __func__, + [promise, window = mWindow]( + const nsTArray<UniquePtr<RTCStatsCollection>>& aStats) { + RefPtr<RTCStatsReport> report(new RTCStatsReport(window)); + for (const auto& stats : aStats) { + report->Incorporate(*stats); + } + promise->MaybeResolve(std::move(report)); + }, + [promise](nsresult aError) { + promise->MaybeReject(NS_ERROR_FAILURE); + }); + + return promise.forget(); +} + +static UniquePtr<dom::RTCStatsCollection> GetReceiverStats_s( + const RefPtr<MediaPipelineReceive>& aPipeline, + const nsString& aRecvTrackId) { + UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection); + auto asVideo = aPipeline->Conduit()->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast<uint32_t>(aPipeline->Level())); + + // Add bandwidth estimation stats + aPipeline->Conduit()->GetBandwidthEstimation().apply([&](auto& bw) { + bw.mTrackIdentifier = aRecvTrackId; + if (!report->mBandwidthEstimations.AppendElement(bw, fallible)) { + mozalloc_handle_oom(0); + } + }); + + Maybe<uint32_t> ssrc; + unsigned int ssrcval; + if (aPipeline->Conduit()->GetRemoteSSRC(&ssrcval)) { + ssrc = Some(ssrcval); + } + + // Add frame history + asVideo.apply([&](const auto& conduit) { + if (conduit->AddFrameHistory(&report->mVideoFrameHistories)) { + auto& history = report->mVideoFrameHistories.LastElement(); + history.mTrackIdentifier = aRecvTrackId; + } + }); + + // TODO(@@NG):ssrcs handle Conduits having multiple stats at the same level + // This is pending spec work + // Gather pipeline stats. + nsString localId = u"inbound_rtp_"_ns + idstr; + nsString remoteId; + + // First, fill in remote stat with rtcp sender data, if present. + uint32_t packetsSent; + uint64_t bytesSent; + DOMHighResTimeStamp remoteTimestamp; + Maybe<DOMHighResTimeStamp> timestamp = + aPipeline->Conduit()->LastRtcpReceived(); + if (timestamp.isSome() && aPipeline->Conduit()->GetRTCPSenderReport( + &packetsSent, &bytesSent, &remoteTimestamp)) { + RTCRemoteOutboundRtpStreamStats s; + remoteId = u"inbound_rtcp_"_ns + idstr; + s.mTimestamp.Construct(*timestamp); + s.mId.Construct(remoteId); + s.mType.Construct(RTCStatsType::Remote_outbound_rtp); + ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); }); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); + s.mLocalId.Construct(localId); + s.mPacketsSent.Construct(packetsSent); + s.mBytesSent.Construct(bytesSent); + s.mRemoteTimestamp.Construct(remoteTimestamp); + if (!report->mRemoteOutboundRtpStreamStats.AppendElement(s, fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if present) + RTCInboundRtpStreamStats s; + // TODO(bug 1496533): Should we use the time of the most-recently received + // RTP packet? If so, what do we use if we haven't received any RTP? Now? + s.mTimestamp.Construct(aPipeline->GetNow()); + s.mId.Construct(localId); + s.mType.Construct(RTCStatsType::Inbound_rtp); + ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); }); + s.mMediaType.Construct(kind); // mediaType is the old name for kind. + s.mKind.Construct(kind); + unsigned int jitterMs, packetsLost; + if (aPipeline->Conduit()->GetRTPReceiverStats(&jitterMs, &packetsLost)) { + s.mJitter.Construct(double(jitterMs) / 1000); + s.mPacketsLost.Construct(packetsLost); + } + if (remoteId.Length()) { + s.mRemoteId.Construct(remoteId); + } + s.mPacketsReceived.Construct(aPipeline->RtpPacketsReceived()); + s.mBytesReceived.Construct(aPipeline->RtpBytesReceived()); + + // Fill in packet type statistics + webrtc::RtcpPacketTypeCounter counters; + if (aPipeline->Conduit()->GetRecvPacketTypeStats(&counters)) { + s.mNackCount.Construct(counters.nack_packets); + // Fill in video only packet type stats + if (asVideo) { + s.mFirCount.Construct(counters.fir_packets); + s.mPliCount.Construct(counters.pli_packets); + } + } + // Lastly, fill in video decoder stats if this is video + asVideo.apply([&s](auto conduit) { + double framerateMean; + double framerateStdDev; + double bitrateMean; + double bitrateStdDev; + uint32_t discardedPackets; + uint32_t framesDecoded; + if (conduit->GetVideoDecoderStats(&framerateMean, &framerateStdDev, + &bitrateMean, &bitrateStdDev, + &discardedPackets, &framesDecoded)) { + s.mFramerateMean.Construct(framerateMean); + s.mFramerateStdDev.Construct(framerateStdDev); + s.mBitrateMean.Construct(bitrateMean); + s.mBitrateStdDev.Construct(bitrateStdDev); + s.mDiscardedPackets.Construct(discardedPackets); + s.mFramesDecoded.Construct(framesDecoded); + } + }); + if (!report->mInboundRtpStreamStats.AppendElement(s, fallible)) { + mozalloc_handle_oom(0); + } + + // Fill in Contributing Source statistics + aPipeline->GetContributingSourceStats(localId, + report->mRtpContributingSourceStats); + + return report; +} + +nsTArray<RefPtr<RTCStatsPromise>> RTCRtpReceiver::GetStatsInternal() { + nsTArray<RefPtr<RTCStatsPromise>> promises; + if (mPipeline && mHaveStartedReceiving) { + nsString recvTrackId; + MOZ_ASSERT(mTrack); + if (mTrack) { + mTrack->GetId(recvTrackId); + } + promises.AppendElement(InvokeAsync( + mStsThread, __func__, [pipeline = mPipeline, recvTrackId]() { + return RTCStatsPromise::CreateAndResolve( + GetReceiverStats_s(pipeline, recvTrackId), __func__); + })); + + if (mJsepTransceiver->mTransport.mComponents) { + promises.AppendElement(mTransportHandler->GetIceStats( + mJsepTransceiver->mTransport.mTransportId, mPipeline->GetNow())); + } + } + return promises; +} + +void RTCRtpReceiver::GetContributingSources( + nsTArray<RTCRtpContributingSource>& aSources) { + // Duplicate code... + if (mPipeline && mPipeline->Conduit()) { + RefPtr<AudioSessionConduit> conduit( + static_cast<AudioSessionConduit*>(mPipeline->Conduit())); + nsTArray<dom::RTCRtpSourceEntry> sources; + conduit->GetRtpSources(sources); + sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) { + return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Contributing; + }); + aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(), + sources.Length()); + } +} + +void RTCRtpReceiver::GetSynchronizationSources( + nsTArray<dom::RTCRtpSynchronizationSource>& aSources) { + // Duplicate code... + if (mPipeline && mPipeline->Conduit()) { + RefPtr<AudioSessionConduit> conduit( + static_cast<AudioSessionConduit*>(mPipeline->Conduit())); + nsTArray<dom::RTCRtpSourceEntry> sources; + conduit->GetRtpSources(sources); + sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) { + return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Synchronization; + }); + aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(), + sources.Length()); + } +} + +nsPIDOMWindowInner* RTCRtpReceiver::GetParentObject() const { return mWindow; } + +void RTCRtpReceiver::Shutdown() { + ASSERT_ON_THREAD(mMainThread); + if (mPipeline) { + mPipeline->Shutdown_m(); + mPipeline->Conduit()->SetRtcpEventObserver(nullptr); + mPipeline = nullptr; + } + mTransceiverImpl = nullptr; +} + +void RTCRtpReceiver::UpdateTransport() { + ASSERT_ON_THREAD(mMainThread); + if (!mHaveSetupTransport) { + mPipeline->SetLevel(mJsepTransceiver->GetLevel()); + mHaveSetupTransport = true; + } + + UniquePtr<MediaPipelineFilter> filter; + + auto const& details = mJsepTransceiver->mRecvTrack.GetNegotiatedDetails(); + if (mJsepTransceiver->HasBundleLevel() && details) { + std::vector<webrtc::RtpExtension> extmaps; + details->ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + filter = MakeUnique<MediaPipelineFilter>(extmaps); + + // Add remote SSRCs so we can distinguish which RTP packets actually + // belong to this pipeline (also RTCP sender reports). + for (uint32_t ssrc : mJsepTransceiver->mRecvTrack.GetSsrcs()) { + filter->AddRemoteSSRC(ssrc); + } + for (uint32_t ssrc : mJsepTransceiver->mRecvTrack.GetRtxSsrcs()) { + filter->AddRemoteSSRC(ssrc); + } + auto mid = Maybe<std::string>(); + if (GetMid() != "") { + mid = Some(GetMid()); + } + filter->SetRemoteMediaStreamId(mid); + + // Add unique payload types as a last-ditch fallback + auto uniquePts = mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() + ->GetUniquePayloadTypes(); + for (unsigned char& uniquePt : uniquePts) { + filter->AddUniquePT(uniquePt); + } + } + + mPipeline->UpdateTransport_m(mJsepTransceiver->mTransport.mTransportId, + std::move(filter)); +} + +nsresult RTCRtpReceiver::UpdateConduit() { + if (mPipeline->Conduit()->type() == MediaSessionConduit::VIDEO) { + return UpdateVideoConduit(); + } + return UpdateAudioConduit(); +} + +nsresult RTCRtpReceiver::UpdateVideoConduit() { + RefPtr<VideoSessionConduit> conduit = + static_cast<VideoSessionConduit*>(mPipeline->Conduit()); + + // NOTE(pkerr) - this is new behavior. Needed because the + // CreateVideoReceiveStream method of the Call API will assert (in debug) + // and fail if a value is not provided for the remote_ssrc that will be used + // by the far-end sender. + if (!mJsepTransceiver->mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPCHandle.c_str(), + GetMid().c_str(), __FUNCTION__, + mJsepTransceiver->mRecvTrack.GetSsrcs().front())); + uint32_t rtxSsrc = mJsepTransceiver->mRecvTrack.GetRtxSsrcs().empty() + ? 0 + : mJsepTransceiver->mRecvTrack.GetRtxSsrcs().front(); + conduit->SetRemoteSSRC(mJsepTransceiver->mRecvTrack.GetSsrcs().front(), + rtxSsrc); + } + + // TODO (bug 1423041) once we pay attention to receiving MID's in RTP + // packets (see bug 1405495) we could make this depending on the presence of + // MID in the RTP packets instead of relying on the signaling. + if (mJsepTransceiver->HasBundleLevel() && + (!mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() || + !mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()->GetExt( + webrtc::RtpExtension::kMIdUri))) { + mStsThread->Dispatch( + NewRunnableMethod("VideoSessionConduit::DisableSsrcChanges", conduit, + &VideoSessionConduit::DisableSsrcChanges)); + } + + if (mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() && + mJsepTransceiver->mRecvTrack.GetActive()) { + const auto& details(*mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()); + + TransceiverImpl::UpdateConduitRtpExtmap( + *conduit, details, MediaSessionConduitLocalDirection::kRecv); + + std::vector<UniquePtr<VideoCodecConfig>> configs; + nsresult rv = TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs( + details, &configs); + + if (NS_FAILED(rv)) { + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s Failed to convert " + "JsepCodecDescriptions to VideoCodecConfigs (recv).", + mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__)); + return rv; + } + + auto error = + conduit->ConfigureRecvMediaCodecs(configs, details.GetRtpRtcpConfig()); + + if (error) { + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s " + "ConfigureRecvMediaCodecs failed: %u", + mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__, error)); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +nsresult RTCRtpReceiver::UpdateAudioConduit() { + RefPtr<AudioSessionConduit> conduit = + static_cast<AudioSessionConduit*>(mPipeline->Conduit()); + + if (!mJsepTransceiver->mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPCHandle.c_str(), + GetMid().c_str(), __FUNCTION__, + mJsepTransceiver->mRecvTrack.GetSsrcs().front())); + uint32_t rtxSsrc = mJsepTransceiver->mRecvTrack.GetRtxSsrcs().empty() + ? 0 + : mJsepTransceiver->mRecvTrack.GetRtxSsrcs().front(); + conduit->SetRemoteSSRC(mJsepTransceiver->mRecvTrack.GetSsrcs().front(), + rtxSsrc); + } + + if (mJsepTransceiver->mRecvTrack.GetNegotiatedDetails() && + mJsepTransceiver->mRecvTrack.GetActive()) { + const auto& details(*mJsepTransceiver->mRecvTrack.GetNegotiatedDetails()); + std::vector<UniquePtr<AudioCodecConfig>> configs; + nsresult rv = TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs( + details, &configs); + + if (NS_FAILED(rv)) { + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s Failed to convert " + "JsepCodecDescriptions to AudioCodecConfigs (recv).", + mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__)); + return rv; + } + + // Ensure conduit knows about extensions prior to creating streams + TransceiverImpl::UpdateConduitRtpExtmap( + *conduit, details, MediaSessionConduitLocalDirection::kRecv); + + auto error = conduit->ConfigureRecvMediaCodecs(configs); + + if (error) { + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s " + "ConfigureRecvMediaCodecs failed: %u", + mPCHandle.c_str(), GetMid().c_str(), __FUNCTION__, error)); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +void RTCRtpReceiver::Stop() { + if (mPipeline) { + mPipeline->Stop(); + } +} + +void RTCRtpReceiver::Start() { + mPipeline->Start(); + mHaveStartedReceiving = true; +} + +bool RTCRtpReceiver::HasTrack(const dom::MediaStreamTrack* aTrack) const { + return !aTrack || (mTrack == aTrack); +} + +void RTCRtpReceiver::UpdateStreams(StreamAssociationChanges* aChanges) { + // We don't sort and use set_difference, because we need to report the + // added/removed streams in the order that they appear in the SDP. + std::set<std::string> newIds( + mJsepTransceiver->mRecvTrack.GetStreamIds().begin(), + mJsepTransceiver->mRecvTrack.GetStreamIds().end()); + MOZ_ASSERT(mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit() || + newIds.empty()); + bool needsTrackEvent = false; + for (const auto& id : mStreamIds) { + if (!newIds.count(id)) { + aChanges->mStreamAssociationsRemoved.push_back({mTrack, id}); + } + } + + std::set<std::string> oldIds(mStreamIds.begin(), mStreamIds.end()); + for (const auto& id : mJsepTransceiver->mRecvTrack.GetStreamIds()) { + if (!oldIds.count(id)) { + needsTrackEvent = true; + aChanges->mStreamAssociationsAdded.push_back({mTrack, id}); + } + } + + mStreamIds = mJsepTransceiver->mRecvTrack.GetStreamIds(); + + if (mRemoteSetSendBit != mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit()) { + mRemoteSetSendBit = mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit(); + if (mRemoteSetSendBit) { + needsTrackEvent = true; + } else { + aChanges->mTracksToMute.push_back(mTrack); + } + } + + if (needsTrackEvent) { + aChanges->mTrackEvents.push_back({this, mStreamIds}); + } +} + +void RTCRtpReceiver::MozAddRIDExtension(unsigned short aExtensionId) { + if (mPipeline) { + mPipeline->AddRIDExtension_m(aExtensionId); + } +} + +void RTCRtpReceiver::MozAddRIDFilter(const nsAString& aRid) { + if (mPipeline) { + mPipeline->AddRIDFilter_m(NS_ConvertUTF16toUTF8(aRid).get()); + } +} + +// test-only: adds fake CSRCs and audio data +void RTCRtpReceiver::MozInsertAudioLevelForContributingSource( + const uint32_t aSource, const DOMHighResTimeStamp aTimestamp, + const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel) { + if (!mPipeline || mPipeline->IsVideo() || !mPipeline->Conduit()) { + return; + } + WebrtcAudioConduit* audio_conduit = + static_cast<WebrtcAudioConduit*>(mPipeline->Conduit()); + audio_conduit->InsertAudioLevelForContributingSource( + aSource, aTimestamp, aRtpTimestamp, aHasLevel, aLevel); +} + +void RTCRtpReceiver::OnRtcpBye() { SetReceiveTrackMuted(true); } + +void RTCRtpReceiver::OnRtcpTimeout() { SetReceiveTrackMuted(true); } + +void RTCRtpReceiver::SetReceiveTrackMuted(bool aMuted) { + if (mTrack) { + // This sets the muted state for mTrack and all its clones. + static_cast<RemoteTrackSource&>(mTrack->GetSource()).SetMuted(aMuted); + } +} + +std::string RTCRtpReceiver::GetMid() const { + if (mJsepTransceiver->IsAssociated()) { + return mJsepTransceiver->GetMid(); + } + return std::string(); +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.h b/dom/media/webrtc/jsapi/RTCRtpReceiver.h new file mode 100644 index 0000000000..61ebf3b8f2 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _RTCRtpReceiver_h_ +#define _RTCRtpReceiver_h_ + +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Maybe.h" +#include "js/RootingAPI.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "RTCStatsReport.h" +#include "libwebrtcglue/RtcpEventObserver.h" +#include <vector> + +class nsPIDOMWindowInner; + +namespace mozilla { +class MediaPipelineReceive; +class MediaSessionConduit; +class MediaTransportHandler; +class JsepTransceiver; +class TransceiverImpl; + +namespace dom { +class MediaStreamTrack; +class Promise; +class RTCDtlsTransport; +struct RTCRtpContributingSource; +struct RTCRtpSynchronizationSource; + +class RTCRtpReceiver : public nsISupports, + public nsWrapperCache, + public RtcpEventObserver { + public: + explicit RTCRtpReceiver(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, + const std::string& aPCHandle, + MediaTransportHandler* aTransportHandler, + JsepTransceiver* aJsepTransceiver, + nsISerialEventTarget* aMainThread, + nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, + TransceiverImpl* aTransceiverImpl); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCRtpReceiver) + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // webidl + MediaStreamTrack* Track() const { return mTrack; } + RTCDtlsTransport* GetTransport() const; + already_AddRefed<Promise> GetStats(); + void GetContributingSources( + nsTArray<dom::RTCRtpContributingSource>& aSources); + void GetSynchronizationSources( + nsTArray<dom::RTCRtpSynchronizationSource>& aSources); + // test-only: called from simulcast mochitests. + void MozAddRIDExtension(unsigned short aExtensionId); + void MozAddRIDFilter(const nsAString& aRid); + // test-only: insert fake CSRCs and audio levels for testing + void MozInsertAudioLevelForContributingSource( + const uint32_t aSource, const DOMHighResTimeStamp aTimestamp, + const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel); + + nsPIDOMWindowInner* GetParentObject() const; + nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal(); + + void Shutdown(); + void Stop(); + void Start(); + bool HasTrack(const dom::MediaStreamTrack* aTrack) const; + + struct StreamAssociation { + RefPtr<MediaStreamTrack> mTrack; + std::string mStreamId; + }; + + struct TrackEventInfo { + RefPtr<RTCRtpReceiver> mReceiver; + std::vector<std::string> mStreamIds; + }; + + struct StreamAssociationChanges { + std::vector<RefPtr<MediaStreamTrack>> mTracksToMute; + std::vector<StreamAssociation> mStreamAssociationsRemoved; + std::vector<StreamAssociation> mStreamAssociationsAdded; + std::vector<TrackEventInfo> mTrackEvents; + }; + + // This is called when we set an answer (ie; when the transport is finalized). + void UpdateTransport(); + nsresult UpdateConduit(); + + // This is called when we set a remote description; may be an offer or answer. + void UpdateStreams(StreamAssociationChanges* aChanges); + + void OnRtcpBye() override; + + void OnRtcpTimeout() override; + + void SetReceiveTrackMuted(bool aMuted); + + private: + virtual ~RTCRtpReceiver(); + + nsresult UpdateVideoConduit(); + nsresult UpdateAudioConduit(); + + std::string GetMid() const; + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + const std::string mPCHandle; + RefPtr<JsepTransceiver> mJsepTransceiver; + bool mHaveStartedReceiving = false; + bool mHaveSetupTransport = false; + nsCOMPtr<nsISerialEventTarget> mMainThread; + nsCOMPtr<nsISerialEventTarget> mStsThread; + RefPtr<dom::MediaStreamTrack> mTrack; + RefPtr<MediaPipelineReceive> mPipeline; + RefPtr<MediaTransportHandler> mTransportHandler; + RefPtr<TransceiverImpl> mTransceiverImpl; + // This is [[AssociatedRemoteMediaStreams]], basically. We do not keep the + // streams themselves here, because that would require this object to know + // where the stream list for the whole RTCPeerConnection lives.. + std::vector<std::string> mStreamIds; + bool mRemoteSetSendBit = false; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCRtpReceiver_h_ diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp new file mode 100644 index 0000000000..488c65cf44 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp @@ -0,0 +1,34 @@ +/* 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/. */ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#include "RTCStatsIdGenerator.h" +#include "mozilla/RandomNum.h" +#include <iostream> +namespace mozilla { + +RTCStatsIdGenerator::RTCStatsIdGenerator() + : mSalt(RandomUint64().valueOr(0xa5a5a5a5)), mCounter(0) {} + +nsString RTCStatsIdGenerator::Id(const nsString& aKey) { + if (!aKey.Length()) { + MOZ_ASSERT(aKey.Length(), "Stats IDs should never be empty."); + return aKey; + } + if (mAllocated.find(aKey) == mAllocated.end()) { + mAllocated[aKey] = Generate(); + } + return mAllocated[aKey]; +} + +nsString RTCStatsIdGenerator::Generate() { + auto random = RandomUint64().valueOr(0x1a22); + auto idNum = static_cast<uint32_t>(mSalt ^ ((mCounter++ << 16) | random)); + nsString id; + id.AppendInt(idNum, 16); // Append as hex + return id; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h new file mode 100644 index 0000000000..0ce858e64b --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h @@ -0,0 +1,34 @@ +/* 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/. */ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#ifndef _RTCSTATSIDGENERATOR_H_ +#define _RTCSTATSIDGENERATOR_H_ + +#include "mozilla/Atomics.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +#include <map> + +namespace mozilla { + +class RTCStatsIdGenerator { + public: + RTCStatsIdGenerator(); + nsString Id(const nsString& aKey); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RTCStatsIdGenerator); + + private: + virtual ~RTCStatsIdGenerator(){}; + nsString Generate(); + + const uint64_t mSalt; + uint64_t mCounter; + std::map<nsString, nsString> mAllocated; +}; + +} // namespace mozilla +#endif diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.cpp b/dom/media/webrtc/jsapi/RTCStatsReport.cpp new file mode 100644 index 0000000000..c32a000d11 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.cpp @@ -0,0 +1,94 @@ + +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#include "RTCStatsReport.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/PerformanceService.h" +#include "nsRFPService.h" + +namespace mozilla::dom { + +RTCStatsTimestampMaker::RTCStatsTimestampMaker(const GlobalObject* aGlobal) { + nsCOMPtr<nsPIDOMWindowInner> window = + do_QueryInterface(aGlobal->GetAsSupports()); + if (window) { + mRandomTimelineSeed = window->GetPerformance()->GetRandomTimelineSeed(); + mStartMonotonic = window->GetPerformance()->CreationTimeStamp(); + // Ugh. Performance::TimeOrigin is not constant, which means we need to + // emulate this weird behavior so our time stamps are consistent with JS + // timeOrigin. This is based on the code here: + // https://searchfox.org/mozilla-central/rev/ + // 053826b10f838f77c27507e5efecc96e34718541/dom/performance/Performance.cpp#111-117 + mStartWallClockRaw = + PerformanceService::GetOrCreate()->TimeOrigin(mStartMonotonic); + MOZ_ASSERT(window->AsGlobal()); + mCrossOriginIsolated = window->AsGlobal()->CrossOriginIsolated(); + } +} + +DOMHighResTimeStamp RTCStatsTimestampMaker::GetNow() const { + // webrtc-pc says to use performance.timeOrigin + performance.now(), but + // keeping a Performance object around is difficult because it is + // main-thread-only. So, we perform the same calculation here. Note that this + // can be very different from the current wall-clock time because of changes + // to the wall clock, or monotonic clock drift over long periods of time. + // We are very careful to do exactly what Performance does, to avoid timestamp + // discrepancies. + DOMHighResTimeStamp msSinceStart = + (TimeStamp::NowUnfuzzed() - mStartMonotonic).ToMilliseconds(); + // mRandomTimelineSeed is not set in the unit-tests. + if (mRandomTimelineSeed) { + msSinceStart = nsRFPService::ReduceTimePrecisionAsMSecs( + msSinceStart, mRandomTimelineSeed, /* aIsSystemPrincipal */ false, + mCrossOriginIsolated); + } + return msSinceStart + nsRFPService::ReduceTimePrecisionAsMSecs( + mStartWallClockRaw, 0, + /* aIsSystemPrincipal */ false, + mCrossOriginIsolated); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCStatsReport, mParent) + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(RTCStatsReport, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(RTCStatsReport, Release) + +RTCStatsReport::RTCStatsReport(nsPIDOMWindowInner* aParent) + : mParent(aParent) {} + +/*static*/ +already_AddRefed<RTCStatsReport> RTCStatsReport::Constructor( + const GlobalObject& aGlobal) { + nsCOMPtr<nsPIDOMWindowInner> window( + do_QueryInterface(aGlobal.GetAsSupports())); + RefPtr<RTCStatsReport> report(new RTCStatsReport(window)); + return report.forget(); +} + +JSObject* RTCStatsReport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCStatsReport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCStatsReport::Incorporate(RTCStatsCollection& aStats) { + SetRTCStats(aStats.mIceCandidatePairStats); + SetRTCStats(aStats.mIceCandidateStats); + SetRTCStats(aStats.mInboundRtpStreamStats); + SetRTCStats(aStats.mOutboundRtpStreamStats); + SetRTCStats(aStats.mRemoteInboundRtpStreamStats); + SetRTCStats(aStats.mRemoteOutboundRtpStreamStats); + SetRTCStats(aStats.mRtpContributingSourceStats); + SetRTCStats(aStats.mTrickledIceCandidateStats); + SetRTCStats(aStats.mDataChannelStats); +} + +void RTCStatsReport::Set(const nsAString& aKey, JS::Handle<JSObject*> aValue, + ErrorResult& aRv) { + RTCStatsReport_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.h b/dom/media/webrtc/jsapi/RTCStatsReport.h new file mode 100644 index 0000000000..0de84114da --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.h @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef RTCStatsReport_h_ +#define RTCStatsReport_h_ + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +#include "nsPIDOMWindow.h" // nsPIDOMWindowInner +#include "mozilla/dom/ScriptSettings.h" // AutoEntryScript +#include "nsIGlobalObject.h" +#include "js/RootingAPI.h" // JS::Rooted +#include "js/Value.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/UniquePtr.h" +#include "prtime.h" // PR_Now +#include "mozilla/MozPromise.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // RTCStatsCollection + +namespace mozilla { +namespace dom { + +class RTCStatsTimestampMaker { + public: + RTCStatsTimestampMaker() = default; + explicit RTCStatsTimestampMaker(const GlobalObject* aGlobal); + DOMHighResTimeStamp GetNow() const; + + private: + uint64_t mRandomTimelineSeed = 0; + DOMHighResTimeStamp mStartWallClockRaw = (double)PR_Now() / PR_USEC_PER_MSEC; + TimeStamp mStartMonotonic = TimeStamp::NowUnfuzzed(); + bool mCrossOriginIsolated = false; +}; + +// TODO(bug 1588303): If we ever get move semantics for webidl dictionaries, we +// can stop wrapping these in UniquePtr, which will allow us to simplify code +// in several places. +typedef MozPromise<UniquePtr<RTCStatsCollection>, nsresult, true> + RTCStatsPromise; + +typedef MozPromise<UniquePtr<RTCStatsReportInternal>, nsresult, true> + RTCStatsReportPromise; + +class RTCStatsReport final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RTCStatsReport) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(RTCStatsReport) + + explicit RTCStatsReport(nsPIDOMWindowInner* aParent); + + // TODO(bug 1586109): Remove this once we no longer have to create empty + // RTCStatsReports from JS. + static already_AddRefed<RTCStatsReport> Constructor( + const GlobalObject& aGlobal); + + void Incorporate(RTCStatsCollection& aStats); + + nsPIDOMWindowInner* GetParentObject() const { return mParent; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + private: + ~RTCStatsReport() = default; + void Set(const nsAString& aKey, JS::Handle<JSObject*> aValue, + ErrorResult& aRv); + + template <typename T> + nsresult SetRTCStats(Sequence<T>& aValues) { + for (T& value : aValues) { + nsresult rv = SetRTCStats(value); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; + } + + // We cannot just declare this as SetRTCStats(RTCStats&), because the + // conversion function that ToJSValue uses is non-virtual. + template <typename T> + nsresult SetRTCStats(T& aValue) { + static_assert(std::is_base_of<RTCStats, T>::value, + "SetRTCStats is for setting RTCStats only"); + + if (!aValue.mId.WasPassed()) { + return NS_OK; + } + + const nsString key(aValue.mId.Value()); + + // Cargo-culted from dom::Promise; converts aValue to a JSObject + AutoEntryScript aes(mParent->AsGlobal()->GetGlobalJSObject(), + "RTCStatsReport::SetRTCStats"); + JSContext* cx = aes.cx(); + JS::Rooted<JS::Value> val(cx); + if (!ToJSValue(cx, std::forward<T>(aValue), &val)) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> jsObject(cx, &val.toObject()); + + ErrorResult rv; + Set(key, jsObject, rv); + return rv.StealNSResult(); + } + + nsCOMPtr<nsPIDOMWindowInner> mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // RTCStatsReport_h_ diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.h b/dom/media/webrtc/jsapi/RemoteTrackSource.h new file mode 100644 index 0000000000..05fae35646 --- /dev/null +++ b/dom/media/webrtc/jsapi/RemoteTrackSource.h @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _REMOTE_TRACK_SOURCE_H_ +#define _REMOTE_TRACK_SOURCE_H_ + +#include "MediaStreamTrack.h" +#include "MediaStreamError.h" + +namespace mozilla { + +class RemoteTrackSource : public dom::MediaStreamTrackSource { + public: + explicit RemoteTrackSource(SourceMediaTrack* aStream, + nsIPrincipal* aPrincipal, const nsString& aLabel) + : dom::MediaStreamTrackSource(aPrincipal, aLabel), mStream(aStream) {} + + dom::MediaSourceEnum GetMediaSource() const override { + return dom::MediaSourceEnum::Other; + } + + RefPtr<ApplyConstraintsPromise> ApplyConstraints( + const dom::MediaTrackConstraints& aConstraints, + dom::CallerType aCallerType) override { + return ApplyConstraintsPromise::CreateAndReject( + MakeRefPtr<MediaMgrError>( + dom::MediaStreamError::Name::OverconstrainedError, ""), + __func__); + } + + void Stop() override { + // XXX (Bug 1314270): Implement rejection logic if necessary when we have + // clarity in the spec. + } + + void Disable() override {} + + void Enable() override {} + + void SetPrincipal(nsIPrincipal* aPrincipal) { + mPrincipal = aPrincipal; + PrincipalChanged(); + } + void SetMuted(bool aMuted) { MutedChanged(aMuted); } + void ForceEnded() { OverrideEnded(); } + + const RefPtr<SourceMediaTrack> mStream; + + protected: + virtual ~RemoteTrackSource() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mStream->IsDestroyed()); + } +}; + +} // namespace mozilla + +#endif // _REMOTE_TRACK_SOURCE_H_ diff --git a/dom/media/webrtc/jsapi/TransceiverImpl.cpp b/dom/media/webrtc/jsapi/TransceiverImpl.cpp new file mode 100644 index 0000000000..6db46af15e --- /dev/null +++ b/dom/media/webrtc/jsapi/TransceiverImpl.cpp @@ -0,0 +1,900 @@ +/* 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 "jsapi/TransceiverImpl.h" +#include "mozilla/UniquePtr.h" +#include <string> +#include <vector> +#include "libwebrtcglue/AudioConduit.h" +#include "libwebrtcglue/VideoConduit.h" +#include "MediaTrackGraph.h" +#include "transportbridge/MediaPipeline.h" +#include "transportbridge/MediaPipelineFilter.h" +#include "jsep/JsepTrack.h" +#include "sdp/SdpHelper.h" +#include "MediaTrackGraphImpl.h" +#include "transport/logging.h" +#include "MediaEngine.h" +#include "nsIPrincipal.h" +#include "MediaSegment.h" +#include "RemoteTrackSource.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "MediaTransportHandler.h" +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/TransceiverImplBinding.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpReceiver.h" +#include "RTCDTMFSender.h" +#include "libwebrtcglue/WebrtcGmpVideoCodec.h" + +namespace mozilla { + +using namespace dom; + +MOZ_MTLOG_MODULE("transceiverimpl") + +using LocalDirection = MediaSessionConduitLocalDirection; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransceiverImpl, mWindow, mSendTrack, + mReceiver, mDtmf, mDtlsTransport, + mLastStableDtlsTransport) +NS_IMPL_CYCLE_COLLECTING_ADDREF(TransceiverImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TransceiverImpl) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransceiverImpl) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TransceiverImpl::TransceiverImpl( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, + const std::string& aPCHandle, MediaTransportHandler* aTransportHandler, + JsepTransceiver* aJsepTransceiver, nsISerialEventTarget* aMainThread, + nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack, + WebRtcCallWrapper* aCallWrapper) + : mWindow(aWindow), + mPCHandle(aPCHandle), + mTransportHandler(aTransportHandler), + mJsepTransceiver(aJsepTransceiver), + mHaveSetupTransport(false), + mMainThread(aMainThread), + mStsThread(aStsThread), + mSendTrack(aSendTrack), + mCallWrapper(aCallWrapper) { + if (IsVideo()) { + InitVideo(); + } else { + InitAudio(); + } + + if (!IsValid()) { + return; + } + + mConduit->SetPCHandle(mPCHandle); + + mReceiver = new RTCRtpReceiver(aWindow, aPrivacyNeeded, aPCHandle, + aTransportHandler, aJsepTransceiver, + aMainThread, aStsThread, mConduit, this); + + if (!IsVideo()) { + mDtmf = new RTCDTMFSender( + aWindow, this, static_cast<AudioSessionConduit*>(mConduit.get())); + } + + mTransmitPipeline = + new MediaPipelineTransmit(mPCHandle, mTransportHandler, mMainThread.get(), + mStsThread.get(), IsVideo(), mConduit); + + mTransmitPipeline->SetTrack(mSendTrack); + + auto self = nsMainThreadPtrHandle<TransceiverImpl>( + new nsMainThreadPtrHolder<TransceiverImpl>( + "TransceiverImpl::TransceiverImpl::self", this, false)); + mStsThread->Dispatch( + NS_NewRunnableFunction("TransceiverImpl::TransceiverImpl", [self] { + self->mTransportHandler->SignalStateChange.connect( + self.get(), &TransceiverImpl::UpdateDtlsTransportState); + self->mTransportHandler->SignalRtcpStateChange.connect( + self.get(), &TransceiverImpl::UpdateDtlsTransportState); + })); +} + +TransceiverImpl::~TransceiverImpl() = default; + +void TransceiverImpl::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, + bool aStable) { + mDtlsTransport = aDtlsTransport; + if (aStable) { + mLastStableDtlsTransport = mDtlsTransport; + } +} + +void TransceiverImpl::RollbackToStableDtlsTransport() { + mDtlsTransport = mLastStableDtlsTransport; +} + +void TransceiverImpl::UpdateDtlsTransportState(const std::string& aTransportId, + TransportLayer::State aState) { + if (!mMainThread->IsOnCurrentThread()) { + mMainThread->Dispatch( + WrapRunnable(this, &TransceiverImpl::UpdateDtlsTransportState, + aTransportId, aState), + NS_DISPATCH_NORMAL); + return; + } + + if (!mDtlsTransport) { + return; + } + + mDtlsTransport->UpdateState(aState); +} + +void TransceiverImpl::InitAudio() { + mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << ": Failed to create AudioSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void TransceiverImpl::InitVideo() { + mConduit = VideoSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << ": Failed to create VideoSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +nsresult TransceiverImpl::UpdateSinkIdentity( + const dom::MediaStreamTrack* aTrack, nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity) { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + mTransmitPipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity); + return NS_OK; +} + +void TransceiverImpl::Shutdown_m() { + // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia -> + // PCMedia::SelfDestruct. Satisfies step 7 of + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close + if (mDtlsTransport) { + mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED); + } + Stop(); + mTransmitPipeline = nullptr; + auto self = nsMainThreadPtrHandle<TransceiverImpl>( + new nsMainThreadPtrHolder<TransceiverImpl>( + "TransceiverImpl::Shutdown_m::self", this, false)); + mStsThread->Dispatch(NS_NewRunnableFunction(__func__, [self] { + self->disconnect_all(); + self->mTransportHandler = nullptr; + })); +} + +nsresult TransceiverImpl::UpdateSendTrack(dom::MediaStreamTrack* aSendTrack) { + if (mJsepTransceiver->IsStopped()) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ << "(" + << aSendTrack << ")"); + mSendTrack = aSendTrack; + return mTransmitPipeline->SetTrack(mSendTrack); +} + +nsresult TransceiverImpl::UpdateTransport() { + if (!mJsepTransceiver->HasLevel() || mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + mReceiver->UpdateTransport(); + + if (!mHaveSetupTransport) { + mTransmitPipeline->SetLevel(mJsepTransceiver->GetLevel()); + mHaveSetupTransport = true; + } + + mTransmitPipeline->UpdateTransport_m( + mJsepTransceiver->mTransport.mTransportId, nullptr); + return NS_OK; +} + +nsresult TransceiverImpl::UpdateConduit() { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + if (mJsepTransceiver->IsAssociated()) { + mMid = mJsepTransceiver->GetMid(); + } else { + mMid.clear(); + } + + mReceiver->Stop(); + + mTransmitPipeline->Stop(); + + // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be + // set to a non-zero value or the CreateVideo...Stream call will fail. + if (mJsepTransceiver->mSendTrack.GetSsrcs().empty()) { + MOZ_MTLOG(ML_ERROR, + mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " No local SSRC set! (Should be set regardless of " + "whether we're sending RTP; we need a local SSRC in " + "all cases)"); + return NS_ERROR_FAILURE; + } + + if (!mConduit->SetLocalSSRCs(mJsepTransceiver->mSendTrack.GetSsrcs(), + mJsepTransceiver->mSendTrack.GetRtxSsrcs())) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " SetLocalSSRCs failed"); + return NS_ERROR_FAILURE; + } + + mConduit->SetLocalCNAME(mJsepTransceiver->mSendTrack.GetCNAME().c_str()); + mConduit->SetLocalMID(mMid); + + nsresult rv; + + mReceiver->UpdateConduit(); + + // TODO(bug 1616937): Move this stuff into RTCRtpSender. + if (IsVideo()) { + rv = UpdateVideoConduit(); + } else { + rv = UpdateAudioConduit(); + } + + if (NS_FAILED(rv)) { + return rv; + } + + if (mJsepTransceiver->mRecvTrack.GetActive()) { + mReceiver->Start(); + } + + if (mJsepTransceiver->mSendTrack.GetActive()) { + if (!mSendTrack) { + MOZ_MTLOG(ML_WARNING, + mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Starting transmit conduit without send track!"); + } + mTransmitPipeline->Start(); + } + + return NS_OK; +} + +void TransceiverImpl::ResetSync() { + if (mConduit) { + mConduit->SetSyncGroup(""); + } +} + +nsresult TransceiverImpl::SyncWithMatchingVideoConduits( + std::vector<RefPtr<TransceiverImpl>>& transceivers) { + if (mJsepTransceiver->IsStopped()) { + return NS_OK; + } + + if (IsVideo()) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " called when transceiver is not " + "video! This should never happen."); + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + std::set<std::string> myReceiveStreamIds; + myReceiveStreamIds.insert(mJsepTransceiver->mRecvTrack.GetStreamIds().begin(), + mJsepTransceiver->mRecvTrack.GetStreamIds().end()); + + for (RefPtr<TransceiverImpl>& transceiver : transceivers) { + if (!transceiver->IsValid()) { + continue; + } + + if (!transceiver->IsVideo()) { + // |this| is an audio transceiver, so we skip other audio transceivers + continue; + } + + // Maybe could make this more efficient by cacheing this set, but probably + // not worth it. + for (const std::string& streamId : + transceiver->mJsepTransceiver->mRecvTrack.GetStreamIds()) { + if (myReceiveStreamIds.count(streamId)) { + // Ok, we have one video, one non-video - cross the streams! + mConduit->SetSyncGroup(streamId); + transceiver->mConduit->SetSyncGroup(streamId); + + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Syncing " << mConduit.get() << " to " + << transceiver->mConduit.get()); + + // The sync code in call.cc only permits sync between audio stream and + // one video stream. They take the first match, so there's no point in + // continuing here. If we want to change the default, we should sort + // video streams here and only call SetSyncGroup on the chosen stream. + break; + } + } + } + + return NS_OK; +} + +bool TransceiverImpl::ConduitHasPluginID(uint64_t aPluginID) { + return mConduit ? mConduit->CodecPluginID() == aPluginID : false; +} + +bool TransceiverImpl::HasSendTrack( + const dom::MediaStreamTrack* aSendTrack) const { + if (!mSendTrack) { + return false; + } + + if (!aSendTrack) { + return true; + } + + return mSendTrack.get() == aSendTrack; +} + +void TransceiverImpl::SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver, + ErrorResult& aRv) { + MOZ_MTLOG(ML_DEBUG, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " Syncing with JS transceiver"); + + if (!mTransmitPipeline) { + // Shutdown_m has already been called, probably due to pc.close(). Just + // nod and smile. + return; + } + + // Update stopped, both ways, since either JSEP or JS can stop these + if (mJsepTransceiver->IsStopped()) { + // We don't call RTCRtpTransceiver::Stop(), because that causes another sync + aJsTransceiver.SetStopped(aRv); + Stop(); + } else if (aJsTransceiver.GetStopped(aRv)) { + mJsepTransceiver->Stop(); + Stop(); + } + + // Lots of this in here for simple getters that should never fail. Lame. + // Just propagate the exception and let JS log it. + if (aRv.Failed()) { + return; + } + + // Update direction from JS only + dom::RTCRtpTransceiverDirection direction = aJsTransceiver.GetDirection(aRv); + + if (aRv.Failed()) { + return; + } + + switch (direction) { + case dom::RTCRtpTransceiverDirection::Sendrecv: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kSendrecv; + break; + case dom::RTCRtpTransceiverDirection::Sendonly: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kSendonly; + break; + case dom::RTCRtpTransceiverDirection::Recvonly: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kRecvonly; + break; + case dom::RTCRtpTransceiverDirection::Inactive: + mJsepTransceiver->mJsDirection = + SdpDirectionAttribute::Direction::kInactive; + break; + default: + MOZ_ASSERT(false); + aRv = NS_ERROR_INVALID_ARG; + return; + } + + // Update send track ids in JSEP + RefPtr<dom::RTCRtpSender> sender = aJsTransceiver.GetSender(aRv); + if (aRv.Failed()) { + return; + } + + nsTArray<RefPtr<DOMMediaStream>> streams; + sender->GetStreams(streams, aRv); + if (aRv.Failed()) { + return; + } + + std::vector<std::string> streamIds; + for (const auto& stream : streams) { + nsString wideStreamId; + stream->GetId(wideStreamId); + std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get(); + MOZ_ASSERT(!streamId.empty()); + streamIds.push_back(streamId); + } + + mJsepTransceiver->mSendTrack.UpdateStreamIds(streamIds); + + // Update RTCRtpParameters + // TODO: Both ways for things like ssrc, codecs, header extensions, etc + + dom::RTCRtpParameters parameters; + sender->GetParameters(parameters, aRv); + + if (aRv.Failed()) { + return; + } + + std::vector<JsepTrack::JsConstraints> constraints; + + if (parameters.mEncodings.WasPassed()) { + for (auto& encoding : parameters.mEncodings.Value()) { + JsepTrack::JsConstraints constraint; + if (encoding.mRid.WasPassed()) { + // TODO: Either turn on the RID RTP header extension in JsepSession, or + // just leave that extension on all the time? + constraint.rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); + } + if (encoding.mMaxBitrate.WasPassed()) { + constraint.constraints.maxBr = encoding.mMaxBitrate.Value(); + } + constraint.constraints.scaleDownBy = encoding.mScaleResolutionDownBy; + constraints.push_back(constraint); + } + } + + if (mJsepTransceiver->mSendTrack.SetJsConstraints(constraints)) { + if (mTransmitPipeline->Transmitting()) { + WebrtcGmpPCHandleSetter setter(mPCHandle); + DebugOnly<nsresult> rv = UpdateConduit(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + } + + // If a SRD has unset the receive bit, stop the receive pipeline so incoming + // RTP does not unmute the receive track. + if (!mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit() || + !mJsepTransceiver->mRecvTrack.GetActive()) { + mReceiver->Stop(); + } + + // mid from JSEP + if (mJsepTransceiver->IsAssociated()) { + aJsTransceiver.SetMid( + NS_ConvertUTF8toUTF16(mJsepTransceiver->GetMid().c_str()), aRv); + } else { + aJsTransceiver.UnsetMid(aRv); + } + + if (aRv.Failed()) { + return; + } + + // currentDirection from JSEP, but not if "this transceiver has never been + // represented in an offer/answer exchange" + if (mJsepTransceiver->HasLevel() && mJsepTransceiver->IsNegotiated()) { + if (IsReceiving()) { + if (IsSending()) { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Sendrecv, aRv); + } else { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Recvonly, aRv); + } + } else { + if (IsSending()) { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Sendonly, aRv); + } else { + aJsTransceiver.SetCurrentDirection( + dom::RTCRtpTransceiverDirection::Inactive, aRv); + } + } + + if (aRv.Failed()) { + return; + } + } + + // AddTrack magic from JS + if (aJsTransceiver.GetAddTrackMagic(aRv)) { + mJsepTransceiver->SetAddTrackMagic(); + } + + if (aRv.Failed()) { + return; + } + + if (mJsepTransceiver->IsRemoved()) { + aJsTransceiver.SetShouldRemove(true, aRv); + } +} + +bool TransceiverImpl::CanSendDTMF() const { + // Spec says: "If connection's RTCPeerConnectionState is not "connected" + // return false." We don't support that right now. This is supposed to be + // true once ICE is complete, and _all_ DTLS handshakes are also complete. We + // don't really have access to the state of _all_ of our DTLS states either. + // Our pipeline _does_ know whether SRTP/SRTCP is ready, which happens + // immediately after our transport finishes DTLS (unless there was an error), + // so this is pretty close. + // TODO (bug 1265827): Base this on RTCPeerConnectionState instead. + // TODO (bug 1623193): Tighten this up + if (!IsSending() || !mSendTrack) { + return false; + } + + // Ok, it looks like the connection is up and sending. Did we negotiate + // telephone-event? + JsepTrackNegotiatedDetails* details = + mJsepTransceiver->mSendTrack.GetNegotiatedDetails(); + if (NS_WARN_IF(!details || !details->GetEncodingCount())) { + // What? + return false; + } + + for (size_t i = 0; i < details->GetEncodingCount(); ++i) { + const auto& encoding = details->GetEncoding(i); + for (const auto& codec : encoding.GetCodecs()) { + if (codec->mName == "telephone-event") { + return true; + } + } + } + + return false; +} + +JSObject* TransceiverImpl::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::TransceiverImpl_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* TransceiverImpl::GetParentObject() const { return mWindow; } + +RefPtr<MediaPipelineTransmit> TransceiverImpl::GetSendPipeline() { + return mTransmitPipeline; +} + +static nsresult JsepCodecDescToAudioCodecConfig( + const JsepCodecDescription& aCodec, UniquePtr<AudioCodecConfig>* aConfig) { + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kAudio); + if (aCodec.mType != SdpMediaSection::kAudio) return NS_ERROR_INVALID_ARG; + + const JsepAudioCodecDescription& desc = + static_cast<const JsepAudioCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + aConfig->reset(new AudioCodecConfig(pt, desc.mName, desc.mClock, + desc.mForceMono ? 1 : desc.mChannels, + desc.mFECEnabled)); + (*aConfig)->mMaxPlaybackRate = desc.mMaxPlaybackRate; + (*aConfig)->mDtmfEnabled = desc.mDtmfEnabled; + (*aConfig)->mDTXEnabled = desc.mDTXEnabled; + (*aConfig)->mMaxAverageBitrate = desc.mMaxAverageBitrate; + (*aConfig)->mFrameSizeMs = desc.mFrameSizeMs; + (*aConfig)->mMinFrameSizeMs = desc.mMinFrameSizeMs; + (*aConfig)->mMaxFrameSizeMs = desc.mMaxFrameSizeMs; + (*aConfig)->mCbrEnabled = desc.mCbrEnabled; + + return NS_OK; +} + +// TODO: Maybe move this someplace else? +/*static*/ +nsresult TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<AudioCodecConfig>>* aConfigs) { + UniquePtr<AudioCodecConfig> telephoneEvent; + + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + UniquePtr<AudioCodecConfig> config; + if (NS_FAILED(JsepCodecDescToAudioCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + if (config->mName == "telephone-event") { + telephoneEvent = std::move(config); + } else { + aConfigs->push_back(std::move(config)); + } + } + } + + // Put telephone event at the back, because webrtc.org crashes if we don't + // If we need to do even more sorting, we should use std::sort. + if (telephoneEvent) { + aConfigs->push_back(std::move(telephoneEvent)); + } + + if (aConfigs->empty()) { + MOZ_MTLOG(ML_ERROR, "Can't set up a conduit with 0 codecs"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult TransceiverImpl::UpdateAudioConduit() { + MOZ_ASSERT(IsValid()); + + RefPtr<AudioSessionConduit> conduit = + static_cast<AudioSessionConduit*>(mConduit.get()); + + if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() && + mJsepTransceiver->mSendTrack.GetActive()) { + const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails()); + std::vector<UniquePtr<AudioCodecConfig>> configs; + nsresult rv = TransceiverImpl::NegotiatedDetailsToAudioCodecConfigs( + details, &configs); + + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " Failed to convert JsepCodecDescriptions to " + "AudioCodecConfigs (send)."); + return rv; + } + + for (const auto& value : configs) { + if (value->mName == "telephone-event") { + // we have a telephone event codec, so we need to make sure + // the dynamic pt is set properly + conduit->SetDtmfPayloadType(value->mType, value->mFreq); + break; + } + } + + auto error = conduit->ConfigureSendMediaCodec(configs[0].get()); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend); + } + + return NS_OK; +} + +static nsresult JsepCodecDescToVideoCodecConfig( + const JsepCodecDescription& aCodec, UniquePtr<VideoCodecConfig>* aConfig) { + MOZ_ASSERT(aCodec.mType == SdpMediaSection::kVideo); + if (aCodec.mType != SdpMediaSection::kVideo) { + MOZ_ASSERT(false, "JsepCodecDescription has wrong type"); + return NS_ERROR_INVALID_ARG; + } + + const JsepVideoCodecDescription& desc = + static_cast<const JsepVideoCodecDescription&>(aCodec); + + uint16_t pt; + + if (!desc.GetPtAsInt(&pt)) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << desc.mDefaultPt); + return NS_ERROR_INVALID_ARG; + } + + UniquePtr<VideoCodecConfigH264> h264Config; + + if (desc.mName == "H264") { + h264Config = MakeUnique<VideoCodecConfigH264>(); + size_t spropSize = sizeof(h264Config->sprop_parameter_sets); + strncpy(h264Config->sprop_parameter_sets, desc.mSpropParameterSets.c_str(), + spropSize); + h264Config->sprop_parameter_sets[spropSize - 1] = '\0'; + h264Config->packetization_mode = desc.mPacketizationMode; + h264Config->profile_level_id = desc.mProfileLevelId; + h264Config->tias_bw = 0; // TODO(bug 1403206) + } + + aConfig->reset(new VideoCodecConfig(pt, desc.mName, desc.mConstraints, + h264Config.get())); + + (*aConfig)->mAckFbTypes = desc.mAckFbTypes; + (*aConfig)->mNackFbTypes = desc.mNackFbTypes; + (*aConfig)->mCcmFbTypes = desc.mCcmFbTypes; + (*aConfig)->mRembFbSet = desc.RtcpFbRembIsSet(); + (*aConfig)->mFECFbSet = desc.mFECEnabled; + (*aConfig)->mTransportCCFbSet = desc.RtcpFbTransportCCIsSet(); + if (desc.mFECEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(desc.mREDPayloadType, &pt)) { + (*aConfig)->mREDPayloadType = pt; + } + if (SdpHelper::GetPtAsInt(desc.mULPFECPayloadType, &pt)) { + (*aConfig)->mULPFECPayloadType = pt; + } + } + if (desc.mRtxEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(desc.mRtxPayloadType, &pt)) { + (*aConfig)->mRTXPayloadType = pt; + } + } + + return NS_OK; +} + +// TODO: Maybe move this someplace else? +/*static*/ +nsresult TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<VideoCodecConfig>>* aConfigs) { + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + UniquePtr<VideoCodecConfig> config; + if (NS_FAILED(JsepCodecDescToVideoCodecConfig(*codec, &config))) { + return NS_ERROR_INVALID_ARG; + } + + config->mTias = aDetails.GetTias(); + + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(codec->mDefaultPt)) { + VideoCodecConfig::Encoding encoding; + encoding.rid = jsepEncoding.mRid; + encoding.constraints = jsepEncoding.mConstraints; + config->mEncodings.push_back(encoding); + } + } + + aConfigs->push_back(std::move(config)); + } + } + + return NS_OK; +} + +nsresult TransceiverImpl::UpdateVideoConduit() { + MOZ_ASSERT(IsValid()); + + RefPtr<VideoSessionConduit> conduit = + static_cast<VideoSessionConduit*>(mConduit.get()); + + // It is possible for SDP to signal that there is a send track, but there not + // actually be a send track, according to the specification; all that needs to + // happen is for the transceiver to be configured to send... + if (mJsepTransceiver->mSendTrack.GetNegotiatedDetails() && + mJsepTransceiver->mSendTrack.GetActive() && mSendTrack) { + const auto& details(*mJsepTransceiver->mSendTrack.GetNegotiatedDetails()); + + UpdateConduitRtpExtmap(*conduit, details, LocalDirection::kSend); + + nsresult rv = ConfigureVideoCodecMode(*conduit); + if (NS_FAILED(rv)) { + return rv; + } + + std::vector<UniquePtr<VideoCodecConfig>> configs; + rv = TransceiverImpl::NegotiatedDetailsToVideoCodecConfigs(details, + &configs); + if (NS_FAILED(rv)) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " Failed to convert JsepCodecDescriptions to " + "VideoCodecConfigs (send)."); + return rv; + } + + if (configs.empty()) { + MOZ_MTLOG(ML_INFO, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " No codecs were negotiated (send)."); + return NS_OK; + } + + auto error = conduit->ConfigureSendMediaCodec(configs[0].get(), + details.GetRtpRtcpConfig()); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureSendMediaCodec failed: " << error); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +nsresult TransceiverImpl::ConfigureVideoCodecMode( + VideoSessionConduit& aConduit) { + RefPtr<mozilla::dom::VideoStreamTrack> videotrack = + mSendTrack->AsVideoStreamTrack(); + + if (!videotrack) { + MOZ_MTLOG( + ML_ERROR, mPCHandle + << "[" << mMid << "]: " << __FUNCTION__ + << " mSendTrack is not video! This should never happen!"); + MOZ_CRASH(); + return NS_ERROR_FAILURE; + } + + dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); + webrtc::VideoCodecMode mode = webrtc::kRealtimeVideo; + switch (source) { + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Window: + mode = webrtc::kScreensharing; + break; + + case dom::MediaSourceEnum::Camera: + default: + mode = webrtc::kRealtimeVideo; + break; + } + + auto error = aConduit.ConfigureCodecMode(mode); + if (error) { + MOZ_MTLOG(ML_ERROR, mPCHandle << "[" << mMid << "]: " << __FUNCTION__ + << " ConfigureCodecMode failed: " << error); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void TransceiverImpl::UpdateConduitRtpExtmap( + MediaSessionConduit& aConduit, const JsepTrackNegotiatedDetails& aDetails, + const LocalDirection aDirection) { + std::vector<webrtc::RtpExtension> extmaps; + // @@NG read extmap from track + aDetails.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + if (!extmaps.empty()) { + aConduit.SetLocalRTPExtensions(aDirection, extmaps); + } +} + +void TransceiverImpl::Stop() { + mTransmitPipeline->Shutdown_m(); + mReceiver->Shutdown(); + // Make sure that stats queries stop working on this transceiver. + UpdateSendTrack(nullptr); + + if (mConduit) { + mConduit->DeleteStreams(); + } + mConduit = nullptr; + + if (mDtmf) { + mDtmf->StopPlayout(); + } +} + +bool TransceiverImpl::IsVideo() const { + return mJsepTransceiver->GetMediaType() == SdpMediaSection::MediaType::kVideo; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/TransceiverImpl.h b/dom/media/webrtc/jsapi/TransceiverImpl.h new file mode 100644 index 0000000000..335faa7579 --- /dev/null +++ b/dom/media/webrtc/jsapi/TransceiverImpl.h @@ -0,0 +1,185 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _TRANSCEIVERIMPL_H_ +#define _TRANSCEIVERIMPL_H_ + +#include <string> +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISerialEventTarget.h" +#include "nsTArray.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "ErrorList.h" +#include "jsep/JsepTransceiver.h" +#include "transport/transportlayer.h" // For TransportLayer::State + +class nsIPrincipal; + +namespace mozilla { +class PeerIdentity; +class JsepTransceiver; +enum class MediaSessionConduitLocalDirection : int; +class MediaSessionConduit; +class VideoSessionConduit; +class AudioSessionConduit; +struct AudioCodecConfig; +class VideoCodecConfig; // Why is this a class, but AudioCodecConfig a struct? +class MediaPipelineTransmit; +class MediaPipeline; +class MediaPipelineFilter; +class MediaTransportHandler; +class WebRtcCallWrapper; +class JsepTrackNegotiatedDetails; + +namespace dom { +class RTCDtlsTransport; +class RTCDTMFSender; +class RTCRtpTransceiver; +struct RTCRtpSourceEntry; +class RTCRtpReceiver; +} // namespace dom + +/** + * This is what ties all the various pieces that make up a transceiver + * together. This includes: + * MediaStreamTrack for rendering and capture + * MediaTransportHandler for RTP transmission/reception + * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and + * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP. + */ +class TransceiverImpl : public nsISupports, + public nsWrapperCache, + public sigslot::has_slots<> { + public: + /** + * |aSendTrack| might or might not be set. + */ + TransceiverImpl(nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, + const std::string& aPCHandle, + MediaTransportHandler* aTransportHandler, + JsepTransceiver* aJsepTransceiver, + nsISerialEventTarget* aMainThread, + nsISerialEventTarget* aStsThread, + dom::MediaStreamTrack* aSendTrack, + WebRtcCallWrapper* aCallWrapper); + + bool IsValid() const { return !!mConduit; } + + nsresult UpdateSendTrack(dom::MediaStreamTrack* aSendTrack); + + nsresult UpdateSinkIdentity(const dom::MediaStreamTrack* aTrack, + nsIPrincipal* aPrincipal, + const PeerIdentity* aSinkIdentity); + + nsresult UpdateTransport(); + + nsresult UpdateConduit(); + + void ResetSync(); + + nsresult SyncWithMatchingVideoConduits( + std::vector<RefPtr<TransceiverImpl>>& transceivers); + + void Shutdown_m(); + + bool ConduitHasPluginID(uint64_t aPluginID); + + bool HasSendTrack(const dom::MediaStreamTrack* aSendTrack) const; + + // This is so PCImpl can unregister from PrincipalChanged callbacks; maybe we + // should have TransceiverImpl handle these callbacks instead? It would need + // to be able to get a ref to PCImpl though. + RefPtr<dom::MediaStreamTrack> GetSendTrack() { return mSendTrack; } + + // for webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() const; + void SyncWithJS(dom::RTCRtpTransceiver& aJsTransceiver, ErrorResult& aRv); + dom::RTCRtpReceiver* Receiver() const { return mReceiver; } + dom::RTCDTMFSender* GetDtmf() const { return mDtmf; } + dom::RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; } + + bool CanSendDTMF() const; + + // TODO: These are for stats; try to find a cleaner way. + RefPtr<MediaPipelineTransmit> GetSendPipeline(); + + void UpdateDtlsTransportState(const std::string& aTransportId, + TransportLayer::State aState); + void SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, bool aStable); + void RollbackToStableDtlsTransport(); + + std::string GetTransportId() const { + return mJsepTransceiver->mTransport.mTransportId; + } + + bool IsVideo() const; + + bool IsSending() const { + return !mJsepTransceiver->IsStopped() && + mJsepTransceiver->mSendTrack.GetActive(); + } + + bool IsReceiving() const { + return !mJsepTransceiver->IsStopped() && + mJsepTransceiver->mRecvTrack.GetActive(); + } + + MediaSessionConduit* GetConduit() const { return mConduit; } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransceiverImpl) + + static nsresult NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<AudioCodecConfig>>* aConfigs); + + static nsresult NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<UniquePtr<VideoCodecConfig>>* aConfigs); + + static void UpdateConduitRtpExtmap( + MediaSessionConduit& aConduit, const JsepTrackNegotiatedDetails& aDetails, + const MediaSessionConduitLocalDirection aDir); + + private: + virtual ~TransceiverImpl(); + void InitAudio(); + void InitVideo(); + nsresult UpdateAudioConduit(); + nsresult UpdateVideoConduit(); + nsresult ConfigureVideoCodecMode(VideoSessionConduit& aConduit); + void Stop(); + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + const std::string mPCHandle; + RefPtr<MediaTransportHandler> mTransportHandler; + RefPtr<JsepTransceiver> mJsepTransceiver; + std::string mMid; + bool mHaveSetupTransport; + nsCOMPtr<nsISerialEventTarget> mMainThread; + nsCOMPtr<nsISerialEventTarget> mStsThread; + RefPtr<dom::MediaStreamTrack> mSendTrack; + // state for webrtc.org that is shared between all transceivers + RefPtr<WebRtcCallWrapper> mCallWrapper; + RefPtr<MediaSessionConduit> mConduit; + RefPtr<MediaPipelineTransmit> mTransmitPipeline; + // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for + // an RTCDtlsTransport. They are always the same, so we'll store it + // here. + RefPtr<dom::RTCDtlsTransport> mDtlsTransport; + // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for + // a last stable state RTCDtlsTransport. They are always the same, so + // we'll store it here. + RefPtr<dom::RTCDtlsTransport> mLastStableDtlsTransport; + RefPtr<dom::RTCRtpReceiver> mReceiver; + // TODO(bug 1616937): Move this to RTCRtpSender + RefPtr<dom::RTCDTMFSender> mDtmf; +}; + +} // namespace mozilla + +#endif // _TRANSCEIVERIMPL_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalChild.h b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h new file mode 100644 index 0000000000..cb0ecfd041 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_CHILD_H_ +#define _WEBRTC_GLOBAL_CHILD_H_ + +#include "mozilla/dom/PWebrtcGlobalChild.h" + +namespace mozilla { +namespace dom { + +class WebrtcGlobalChild : public PWebrtcGlobalChild { + friend class ContentChild; + + bool mShutdown; + + MOZ_IMPLICIT WebrtcGlobalChild(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual mozilla::ipc::IPCResult RecvGetStats( + const nsString& aPcIdFilter, GetStatsResolver&& aResolve) override; + virtual mozilla::ipc::IPCResult RecvClearStats() override; + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we can't do MOZ_CAN_RUN_SCRIPT in + // ipdl-generated things yet. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual mozilla::ipc::IPCResult RecvGetLog( + GetLogResolver&& aResolve) override; + virtual mozilla::ipc::IPCResult RecvClearLog() override; + virtual mozilla::ipc::IPCResult RecvSetAecLogging( + const bool& aEnable) override; + virtual mozilla::ipc::IPCResult RecvSetDebugMode(const int& aLevel) override; + + public: + virtual ~WebrtcGlobalChild(); + static WebrtcGlobalChild* Create(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_CHILD_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp new file mode 100644 index 0000000000..25bcc2a6b0 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp @@ -0,0 +1,714 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebrtcGlobalInformation.h" +#include "mozilla/media/webrtc/WebrtcGlobal.h" +#include "WebrtcGlobalChild.h" +#include "WebrtcGlobalParent.h" + +#include <algorithm> +#include <vector> +#include <type_traits> + +#include "mozilla/dom/WebrtcGlobalInformationBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal +#include "mozilla/dom/ContentChild.h" + +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "mozilla/ErrorResult.h" +#include "nsProxyRelease.h" // nsMainThreadPtrHolder +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ClearOnShutdown.h" + +#include "common/browser_logging/WebRtcLog.h" +#include "transport/runnable_utils.h" +#include "MediaTransportHandler.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" + +namespace mozilla::dom { + +typedef nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback> + StatsRequestCallback; + +typedef nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback> LogRequestCallback; + +class WebrtcContentParents { + public: + static WebrtcGlobalParent* Alloc(); + static void Dealloc(WebrtcGlobalParent* aParent); + static bool Empty() { return sContentParents.empty(); } + static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll() { + return sContentParents; + } + + private: + static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents; + WebrtcContentParents() = delete; + WebrtcContentParents(const WebrtcContentParents&) = delete; + WebrtcContentParents& operator=(const WebrtcContentParents&) = delete; +}; + +std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents; + +WebrtcGlobalParent* WebrtcContentParents::Alloc() { + RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent; + sContentParents.push_back(cp); + return cp.get(); +} + +void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) { + if (aParent) { + aParent->mShutdown = true; + auto cp = + std::find(sContentParents.begin(), sContentParents.end(), aParent); + if (cp != sContentParents.end()) { + sContentParents.erase(cp); + } + } +} + +static PeerConnectionCtx* GetPeerConnectionCtx() { + if (PeerConnectionCtx::isActive()) { + MOZ_ASSERT(PeerConnectionCtx::GetInstance()); + return PeerConnectionCtx::GetInstance(); + } + return nullptr; +} + +static RefPtr<PWebrtcGlobalParent::GetStatsPromise> +GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) { + nsTArray<RefPtr<dom::RTCStatsReportPromise>> promises; + + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (ctx) { + // Grab stats for non-closed PCs + for (const auto& [id, pc] : ctx->GetPeerConnections()) { + if (aPcIdFilter.IsEmpty() || aPcIdFilter.EqualsASCII(id.c_str())) { + if (pc->HasMedia()) { + promises.AppendElement(pc->GetStats(nullptr, true)); + } + } + } + + // Grab stats for closed PCs + for (const auto& report : ctx->mStatsForClosedPeerConnections) { + if (aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) { + promises.AppendElement(dom::RTCStatsReportPromise::CreateAndResolve( + MakeUnique<dom::RTCStatsReportInternal>(report), __func__)); + } + } + } + + auto UnwrapUniquePtrs = [](dom::RTCStatsReportPromise::AllSettledPromiseType:: + ResolveOrRejectValue&& aResult) { + nsTArray<dom::RTCStatsReportInternal> reports; + MOZ_RELEASE_ASSERT(aResult.IsResolve(), "AllSettled should never reject!"); + for (auto& reportResult : aResult.ResolveValue()) { + if (reportResult.IsResolve()) { + reports.AppendElement(*reportResult.ResolveValue()); + } + } + return PWebrtcGlobalParent::GetStatsPromise::CreateAndResolve( + std::move(reports), __func__); + }; + + return dom::RTCStatsReportPromise::AllSettled( + GetMainThreadSerialEventTarget(), promises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(UnwrapUniquePtrs)); +} + +static nsTArray<dom::RTCStatsReportInternal>& GetWebrtcGlobalStatsStash() { + static StaticAutoPtr<nsTArray<dom::RTCStatsReportInternal>> sStash; + if (!sStash) { + sStash = new nsTArray<dom::RTCStatsReportInternal>(); + ClearOnShutdown(&sStash); + } + return *sStash; +} + +static std::map<int32_t, dom::Sequence<nsString>>& GetWebrtcGlobalLogStash() { + static StaticAutoPtr<std::map<int32_t, dom::Sequence<nsString>>> sStash; + if (!sStash) { + sStash = new std::map<int32_t, dom::Sequence<nsString>>(); + ClearOnShutdown(&sStash); + } + return *sStash; +} + +static void ClearClosedStats() { + GetWebrtcGlobalStatsStash().Clear(); + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + + if (ctx) { + ctx->mStatsForClosedPeerConnections.Clear(); + } +} + +void WebrtcGlobalInformation::ClearAllStats(const GlobalObject& aGlobal) { + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!WebrtcContentParents::Empty()) { + // Pass on the request to any content process based PeerConnections. + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearStats(); + } + } + + // Flush the history for the chrome process + ClearClosedStats(); +} + +void WebrtcGlobalInformation::GetAllStats( + const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& pcIdFilter, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + nsTArray<RefPtr<PWebrtcGlobalParent::GetStatsPromise>> statsPromises; + + nsString filter; + if (pcIdFilter.WasPassed()) { + filter = pcIdFilter.Value(); + } + + for (const auto& cp : WebrtcContentParents::GetAll()) { + statsPromises.AppendElement(cp->SendGetStats(filter)); + } + + // Stats from this (the parent) process. How long do we keep supporting this? + statsPromises.AppendElement(GetStatsPromiseForThisProcess(filter)); + + // CallbackObject does not support threadsafe refcounting, and must be + // used and destroyed on main. + StatsRequestCallback callbackHandle( + new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>( + "WebrtcGlobalStatisticsCallback", &aStatsCallback)); + + auto FlattenThenStashThenCallback = + [callbackHandle, + filter](PWebrtcGlobalParent::GetStatsPromise::AllSettledPromiseType:: + ResolveOrRejectValue&& aResult) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + std::set<nsString> pcids; + WebrtcGlobalStatisticsReport flattened; + MOZ_RELEASE_ASSERT(aResult.IsResolve(), + "AllSettled should never reject!"); + for (auto& contentProcessResult : aResult.ResolveValue()) { + // TODO: Report rejection on individual content processes someday? + if (contentProcessResult.IsResolve()) { + for (auto& pcStats : contentProcessResult.ResolveValue()) { + pcids.insert(pcStats.mPcid); + if (!flattened.mReports.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + } + } + } + + if (filter.IsEmpty()) { + // Unfiltered is pretty simple; add stuff from stash that is + // missing, then stomp the stash with the new reports. + for (auto& pcStats : GetWebrtcGlobalStatsStash()) { + if (!pcids.count(pcStats.mPcid)) { + // Stats from a closed PC or stopped content process. + // Content process may have gone away before we got to update + // this. + pcStats.mClosed = true; + if (!flattened.mReports.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + } + } + GetWebrtcGlobalStatsStash() = flattened.mReports; + } else { + // Filtered is slightly more complex + if (flattened.mReports.IsEmpty()) { + // Find entry from stash and add it to report + for (auto& pcStats : GetWebrtcGlobalStatsStash()) { + if (pcStats.mPcid == filter) { + pcStats.mClosed = true; + if (!flattened.mReports.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + } + } + } else { + // Find entries in stash, remove them, and then add new entries + for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) { + auto& pcStats = GetWebrtcGlobalStatsStash()[i]; + if (pcStats.mPcid == filter) { + GetWebrtcGlobalStatsStash().RemoveElementAt(i); + } else { + ++i; + } + } + GetWebrtcGlobalStatsStash().AppendElements(flattened.mReports); + } + } + + IgnoredErrorResult rv; + callbackHandle->Call(flattened, rv); + }; + + PWebrtcGlobalParent::GetStatsPromise::AllSettled( + GetMainThreadSerialEventTarget(), statsPromises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(FlattenThenStashThenCallback)); + + aRv = NS_OK; +} + +static RefPtr<PWebrtcGlobalParent::GetLogPromise> GetLogPromise() { + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (!ctx) { + // This process has never created a PeerConnection, so no ICE logging. + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + Sequence<nsString>(), __func__); + } + + nsresult rv; + nsCOMPtr<nsISerialEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_WARN_IF(NS_FAILED(rv) || !stsThread)) { + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + Sequence<nsString>(), __func__); + } + + RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler(); + + auto AddMarkers = + [](MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& aValue) { + nsString pid; + pid.AppendInt(getpid()); + Sequence<nsString> logs; + if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) { + bool ok = logs.AppendElement( + u"+++++++ BEGIN (process id "_ns + pid + u") ++++++++"_ns, + fallible); + ok &= + !!logs.AppendElements(std::move(aValue.ResolveValue()), fallible); + ok &= !!logs.AppendElement( + u"+++++++ END (process id "_ns + pid + u") ++++++++"_ns, + fallible); + if (!ok) { + mozalloc_handle_oom(0); + } + } + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + std::move(logs), __func__); + }; + + return transportHandler->GetIceLog(nsCString()) + ->Then(GetMainThreadSerialEventTarget(), __func__, std::move(AddMarkers)); +} + +static nsresult RunLogClear() { + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (!ctx) { + // This process has never created a PeerConnection, so no ICE logging. + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsISerialEventTarget> stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } + if (!stsThread) { + return NS_ERROR_FAILURE; + } + + RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler(); + + return RUN_ON_THREAD( + stsThread, + WrapRunnable(transportHandler, &MediaTransportHandler::ClearIceLog), + NS_DISPATCH_NORMAL); +} + +void WebrtcGlobalInformation::ClearLogging(const GlobalObject& aGlobal) { + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + GetWebrtcGlobalLogStash().clear(); + + if (!WebrtcContentParents::Empty()) { + // Clear content process signaling logs + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearLog(); + } + } + + // Clear chrome process signaling logs + Unused << RunLogClear(); +} + +static RefPtr<GenericPromise> UpdateLogStash() { + nsTArray<RefPtr<GenericPromise>> logPromises; + MOZ_ASSERT(XRE_IsParentProcess()); + for (const auto& cp : WebrtcContentParents::GetAll()) { + auto StashLog = + [id = cp->Id() * 2 /* Make sure 1 isn't used */]( + PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) { + GetWebrtcGlobalLogStash()[id] = aValue.ResolveValue(); + } + return GenericPromise::CreateAndResolve(true, __func__); + }; + logPromises.AppendElement(cp->SendGetLog()->Then( + GetMainThreadSerialEventTarget(), __func__, std::move(StashLog))); + } + + // Get ICE logging for this (the parent) process. How long do we support this? + logPromises.AppendElement(GetLogPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [](PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve()) { + GetWebrtcGlobalLogStash()[1] = aValue.ResolveValue(); + } + return GenericPromise::CreateAndResolve(true, __func__); + })); + + return GenericPromise::AllSettled(GetMainThreadSerialEventTarget(), + logPromises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [](GenericPromise::AllSettledPromiseType::ResolveOrRejectValue&& + aValue) { + // We don't care about the value, since we're just going to copy + // what is in the stash. This ignores failures too, which is what + // we want. + return GenericPromise::CreateAndResolve(true, __func__); + }); +} + +void WebrtcGlobalInformation::GetLogging( + const GlobalObject& aGlobal, const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + nsAutoCString pattern; + CopyUTF16toUTF8(aPattern, pattern); + + // CallbackObject does not support threadsafe refcounting, and must be + // destroyed on main. + LogRequestCallback callbackHandle( + new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>( + "WebrtcGlobalLoggingCallback", &aLoggingCallback)); + + auto FilterThenCallback = + [pattern, callbackHandle](GenericPromise::ResolveOrRejectValue&& aValue) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + dom::Sequence<nsString> flattened; + for (const auto& [id, log] : GetWebrtcGlobalLogStash()) { + (void)id; + for (const auto& line : log) { + if (pattern.IsEmpty() || (line.Find(pattern) != kNotFound)) { + if (!flattened.AppendElement(line, fallible)) { + mozalloc_handle_oom(0); + } + } + } + } + IgnoredErrorResult rv; + callbackHandle->Call(flattened, rv); + }; + + UpdateLogStash()->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(FilterThenCallback)); + aRv = NS_OK; +} + +static int32_t sLastSetLevel = 0; +static bool sLastAECDebug = false; +static Maybe<nsCString> sAecDebugLogDir; + +void WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal, + int32_t aLevel) { + if (aLevel) { + StartWebRtcLog(mozilla::LogLevel(aLevel)); + } else { + StopWebRtcLog(); + } + sLastSetLevel = aLevel; + + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendSetDebugMode(aLevel); + } +} + +int32_t WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) { + return sLastSetLevel; +} + +void WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal, + bool aEnable) { + if (aEnable) { + sAecDebugLogDir = Some(StartAecLog()); + } else { + StopAecLog(); + } + + sLastAECDebug = aEnable; + + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendSetAecLogging(aEnable); + } +} + +bool WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) { + return sLastAECDebug; +} + +void WebrtcGlobalInformation::GetAecDebugLogDir(const GlobalObject& aGlobal, + nsAString& aDir) { + aDir = NS_ConvertASCIItoUTF16(sAecDebugLogDir.valueOr(""_ns)); +} + +WebrtcGlobalParent* WebrtcGlobalParent::Alloc() { + return WebrtcContentParents::Alloc(); +} + +bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) { + WebrtcContentParents::Dealloc(aActor); + return true; +} + +void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + mShutdown = true; +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() { + return IPC_OK(); +} + +MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() : mShutdown(false) { + MOZ_COUNT_CTOR(WebrtcGlobalParent); +} + +MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() { + MOZ_COUNT_DTOR(WebrtcGlobalParent); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetStats( + const nsString& aPcIdFilter, GetStatsResolver&& aResolve) { + if (!mShutdown) { + GetStatsPromiseForThisProcess(aPcIdFilter) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [resolve = std::move(aResolve)]( + nsTArray<dom::RTCStatsReportInternal>&& aReports) { + resolve(std::move(aReports)); + }, + []() { MOZ_CRASH(); }); + return IPC_OK(); + } + + aResolve(nsTArray<RTCStatsReportInternal>()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearStats() { + if (mShutdown) { + return IPC_OK(); + } + + ClearClosedStats(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetLog( + GetLogResolver&& aResolve) { + if (mShutdown) { + aResolve(Sequence<nsString>()); + return IPC_OK(); + } + + GetLogPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolve = std::move(aResolve)]( + PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve()) { + aResolve(aValue.ResolveValue()); + } else { + aResolve(Sequence<nsString>()); + } + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearLog() { + if (mShutdown) { + return IPC_OK(); + } + + RunLogClear(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetAecLogging( + const bool& aEnable) { + if (!mShutdown) { + if (aEnable) { + StartAecLog(); + } else { + StopAecLog(); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) { + if (!mShutdown) { + if (aLevel) { + StartWebRtcLog(mozilla::LogLevel(aLevel)); + } else { + StopWebRtcLog(); + } + } + return IPC_OK(); +} + +WebrtcGlobalChild* WebrtcGlobalChild::Create() { + WebrtcGlobalChild* child = static_cast<WebrtcGlobalChild*>( + ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor()); + return child; +} + +void WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) { + mShutdown = true; +} + +MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() : mShutdown(false) { + MOZ_COUNT_CTOR(WebrtcGlobalChild); +} + +MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() { + MOZ_COUNT_DTOR(WebrtcGlobalChild); +} + +static void StoreLongTermICEStatisticsImpl_m(RTCStatsReportInternal* report) { + using namespace Telemetry; + + report->mClosed = true; + + for (const auto& outboundRtpStats : report->mOutboundRtpStreamStats) { + bool isVideo = (outboundRtpStats.mId.Value().Find("video") != -1); + if (!isVideo) { + continue; + } + if (outboundRtpStats.mBitrateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS, + uint32_t(outboundRtpStats.mBitrateMean.Value() / 1000)); + } + if (outboundRtpStats.mBitrateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS, + uint32_t(outboundRtpStats.mBitrateStdDev.Value() / 1000)); + } + if (outboundRtpStats.mFramerateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL, + uint32_t(outboundRtpStats.mFramerateMean.Value())); + } + if (outboundRtpStats.mFramerateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL, + uint32_t(outboundRtpStats.mFramerateStdDev.Value() * 10)); + } + if (outboundRtpStats.mDroppedFrames.WasPassed() && + report->mCallDurationMs.WasPassed()) { + double mins = report->mCallDurationMs.Value() / (1000 * 60); + if (mins > 0) { + Accumulate( + WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM, + uint32_t(double(outboundRtpStats.mDroppedFrames.Value()) / mins)); + } + } + } + + for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) { + bool isVideo = (inboundRtpStats.mId.Value().Find("video") != -1); + if (!isVideo) { + continue; + } + if (inboundRtpStats.mBitrateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS, + uint32_t(inboundRtpStats.mBitrateMean.Value() / 1000)); + } + if (inboundRtpStats.mBitrateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS, + uint32_t(inboundRtpStats.mBitrateStdDev.Value() / 1000)); + } + if (inboundRtpStats.mFramerateMean.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL, + uint32_t(inboundRtpStats.mFramerateMean.Value())); + } + if (inboundRtpStats.mFramerateStdDev.WasPassed()) { + Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL, + uint32_t(inboundRtpStats.mFramerateStdDev.Value() * 10)); + } + if (inboundRtpStats.mDiscardedPackets.WasPassed() && + report->mCallDurationMs.WasPassed()) { + double mins = report->mCallDurationMs.Value() / (1000 * 60); + if (mins > 0) { + Accumulate( + WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM, + uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins)); + } + } + } + + // Finally, store the stats + + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (ctx) { + if (!ctx->mStatsForClosedPeerConnections.AppendElement(*report, fallible)) { + mozalloc_handle_oom(0); + } + } +} + +void WebrtcGlobalInformation::StoreLongTermICEStatistics( + PeerConnectionImpl& aPc) { + if (aPc.IceConnectionState() == RTCIceConnectionState::New) { + // ICE has not started; we won't have any remote candidates, so recording + // statistics on gathered candidates is pointless. + return; + } + + aPc.GetStats(nullptr, true) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) { + StoreLongTermICEStatisticsImpl_m(aReport.get()); + }, + [=](nsresult aError) {}); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h new file mode 100644 index 0000000000..081fcc1279 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_INFORMATION_H_ +#define _WEBRTC_GLOBAL_INFORMATION_H_ + +#include "nsString.h" +#include "mozilla/dom/BindingDeclarations.h" // for Optional + +namespace mozilla { +class PeerConnectionImpl; +class ErrorResult; + +namespace dom { + +class GlobalObject; +class WebrtcGlobalStatisticsCallback; +class WebrtcGlobalLoggingCallback; + +class WebrtcGlobalInformation { + public: + MOZ_CAN_RUN_SCRIPT + static void GetAllStats(const GlobalObject& aGlobal, + WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& pcIdFilter, + ErrorResult& aRv); + + static void ClearAllStats(const GlobalObject& aGlobal); + + MOZ_CAN_RUN_SCRIPT + static void GetLogging(const GlobalObject& aGlobal, const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, + ErrorResult& aRv); + + static void ClearLogging(const GlobalObject& aGlobal); + + static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel); + static int32_t DebugLevel(const GlobalObject& aGlobal); + + static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable); + static bool AecDebug(const GlobalObject& aGlobal); + static void GetAecDebugLogDir(const GlobalObject& aGlobal, nsAString& aDir); + + static void StoreLongTermICEStatistics(PeerConnectionImpl& aPc); + + private: + WebrtcGlobalInformation() = delete; + WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete; + WebrtcGlobalInformation& operator=(const WebrtcGlobalInformation& aRhs) = + delete; +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_INFORMATION_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalParent.h b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h new file mode 100644 index 0000000000..c7eebd7060 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _WEBRTC_GLOBAL_PARENT_H_ +#define _WEBRTC_GLOBAL_PARENT_H_ + +#include "mozilla/dom/PWebrtcGlobalParent.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace dom { + +class WebrtcParents; + +class WebrtcGlobalParent : public PWebrtcGlobalParent { + friend class ContentParent; + friend class WebrtcGlobalInformation; + friend class WebrtcContentParents; + + bool mShutdown; + + MOZ_IMPLICIT WebrtcGlobalParent(); + + static WebrtcGlobalParent* Alloc(); + static bool Dealloc(WebrtcGlobalParent* aActor); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual mozilla::ipc::IPCResult Recv__delete__() override; + + virtual ~WebrtcGlobalParent(); + + public: + NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent) + + bool IsActive() { return !mShutdown; } +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_PARENT_H_ diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build new file mode 100644 index 0000000000..978df17d29 --- /dev/null +++ b/dom/media/webrtc/jsapi/moz.build @@ -0,0 +1,37 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild") + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/dom/base", + "/dom/media", + "/dom/media/webrtc", + "/ipc/chromium/src", + "/media/webrtc", + "/netwerk/srtp/src/include", + "/third_party/libwebrtc", + "/third_party/libwebrtc/webrtc", +] + +UNIFIED_SOURCES += [ + "MediaTransportHandler.cpp", + "MediaTransportHandlerIPC.cpp", + "MediaTransportParent.cpp", + "PacketDumper.cpp", + "PeerConnectionCtx.cpp", + "PeerConnectionImpl.cpp", + "PeerConnectionMedia.cpp", + "RTCDtlsTransport.cpp", + "RTCDTMFSender.cpp", + "RTCRtpReceiver.cpp", + "RTCStatsIdGenerator.cpp", + "RTCStatsReport.cpp", + "TransceiverImpl.cpp", + "WebrtcGlobalInformation.cpp", +] + +FINAL_LIBRARY = "xul" |