diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/media/webrtc/jsapi | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/jsapi')
36 files changed, 16250 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..3ee95f36f6 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp @@ -0,0 +1,1727 @@ +/* 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" +#include "mozilla/StaticPrefs_network.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 "nsDNSService2.h" + +#include <string> +#include <vector> +#include <map> + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/ProfilerMarkers.h" + +# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) \ + PROFILER_MARKER_TEXT("WebRTC Packet Received", MEDIA_RT, {}, \ + ProfilerString8View::WrapNullTerminatedString( \ + PacketTypeToString((aPacket).type()))); +#else +# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) +#endif + +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; + + void CreateIceCtx(const std::string& aName) override; + + nsresult SetIceConfig(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, + int 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, + const 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; + bool mTurnDisabled = false; + uint32_t mMinDtlsVersion = 0; + uint32_t mMaxDtlsVersion = 0; + bool mForceNoHost = false; + Maybe<NrIceCtx::NatSimulatorConfig> mNatConfig; + + std::set<std::string> mSignaledAddresses; + + // Init can only be done on main, but we want this to be usable on any thread + using InitPromise = MozPromise<bool, std::string, false>; + RefPtr<InitPromise> mInitPromise; +}; + +/* static */ +already_AddRefed<MediaTransportHandler> MediaTransportHandler::Create( + nsISerialEventTarget* aCallbackThread) { + RefPtr<MediaTransportHandler> result; + if (XRE_IsContentProcess() && + Preferences::GetBool("media.peerconnection.mtransport_process") && + StaticPrefs::network_process_enabled()) { + result = new MediaTransportHandlerIPC(aCallbackThread); + } else { + result = new MediaTransportHandlerSTS(aCallbackThread); + } + result->Initialize(); + 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() = default; + + // 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: + return NrIceCtx::ICE_POLICY_ALL; + default: + MOZ_CRASH(); + } + return NrIceCtx::ICE_POLICY_ALL; +} + +// list of known acceptable ports for webrtc +int16_t gGoodWebrtcPortList[] = { + 53, // Some deplyoments use DNS port to punch through overzealous NATs + 3478, // stun or turn + 5349, // stuns or turns + 0, // Sentinel value: This MUST be zero +}; + +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(), static_cast<int>(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); + // Strip off brackets around IPv6 literals + host.Trim("[]"); + } + if (port == -1) port = (isStuns || isTurns) ? 5349 : 3478; + + // First check the known good ports for webrtc + bool goodPort = false; + for (int i = 0; !goodPort && gGoodWebrtcPortList[i]; i++) { + if (port == gGoodWebrtcPortList[i]) { + goodPort = true; + } + } + + // if not in the list of known good ports for webrtc, check + // the generic block list using NS_CheckPortSafety. + if (!goodPort) { + rv = NS_CheckPortSafety(port, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (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; + } + if (server->HasFqdn()) { + // Add an IPv4 entry, then an IPv6 entry + aTurnServersOut->push_back(*server); + server->SetUseIPv6IfFqdn(); + } + aTurnServersOut->emplace_back(std::move(*server)); + } else { + UniquePtr<NrIceStunServer> server( + NrIceStunServer::Create(host.get(), port, transport.get())); + if (!server) { + return NS_ERROR_FAILURE; + } + if (server->HasFqdn()) { + // Add an IPv4 entry, then an IPv6 entry + aStunServersOut->push_back(*server); + server->SetUseIPv6IfFqdn(); + } + 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); + bool block_tls = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_tls", 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); + nsAutoCString redirect_address; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.redirect_address", redirect_address); + nsAutoCString redirect_targets; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.redirect_targets", redirect_targets); + + if (block_udp || block_tcp || block_tls || !mapping_type.IsEmpty() || + !filtering_type.IsEmpty() || !redirect_address.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.mBlockTls = block_tls; + natConfig.mErrorCodeForDrop = error_code_for_drop; + natConfig.mFilteringType = filtering_type; + natConfig.mMappingType = mapping_type; + if (redirect_address.Length()) { + CSFLogDebug(LOGTAG, "Redirect address: %s", redirect_address.get()); + CSFLogDebug(LOGTAG, "Redirect targets: %s", redirect_targets.get()); + natConfig.mRedirectAddress = redirect_address; + std::stringstream str(redirect_targets.Data()); + std::string target; + while (getline(str, target, ',')) { + CSFLogDebug(LOGTAG, "Adding target: %s", target.c_str()); + natConfig.mRedirectTargets.AppendElement(target); + } + } + return Some(natConfig); + } + return Nothing(); +} + +void MediaTransportHandlerSTS::CreateIceCtx(const std::string& aName) { + 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) { + // Ensure the DNS service is initted for the first time on main + DebugOnly<RefPtr<nsIDNSService>> dnsService = + RefPtr<nsIDNSService>(nsDNSService::GetXPCOMSingleton()); + MOZ_ASSERT(dnsService.value); + mStsThread->Dispatch( + WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()), + NS_DISPATCH_NORMAL); + globalInitDone = true; + } + + // Give us a way to globally turn off TURN support + mTurnDisabled = + 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"); + mForceNoHost = + Preferences::GetBool("media.peerconnection.ice.no_host", false); + mNatConfig = GetNatConfig(); + + MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance()); + STSShutdownHandler::Instance()->Register(this); + + return InvokeAsync( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + mIceCtx = NrIceCtx::Create(aName); + if (!mIceCtx) { + return InitPromise::CreateAndReject("NrIceCtx::Create failed", + __func__); + } + + mIceCtx->SignalGatheringStateChange.connect( + this, &MediaTransportHandlerSTS::OnGatheringStateChange); + mIceCtx->SignalConnectionStateChange.connect( + this, &MediaTransportHandlerSTS::OnConnectionStateChange); + + mDNSResolver = new NrIceResolver; + nsresult rv; + 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__); + }); + }); +} + +nsresult MediaTransportHandlerSTS::SetIceConfig( + 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; + } + + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() { + if (!mIceCtx) { + CSFLogError(LOGTAG, "%s: mIceCtx is null", __FUNCTION__); + return; + } + NrIceCtx::Config config; + config.mPolicy = toNrIcePolicy(aIcePolicy); + if (config.mPolicy == NrIceCtx::ICE_POLICY_ALL && mForceNoHost) { + config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; + } + config.mNatSimulatorConfig = mNatConfig; + + nsresult rv; + + if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) { + CSFLogError(LOGTAG, "%s: Failed to set stun servers", __FUNCTION__); + return; + } + if (!mTurnDisabled) { + if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) { + CSFLogError(LOGTAG, "%s: Failed to set turn servers", __FUNCTION__); + return; + } + } else if (!turnServers.empty()) { + CSFLogError(LOGTAG, "%s: Setting turn servers disabled", + __FUNCTION__); + } + if (NS_FAILED(rv = mIceCtx->SetIceConfig(config))) { + CSFLogError(LOGTAG, "%s: Failed to set config", __FUNCTION__); + } + }); + + 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()) { + GetMainThreadSerialEventTarget()->Dispatch( + NewNonOwningRunnableMethod("MediaTransportHandlerSTS::Destroy", this, + &MediaTransportHandlerSTS::Destroy)); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + if (STSShutdownHandler::Instance()) { + 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("MediaTransportHandlerSTS::Destroy_s", 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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, int aComponentCount) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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=%d", + mIceCtx->name().c_str(), aTransportId.c_str(), + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + 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, aPacket.Clone()), + 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, aPacket.Clone()), + 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) { + MOZ_RELEASE_ASSERT(mInitPromise); + + return mInitPromise->Then(mStsThread, __func__, [=, self = RefPtr(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, + const 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); +} + +constexpr static const char* PacketTypeToString(MediaPacket::Type type) { + switch (type) { + case MediaPacket::Type::UNCLASSIFIED: + return "UNCLASSIFIED"; + case MediaPacket::Type::SRTP: + return "SRTP"; + case MediaPacket::Type::SRTCP: + return "SRTCP"; + case MediaPacket::Type::DTLS: + return "DTLS"; + case MediaPacket::Type::RTP: + return "RTP"; + case MediaPacket::Type::RTCP: + return "RTCP"; + case MediaPacket::Type::SCTP: + return "SCTP"; + default: + MOZ_ASSERT(false, "unreached"); + return ""; + } +} + +void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer, + MediaPacket& aPacket) { + MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket); + OnPacketReceived(aLayer->flow_id(), aPacket); +} + +void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer, + MediaPacket& aPacket) { + OnEncryptedSending(aLayer->flow_id(), aPacket); +} + +} // namespace mozilla + +#undef MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h new file mode 100644 index 0000000000..a776cb6fd7 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h @@ -0,0 +1,167 @@ +/* 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) {} + + // Exposed so we can synchronously validate ICE servers from PeerConnection + static nsresult ConvertIceServers( + const nsTArray<dom::RTCIceServer>& aIceServers, + std::vector<NrIceStunServer>* aStunServers, + std::vector<NrIceTurnServer>* aTurnServers); + + typedef MozPromise<dom::Sequence<nsString>, nsresult, true> IceLogPromise; + + virtual void Initialize() {} + + // 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 void CreateIceCtx(const std::string& aName) = 0; + + virtual nsresult SetIceConfig(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, + int 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..6369d229ee --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp @@ -0,0 +1,414 @@ +/* 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) {} + +void MediaTransportHandlerIPC::Initialize() { + mInitPromise = net::SocketProcessBridgeChild::GetSocketProcessBridge()->Then( + mCallbackThread, __func__, + [this, self = RefPtr<MediaTransportHandlerIPC>(this)]( + const RefPtr<net::SocketProcessBridgeChild>& aBridge) { + ipc::PBackgroundChild* actor = + ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread(); + // An actor that can't send is possible if the socket process has + // crashed but hasn't been reconnected properly. See + // SocketProcessBridgeChild::ActorDestroy for more info. + if (!actor || !actor->CanSend()) { + NS_WARNING( + "MediaTransportHandlerIPC async init failed! Webrtc networking " + "will not work!"); + return InitPromise::CreateAndReject( + nsCString("GetOrCreateSocketActorForCurrentThread failed!"), + __func__); + } + MediaTransportChild* child = new MediaTransportChild(this); + // 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) {}); +} + +void MediaTransportHandlerIPC::CreateIceCtx(const std::string& aName) { + CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start"); + + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) { + if (mChild) { + CSFLogDebug(LOGTAG, "%s starting", __func__); + if (NS_WARN_IF(!mChild->SendCreateIceCtx(aName))) { + CSFLogError(LOGTAG, "%s failed!", __func__); + } + } + }, + [](const nsCString& aError) {}); +} + +nsresult MediaTransportHandlerIPC::SetIceConfig( + const nsTArray<dom::RTCIceServer>& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) { + // 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) { + if (NS_WARN_IF(!mChild->SendSetIceConfig(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, int 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) { + using IPCPromise = dom::PMediaTransportChild::GetIceStatsPromise; + return mInitPromise + ->Then(mCallbackThread, __func__, + [aTransportId, aNow, this, self = RefPtr(this)]( + const InitPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsReject()) { + return IPCPromise::CreateAndResolve( + MakeUnique<dom::RTCStatsCollection>(), + "MediaTransportHandlerIPC::GetIceStats_1"); + } + if (!mChild) { + return IPCPromise::CreateAndResolve( + MakeUnique<dom::RTCStatsCollection>(), + "MediaTransportHandlerIPC::GetIceStats_1"); + } + return mChild->SendGetIceStats(aTransportId, aNow); + }) + ->Then(mCallbackThread, __func__, + [](IPCPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsReject()) { + return dom::RTCStatsPromise::CreateAndResolve( + MakeUnique<dom::RTCStatsCollection>(), + "MediaTransportHandlerIPC::GetIceStats_2"); + } + return dom::RTCStatsPromise::CreateAndResolve( + std::move(aValue.ResolveValue()), + "MediaTransportHandlerIPC::GetIceStats_2"); + }); +} + +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) { + mUser->OnPacketReceived(transportId, packet); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending( + const string& transportId, const MediaPacket& packet) { + mUser->OnEncryptedSending(transportId, packet); + 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..6b285dee2a --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h @@ -0,0 +1,96 @@ +/* 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 final : public MediaTransportHandler { + public: + explicit MediaTransportHandlerIPC(nsISerialEventTarget* aCallbackThread); + void Initialize() override; + RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override; + void ClearIceLog() override; + void EnterPrivateMode() override; + void ExitPrivateMode() override; + + void CreateIceCtx(const std::string& aName) override; + + nsresult SetIceConfig(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, + int 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..d0f90df8c5 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp @@ -0,0 +1,240 @@ +/* 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) { + mImpl->mHandler->CreateIceCtx(name); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSetIceConfig( + nsTArray<RTCIceServer>&& iceServers, + const RTCIceTransportPolicy& icePolicy) { + nsresult rv = mImpl->mHandler->SetIceConfig(iceServers, icePolicy); + if (NS_FAILED(rv)) { + return ipc::IPCResult::Fail(WrapNotNull(this), __func__, + "MediaTransportHandler::SetIceConfig 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, MediaPacket&& packet) { + mImpl->mHandler->SendPacket(transportId, std::move(packet)); + 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(aResult.ResolveValue()); + } else { + aResolve(MakeUnique<dom::RTCStatsCollection>()); + } + }); + + 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..2b62e0806e --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.cpp @@ -0,0 +1,124 @@ +/* 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 { + +/* static */ +RefPtr<PacketDumper> PacketDumper::GetPacketDumper( + const std::string& aPcHandle) { + MOZ_ASSERT(NS_IsMainThread()); + PeerConnectionWrapper pcw(aPcHandle); + if (pcw.impl()) { + return pcw.impl()->GetPacketDumper(); + } + + return new PacketDumper(""); +} + +PacketDumper::PacketDumper(const std::string& aPcHandle) + : mPcHandle(aPcHandle), + mPacketDumpEnabled(false), + mPacketDumpFlagsMutex("Packet dump flags mutex") {} + +void PacketDumper::Dump(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending, const void* aData, size_t aSize) { + // 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 (!ShouldDumpPacket(aLevel, aType, aSending)) { + return; + } + + UniquePtr<uint8_t[]> ownedPacket = MakeUnique<uint8_t[]>(aSize); + memcpy(ownedPacket.get(), aData, aSize); + + RefPtr<Runnable> dumpRunnable = media::NewRunnableFrom(std::bind( + [this, self = RefPtr<PacketDumper>(this), aLevel, aType, aSending, + aSize](UniquePtr<uint8_t[]>& aPacket) -> nsresult { + // Check again; packet dump might have been disabled since the dispatch + if (ShouldDumpPacket(aLevel, aType, aSending)) { + PeerConnectionWrapper pcw(mPcHandle); + RefPtr<PeerConnectionImpl> pc = pcw.impl(); + if (pc) { + pc->DumpPacket_m(aLevel, aType, aSending, aPacket, aSize); + } + } + return NS_OK; + }, + std::move(ownedPacket))); + + NS_DispatchToMainThread(dumpRunnable); +} + +nsresult PacketDumper::EnablePacketDump(unsigned long aLevel, + dom::mozPacketDumpType aType, + bool aSending) { + mPacketDumpEnabled = true; + std::vector<unsigned>* packetDumpFlags; + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)aType; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (aLevel >= packetDumpFlags->size()) { + packetDumpFlags->resize(aLevel + 1); + } + + (*packetDumpFlags)[aLevel] |= flag; + return NS_OK; +} + +nsresult PacketDumper::DisablePacketDump(unsigned long aLevel, + dom::mozPacketDumpType aType, + bool aSending) { + std::vector<unsigned>* packetDumpFlags; + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)aType; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (aLevel < packetDumpFlags->size()) { + (*packetDumpFlags)[aLevel] &= ~flag; + } + + return NS_OK; +} + +bool PacketDumper::ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending) const { + if (!mPacketDumpEnabled) { + return false; + } + + MutexAutoLock lock(mPacketDumpFlagsMutex); + + const std::vector<unsigned>* packetDumpFlags; + + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + if (aLevel < packetDumpFlags->size()) { + unsigned flag = 1 << (unsigned)aType; + return flag & packetDumpFlags->at(aLevel); + } + + return false; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PacketDumper.h b/dom/media/webrtc/jsapi/PacketDumper.h new file mode 100644 index 0000000000..e998b3871f --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.h @@ -0,0 +1,52 @@ +/* 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" + +#include <vector> + +namespace mozilla { +class PeerConnectionImpl; + +class PacketDumper { + public: + static RefPtr<PacketDumper> GetPacketDumper(const std::string& aPcHandle); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PacketDumper) + + PacketDumper(const PacketDumper&) = delete; + PacketDumper& operator=(const PacketDumper&) = delete; + + void Dump(size_t aLevel, dom::mozPacketDumpType aType, bool aSending, + const void* aData, size_t aSize); + + nsresult EnablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType, + bool aSending); + + nsresult DisablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType, + bool aSending); + + private: + friend class PeerConnectionImpl; + explicit PacketDumper(const std::string& aPcHandle); + ~PacketDumper() = default; + bool ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending) const; + + // This class is not cycle-collected, so it cannot hold onto a strong ref + const std::string mPcHandle; + std::vector<unsigned> mSendPacketDumpFlags; + std::vector<unsigned> mRecvPacketDumpFlags; + Atomic<bool> mPacketDumpEnabled; + mutable Mutex mPacketDumpFlagsMutex; +}; + +} // 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..9a8f27fb59 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp @@ -0,0 +1,650 @@ +/* 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 "PeerConnectionCtx.h" + +#include "WebrtcGlobalStatsHistory.h" +#include "api/audio/audio_mixer.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "call/audio_state.h" +#include "common/browser_logging/CSFLog.h" +#include "common/browser_logging/WebRtcLog.h" +#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER +#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER +#include "libwebrtcglue/CallWorkerThread.h" +#include "modules/audio_device/include/fake_audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/include/aec_dump.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Types.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsCRTGlue.h" +#include "nsIIOService.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "PeerConnectionImpl.h" +#include "prcvar.h" +#include "transport/runnable_utils.h" +#include "WebrtcGlobalChild.h" +#include "WebrtcGlobalInformation.h" + +static const char* pccLogTag = "PeerConnectionCtx"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pccLogTag + +using namespace webrtc; + +namespace { +class DummyAudioMixer : public AudioMixer { + public: + bool AddSource(Source*) override { return true; } + void RemoveSource(Source*) override {} + void Mix(size_t, AudioFrame*) override { MOZ_CRASH("Unexpected call"); } +}; + +class DummyAudioProcessing : public AudioProcessing { + public: + int Initialize() override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int Initialize(const ProcessingConfig&) override { return Initialize(); } + void ApplyConfig(const Config&) override { MOZ_CRASH("Unexpected call"); } + int proc_sample_rate_hz() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + int proc_split_sample_rate_hz() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_input_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_proc_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_output_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_reverse_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + void set_output_will_be_muted(bool) override { MOZ_CRASH("Unexpected call"); } + void SetRuntimeSetting(RuntimeSetting) override { + MOZ_CRASH("Unexpected call"); + } + bool PostRuntimeSetting(RuntimeSetting setting) override { return false; } + int ProcessStream(const int16_t* const, const StreamConfig&, + const StreamConfig&, int16_t* const) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessStream(const float* const*, const StreamConfig&, + const StreamConfig&, float* const*) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessReverseStream(const int16_t* const, const StreamConfig&, + const StreamConfig&, int16_t* const) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessReverseStream(const float* const*, const StreamConfig&, + const StreamConfig&, float* const*) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int AnalyzeReverseStream(const float* const*, const StreamConfig&) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + bool GetLinearAecOutput( + rtc::ArrayView<std::array<float, 160>>) const override { + MOZ_CRASH("Unexpected call"); + return false; + } + void set_stream_analog_level(int) override { MOZ_CRASH("Unexpected call"); } + int recommended_stream_analog_level() const override { + MOZ_CRASH("Unexpected call"); + return -1; + } + int set_stream_delay_ms(int) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int stream_delay_ms() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + void set_stream_key_pressed(bool) override { MOZ_CRASH("Unexpected call"); } + bool CreateAndAttachAecDump(absl::string_view, int64_t, + rtc::TaskQueue*) override { + MOZ_CRASH("Unexpected call"); + return false; + } + bool CreateAndAttachAecDump(FILE*, int64_t, rtc::TaskQueue*) override { + MOZ_CRASH("Unexpected call"); + return false; + } + void AttachAecDump(std::unique_ptr<AecDump>) override { + MOZ_CRASH("Unexpected call"); + } + void DetachAecDump() override { MOZ_CRASH("Unexpected call"); } + AudioProcessingStats GetStatistics() override { + return AudioProcessingStats(); + } + AudioProcessingStats GetStatistics(bool) override { return GetStatistics(); } + AudioProcessing::Config GetConfig() const override { + MOZ_CRASH("Unexpected call"); + return Config(); + } +}; +} // namespace + +namespace mozilla { + +using namespace dom; + +SharedWebrtcState::SharedWebrtcState( + RefPtr<AbstractThread> aCallWorkerThread, + webrtc::AudioState::Config&& aAudioStateConfig, + RefPtr<webrtc::AudioDecoderFactory> aAudioDecoderFactory, + UniquePtr<webrtc::WebRtcKeyValueConfig> aTrials) + : mCallWorkerThread(std::move(aCallWorkerThread)), + mAudioStateConfig(std::move(aAudioStateConfig)), + mAudioDecoderFactory(std::move(aAudioDecoderFactory)), + mTrials(std::move(aTrials)) {} + +SharedWebrtcState::~SharedWebrtcState() = default; + +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_WILL_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_WILL_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_WILL_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_WILL_SHUTDOWN_OBSERVER_ID); + } + } +}; + +NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver); + +PeerConnectionCtx* PeerConnectionCtx::gInstance; +StaticRefPtr<PeerConnectionCtxObserver> + PeerConnectionCtx::gPeerConnectionCtxObserver; + +nsresult PeerConnectionCtx::InitializeGlobal() { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult res; + + 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) { + // Null out gInstance first, so PeerConnectionImpl doesn't try to use it + // in Cleanup. + auto* instance = gInstance; + gInstance = nullptr; + instance->Cleanup(); + delete instance; + } + + 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.Find(u"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); + } + } +} + +// 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); + // Record bandwidth telemetry + for (const auto& s : aReport->mInboundRtpStreamStats) { + if (s.mBytesReceived.WasPassed()) { + const bool isAudio = s.mKind.Find(u"audio") != -1; + for (const auto& lastS : lastReport->mInboundRtpStreamStats) { + 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 = 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; + } + } + } + } + RecordCommonRtpTelemetry(aReport->mRemoteInboundRtpStreamStats, + lastReport->mRemoteInboundRtpStreamStats, true); + for (const auto& s : aReport->mRemoteInboundRtpStreamStats) { + if (s.mRoundTripTime.WasPassed()) { + const bool isAudio = s.mKind.Find(u"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->IsClosed()) { + 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->CollectConduitTelemetryData(); + } + } +} + +void PeerConnectionCtx::UpdateNetworkState(bool online) { + auto ctx = GetInstance(); + if (ctx->mPeerConnections.empty()) { + return; + } + for (auto pc : ctx->mPeerConnections) { + pc.second->UpdateNetworkState(online); + } +} + +SharedWebrtcState* PeerConnectionCtx::GetSharedWebrtcState() const { + MOZ_ASSERT(NS_IsMainThread()); + return mSharedWebrtcState; +} + +void PeerConnectionCtx::RemovePeerConnection(const std::string& aKey) { + MOZ_ASSERT(NS_IsMainThread()); + auto it = mPeerConnections.find(aKey); + if (it != mPeerConnections.end()) { + if (it->second->GetFinalStats() && !it->second->LongTermStatsIsDisabled()) { + WebrtcGlobalInformation::StashStats(*(it->second->GetFinalStats())); + } + nsAutoString pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str()); + if (XRE_IsContentProcess()) { + if (auto* child = WebrtcGlobalChild::Get(); child) { + auto pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str()); + child->SendPeerConnectionFinalStats(*(it->second->GetFinalStats())); + child->SendPeerConnectionDestroyed(pcId); + } + } else { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(pcId); + auto finalStats = + MakeUnique<RTCStatsReportInternal>(*(it->second->GetFinalStats())); + WebrtcGlobalStatsHistory::Record(std::move(finalStats)); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } + + mPeerConnections.erase(it); + if (mPeerConnections.empty()) { + mSharedWebrtcState = nullptr; + StopTelemetryTimer(); + } + } +} + +void PeerConnectionCtx::AddPeerConnection(const std::string& aKey, + PeerConnectionImpl* aPeerConnection) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPeerConnections.count(aKey) == 0, + "PeerConnection with this key should not already exist"); + if (mPeerConnections.empty()) { + AudioState::Config audioStateConfig; + audioStateConfig.audio_mixer = new rtc::RefCountedObject<DummyAudioMixer>(); + AudioProcessingBuilder audio_processing_builder; + audioStateConfig.audio_processing = + new rtc::RefCountedObject<DummyAudioProcessing>(); + audioStateConfig.audio_device_module = + new rtc::RefCountedObject<FakeAudioDeviceModule>(); + + SharedThreadPoolWebRtcTaskQueueFactory taskQueueFactory; + constexpr bool supportTailDispatch = true; + // Note the NonBlocking DeletionPolicy! + // This task queue is passed into libwebrtc as a raw pointer. + // WebrtcCallWrapper guarantees that it outlives its webrtc::Call instance. + // Outside of libwebrtc we must use ref-counting to either the + // WebrtcCallWrapper or to the CallWorkerThread to keep it alive. + auto callWorkerThread = + WrapUnique(taskQueueFactory + .CreateTaskQueueWrapper<DeletionPolicy::NonBlocking>( + "CallWorker", supportTailDispatch, + webrtc::TaskQueueFactory::Priority::NORMAL, + MediaThreadType::WEBRTC_CALL_THREAD) + .release()); + + UniquePtr<webrtc::WebRtcKeyValueConfig> trials = + WrapUnique(new NoTrialsConfig()); + + mSharedWebrtcState = MakeAndAddRef<SharedWebrtcState>( + new CallWorkerThread(std::move(callWorkerThread)), + std::move(audioStateConfig), + already_AddRefed(CreateBuiltinAudioDecoderFactory().release()), + std::move(trials)); + StartTelemetryTimer(); + } + auto pcId = NS_ConvertASCIItoUTF16(aPeerConnection->GetName().c_str()); + if (XRE_IsContentProcess()) { + if (auto* child = WebrtcGlobalChild::Get(); child) { + child->SendPeerConnectionCreated( + pcId, aPeerConnection->LongTermStatsIsDisabled()); + } + } else { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Add(pcId, aPeerConnection->LongTermStatsIsDisabled()); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } + mPeerConnections[aKey] = aPeerConnection; +} + +PeerConnectionImpl* PeerConnectionCtx::GetPeerConnection( + const std::string& aKey) const { + MOZ_ASSERT(NS_IsMainThread()); + auto iterator = mPeerConnections.find(aKey); + if (iterator == mPeerConnections.end()) { + return nullptr; + } + return iterator->second; +} + +void PeerConnectionCtx::ClearClosedStats() { + for (auto& [id, pc] : mPeerConnections) { + Unused << id; + if (pc->IsClosed()) { + // Rare case + pc->DisableLongTermStats(); + } + } +} + +nsresult PeerConnectionCtx::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + initGMP(); + SdpRidAttributeList::kMaxRidLength = + webrtc::BaseRtpStringExtension::kMaxValueSizeBytes; + + if (XRE_IsContentProcess()) { + WebrtcGlobalChild::Get(); + } + + return NS_OK; +} + +nsresult PeerConnectionCtx::StartTelemetryTimer() { + return NS_NewTimerWithFuncCallback(getter_AddRefs(mTelemetryTimer), + EverySecondTelemetryCallback_m, this, 1000, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "EverySecondTelemetryCallback_m"); +} + +void PeerConnectionCtx::StopTelemetryTimer() { + if (mTelemetryTimer) { + mTelemetryTimer->Cancel(); + mTelemetryTimer = nullptr; + } +} + +static void GMPReady_m() { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->onGMPReady(); + } +}; + +static void GMPReady() { + GetMainThreadSerialEventTarget()->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__); + MOZ_ASSERT(NS_IsMainThread()); + + mQueuedJSEPOperations.Clear(); + mGMPService = nullptr; + mTransportHandler = nullptr; + for (auto& [id, pc] : mPeerConnections) { + (void)id; + pc->Close(); + } + mPeerConnections.clear(); + mSharedWebrtcState = nullptr; + return NS_OK; +} + +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... + + AutoTArray<nsCString, 1> 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..fdd81f6406 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.h @@ -0,0 +1,194 @@ +/* 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 <map> +#include <string> + +#include "WebrtcGlobalChild.h" +#include "api/field_trials_view.h" +#include "api/scoped_refptr.h" +#include "call/audio_state.h" +#include "MediaTransportHandler.h" // Mostly for IceLogPromise +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "nsIRunnable.h" +#include "PeerConnectionImpl.h" + +namespace webrtc { +class AudioDecoderFactory; + +// Used for testing in mediapipeline_unittest.cpp, MockCall.h +class NoTrialsConfig : public FieldTrialsView { + public: + NoTrialsConfig() = default; + std::string Lookup(absl::string_view key) const override { + // Upstream added a new default field trial string for + // CongestionWindow, that we don't want. In + // third_party/libwebrtc/rtc_base/experiments/rate_control_settings.cc + // they set kCongestionWindowDefaultFieldTrialString to + // "QueueSize:350,MinBitrate:30000,DropFrame:true". With QueueSize + // set, GoogCcNetworkController::UpdateCongestionWindowSize is + // called. Because negative values are calculated in + // feedback_rtt, an assert fires when calculating data_window in + // GoogCcNetworkController::UpdateCongestionWindowSize. We probably + // need to figure out why we're calculating negative feedback_rtt. + // See Bug 1780620. + if ("WebRTC-CongestionWindow" == key) { + return std::string("MinBitrate:30000,DropFrame:true"); + } + return std::string(); + } +}; +} // namespace webrtc + +namespace mozilla { +class PeerConnectionCtxObserver; + +namespace dom { +class WebrtcGlobalInformation; +} + +/** + * Refcounted class containing state shared across all PeerConnections and all + * Call instances. Managed by PeerConnectionCtx, and kept around while there are + * registered peer connections. + */ +class SharedWebrtcState { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWebrtcState) + + SharedWebrtcState(RefPtr<AbstractThread> aCallWorkerThread, + webrtc::AudioState::Config&& aAudioStateConfig, + RefPtr<webrtc::AudioDecoderFactory> aAudioDecoderFactory, + UniquePtr<webrtc::FieldTrialsView> aTrials); + + // A global Call worker thread shared between all Call instances. Implements + // AbstractThread for running tasks that call into a Call instance through its + // webrtc::TaskQueue member, and for using AbstractThread-specific higher + // order constructs like StateMirroring. + const RefPtr<AbstractThread> mCallWorkerThread; + + // AudioState config containing dummy implementations of the audio stack, + // since we use our own audio stack instead. Shared across all Call instances. + const webrtc::AudioState::Config mAudioStateConfig; + + // AudioDecoderFactory instance shared between calls, to limit the number of + // instances in large calls. + const RefPtr<webrtc::AudioDecoderFactory> mAudioDecoderFactory; + + // Trials instance shared between calls, to limit the number of instances in + // large calls. + const UniquePtr<webrtc::FieldTrialsView> mTrials; + + private: + virtual ~SharedWebrtcState(); +}; + +// 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 +// * Upstream webrtc state shared across all Calls (processing thread) +class PeerConnectionCtx { + public: + static nsresult InitializeGlobal(); + 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; + } + + SharedWebrtcState* GetSharedWebrtcState() const; + + void RemovePeerConnection(const std::string& aKey); + void AddPeerConnection(const std::string& aKey, + PeerConnectionImpl* aPeerConnection); + PeerConnectionImpl* GetPeerConnection(const std::string& aKey) const; + template <typename Function> + void ForEachPeerConnection(Function&& aFunction) const { + MOZ_ASSERT(NS_IsMainThread()); + for (const auto& pair : mPeerConnections) { + aFunction(pair.second); + } + } + + void ClearClosedStats(); + + private: + 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() = default; + + nsresult Initialize(); + nsresult StartTelemetryTimer(); + void StopTelemetryTimer(); + 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; + + // State used by libwebrtc that needs to be shared across all PeerConnections + // and all Call instances. Set while there is at least one peer connection + // registered. CallWrappers can hold a ref to this object to be sure members + // are alive long enough. + RefPtr<SharedWebrtcState> mSharedWebrtcState; + + static PeerConnectionCtx* gInstance; + + public: + 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..567b682b2a --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp @@ -0,0 +1,4640 @@ +/* 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 "nsEffectiveTLDService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsProxyRelease.h" +#include "prtime.h" + +#include "libwebrtcglue/AudioConduit.h" +#include "libwebrtcglue/VideoConduit.h" +#include "libwebrtcglue/WebrtcCallWrapper.h" +#include "MediaTrackGraph.h" +#include "transport/runnable_utils.h" +#include "IPeerConnection.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" +#include "RemoteTrackSource.h" +#include "nsDOMDataChannelDeclarations.h" +#include "transport/dtlsidentity.h" +#include "sdp/SdpAttribute.h" + +#include "jsep/JsepTrack.h" +#include "jsep/JsepSession.h" +#include "jsep/JsepSessionImpl.h" + +#include "transportbridge/MediaPipeline.h" +#include "jsapi/RTCRtpReceiver.h" +#include "jsapi/RTCRtpSender.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/RTCSctpTransportBinding.h" // RTCSctpTransportState +#include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/dom/PeerConnectionImplBinding.h" +#include "mozilla/dom/RTCDataChannelBinding.h" +#include "mozilla/dom/PluginCrashedEvent.h" +#include "MediaStreamTrack.h" +#include "AudioStreamTrack.h" +#include "VideoStreamTrack.h" +#include "nsIScriptGlobalObject.h" +#include "DOMMediaStream.h" +#include "WebrtcGlobalInformation.h" +#include "mozilla/dom/Event.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/net/DataChannelProtocol.h" +#include "MediaManager.h" + +#include "transport/nr_socket_proxy_config.h" +#include "RTCSctpTransport.h" +#include "RTCDtlsTransport.h" +#include "jsep/JsepTransport.h" + +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "nsIProxiedChannel.h" + +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/net/WebrtcProxyConfig.h" + +#ifdef XP_WIN +// We need to undef the MS macro again in case the windows include file +// got imported after we included mozilla/dom/Document.h +# ifdef CreateEvent +# undef CreateEvent +# endif +#endif // XP_WIN + +#include "MediaSegment.h" + +#ifdef USE_FAKE_PCOBSERVER +# include "FakePCObserver.h" +#else +# include "mozilla/dom/PeerConnectionObserverBinding.h" +#endif +#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" + +#define ICE_PARSING \ + "In RTCConfiguration passed to RTCPeerConnection constructor" + +using namespace mozilla; +using namespace mozilla::dom; + +typedef PCObserverString ObString; + +static const char* pciLogTag = "PeerConnectionImpl"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pciLogTag + +static mozilla::LazyLogModule logModuleInfo("signaling"); + +// Getting exceptions back down from PCObserver is generally not harmful. +namespace { +// This is a terrible hack. The problem is that SuppressException is not +// inline, and we link this file without libxul in some cases (e.g. for our test +// setup). So we can't use ErrorResult or IgnoredErrorResult because those call +// SuppressException... And we can't use FastErrorResult because we can't +// include BindingUtils.h, because our linking is completely broken. Use +// BaseErrorResult directly. Please do not let me see _anyone_ doing this +// without really careful review from someone who knows what they are doing. +class JSErrorResult : public binding_danger::TErrorResult< + binding_danger::JustAssertCleanupPolicy> { + public: + ~JSErrorResult() { SuppressException(); } +} JS_HAZ_ROOTED; + +// The WrapRunnable() macros copy passed-in args and passes them to the function +// later on the other thread. ErrorResult cannot be passed like this because it +// disallows copy-semantics. +// +// This WrappableJSErrorResult hack solves this by not actually copying the +// ErrorResult, but creating a new one instead, which works because we don't +// care about the result. +// +// Since this is for JS-calls, these can only be dispatched to the main thread. + +class WrappableJSErrorResult { + public: + WrappableJSErrorResult() : mRv(MakeUnique<JSErrorResult>()), isCopy(false) {} + WrappableJSErrorResult(const WrappableJSErrorResult& other) + : mRv(MakeUnique<JSErrorResult>()), isCopy(true) {} + ~WrappableJSErrorResult() { + if (isCopy) { + MOZ_ASSERT(NS_IsMainThread()); + } + } + operator ErrorResult&() { return *mRv; } + + private: + mozilla::UniquePtr<JSErrorResult> mRv; + bool isCopy; +} JS_HAZ_ROOTED; + +} // namespace + +static nsresult InitNSSInContent() { + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); + + if (!XRE_IsContentProcess()) { + MOZ_ASSERT_UNREACHABLE("Must be called in content process"); + return NS_ERROR_FAILURE; + } + + static bool nssStarted = false; + if (nssStarted) { + return NS_OK; + } + + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + CSFLogError(LOGTAG, "NSS_NoDB_Init failed."); + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + CSFLogError(LOGTAG, "Fail to set up nss cipher suite."); + return NS_ERROR_FAILURE; + } + + mozilla::psm::DisableMD5(); + + nssStarted = true; + + return NS_OK; +} + +namespace mozilla { +class DataChannel; +} + +namespace mozilla { + +void PeerConnectionAutoTimer::RegisterConnection() { mRefCnt++; } + +void PeerConnectionAutoTimer::UnregisterConnection(bool aContainedAV) { + MOZ_ASSERT(mRefCnt); + mRefCnt--; + mUsedAV |= aContainedAV; + if (mRefCnt == 0) { + if (mUsedAV) { + Telemetry::Accumulate( + Telemetry::WEBRTC_AV_CALL_DURATION, + static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds())); + } + Telemetry::Accumulate( + Telemetry::WEBRTC_CALL_DURATION, + static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds())); + } +} + +bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; } + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl) + tmp->Close(); + tmp->BreakCycles(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPCObserver, mWindow, mCertificate, + mSTSThread, mReceiveStreams, mOperations, + mSctpTransport, mKungFuDeathGrip) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( + mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams, + mOperations, mTransceivers, mSctpTransport, mKungFuDeathGrip) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +already_AddRefed<PeerConnectionImpl> PeerConnectionImpl::Constructor( + const dom::GlobalObject& aGlobal) { + RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal); + + CSFLogDebug(LOGTAG, "Created PeerConnection: %p", pc.get()); + + return pc.forget(); +} + +JSObject* PeerConnectionImpl::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* PeerConnectionImpl::GetParentObject() const { + return mWindow; +} + +bool PCUuidGenerator::Generate(std::string* idp) { + nsresult rv; + + if (!mGenerator) { + mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + if (!mGenerator) { + return false; + } + } + + nsID id; + rv = mGenerator->GenerateUUIDInPlace(&id); + if (NS_FAILED(rv)) { + return false; + } + char buffer[NSID_LENGTH]; + id.ToProvidedString(buffer); + idp->assign(buffer); + + return true; +} + +bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) { + if (!aWindow) { + return false; + } + + Document* doc = aWindow->GetExtantDoc(); + if (!doc) { + return false; + } + + nsILoadContext* loadContext = doc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) + : mTimeCard(MOZ_LOG_TEST(logModuleInfo, LogLevel::Error) ? create_timecard() + : nullptr), + mJsConfiguration(), + mSignalingState(RTCSignalingState::Stable), + mIceConnectionState(RTCIceConnectionState::New), + mIceGatheringState(RTCIceGatheringState::New), + mConnectionState(RTCPeerConnectionState::New), + mWindow(do_QueryInterface(aGlobal ? aGlobal->GetAsSupports() : nullptr)), + mCertificate(nullptr), + mSTSThread(nullptr), + mForceIceTcp(false), + mTransportHandler(nullptr), + mUuidGen(MakeUnique<PCUuidGenerator>()), + mIceRestartCount(0), + mIceRollbackCount(0), + mHaveConfiguredCodecs(false), + mTrickle(true) // TODO(ekr@rtfm.com): Use pref + , + mPrivateWindow(false), + mActiveOnWindow(false), + mTimestampMaker(dom::RTCStatsTimestampMaker::Create(mWindow)), + mIdGenerator(new RTCStatsIdGenerator()), + listenPort(0), + connectPort(0), + connectStr(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT_IF(aGlobal, mWindow); + mKungFuDeathGrip = this; + if (aGlobal) { + if (IsPrivateBrowsing(mWindow)) { + mPrivateWindow = true; + mDisableLongTermStats = true; + } + mWindow->AddPeerConnection(); + mActiveOnWindow = true; + + if (mWindow->GetDocumentURI()) { + mWindow->GetDocumentURI()->GetAsciiHost(mHostname); + nsresult rv; + nsCOMPtr<nsIEffectiveTLDService> eTLDService( + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv)); + if (eTLDService) { + Unused << eTLDService->GetBaseDomain(mWindow->GetDocumentURI(), 0, + mEffectiveTLDPlus1); + } + + mRtxIsAllowed = !HostnameInPref( + "media.peerconnection.video.use_rtx.blocklist", mHostname); + } + } + + if (!mUuidGen->Generate(&mHandle)) { + MOZ_CRASH(); + } + + CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__, + mHandle.c_str()); + STAMP_TIMECARD(mTimeCard, "Constructor Completed"); + mForceIceTcp = + Preferences::GetBool("media.peerconnection.ice.force_ice_tcp", false); + memset(mMaxReceiving, 0, sizeof(mMaxReceiving)); + memset(mMaxSending, 0, sizeof(mMaxSending)); + mJsConfiguration.mCertificatesProvided = false; + mJsConfiguration.mPeerIdentityProvided = false; +} + +PeerConnectionImpl::~PeerConnectionImpl() { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!mTransportHandler, + "PeerConnection should either be closed, or not initted in the " + "first place."); + + if (mTimeCard) { + STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); + STAMP_TIMECARD(mTimeCard, mHandle.c_str()); + print_timecard(mTimeCard); + destroy_timecard(mTimeCard); + mTimeCard = nullptr; + } + + CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s", + __FUNCTION__, mHandle.c_str()); +} + +nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow) { + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + + mPCObserver = &aObserver; + + // Find the STS thread + + mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_ASSERT(mSTSThread); + + // We do callback handling on STS instead of main to avoid media jank. + // Someday, we may have a dedicated thread for this. + 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, but we want it to be large enough to reliably + // contain the location on the tests we run in CI. + char temp[256]; + + nsAutoCString locationCStr; + + RefPtr<Location> location = mWindow->Location(); + nsAutoString locationAStr; + res = location->ToString(locationAStr); + NS_ENSURE_SUCCESS(res, res); + + CopyUTF16toUTF8(locationAStr, locationCStr); + + SprintfLiteral(temp, "%s %" PRIu64 " (id=%" PRIu64 " url=%s)", + mHandle.c_str(), static_cast<uint64_t>(timestamp), + static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0), + locationCStr.get() ? locationCStr.get() : "NULL"); + + mName = temp; + + STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx"); + res = PeerConnectionCtx::InitializeGlobal(); + NS_ENSURE_SUCCESS(res, res); + + mTransportHandler->CreateIceCtx("PC:" + GetName()); + + mJsepSession = + MakeUnique<JsepSessionImpl>(mName, MakeUnique<PCUuidGenerator>()); + mJsepSession->SetRtxIsAllowed(mRtxIsAllowed); + + res = mJsepSession->Init(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__, + static_cast<unsigned>(res)); + return res; + } + + std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs; + SetupPreferredCodecs(preferredCodecs); + mJsepSession->SetDefaultCodecs(preferredCodecs); + + std::vector<RtpExtensionHeader> preferredHeaders; + SetupPreferredRtpExtensions(preferredHeaders); + + for (const auto& header : preferredHeaders) { + mJsepSession->AddRtpExtension(header.mMediaType, header.extensionname, + header.direction); + } + + if (XRE_IsContentProcess()) { + mStunAddrsRequest = + new net::StunAddrsRequestChild(new StunAddrsHandler(this)); + } + + // Initialize the media object. + mForceProxy = ShouldForceProxy(); + + // We put this here, in case we later want to set this based on a non-standard + // param in RTCConfiguration. + mAllowOldSetParameters = Preferences::GetBool( + "media.peerconnection.allow_old_setParameters", false); + + // setup the stun local addresses IPC async call + InitLocalAddrs(); + + mSignalHandler = MakeUnique<SignalHandler>(this, mTransportHandler.get()); + + PeerConnectionCtx::GetInstance()->AddPeerConnection(mHandle, this); + + return NS_OK; +} + +void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, + ErrorResult& rv) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult res = Initialize(aObserver, &aWindow); + if (NS_FAILED(res)) { + rv.Throw(res); + return; + } +} + +void PeerConnectionImpl::SetCertificate( + mozilla::dom::RTCCertificate& aCertificate) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(!mCertificate, "This can only be called once"); + mCertificate = &aCertificate; + + std::vector<uint8_t> fingerprint; + nsresult rv = + CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't calculate fingerprint, rv=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + mCertificate = nullptr; + return; + } + rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, + fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't set DTLS credentials, rv=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + mCertificate = nullptr; + } + + if (mUncommittedJsepSession) { + Unused << mUncommittedJsepSession->AddDtlsFingerprint( + DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint); + } +} + +const RefPtr<mozilla::dom::RTCCertificate>& PeerConnectionImpl::Certificate() + const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mCertificate; +} + +RefPtr<DtlsIdentity> PeerConnectionImpl::Identity() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(mCertificate); + return mCertificate->CreateDtlsIdentity(); +} + +class CompareCodecPriority { + public: + void SetPreferredCodec(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()) { + Telemetry::Accumulate(Telemetry::WEBRTC_HAS_H264_HARDWARE, true); + branch->GetBoolPref("media.webrtc.hw.h264.enabled", + &mHardwareH264Enabled); + } + + mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled; + Telemetry::Accumulate(Telemetry::WEBRTC_SOFTWARE_H264_ENABLED, + mSoftwareH264Enabled); + Telemetry::Accumulate(Telemetry::WEBRTC_HARDWARE_H264_ENABLED, + mHardwareH264Enabled); + Telemetry::Accumulate(Telemetry::WEBRTC_H264_ENABLED, mH264Enabled); + + 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->Type()) { + 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 = Some(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->Type() == SdpMediaSection::kVideo && !codec->mEnabled) { + 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(); + + Maybe<const JsepTransceiver> datachannelTransceiver = + mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) { + return aTransceiver.GetMediaType() == SdpMediaSection::kApplication; + }); + + if (!datachannelTransceiver || + !datachannelTransceiver->mTransport.mComponents || + !datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) { + return NS_ERROR_FAILURE; + } + + // This will release assert if there is no such index, and that's ok + const JsepTrackEncoding& encoding = + datachannelTransceiver->mSendTrack.GetNegotiatedDetails()->GetEncoding(0); + + if (NS_WARN_IF(encoding.GetCodecs().empty())) { + CSFLogError(LOGTAG, + "%s: Negotiated m=application with no codec. " + "This is likely to be broken.", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + + for (const auto& codec : encoding.GetCodecs()) { + if (codec->Type() != SdpMediaSection::kApplication) { + CSFLogError(LOGTAG, + "%s: Codec type for m=application was %u, this " + "is a bug.", + __FUNCTION__, static_cast<unsigned>(codec->Type())); + MOZ_ASSERT(false, "Codec for m=application was not \"application\""); + return NS_ERROR_FAILURE; + } + + if (codec->mName != "webrtc-datachannel") { + CSFLogWarn(LOGTAG, + "%s: Codec for m=application was not " + "webrtc-datachannel (was instead %s). ", + __FUNCTION__, codec->mName.c_str()); + continue; + } + + if (codec->mChannels) { + *channels = codec->mChannels; + } else { + *channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT; + } + const JsepApplicationCodecDescription* appCodec = + static_cast<const JsepApplicationCodecDescription*>(codec.get()); + *localport = appCodec->mLocalPort; + *remoteport = appCodec->mRemotePort; + *remotemaxmessagesize = appCodec->mRemoteMaxMessageSize; + *mmsset = appCodec->mRemoteMMSSet; + MOZ_ASSERT(!datachannelTransceiver->mTransport.mTransportId.empty()); + *transportId = datachannelTransceiver->mTransport.mTransportId; + *client = datachannelTransceiver->mTransport.mDtls->GetRole() == + JsepDtlsTransport::kJsepDtlsClient; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession( + JsepTransceiver& transceiver) { + nsresult res = ConfigureJsepSessionCodecs(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return res; + } + + mJsepSession->AddTransceiver(transceiver); + return NS_OK; +} + +static Maybe<SdpMediaSection::MediaType> ToSdpMediaType( + const nsAString& aKind) { + if (aKind.EqualsASCII("audio")) { + return Some(SdpMediaSection::MediaType::kAudio); + } else if (aKind.EqualsASCII("video")) { + return Some(SdpMediaSection::MediaType::kVideo); + } + return Nothing(); +} + +already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver( + const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) { + // Copy, because we might need to modify + RTCRtpTransceiverInit init(aInit); + + Maybe<SdpMediaSection::MediaType> type = ToSdpMediaType(aKind); + if (NS_WARN_IF(!type.isSome())) { + MOZ_ASSERT(false, "Invalid media kind"); + aRv = NS_ERROR_INVALID_ARG; + return nullptr; + } + + JsepTransceiver jsepTransceiver(*type, *mUuidGen); + jsepTransceiver.SetRtxIsAllowed(mRtxIsAllowed); + + // Do this last, since it is not possible to roll back. + nsresult rv = AddRtpTransceiverToJsepSession(jsepTransceiver); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: AddRtpTransceiverToJsepSession failed, res=%u", + __FUNCTION__, static_cast<unsigned>(rv)); + aRv = rv; + return nullptr; + } + + auto& sendEncodings = init.mSendEncodings; + + // CheckAndRectifyEncodings covers these six: + // If any encoding contains a rid member whose value does not conform to the + // grammar requirements specified in Section 10 of [RFC8851], throw a + // TypeError. + + // If some but not all encodings contain a rid member, throw a TypeError. + + // If any encoding contains a rid member whose value is the same as that of a + // rid contained in another encoding in sendEncodings, throw a TypeError. + + // If kind is "audio", remove the scaleResolutionDownBy member from all + // encodings that contain one. + + // If any encoding contains a scaleResolutionDownBy member whose value is + // less than 1.0, throw a RangeError. + + // Verify that the value of each maxFramerate member in sendEncodings that is + // defined is greater than 0.0. If one of the maxFramerate values does not + // meet this requirement, throw a RangeError. + RTCRtpSender::CheckAndRectifyEncodings(sendEncodings, + *type == SdpMediaSection::kVideo, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // If any encoding contains a read-only parameter other than rid, throw an + // InvalidAccessError. + // NOTE: We don't support any additional read-only params right now. Also, + // spec shoehorns this in between checks that setParameters also performs + // (between the rid checks and the scaleResolutionDownBy checks). + + // If any encoding contains a scaleResolutionDownBy member, then for each + // encoding without one, add a scaleResolutionDownBy member with the value + // 1.0. + for (const auto& constEncoding : sendEncodings) { + if (constEncoding.mScaleResolutionDownBy.WasPassed()) { + for (auto& encoding : sendEncodings) { + if (!encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Construct(1.0f); + } + } + break; + } + } + + // Let maxN be the maximum number of total simultaneous encodings the user + // agent may support for this kind, at minimum 1.This should be an optimistic + // number since the codec to be used is not known yet. + size_t maxN = + (*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1; + + // If the number of encodings stored in sendEncodings exceeds maxN, then trim + // sendEncodings from the tail until its length is maxN. + // NOTE: Spec has this after all validation steps; even if there are elements + // that we will trim off, we still validate them. + if (sendEncodings.Length() > maxN) { + sendEncodings.TruncateLength(maxN); + } + + // If kind is "video" and none of the encodings contain a + // scaleResolutionDownBy member, then for each encoding, add a + // scaleResolutionDownBy member with the value 2^(length of sendEncodings - + // encoding index - 1). This results in smaller-to-larger resolutions where + // the last encoding has no scaling applied to it, e.g. 4:2:1 if the length + // is 3. + // NOTE: The code above ensures that these are all set, or all unset, so we + // can just check the first one. + if (sendEncodings.Length() && *type == SdpMediaSection::kVideo && + !sendEncodings[0].mScaleResolutionDownBy.WasPassed()) { + double scale = 1.0f; + for (auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) { + it->mScaleResolutionDownBy.Construct(scale); + scale *= 2; + } + } + + // If the number of encodings now stored in sendEncodings is 1, then remove + // any rid member from the lone entry. + if (sendEncodings.Length() == 1) { + sendEncodings[0].mRid.Reset(); + } + + RefPtr<RTCRtpTransceiver> transceiver = CreateTransceiver( + jsepTransceiver.GetUuid(), + jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init, + aSendTrack, aAddTrackMagic, aRv); + + if (aRv.Failed()) { + // Would be nice if we could peek at the rv without stealing it, so we + // could log... + CSFLogError(LOGTAG, "%s: failed", __FUNCTION__); + return nullptr; + } + + mTransceivers.AppendElement(transceiver); + return transceiver.forget(); +} + +bool PeerConnectionImpl::CheckNegotiationNeeded() { + MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable); + SyncToJsep(); + return !mLocalIceCredentialsToReplace.empty() || + mJsepSession->CheckNegotiationNeeded(); +} + +bool PeerConnectionImpl::CreatedSender(const dom::RTCRtpSender& aSender) const { + return aSender.IsMyPc(this); +} + +nsresult PeerConnectionImpl::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__); + + Maybe<JsepTransceiver> dcTransceiver = + mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) { + return aTransceiver.GetMediaType() == SdpMediaSection::kApplication; + }); + + if (dcTransceiver) { + dcTransceiver->RestartDatachannelTransceiver(); + mJsepSession->SetTransceiver(*dcTransceiver); + } else { + mJsepSession->AddTransceiver( + JsepTransceiver(SdpMediaSection::MediaType::kApplication, *mUuidGen)); + } + + RefPtr<nsDOMDataChannel> retval; + rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, + getter_AddRefs(retval)); + if (NS_FAILED(rv)) { + return rv; + } + retval.forget(aRetval); + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION(PeerConnectionImpl::Operation, mPromise, mPc) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::Operation) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl::Operation) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl::Operation) + +PeerConnectionImpl::Operation::Operation(PeerConnectionImpl* aPc, + ErrorResult& aError) + : mPromise(aPc->MakePromise(aError)), mPc(aPc) {} + +PeerConnectionImpl::Operation::~Operation() = default; + +void PeerConnectionImpl::Operation::Call(ErrorResult& aError) { + RefPtr<dom::Promise> opPromise = CallImpl(aError); + if (aError.Failed()) { + return; + } + // Upon fulfillment or rejection of the promise returned by the operation, + // run the following steps: + // (NOTE: mPromise is p from https://w3c.github.io/webrtc-pc/#dfn-chain, + // and CallImpl() is what returns the promise for the operation itself) + opPromise->AppendNativeHandler(this); +} + +void PeerConnectionImpl::Operation::ResolvedCallback( + JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + // If connection.[[IsClosed]] is true, abort these steps. + // (the spec wants p to never settle in this event) + if (!mPc->IsClosed()) { + // If the promise returned by operation was fulfilled with a + // value, fulfill p with that value. + mPromise->MaybeResolveWithClone(aCx, aValue); + // Upon fulfillment or rejection of p, execute the following + // steps: + // (Static analysis forces us to use a temporary) + RefPtr<PeerConnectionImpl> pc = mPc; + pc->RunNextOperation(aRv); + } +} + +void PeerConnectionImpl::Operation::RejectedCallback( + JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + // If connection.[[IsClosed]] is true, abort these steps. + // (the spec wants p to never settle in this event) + if (!mPc->IsClosed()) { + // If the promise returned by operation was rejected with a + // value, reject p with that value. + mPromise->MaybeRejectWithClone(aCx, aValue); + // Upon fulfillment or rejection of p, execute the following + // steps: + // (Static analysis forces us to use a temporary) + RefPtr<PeerConnectionImpl> pc = mPc; + pc->RunNextOperation(aRv); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation, mOperation) + +NS_IMPL_ADDREF_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation) +NS_IMPL_RELEASE_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::JSOperation) +NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation) + +PeerConnectionImpl::JSOperation::JSOperation(PeerConnectionImpl* aPc, + dom::ChainedOperation& aOp, + ErrorResult& aError) + : Operation(aPc, aError), mOperation(&aOp) {} + +RefPtr<dom::Promise> PeerConnectionImpl::JSOperation::CallImpl( + ErrorResult& aError) { + // Static analysis will not let us call this without a temporary :( + RefPtr<dom::ChainedOperation> op = mOperation; + return op->Call(aError); +} + +already_AddRefed<dom::Promise> PeerConnectionImpl::Chain( + dom::ChainedOperation& aOperation, ErrorResult& aError) { + MOZ_RELEASE_ASSERT(!mChainingOperation); + mChainingOperation = true; + RefPtr<Operation> operation = new JSOperation(this, aOperation, aError); + if (aError.Failed()) { + return nullptr; + } + RefPtr<Promise> promise = Chain(operation, aError); + if (aError.Failed()) { + return nullptr; + } + mChainingOperation = false; + return promise.forget(); +} + +// This is kinda complicated, but it is what the spec requires us to do. The +// core of what makes this complicated is the requirement that |aOperation| be +// run _immediately_ (without any Promise.Then!) if the operations chain is +// empty. +already_AddRefed<dom::Promise> PeerConnectionImpl::Chain( + const RefPtr<Operation>& aOperation, ErrorResult& aError) { + // If connection.[[IsClosed]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (IsClosed()) { + CSFLogDebug(LOGTAG, "%s:%d: Peer connection is closed", __FILE__, __LINE__); + RefPtr<dom::Promise> error = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + error->MaybeRejectWithInvalidStateError("Peer connection is closed"); + return error.forget(); + } + + // Append operation to [[Operations]]. + mOperations.AppendElement(aOperation); + + // If the length of [[Operations]] is exactly 1, execute operation. + if (mOperations.Length() == 1) { + aOperation->Call(aError); + if (aError.Failed()) { + return nullptr; + } + } + + // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain + return do_AddRef(aOperation->GetPromise()); +} + +void PeerConnectionImpl::RunNextOperation(ErrorResult& aError) { + // If connection.[[IsClosed]] is true, abort these steps. + if (IsClosed()) { + return; + } + + // Remove the first element of [[Operations]]. + mOperations.RemoveElementAt(0); + + // If [[Operations]] is non-empty, execute the operation represented by the + // first element of [[Operations]], and abort these steps. + if (mOperations.Length()) { + // Cannot call without a temporary :( + RefPtr<Operation> op = mOperations[0]; + op->Call(aError); + return; + } + + // If connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] is false, abort + // these steps. + if (!mUpdateNegotiationNeededFlagOnEmptyChain) { + return; + } + + // Set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to false. + mUpdateNegotiationNeededFlagOnEmptyChain = false; + // Update the negotiation-needed flag for connection. + UpdateNegotiationNeeded(); +} + +void PeerConnectionImpl::SyncToJsep() { + for (const auto& transceiver : mTransceivers) { + transceiver->SyncToJsep(*mJsepSession); + } +} + +void PeerConnectionImpl::SyncFromJsep() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + mJsepSession->ForEachTransceiver( + [this, self = RefPtr<PeerConnectionImpl>(this)]( + const JsepTransceiver& jsepTransceiver) { + if (jsepTransceiver.GetMediaType() == + SdpMediaSection::MediaType::kApplication) { + return; + } + + CSFLogDebug(LOGTAG, "%s: Looking for match", __FUNCTION__); + RefPtr<RTCRtpTransceiver> transceiver; + for (auto& temp : mTransceivers) { + if (temp->GetJsepTransceiverId() == jsepTransceiver.GetUuid()) { + CSFLogDebug(LOGTAG, "%s: Found match", __FUNCTION__); + transceiver = temp; + break; + } + } + + if (!transceiver) { + CSFLogDebug(LOGTAG, "%s: No match, making new", __FUNCTION__); + dom::RTCRtpTransceiverInit init; + init.mDirection = RTCRtpTransceiverDirection::Recvonly; + IgnoredErrorResult rv; + transceiver = CreateTransceiver( + jsepTransceiver.GetUuid(), + jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init, + nullptr, false, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_ASSERT(false); + return; + } + mTransceivers.AppendElement(transceiver); + } + + CSFLogDebug(LOGTAG, "%s: Syncing transceiver", __FUNCTION__); + transceiver->SyncFromJsep(*mJsepSession); + }); +} + +already_AddRefed<dom::Promise> PeerConnectionImpl::MakePromise( + ErrorResult& aError) const { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + return dom::Promise::Create(global, aError); +} + +void PeerConnectionImpl::UpdateNegotiationNeeded() { + // If the length of connection.[[Operations]] is not 0, then set + // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort + // these steps. + if (mOperations.Length() != 0) { + mUpdateNegotiationNeededFlagOnEmptyChain = true; + return; + } + + // Queue a task to run the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this)] { + // If connection.[[IsClosed]] is true, abort these steps. + if (IsClosed()) { + return; + } + // If the length of connection.[[Operations]] is not 0, then set + // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and + // abort these steps. + if (mOperations.Length()) { + mUpdateNegotiationNeededFlagOnEmptyChain = true; + return; + } + // If connection's signaling state is not "stable", abort these steps. + if (mSignalingState != RTCSignalingState::Stable) { + return; + } + // If the result of checking if negotiation is needed is false, clear + // the negotiation-needed flag by setting + // connection.[[NegotiationNeeded]] to false, and abort these steps. + if (!CheckNegotiationNeeded()) { + mNegotiationNeeded = false; + return; + } + + // If connection.[[NegotiationNeeded]] is already true, abort these + // steps. + if (mNegotiationNeeded) { + return; + } + + // Set connection.[[NegotiationNeeded]] to true. + mNegotiationNeeded = true; + + // Fire an event named negotiationneeded at connection. + ErrorResult rv; + mPCObserver->FireNegotiationNeededEvent(rv); + })); +} + +void PeerConnectionImpl::NotifyDataChannel( + already_AddRefed<DataChannel> aChannel) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + RefPtr<DataChannel> channel(aChannel); + MOZ_ASSERT(channel); + CSFLogDebug(LOGTAG, "%s: channel: %p", __FUNCTION__, channel.get()); + + RefPtr<nsDOMDataChannel> domchannel; + nsresult rv = NS_NewDOMDataChannel(channel.forget(), mWindow, + getter_AddRefs(domchannel)); + NS_ENSURE_SUCCESS_VOID(rv); + + JSErrorResult jrv; + mPCObserver->NotifyDataChannel(*domchannel, jrv); +} + +void PeerConnectionImpl::NotifyDataChannelOpen(DataChannel*) { + mDataChannelsOpened++; +} + +void PeerConnectionImpl::NotifyDataChannelClosed(DataChannel*) { + mDataChannelsClosed++; +} + +void PeerConnectionImpl::NotifySctpConnected() { + if (!mSctpTransport) { + MOZ_ASSERT(false); + return; + } + + mSctpTransport->UpdateState(RTCSctpTransportState::Connected); +} + +void PeerConnectionImpl::NotifySctpClosed() { + if (!mSctpTransport) { + MOZ_ASSERT(false); + return; + } + + mSctpTransport->UpdateState(RTCSctpTransportState::Closed); +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) { + JsepOfferOptions options; + // convert the RTCOfferOptions to JsepOfferOptions + if (aOptions.mOfferToReceiveAudio.WasPassed()) { + options.mOfferToReceiveAudio = + mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value())); + } + + if (aOptions.mOfferToReceiveVideo.WasPassed()) { + options.mOfferToReceiveVideo = + mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value())); + } + + options.mIceRestart = mozilla::Some(aOptions.mIceRestart || + !mLocalIceCredentialsToReplace.empty()); + + return CreateOffer(options); +} + +static void DeferredCreateOffer(const std::string& aPcHandle, + const JsepOfferOptions& aOptions) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH( + "Why is DeferredCreateOffer being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->CreateOffer(aOptions); + } +} + +// Have to use unique_ptr because webidl enums are generated without a +// copy c'tor. +static std::unique_ptr<dom::PCErrorData> buildJSErrorData( + const JsepSession::Result& aResult, const std::string& aMessage) { + std::unique_ptr<dom::PCErrorData> result(new dom::PCErrorData); + result->mName = *aResult.mError; + result->mMessage = NS_ConvertASCIItoUTF16(aMessage.c_str()); + return result; +} + +// Used by unit tests and the IDL CreateOffer. +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) { + PC_AUTO_ENTER_API_CALL(true); + + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. + PeerConnectionCtx::GetInstance()->queueJSEPOperation( + WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions)); + STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)"); + return NS_OK; + } + + CSFLogDebug(LOGTAG, "CreateOffer()"); + + nsresult nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return nrv; + } + + STAMP_TIMECARD(mTimeCard, "Create Offer"); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this), aOptions] { + std::string offer; + + SyncToJsep(); + UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone()); + JsepSession::Result result = + uncommittedJsepSession->CreateOffer(aOptions, &offer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = uncommittedJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateOfferError( + *buildJSErrorData(result, errorString), rv); + } else { + mJsepSession = std::move(uncommittedJsepSession); + mPCObserver->OnCreateOfferSuccess(ObString(offer.c_str()), rv); + } + })); + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateAnswer() { + PC_AUTO_ENTER_API_CALL(true); + + CSFLogDebug(LOGTAG, "CreateAnswer()"); + + STAMP_TIMECARD(mTimeCard, "Create Answer"); + // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to + // add it as a param to CreateAnswer, and convert it here. + JsepAnswerOptions options; + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this), options] { + std::string answer; + SyncToJsep(); + UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone()); + JsepSession::Result result = + uncommittedJsepSession->CreateAnswer(options, &answer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = uncommittedJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateAnswerError( + *buildJSErrorData(result, errorString), rv); + } else { + mJsepSession = std::move(uncommittedJsepSession); + mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv); + } + })); + + return NS_OK; +} + +dom::RTCSdpType ToDomSdpType(JsepSdpType aType) { + switch (aType) { + case kJsepSdpOffer: + return dom::RTCSdpType::Offer; + case kJsepSdpAnswer: + return dom::RTCSdpType::Answer; + case kJsepSdpPranswer: + return dom::RTCSdpType::Pranswer; + case kJsepSdpRollback: + return dom::RTCSdpType::Rollback; + } + + MOZ_CRASH("Nonexistent JsepSdpType"); +} + +JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) { + switch (aType) { + case dom::RTCSdpType::Offer: + return kJsepSdpOffer; + case dom::RTCSdpType::Pranswer: + return kJsepSdpPranswer; + case dom::RTCSdpType::Answer: + return kJsepSdpAnswer; + case dom::RTCSdpType::Rollback: + return kJsepSdpRollback; + case dom::RTCSdpType::EndGuard_:; + } + + MOZ_CRASH("Nonexistent dom::RTCSdpType"); +} + +NS_IMETHODIMP +PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + STAMP_TIMECARD(mTimeCard, "Set Local Description"); + + if (AnyLocalTrackHasPeerIdentity()) { + mRequestedPrivacy = Some(PrincipalPrivacy::Private); + } + + mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry; + sdpEntry.mIsLocal = true; + sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + mLocalRequestedSDP = aSDP; + + SyncToJsep(); + + bool wasRestartingIce = mJsepSession->IsIceRestarting(); + JsepSdpType sdpType; + switch (aAction) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + appendHistory(); + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(!mUncommittedJsepSession); + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result = + mUncommittedJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = mUncommittedJsepSession->GetLastError(); + mUncommittedJsepSession = nullptr; + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString), + rv); + sdpEntry.mErrors = GetLastSdpParsingErrors(); + } else { + if (wasRestartingIce) { + RecordIceRestartStatistics(sdpType); + } + + mPCObserver->OnSetDescriptionSuccess(rv); + } + + appendHistory(); + + if (rv.Failed()) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction, + const std::string& aSdp) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH( + "Why is DeferredSetRemote being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str()); + } +} + +NS_IMETHODIMP +PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + if (action == IPeerConnection::kActionOffer) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. (This must be a + // remote offer, or else we would not have gotten this far) + PeerConnectionCtx::GetInstance()->queueJSEPOperation(WrapRunnableNM( + DeferredSetRemote, mHandle, action, std::string(aSDP))); + STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)"); + return NS_OK; + } + + nsresult nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return nrv; + } + } + + STAMP_TIMECARD(mTimeCard, "Set Remote Description"); + + mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry; + sdpEntry.mIsLocal = false; + sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + SyncToJsep(); + + mRemoteRequestedSDP = aSDP; + bool wasRestartingIce = mJsepSession->IsIceRestarting(); + JsepSdpType sdpType; + switch (action) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!mUncommittedJsepSession); + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result = mUncommittedJsepSession->SetRemoteDescription( + sdpType, mRemoteRequestedSDP); + JSErrorResult jrv; + if (result.mError.isSome()) { + std::string errorString = mUncommittedJsepSession->GetLastError(); + mUncommittedJsepSession = nullptr; + sdpEntry.mErrors = GetLastSdpParsingErrors(); + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString), + jrv); + } else { + if (wasRestartingIce) { + RecordIceRestartStatistics(sdpType); + } + + mPCObserver->OnSetDescriptionSuccess(jrv); + } + + appendHistory(); + + if (jrv.Failed()) { + return jrv.StealNSResult(); + } + + return NS_OK; +} + +already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats( + MediaStreamTrack* aSelector) { + if (!mWindow) { + MOZ_CRASH("Cannot create a promise without a window!"); + } + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + ErrorResult rv; + RefPtr<Promise> promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_CRASH("Failed to create a promise!"); + } + + if (!IsClosed()) { + GetStats(aSelector, false) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise, window = mWindow]( + UniquePtr<dom::RTCStatsReportInternal>&& aReport) { + RefPtr<RTCStatsReport> report(new RTCStatsReport(window)); + report->Incorporate(*aReport); + promise->MaybeResolve(std::move(report)); + }, + [promise, window = mWindow](nsresult aError) { + RefPtr<RTCStatsReport> report(new RTCStatsReport(window)); + promise->MaybeResolve(std::move(report)); + }); + } else { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return promise.forget(); +} + +void PeerConnectionImpl::GetRemoteStreams( + nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const { + aStreamsOut = mReceiveStreams.Clone(); +} + +NS_IMETHODIMP +PeerConnectionImpl::AddIceCandidate( + const char* aCandidate, const char* aMid, const char* aUfrag, + const dom::Nullable<unsigned short>& aLevel) { + PC_AUTO_ENTER_API_CALL(true); + + if (mForceIceTcp && + std::string::npos != std::string(aCandidate).find(" UDP ")) { + CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate); + return NS_OK; + } + + STAMP_TIMECARD(mTimeCard, "Add Ice Candidate"); + + CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag); + + std::string transportId; + Maybe<unsigned short> level; + if (!aLevel.IsNull()) { + level = Some(aLevel.Value()); + } + MOZ_DIAGNOSTIC_ASSERT( + !mUncommittedJsepSession, + "AddIceCandidate is chained, which means it should never " + "run while an sRD/sLD is in progress"); + JsepSession::Result result = mJsepSession->AddRemoteIceCandidate( + aCandidate, aMid, level, aUfrag, &transportId); + + if (!result.mError.isSome()) { + // We do not bother the MediaTransportHandler about this before + // offer/answer concludes. Once offer/answer concludes, we will extract + // these candidates from the remote SDP. + if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) { + AddIceCandidate(aCandidate, transportId, aUfrag); + mRawTrickledCandidates.push_back(aCandidate); + } + // Spec says we queue a task for these updates + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<PeerConnectionImpl>(this)] { + if (IsClosed()) { + return; + } + mPendingRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPending); + mCurrentRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent); + JSErrorResult rv; + mPCObserver->OnAddIceCandidateSuccess(rv); + })); + } else { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(LOGTAG, + "Failed to incorporate remote candidate into SDP:" + " res = %u, candidate = %s, level = %i, error = %s", + static_cast<unsigned>(*result.mError), aCandidate, + level.valueOr(-1), errorString.c_str()); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr<PeerConnectionImpl>(this), errorString, result] { + if (IsClosed()) { + return; + } + JSErrorResult rv; + mPCObserver->OnAddIceCandidateError( + *buildJSErrorData(result, errorString), rv); + })); + } + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CloseStreams() { + PC_AUTO_ENTER_API_CALL(false); + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) { + PC_AUTO_ENTER_API_CALL(true); + MOZ_ASSERT(!aPeerIdentity.IsEmpty()); + + // once set, this can't be changed + if (mPeerIdentity) { + if (!mPeerIdentity->Equals(aPeerIdentity)) { + return NS_ERROR_FAILURE; + } + } else { + mPeerIdentity = new PeerIdentity(aPeerIdentity); + Document* doc = mWindow->GetExtantDoc(); + if (!doc) { + CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + for (const auto& transceiver : mTransceivers) { + transceiver->Sender()->GetPipeline()->UpdateSinkIdentity( + doc->NodePrincipal(), mPeerIdentity); + } + } + return NS_OK; +} + +nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) { + PC_AUTO_ENTER_API_CALL(false); + MOZ_DIAGNOSTIC_ASSERT(!mRequestedPrivacy || + (*mRequestedPrivacy == PrincipalPrivacy::Private) == + aPrivacyRequested); + + mRequestedPrivacy = Some(aPrivacyRequested ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate); + // This updates the MediaPipelines with a private PrincipalHandle. Note that + // MediaPipelineReceive has its own AlpnNegotiated handler so it can get + // signaled off-main to drop data until it receives the new PrincipalHandle + // from us. + UpdateMediaPipelines(); + return NS_OK; +} + +void PeerConnectionImpl::OnDtlsStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + auto it = mTransportIdToRTCDtlsTransport.find(aTransportId); + if (it != mTransportIdToRTCDtlsTransport.end()) { + it->second->UpdateState(aState); + } + UpdateConnectionState(); +} + +RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const { + // closed The RTCPeerConnection object's [[IsClosed]] slot is true. + if (IsClosed()) { + return RTCPeerConnectionState::Closed; + } + + // Would use a bitset, but that requires lots of static_cast<size_t> + // Oh well. + std::set<RTCDtlsTransportState> statesFound; + for (const auto& [id, dtlsTransport] : mTransportIdToRTCDtlsTransport) { + Unused << id; + statesFound.insert(dtlsTransport->State()); + } + + // failed The previous state doesn't apply and any RTCIceTransports are + // in the "failed" state or any RTCDtlsTransports are in the "failed" state. + // NOTE: "any RTCIceTransports are in the failed state" is equivalent to + // mIceConnectionState == Failed + if (mIceConnectionState == RTCIceConnectionState::Failed || + statesFound.count(RTCDtlsTransportState::Failed)) { + return RTCPeerConnectionState::Failed; + } + + // disconnected None of the previous states apply and any + // RTCIceTransports are in the "disconnected" state. + // NOTE: "any RTCIceTransports are in the disconnected state" is equivalent to + // mIceConnectionState == Disconnected. + if (mIceConnectionState == RTCIceConnectionState::Disconnected) { + return RTCPeerConnectionState::Disconnected; + } + + // new None of the previous states apply and all RTCIceTransports are + // in the "new" or "closed" state, and all RTCDtlsTransports are in the "new" + // or "closed" state, or there are no transports. + // NOTE: "all RTCIceTransports are in the new or closed state" is equivalent + // to mIceConnectionState == New. + if (mIceConnectionState == RTCIceConnectionState::New && + !statesFound.count(RTCDtlsTransportState::Connecting) && + !statesFound.count(RTCDtlsTransportState::Connected) && + !statesFound.count(RTCDtlsTransportState::Failed)) { + return RTCPeerConnectionState::New; + } + + // No transports + if (statesFound.empty()) { + return RTCPeerConnectionState::New; + } + + // connecting None of the previous states apply and any + // RTCIceTransport is in the "new" or "checking" state or any + // RTCDtlsTransport is in the "new" or "connecting" state. + // NOTE: "None of the previous states apply and any RTCIceTransport is in the + // "new" or "checking" state" is equivalent to mIceConnectionState == + // Checking. + if (mIceConnectionState == RTCIceConnectionState::Checking || + statesFound.count(RTCDtlsTransportState::New) || + statesFound.count(RTCDtlsTransportState::Connecting)) { + return RTCPeerConnectionState::Connecting; + } + + // connected None of the previous states apply and all RTCIceTransports are + // in the "connected", "completed" or "closed" state, and all + // RTCDtlsTransports are in the "connected" or "closed" state. + // NOTE: "None of the previous states apply and all RTCIceTransports are in + // the "connected", "completed" or "closed" state" is equivalent to + // mIceConnectionState == Connected. + if (mIceConnectionState == RTCIceConnectionState::Connected && + !statesFound.count(RTCDtlsTransportState::New) && + !statesFound.count(RTCDtlsTransportState::Failed) && + !statesFound.count(RTCDtlsTransportState::Connecting)) { + return RTCPeerConnectionState::Connected; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // THERE IS NO CATCH-ALL NONE-OF-THE-ABOVE IN THE SPEC! THIS IS REALLY BAD! !! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Let's try to figure out how bad, precisely. + // Any one of these will cause us to bail above. + MOZ_ASSERT(mIceConnectionState != RTCIceConnectionState::Failed && + mIceConnectionState != RTCIceConnectionState::Disconnected && + mIceConnectionState != RTCIceConnectionState::Checking); + MOZ_ASSERT(!statesFound.count(RTCDtlsTransportState::New) && + !statesFound.count(RTCDtlsTransportState::Connecting) && + !statesFound.count(RTCDtlsTransportState::Failed)); + + // One of these must be set, or the empty() check would have failed above. + MOZ_ASSERT(statesFound.count(RTCDtlsTransportState::Connected) || + statesFound.count(RTCDtlsTransportState::Closed)); + + // Here are our remaining possibilities: + // ICE connected, !statesFound.count(Connected), statesFound.count(Closed) + // ICE connected, statesFound.count(Connected), !statesFound.count(Closed) + // ICE connected, statesFound.count(Connected), statesFound.count(Closed) + // All three of these would result in returning Connected above. + + // ICE new, !statesFound.count(Connected), statesFound.count(Closed) + // This results in returning New above. Whew. + + // ICE new, statesFound.count(Connected), !statesFound.count(Closed) + // ICE new, statesFound.count(Connected), statesFound.count(Closed) + // These would make it all the way here! Very weird state though, for all + // ICE transports to be new/closed, but having a connected DTLS transport. + // Handle this as a non-transition, just in case. + return mConnectionState; +} + +void PeerConnectionImpl::UpdateConnectionState() { + auto newState = GetNewConnectionState(); + if (newState != mConnectionState) { + CSFLogDebug(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__, + static_cast<int>(mConnectionState), static_cast<int>(newState), + this); + mConnectionState = newState; + if (mConnectionState != RTCPeerConnectionState::Closed) { + JSErrorResult jrv; + mPCObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv); + } + } +} + +void PeerConnectionImpl::OnMediaError(const std::string& aError) { + CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str()); + // TODO: Let content know about this somehow. +} + +void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type, + bool sending, + UniquePtr<uint8_t[]>& packet, + size_t size) { + if (IsClosed()) { + return; + } + + // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere + // else? + AutoJSAPI jsapi; + if (!jsapi.Init(mWindow)) { + return; + } + + 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, + const nsCString& aHostName) { + 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); + }; + + nsCString domainList; + nsresult nr = Preferences::GetCString(aPref, domainList); + + if (NS_FAILED(nr)) { + return false; + } + + domainList.StripWhitespace(); + + if (domainList.IsEmpty() || aHostName.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 domainPattern; + rv = idnService->ConvertUTF8toACE(each, domainPattern); + if (NS_SUCCEEDED(rv)) { + if (HostInDomain(aHostName, domainPattern)) { + 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) { + return GetPacketDumper()->EnablePacketDump(level, type, sending); +} + +nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level, + dom::mozPacketDumpType type, + bool sending) { + return GetPacketDumper()->DisablePacketDump(level, type, sending); +} + +void PeerConnectionImpl::StampTimecard(const char* aEvent) { + MOZ_ASSERT(NS_IsMainThread()); + STAMP_TIMECARD(mTimeCard, aEvent); +} + +void PeerConnectionImpl::SendWarningToConsole(const nsCString& aWarning) { + nsAutoString msg = NS_ConvertASCIItoUTF16(aWarning); + nsContentUtils::ReportToConsoleByWindowID(msg, nsIScriptError::warningFlag, + "WebRTC"_ns, mWindow->WindowID()); +} + +void PeerConnectionImpl::GetDefaultVideoCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs, + bool aUseRtx) { + // Supported video codecs. + // Note: order here implies priority for building offers! + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultVP8(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultVP9(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultH264_1(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultH264_0(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultUlpFec()); + aSupportedCodecs.emplace_back( + JsepApplicationCodecDescription::CreateDefault()); + aSupportedCodecs.emplace_back(JsepVideoCodecDescription::CreateDefaultRed()); +} + +void PeerConnectionImpl::GetDefaultAudioCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs) { + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultG722()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMU()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMA()); + aSupportedCodecs.emplace_back( + JsepAudioCodecDescription::CreateDefaultTelephoneEvent()); +} + +void PeerConnectionImpl::GetDefaultRtpExtensions( + std::vector<RtpExtensionHeader>& aRtpExtensions) { + RtpExtensionHeader audioLevel = {JsepMediaType::kAudio, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kAudioLevelUri}; + aRtpExtensions.push_back(audioLevel); + + RtpExtensionHeader csrcAudioLevels = { + JsepMediaType::kAudio, SdpDirectionAttribute::Direction::kRecvonly, + webrtc::RtpExtension::kCsrcAudioLevelsUri}; + aRtpExtensions.push_back(csrcAudioLevels); + + RtpExtensionHeader mid = {JsepMediaType::kAudioVideo, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kMidUri}; + aRtpExtensions.push_back(mid); + + RtpExtensionHeader absSendTime = {JsepMediaType::kVideo, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kAbsSendTimeUri}; + aRtpExtensions.push_back(absSendTime); + + RtpExtensionHeader timestampOffset = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kTimestampOffsetUri}; + aRtpExtensions.push_back(timestampOffset); + + RtpExtensionHeader playoutDelay = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kRecvonly, + webrtc::RtpExtension::kPlayoutDelayUri}; + aRtpExtensions.push_back(playoutDelay); + + RtpExtensionHeader transportSequenceNumber = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kTransportSequenceNumberUri}; + aRtpExtensions.push_back(transportSequenceNumber); +} + +void PeerConnectionImpl::GetCapabilities( + const nsAString& aKind, dom::Nullable<dom::RTCRtpCapabilities>& aResult, + sdp::Direction aDirection) { + std::vector<UniquePtr<JsepCodecDescription>> codecs; + std::vector<RtpExtensionHeader> headers; + auto mediaType = JsepMediaType::kNone; + + if (aKind.EqualsASCII("video")) { + GetDefaultVideoCodecs(codecs, true); + mediaType = JsepMediaType::kVideo; + } else if (aKind.EqualsASCII("audio")) { + GetDefaultAudioCodecs(codecs); + mediaType = JsepMediaType::kAudio; + } else { + return; + } + + GetDefaultRtpExtensions(headers); + + // Use the codecs for kind to fill out the RTCRtpCodecCapability + for (const auto& codec : codecs) { + // To avoid misleading information on codec capabilities skip those + // not signaled for audio/video (webrtc-datachannel) + // and any disabled by default (ulpfec and red). + if (codec->mName == "webrtc-datachannel" || codec->mName == "ulpfec" || + codec->mName == "red") { + continue; + } + + dom::RTCRtpCodecCapability capability; + capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/" + codec->mName); + capability.mClockRate = codec->mClock; + + if (codec->mChannels) { + capability.mChannels.Construct(codec->mChannels); + } + + UniquePtr<SdpFmtpAttributeList::Parameters> params; + codec->ApplyConfigToFmtp(params); + + if (params != nullptr) { + std::ostringstream paramsString; + params->Serialize(paramsString); + nsTString<char16_t> fmtp; + fmtp.AssignASCII(paramsString.str()); + capability.mSdpFmtpLine.Construct(fmtp); + } + + if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) { + mozalloc_handle_oom(0); + } + } + + // We need to manually add rtx for video. + if (mediaType == JsepMediaType::kVideo) { + dom::RTCRtpCodecCapability capability; + capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/rtx"); + capability.mClockRate = 90000; + if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) { + mozalloc_handle_oom(0); + } + } + + // Add headers that match the direction and media type requested. + for (const auto& header : headers) { + if ((header.direction & aDirection) && (header.mMediaType & mediaType)) { + dom::RTCRtpHeaderExtensionCapability rtpHeader; + rtpHeader.mUri.AssignASCII(header.extensionname); + if (!aResult.SetValue().mHeaderExtensions.AppendElement(rtpHeader, + fallible)) { + mozalloc_handle_oom(0); + } + } + } +} + +void PeerConnectionImpl::SetupPreferredCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) { + bool useRtx = + Preferences::GetBool("media.peerconnection.video.use_rtx", false); + + GetDefaultVideoCodecs(aPreferredCodecs, useRtx); + GetDefaultAudioCodecs(aPreferredCodecs); + + // With red update the redundant encodings list + for (auto& videoCodec : aPreferredCodecs) { + if (videoCodec->mName == "red") { + JsepVideoCodecDescription& red = + static_cast<JsepVideoCodecDescription&>(*videoCodec); + red.UpdateRedundantEncodings(aPreferredCodecs); + } + } +} + +void PeerConnectionImpl::SetupPreferredRtpExtensions( + std::vector<RtpExtensionHeader>& aPreferredheaders) { + GetDefaultRtpExtensions(aPreferredheaders); + + if (!Preferences::GetBool("media.navigator.video.use_transport_cc", false)) { + aPreferredheaders.erase( + std::remove_if( + aPreferredheaders.begin(), aPreferredheaders.end(), + [&](const RtpExtensionHeader& header) { + return header.extensionname == + webrtc::RtpExtension::kTransportSequenceNumberUri; + }), + aPreferredheaders.end()); + } +} + +nsresult PeerConnectionImpl::CalculateFingerprint( + const 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; +} + +NS_IMETHODIMP +PeerConnectionImpl::ConnectionState(RTCPeerConnectionState* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(aState); + + *aState = mConnectionState; + return NS_OK; +} + +nsresult PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(mTrickle || !assert_ice_ready || + (mIceGatheringState == RTCIceGatheringState::Complete)); + + if (IsClosed()) { + CSFLogError(LOGTAG, "%s: called API while closed", __FUNCTION__); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void PeerConnectionImpl::StoreFinalStats( + UniquePtr<RTCStatsReportInternal>&& report) { + using namespace Telemetry; + + report->mClosed = true; + + for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) { + bool isVideo = (inboundRtpStats.mId.Value().Find(u"video") != -1); + if (!isVideo) { + continue; + } + if (inboundRtpStats.mDiscardedPackets.WasPassed() && + report->mCallDurationMs.WasPassed()) { + double mins = report->mCallDurationMs.Value() / (1000 * 60); + if (mins > 0) { + Accumulate( + WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM, + uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins)); + } + } + } + + // Finally, store the stats + mFinalStats = std::move(report); +} + +NS_IMETHODIMP +PeerConnectionImpl::Close() { + CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + if (IsClosed()) { + return NS_OK; + } + + STAMP_TIMECARD(mTimeCard, "Close"); + + // When ICE completes, we record some telemetry. We do this at the end of the + // call because we want to make sure we've waited for all trickle ICE + // candidates to come in; this can happen well after we've transitioned to + // connected. As a bonus, this allows us to detect race conditions where a + // stats dispatch happens right as the PC closes. + RecordEndOfCallTelemetry(); + + CSFLogInfo(LOGTAG, + "%s: Closing PeerConnectionImpl %s; " + "ending call", + __FUNCTION__, mHandle.c_str()); + if (mJsepSession) { + mJsepSession->Close(); + } + if (mDataConnection) { + CSFLogInfo(LOGTAG, "%s: Destroying DataChannelConnection %p for %s", + __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str()); + mDataConnection->Destroy(); + mDataConnection = + nullptr; // it may not go away until the runnables are dead + } + + if (mStunAddrsRequest) { + for (const auto& hostname : mRegisteredMDNSHostnames) { + mStunAddrsRequest->SendUnregisterMDNSHostname( + nsCString(hostname.c_str())); + } + mRegisteredMDNSHostnames.clear(); + mStunAddrsRequest->Cancel(); + mStunAddrsRequest = nullptr; + } + + for (auto& transceiver : mTransceivers) { + transceiver->Close(); + } + + mTransportIdToRTCDtlsTransport.clear(); + + mQueuedIceCtxOperations.clear(); + + mOperations.Clear(); + + // Uncount this connection as active on the inner window upon close. + if (mWindow && mActiveOnWindow) { + mWindow->RemovePeerConnection(); + mActiveOnWindow = false; + } + + mSignalingState = RTCSignalingState::Closed; + mConnectionState = RTCPeerConnectionState::Closed; + + if (!mTransportHandler) { + // We were never initialized, apparently. + return NS_OK; + } + + // Clear any resources held by libwebrtc through our Call instance. + RefPtr<GenericPromise> callDestroyPromise; + if (mCall) { + // Make sure the compiler does not get confused and try to acquire a + // reference to this thread _after_ we null out mCall. + auto callThread = mCall->mCallThread; + callDestroyPromise = + InvokeAsync(callThread, __func__, [call = std::move(mCall)]() { + call->Destroy(); + return GenericPromise::CreateAndResolve( + true, "PCImpl->WebRtcCallWrapper::Destroy"); + }); + } else { + callDestroyPromise = GenericPromise::CreateAndResolve(true, __func__); + } + + mFinalStatsQuery = + GetStats(nullptr, true) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr<PeerConnectionImpl>(this)]( + UniquePtr<dom::RTCStatsReportInternal>&& aReport) mutable { + StoreFinalStats(std::move(aReport)); + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }, + [](nsresult aError) { + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }); + + // 1. Allow final stats query to complete. + // 2. Tear down call, if necessary. We do this before we shut down the + // transport handler, so RTCP BYE can be sent. + // 3. Unhook from the signal handler (sigslot) for transport stuff. This must + // be done before we tear down the transport handler. + // 4. Tear down the transport handler, and deregister from PeerConnectionCtx. + // When we deregister from PeerConnectionCtx, our final stats (if any) + // will be stored. + MOZ_RELEASE_ASSERT(mSTSThread); + mFinalStatsQuery + ->Then(GetMainThreadSerialEventTarget(), __func__, + [callDestroyPromise]() mutable { return callDestroyPromise; }) + ->Then( + mSTSThread, __func__, + [signalHandler = std::move(mSignalHandler)]() mutable { + CSFLogDebug( + LOGTAG, + "Destroying PeerConnectionImpl::SignalHandler on STS thread"); + return GenericPromise::CreateAndResolve( + true, "PeerConnectionImpl::~SignalHandler"); + }) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr<PeerConnectionImpl>(this)]() mutable { + CSFLogDebug(LOGTAG, "PCImpl->mTransportHandler::RemoveTransports"); + mTransportHandler->RemoveTransportsExcept(std::set<std::string>()); + if (mPrivateWindow) { + mTransportHandler->ExitPrivateMode(); + } + mTransportHandler = nullptr; + if (PeerConnectionCtx::isActive()) { + // If we're shutting down xpcom, this Instance will be unset + // before calling Close() on all remaining PCs, to avoid + // reentrancy. + PeerConnectionCtx::GetInstance()->RemovePeerConnection(mHandle); + } + }); + + return NS_OK; +} + +void PeerConnectionImpl::BreakCycles() { + for (auto& transceiver : mTransceivers) { + transceiver->BreakCycles(); + } + mTransceivers.Clear(); +} + +bool PeerConnectionImpl::HasPendingSetParameters() const { + for (const auto& transceiver : mTransceivers) { + if (transceiver->Sender()->HasPendingSetParameters()) { + return true; + } + } + return false; +} + +void PeerConnectionImpl::InvalidateLastReturnedParameters() { + for (const auto& transceiver : mTransceivers) { + transceiver->Sender()->InvalidateLastReturnedParameters(); + } +} + +nsresult PeerConnectionImpl::SetConfiguration( + const RTCConfiguration& aConfiguration) { + nsresult rv = mTransportHandler->SetIceConfig( + aConfiguration.mIceServers, aConfiguration.mIceTransportPolicy); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + JsepBundlePolicy bundlePolicy; + switch (aConfiguration.mBundlePolicy) { + case dom::RTCBundlePolicy::Balanced: + bundlePolicy = kBundleBalanced; + break; + case dom::RTCBundlePolicy::Max_compat: + bundlePolicy = kBundleMaxCompat; + break; + case dom::RTCBundlePolicy::Max_bundle: + bundlePolicy = kBundleMaxBundle; + break; + default: + MOZ_CRASH(); + } + + // Ignore errors, since those ought to be handled earlier. + Unused << mJsepSession->SetBundlePolicy(bundlePolicy); + + if (!aConfiguration.mPeerIdentity.IsEmpty()) { + mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity); + mRequestedPrivacy = Some(PrincipalPrivacy::Private); + } + + auto proxyConfig = GetProxyConfig(); + if (proxyConfig) { + // Note that this could check if PrivacyRequested() is set on the PC and + // remove "webrtc" from the ALPN list. But that would only work if the PC + // was constructed with a peerIdentity constraint, not when isolated + // streams are added. If we ever need to signal to the proxy that the + // media is isolated, then we would need to restructure this code. + mTransportHandler->SetProxyConfig(std::move(*proxyConfig)); + } + + // Store the configuration for about:webrtc + StoreConfigurationForAboutWebrtc(aConfiguration); + + return NS_OK; +} + +RTCSctpTransport* PeerConnectionImpl::GetSctp() const { + return mSctpTransport.get(); +} + +void PeerConnectionImpl::RestartIce() { + RestartIceNoRenegotiationNeeded(); + // Update the negotiation-needed flag for connection. + UpdateNegotiationNeeded(); +} + +// webrtc-pc does not specify any situations where this is done, but the JSEP +// spec does, in some situations due to setConfiguration. +void PeerConnectionImpl::RestartIceNoRenegotiationNeeded() { + // Empty connection.[[LocalIceCredentialsToReplace]], and populate it with + // all ICE credentials (ice-ufrag and ice-pwd as defined in section 15.4 of + // [RFC5245]) found in connection.[[CurrentLocalDescription]], as well as all + // ICE credentials found in connection.[[PendingLocalDescription]]. + mLocalIceCredentialsToReplace = mJsepSession->GetLocalIceCredentials(); +} + +bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName) { + // fire an event to the DOM window if this is "ours" + if (!AnyCodecHasPluginID(aPluginID)) { + return false; + } + + CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__, + static_cast<unsigned long long>(aPluginID)); + + RefPtr<Document> doc = mWindow->GetExtantDoc(); + if (!doc) { + NS_WARNING("Couldn't get document for PluginCrashed event!"); + return true; + } + + PluginCrashedEventInit init; + init.mPluginID = aPluginID; + init.mPluginName = aPluginName; + init.mSubmittedCrashReport = false; + init.mGmpPlugin = true; + init.mBubbles = true; + init.mCancelable = true; + + RefPtr<PluginCrashedEvent> event = + PluginCrashedEvent::Constructor(doc, u"PluginCrashed"_ns, init); + + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr<nsPIDOMWindowInner> window = mWindow; + EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr); + + return true; +} + +void PeerConnectionImpl::RecordEndOfCallTelemetry() { + if (!mCallTelemStarted) { + return; + } + MOZ_RELEASE_ASSERT(!mCallTelemEnded, "Don't end telemetry twice"); + MOZ_RELEASE_ASSERT(mJsepSession, + "Call telemetry only starts after jsep session start"); + MOZ_RELEASE_ASSERT(mJsepSession->GetNegotiations() > 0, + "Call telemetry only starts after first connection"); + + // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting + static const uint32_t kAudioTypeMask = 1; + static const uint32_t kVideoTypeMask = 2; + static const uint32_t kDataChannelTypeMask = 4; + + // Report end-of-call Telemetry + 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((type & kAudioTypeMask) || + (type & kVideoTypeMask)); + if (found->second.IsStopped()) { + sCallDurationTimers.erase(found); + } + } + mCallTelemEnded = true; +} + +DOMMediaStream* PeerConnectionImpl::GetReceiveStream( + const std::string& aId) const { + nsString wanted = NS_ConvertASCIItoUTF16(aId.c_str()); + for (auto& stream : mReceiveStreams) { + nsString id; + stream->GetId(id); + if (id == wanted) { + return stream; + } + } + return nullptr; +} + +DOMMediaStream* PeerConnectionImpl::CreateReceiveStream( + const std::string& aId) { + mReceiveStreams.AppendElement(new DOMMediaStream(mWindow)); + mReceiveStreams.LastElement()->AssignId(NS_ConvertASCIItoUTF16(aId.c_str())); + return mReceiveStreams.LastElement(); +} + +already_AddRefed<dom::Promise> PeerConnectionImpl::OnSetDescriptionSuccess( + dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError) { + CSFLogDebug(LOGTAG, __FUNCTION__); + + RefPtr<dom::Promise> p = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, p); + + return p.forget(); +} + +void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( + dom::RTCSdpType aSdpType, bool aRemote, const RefPtr<dom::Promise>& aP) { + // Spec says we queue a task for all the stuff that ends up back in JS + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr<PeerConnectionImpl>(this), aSdpType, aRemote, aP] { + if (IsClosed()) { + // Yes, we do not settle the promise here. Yes, this is what the spec + // wants. + return; + } + + MOZ_ASSERT(mUncommittedJsepSession); + + // sRD/sLD needs to be redone in certain circumstances + bool needsRedo = HasPendingSetParameters(); + if (!needsRedo && aRemote && (aSdpType == dom::RTCSdpType::Offer)) { + for (auto& transceiver : mTransceivers) { + if (!mUncommittedJsepSession->GetTransceiver( + transceiver->GetJsepTransceiverId())) { + needsRedo = true; + break; + } + } + } + + if (needsRedo) { + // Spec says to abort, and re-do the sRD! + // This happens either when there is a SetParameters call in + // flight (that will race against the [[SendEncodings]] + // modification caused by sRD(offer)), or when addTrack has been + // called while sRD(offer) was in progress. + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result; + if (aRemote) { + mUncommittedJsepSession->SetRemoteDescription( + ToJsepSdpType(aSdpType), mRemoteRequestedSDP); + } else { + mUncommittedJsepSession->SetLocalDescription( + ToJsepSdpType(aSdpType), mLocalRequestedSDP); + } + if (result.mError.isSome()) { + // wat + nsCString error( + "When redoing sRD/sLD because it raced against " + "addTrack or setParameters, we encountered a failure that " + "did not happen " + "the first time. This should never happen. The error was: "); + error += mUncommittedJsepSession->GetLastError().c_str(); + aP->MaybeRejectWithOperationError(error); + MOZ_ASSERT(false); + } else { + DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP); + } + return; + } + + for (auto& transceiver : mTransceivers) { + if (!mUncommittedJsepSession->GetTransceiver( + transceiver->GetJsepTransceiverId())) { + // sLD, or sRD(answer), just make sure the new transceiver is + // added, no need to re-do anything. + mUncommittedJsepSession->AddTransceiver( + transceiver->GetJsepTransceiver()); + } + } + + mJsepSession = std::move(mUncommittedJsepSession); + + auto newSignalingState = GetSignalingState(); + SyncFromJsep(); + if (aRemote || aSdpType == dom::RTCSdpType::Pranswer || + aSdpType == dom::RTCSdpType::Answer) { + InvalidateLastReturnedParameters(); + } + + // Section 4.4.1.5 Set the RTCSessionDescription: + if (aSdpType == dom::RTCSdpType::Rollback) { + // - step 4.5.10, type is rollback + RollbackRTCDtlsTransports(); + } else if (!(aRemote && aSdpType == dom::RTCSdpType::Offer)) { + // - step 4.5.9 type is not rollback + // - step 4.5.9.1 when remote is false + // - step 4.5.9.2.13 when remote is true, type answer or pranswer + // More simply: not rollback, and not for remote offers. + bool markAsStable = aSdpType == dom::RTCSdpType::Offer && + mSignalingState == RTCSignalingState::Stable; + UpdateRTCDtlsTransports(markAsStable); + } + + // Did we just apply a local description? + if (!aRemote) { + // We'd like to handle this in PeerConnectionImpl::UpdateNetworkState. + // Unfortunately, if the WiFi switch happens quickly, we never see + // that state change. We need to detect the ice restart here and + // reset the PeerConnectionImpl's stun addresses so they are + // regathered when PeerConnectionImpl::GatherIfReady is called. + if (mJsepSession->IsIceRestarting()) { + ResetStunAddrsForIceRestart(); + } + EnsureTransports(*mJsepSession); + } + + if (mJsepSession->GetState() == kJsepStateStable) { + if (aSdpType != dom::RTCSdpType::Rollback) { + // We need this initted for UpdateTransports + InitializeDataChannel(); + } + + // If we're rolling back a local offer, we might need to remove some + // transports, and stomp some MediaPipeline setup, but nothing further + // needs to be done. + UpdateTransports(*mJsepSession, mForceIceTcp); + if (NS_FAILED(UpdateMediaPipelines())) { + CSFLogError(LOGTAG, "Error Updating MediaPipelines"); + NS_ASSERTION( + false, + "Error Updating MediaPipelines in OnSetDescriptionSuccess()"); + aP->MaybeRejectWithOperationError("Error Updating MediaPipelines"); + } + + if (aSdpType != dom::RTCSdpType::Rollback) { + StartIceChecks(*mJsepSession); + } + + // Telemetry: record info on the current state of + // streams/renegotiations/etc Note: this code gets run on rollbacks as + // well! + + // Update the max channels used with each direction for each type + uint16_t receiving[SdpMediaSection::kMediaTypes]; + uint16_t sending[SdpMediaSection::kMediaTypes]; + mJsepSession->CountTracksAndDatachannels(receiving, sending); + for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) { + if (mMaxReceiving[i] < receiving[i]) { + mMaxReceiving[i] = receiving[i]; + } + if (mMaxSending[i] < sending[i]) { + mMaxSending[i] = sending[i]; + } + } + } + + mPendingRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPending); + mCurrentRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent); + mPendingLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPending); + mCurrentLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionCurrent); + mPendingOfferer = mJsepSession->IsPendingOfferer(); + mCurrentOfferer = mJsepSession->IsCurrentOfferer(); + + if (aSdpType == dom::RTCSdpType::Answer) { + std::set<std::pair<std::string, std::string>> iceCredentials = + mJsepSession->GetLocalIceCredentials(); + std::vector<std::pair<std::string, std::string>> + iceCredentialsNotReplaced; + std::set_intersection(mLocalIceCredentialsToReplace.begin(), + mLocalIceCredentialsToReplace.end(), + iceCredentials.begin(), iceCredentials.end(), + std::back_inserter(iceCredentialsNotReplaced)); + + if (iceCredentialsNotReplaced.empty()) { + mLocalIceCredentialsToReplace.clear(); + } + } + + if (newSignalingState == RTCSignalingState::Stable) { + mNegotiationNeeded = false; + UpdateNegotiationNeeded(); + } + + // Spec does not actually tell us to do this, but that is probably a + // spec bug. + UpdateConnectionState(); + + JSErrorResult jrv; + if (newSignalingState != mSignalingState) { + mSignalingState = newSignalingState; + mPCObserver->OnStateChange(PCObserverStateType::SignalingState, jrv); + } + + if (aRemote) { + dom::RTCRtpReceiver::StreamAssociationChanges changes; + for (const auto& transceiver : mTransceivers) { + transceiver->Receiver()->UpdateStreams(&changes); + } + + for (const auto& receiver : changes.mReceiversToMute) { + // This sets the muted state for the recv track and all its clones. + receiver->SetTrackMuteFromRemoteSdp(); + } + + for (const auto& association : changes.mStreamAssociationsRemoved) { + RefPtr<DOMMediaStream> stream = + GetReceiveStream(association.mStreamId); + if (stream && stream->HasTrack(*association.mTrack)) { + stream->RemoveTrackInternal(association.mTrack); + } + } + + // TODO(Bug 1241291): For legacy event, remove eventually + std::vector<RefPtr<DOMMediaStream>> newStreams; + + for (const auto& association : changes.mStreamAssociationsAdded) { + RefPtr<DOMMediaStream> stream = + GetReceiveStream(association.mStreamId); + if (!stream) { + stream = CreateReceiveStream(association.mStreamId); + newStreams.push_back(stream); + } + + if (!stream->HasTrack(*association.mTrack)) { + stream->AddTrackInternal(association.mTrack); + } + } + + // Make sure to wait until after we've calculated track changes before + // doing this. + for (size_t i = 0; i < mTransceivers.Length();) { + auto& transceiver = mTransceivers[i]; + if (transceiver->ShouldRemove()) { + mTransceivers[i]->Close(); + mTransceivers[i]->SetRemovedFromPc(); + mTransceivers.RemoveElementAt(i); + } else { + ++i; + } + } + + 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); + } + } + aP->MaybeResolveWithUndefined(); + })); +} + +void PeerConnectionImpl::OnSetDescriptionError() { + mUncommittedJsepSession = nullptr; +} + +RTCSignalingState PeerConnectionImpl::GetSignalingState() const { + switch (mJsepSession->GetState()) { + case kJsepStateStable: + return RTCSignalingState::Stable; + break; + case kJsepStateHaveLocalOffer: + return RTCSignalingState::Have_local_offer; + break; + case kJsepStateHaveRemoteOffer: + return RTCSignalingState::Have_remote_offer; + break; + case kJsepStateHaveLocalPranswer: + return RTCSignalingState::Have_local_pranswer; + break; + case kJsepStateHaveRemotePranswer: + return RTCSignalingState::Have_remote_pranswer; + break; + case kJsepStateClosed: + return RTCSignalingState::Closed; + break; + } + MOZ_CRASH("Invalid JSEP state"); +} + +bool PeerConnectionImpl::IsClosed() const { + return mSignalingState == RTCSignalingState::Closed; +} + +PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) + : impl_(nullptr) { + if (PeerConnectionCtx::isActive()) { + impl_ = PeerConnectionCtx::GetInstance()->GetPeerConnection(handle); + } +} + +const RefPtr<MediaTransportHandler> PeerConnectionImpl::GetTransportHandler() + const { + return mTransportHandler; +} + +const std::string& PeerConnectionImpl::GetHandle() { return mHandle; } + +const std::string& PeerConnectionImpl::GetName() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mName; +} + +void PeerConnectionImpl::CandidateReady(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered"); + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) { + CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str()); + STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked"); + return; + } + + // One of the very few places we still use level; required by the JSEP API + uint16_t level = 0; + std::string mid; + bool skipped = false; + + if (mUncommittedJsepSession) { + // An sLD or sRD is in progress, and while that is the case, we need to add + // the candidate to both the current JSEP engine, and the uncommitted JSEP + // engine. We ignore errors because the spec says to only take into account + // the current/pending local descriptions when determining whether to + // surface the candidate to content, which does not take into account any + // in-progress sRD/sLD. + Unused << mUncommittedJsepSession->AddLocalIceCandidate( + candidate, transportId, ufrag, &level, &mid, &skipped); + } + + nsresult res = mJsepSession->AddLocalIceCandidate( + candidate, transportId, ufrag, &level, &mid, &skipped); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + + STAMP_TIMECARD(mTimeCard, "Local Ice Candidate invalid"); + CSFLogError(LOGTAG, + "Failed to incorporate local candidate into SDP:" + " res = %u, candidate = %s, transport-id = %s," + " error = %s", + static_cast<unsigned>(res), candidate.c_str(), + transportId.c_str(), errorString.c_str()); + return; + } + + if (skipped) { + STAMP_TIMECARD(mTimeCard, "Local Ice Candidate skipped"); + CSFLogInfo(LOGTAG, + "Skipped adding local candidate %s (transport-id %s) " + "to SDP, this typically happens because the m-section " + "is bundled, which means it doesn't make sense for it " + "to have its own transport-related attributes.", + candidate.c_str(), transportId.c_str()); + return; + } + + mPendingLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPending); + mCurrentLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionCurrent); + CSFLogInfo(LOGTAG, "Passing local candidate to content: %s", + candidate.c_str()); + SendLocalIceCandidateToContent(level, mid, candidate, ufrag); +} + +void PeerConnectionImpl::SendLocalIceCandidateToContent( + uint16_t level, const std::string& mid, const std::string& candidate, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content"); + JSErrorResult rv; + mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), + ObString(candidate.c_str()), + ObString(ufrag.c_str()), rv); +} + +void PeerConnectionImpl::IceConnectionStateChange( + dom::RTCIceConnectionState domState) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(LOGTAG, "%s: %d -> %d", __FUNCTION__, + static_cast<int>(mIceConnectionState), + 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; + + // 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); + UpdateConnectionState(); +} + +void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) { + MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty()); + + if (mCanRegisterMDNSHostnamesDirectly) { + auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress); + + // We'll see the address twice if we're generating both UDP and TCP + // candidates. + if (itor == mRegisteredMDNSHostnames.end()) { + mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress); + mStunAddrsRequest->SendRegisterMDNSHostname( + nsCString(aCandidateInfo.mMDNSAddress.c_str()), + nsCString(aCandidateInfo.mActualAddress.c_str())); + } + } else { + mMDNSHostnamesToRegister.emplace(aCandidateInfo.mMDNSAddress, + aCandidateInfo.mActualAddress); + } + } + + if (!aCandidateInfo.mDefaultHostRtp.empty()) { + UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp, + aCandidateInfo.mDefaultPortRtp, + aCandidateInfo.mDefaultHostRtcp, + aCandidateInfo.mDefaultPortRtcp, aTransportId); + } + CandidateReady(aCandidateInfo.mCandidate, aTransportId, + aCandidateInfo.mUfrag); +} + +void PeerConnectionImpl::IceGatheringStateChange( + 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); + if (mUncommittedJsepSession) { + mUncommittedJsepSession->UpdateDefaultCandidate( + defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, + transportId); + } +} + +static UniquePtr<dom::RTCStatsCollection> GetDataChannelStats_s( + const RefPtr<DataChannelConnection>& aDataConnection, + const DOMHighResTimeStamp aTimestamp) { + UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection); + if (aDataConnection) { + aDataConnection->AppendStatsToReport(report, aTimestamp); + } + return report; +} + +RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetDataChannelStats( + const RefPtr<DataChannelConnection>& aDataChannelConnection, + const DOMHighResTimeStamp aTimestamp) { + // Gather stats from DataChannels + return InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [aDataChannelConnection, aTimestamp]() { + return dom::RTCStatsPromise::CreateAndResolve( + GetDataChannelStats_s(aDataChannelConnection, aTimestamp), + __func__); + }); +} + +void PeerConnectionImpl::CollectConduitTelemetryData() { + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray<RefPtr<VideoSessionConduit>> conduits; + for (const auto& transceiver : mTransceivers) { + if (RefPtr<MediaSessionConduit> conduit = transceiver->GetConduit()) { + conduit->AsVideoSessionConduit().apply( + [&](const auto& aVideo) { conduits.AppendElement(aVideo); }); + } + } + + if (!conduits.IsEmpty() && mCall) { + mCall->mCallThread->Dispatch( + NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)] { + for (const auto& conduit : conduits) { + conduit->CollectTelemetryData(); + } + })); + } +} + +nsTArray<dom::RTCCodecStats> PeerConnectionImpl::GetCodecStats( + DOMHighResTimeStamp aNow) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray<dom::RTCCodecStats> result; + + struct CodecComparator { + bool operator()(const JsepCodecDescription* aA, + const JsepCodecDescription* aB) const { + return aA->StatsId() < aB->StatsId(); + } + }; + + // transportId -> codec; per direction (whether the codecType + // shall be "encode", "decode" or absent (if a codec exists in both maps for a + // transport)). These do the bookkeeping to ensure codec stats get coalesced + // to transport level. + std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>> + sendCodecMap; + std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>> + recvCodecMap; + + // Find all JsepCodecDescription instances we want to turn into codec stats. + for (const auto& transceiver : mTransceivers) { + // TODO: Grab these from the JSEP transceivers instead + auto sendCodecs = transceiver->GetNegotiatedSendCodecs(); + auto recvCodecs = transceiver->GetNegotiatedRecvCodecs(); + + const std::string transportId = transceiver->GetTransportId(); + // This ensures both codec maps have the same size. + auto& sendMap = sendCodecMap[transportId]; + auto& recvMap = recvCodecMap[transportId]; + + sendCodecs.apply([&](const auto& aCodecs) { + for (const auto& codec : aCodecs) { + sendMap.insert(codec.get()); + } + }); + recvCodecs.apply([&](const auto& aCodecs) { + for (const auto& codec : aCodecs) { + recvMap.insert(codec.get()); + } + }); + } + + auto createCodecStat = [&](const JsepCodecDescription* aCodec, + const nsString& aTransportId, + Maybe<RTCCodecType> aCodecType) { + uint16_t pt; + { + DebugOnly<bool> rv = aCodec->GetPtAsInt(&pt); + MOZ_ASSERT(rv); + } + nsString mimeType; + mimeType.AppendPrintf( + "%s/%s", aCodec->Type() == SdpMediaSection::kVideo ? "video" : "audio", + aCodec->mName.c_str()); + nsString id = aTransportId; + id.Append(u"_"); + id.Append(aCodec->StatsId()); + + dom::RTCCodecStats codec; + codec.mId.Construct(std::move(id)); + codec.mTimestamp.Construct(aNow); + codec.mType.Construct(RTCStatsType::Codec); + codec.mPayloadType = pt; + if (aCodecType) { + codec.mCodecType.Construct(*aCodecType); + } + codec.mTransportId = aTransportId; + codec.mMimeType = std::move(mimeType); + codec.mClockRate.Construct(aCodec->mClock); + if (aCodec->Type() == SdpMediaSection::MediaType::kAudio) { + codec.mChannels.Construct(aCodec->mChannels); + } + if (aCodec->mSdpFmtpLine) { + codec.mSdpFmtpLine.Construct( + NS_ConvertUTF8toUTF16(aCodec->mSdpFmtpLine->c_str())); + } + + result.AppendElement(std::move(codec)); + }; + + // Create codec stats for the gathered codec descriptions, sorted primarily + // by transportId, secondarily by payload type (from StatsId()). + for (const auto& [transportId, sendCodecs] : sendCodecMap) { + const auto& recvCodecs = recvCodecMap[transportId]; + const nsString tid = NS_ConvertASCIItoUTF16(transportId); + AutoTArray<JsepCodecDescription*, 16> bidirectionalCodecs; + AutoTArray<JsepCodecDescription*, 16> unidirectionalCodecs; + std::set_intersection(sendCodecs.cbegin(), sendCodecs.cend(), + recvCodecs.cbegin(), recvCodecs.cend(), + MakeBackInserter(bidirectionalCodecs), + CodecComparator()); + std::set_symmetric_difference(sendCodecs.cbegin(), sendCodecs.cend(), + recvCodecs.cbegin(), recvCodecs.cend(), + MakeBackInserter(unidirectionalCodecs), + CodecComparator()); + for (const auto* codec : bidirectionalCodecs) { + createCodecStat(codec, tid, Nothing()); + } + for (const auto* codec : unidirectionalCodecs) { + createCodecStat( + codec, tid, + Some(codec->mDirection == sdp::kSend ? RTCCodecType::Encode + : RTCCodecType::Decode)); + } + } + + return result; +} + +RefPtr<dom::RTCStatsReportPromise> PeerConnectionImpl::GetStats( + dom::MediaStreamTrack* aSelector, bool aInternalStats) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mFinalStatsQuery) { + // This case should be _extremely_ rare; this will basically only happen + // when WebrtcGlobalInformation tries to get our stats while we are tearing + // down. + return mFinalStatsQuery->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr<PeerConnectionImpl>(this)]() { + UniquePtr<dom::RTCStatsReportInternal> finalStats = + MakeUnique<dom::RTCStatsReportInternal>(); + // Might not be set if this encountered some error. + if (mFinalStats) { + *finalStats = *mFinalStats; + } + return RTCStatsReportPromise::CreateAndResolve(std::move(finalStats), + __func__); + }); + } + + nsTArray<RefPtr<dom::RTCStatsPromise>> promises; + DOMHighResTimeStamp now = mTimestampMaker.GetNow().ToDom(); + + nsTArray<dom::RTCCodecStats> codecStats = GetCodecStats(now); + std::set<std::string> transportIds; + + if (!aSelector) { + // There might not be any senders/receivers if we're DataChannel only, so we + // don't handle the null selector case in the loop below. + transportIds.insert(""); + } + + nsTArray< + std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>> + transceiverStatsPromises; + for (const auto& transceiver : mTransceivers) { + const bool sendSelected = transceiver->Sender()->HasTrack(aSelector); + const bool recvSelected = transceiver->Receiver()->HasTrack(aSelector); + if (!sendSelected && !recvSelected) { + continue; + } + + if (aSelector) { + transportIds.insert(transceiver->GetTransportId()); + } + + nsTArray<RefPtr<RTCStatsPromise>> rtpStreamPromises; + // Get all rtp stream stats for the given selector. Then filter away any + // codec stat not related to the selector, and assign codec ids to the + // stream stats. + // Skips the ICE stats; we do our own queries based on |transportIds| to + // avoid duplicates + if (sendSelected) { + rtpStreamPromises.AppendElements( + transceiver->Sender()->GetStatsInternal(true)); + } + if (recvSelected) { + rtpStreamPromises.AppendElements( + transceiver->Receiver()->GetStatsInternal(true)); + } + transceiverStatsPromises.AppendElement( + std::make_tuple(transceiver.get(), + RTCStatsPromise::All(GetMainThreadSerialEventTarget(), + rtpStreamPromises))); + } + + promises.AppendElement(RTCRtpTransceiver::ApplyCodecStats( + std::move(codecStats), std::move(transceiverStatsPromises))); + + for (const auto& transportId : transportIds) { + promises.AppendElement(mTransportHandler->GetIceStats(transportId, now)); + } + + promises.AppendElement(GetDataChannelStats(mDataConnection, now)); + + auto pcStatsCollection = MakeUnique<dom::RTCStatsCollection>(); + RTCPeerConnectionStats pcStats; + pcStats.mTimestamp.Construct(now); + pcStats.mType.Construct(RTCStatsType::Peer_connection); + pcStats.mId.Construct(NS_ConvertUTF8toUTF16(mHandle.c_str())); + pcStats.mDataChannelsOpened.Construct(mDataChannelsOpened); + pcStats.mDataChannelsClosed.Construct(mDataChannelsClosed); + if (!pcStatsCollection->mPeerConnectionStats.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + promises.AppendElement(RTCStatsPromise::CreateAndResolve( + std::move(pcStatsCollection), __func__)); + + // This is what we're going to return; all the stuff in |promises| will be + // accumulated here. + UniquePtr<dom::RTCStatsReportInternal> report( + new dom::RTCStatsReportInternal); + report->mPcid = NS_ConvertASCIItoUTF16(mName.c_str()); + if (mWindow && mWindow->GetBrowsingContext()) { + report->mBrowserId = mWindow->GetBrowsingContext()->BrowserId(); + } + report->mConfiguration.Construct(mJsConfiguration); + // TODO(bug 1589416): We need to do better here. + if (!mIceStartTime.IsNull()) { + report->mCallDurationMs.Construct( + (TimeStamp::Now() - mIceStartTime).ToMilliseconds()); + } + report->mIceRestarts = mIceRestartCount; + report->mIceRollbacks = mIceRollbackCount; + report->mClosed = false; + report->mTimestamp = now; + + if (aInternalStats && mJsepSession) { + for (const auto& candidate : mRawTrickledCandidates) { + if (!report->mRawRemoteCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.c_str()), fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + + if (mJsepSession) { + // TODO we probably should report Current and Pending SDPs here + // separately. Plus the raw SDP we got from JS (mLocalRequestedSDP). + // And if it's the offer or answer would also be nice. + std::string localDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent); + std::string remoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent); + if (!report->mSdpHistory.AppendElements(mSdpHistory, fallible)) { + mozalloc_handle_oom(0); + } + if (mJsepSession->IsPendingOfferer().isSome()) { + report->mOfferer.Construct(*mJsepSession->IsPendingOfferer()); + } else if (mJsepSession->IsCurrentOfferer().isSome()) { + report->mOfferer.Construct(*mJsepSession->IsCurrentOfferer()); + } else { + // Silly. + report->mOfferer.Construct(false); + } + } + } + + return dom::RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [report = std::move(report), idGen = mIdGenerator]( + nsTArray<UniquePtr<dom::RTCStatsCollection>> aStats) mutable { + idGen->RewriteIds(std::move(aStats), report.get()); + return dom::RTCStatsReportPromise::CreateAndResolve( + std::move(report), __func__); + }, + [](nsresult rv) { + return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__); + }); +} + +void PeerConnectionImpl::RecordIceRestartStatistics(JsepSdpType type) { + switch (type) { + case mozilla::kJsepSdpOffer: + case mozilla::kJsepSdpPranswer: + break; + case mozilla::kJsepSdpAnswer: + ++mIceRestartCount; + break; + case mozilla::kJsepSdpRollback: + ++mIceRollbackCount; + break; + } +} + +void PeerConnectionImpl::StoreConfigurationForAboutWebrtc( + const dom::RTCConfiguration& aConfig) { + // This will only be called once, when the PeerConnection is initially + // configured, at least until setConfiguration is implemented + // see https://bugzilla.mozilla.org/show_bug.cgi?id=1253706 + // @TODO bug 1739451 call this from setConfiguration + mJsConfiguration.mIceServers.Clear(); + for (const auto& server : aConfig.mIceServers) { + RTCIceServerInternal internal; + internal.mCredentialProvided = server.mCredential.WasPassed(); + internal.mUserNameProvided = server.mUsername.WasPassed(); + if (server.mUrl.WasPassed()) { + if (!internal.mUrls.AppendElement(server.mUrl.Value(), fallible)) { + mozalloc_handle_oom(0); + } + } + if (server.mUrls.WasPassed()) { + for (const auto& url : server.mUrls.Value().GetAsStringSequence()) { + if (!internal.mUrls.AppendElement(url, fallible)) { + mozalloc_handle_oom(0); + } + } + } + if (!mJsConfiguration.mIceServers.AppendElement(internal, fallible)) { + mozalloc_handle_oom(0); + } + } + mJsConfiguration.mSdpSemantics.Reset(); + if (aConfig.mSdpSemantics.WasPassed()) { + mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value()); + } + + mJsConfiguration.mIceTransportPolicy.Reset(); + mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy); + mJsConfiguration.mBundlePolicy.Reset(); + mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy); + mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty(); + mJsConfiguration.mCertificatesProvided = !aConfig.mCertificates.Length(); +} + +dom::Sequence<dom::RTCSdpParsingErrorInternal> +PeerConnectionImpl::GetLastSdpParsingErrors() const { + const auto& sdpErrors = mJsepSession->GetLastSdpParsingErrors(); + dom::Sequence<dom::RTCSdpParsingErrorInternal> domErrors; + if (!domErrors.SetCapacity(domErrors.Length(), fallible)) { + mozalloc_handle_oom(0); + } + for (const auto& error : sdpErrors) { + mozilla::dom::RTCSdpParsingErrorInternal internal; + internal.mLineNumber = error.first; + if (!AppendASCIItoUTF16(MakeStringSpan(error.second.c_str()), + internal.mError, fallible)) { + mozalloc_handle_oom(0); + } + if (!domErrors.AppendElement(std::move(internal), fallible)) { + mozalloc_handle_oom(0); + } + } + return domErrors; +} + +// Telemetry for when calls start +void PeerConnectionImpl::StartCallTelem() { + if (mCallTelemStarted) { + return; + } + MOZ_RELEASE_ASSERT(mWindow); + uint64_t windowId = mWindow->WindowID(); + auto found = sCallDurationTimers.find(windowId); + if (found == sCallDurationTimers.end()) { + found = + sCallDurationTimers.emplace(windowId, PeerConnectionAutoTimer()).first; + } + found->second.RegisterConnection(); + mCallTelemStarted = true; + + // Increment session call counter + // If we want to track Loop calls independently here, we need two + // histograms. + // + // NOTE: As of bug 1654248 landing we are no longer counting renegotiations + // as separate calls. Expect numbers to drop compared to + // WEBRTC_CALL_COUNT_2. + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_3, 1); +} + +void PeerConnectionImpl::StunAddrsHandler::OnMDNSQueryComplete( + const nsCString& hostname, const Maybe<nsCString>& address) { + MOZ_ASSERT(NS_IsMainThread()); + PeerConnectionWrapper pcw(mPcHandle); + if (!pcw.impl()) { + return; + } + auto itor = pcw.impl()->mQueriedMDNSHostnames.find(hostname.BeginReading()); + if (itor != pcw.impl()->mQueriedMDNSHostnames.end()) { + if (address) { + for (auto& cand : itor->second) { + // Replace obfuscated address with actual address + std::string obfuscatedAddr = cand.mTokenizedCandidate[4]; + cand.mTokenizedCandidate[4] = address->BeginReading(); + std::ostringstream o; + for (size_t i = 0; i < cand.mTokenizedCandidate.size(); ++i) { + o << cand.mTokenizedCandidate[i]; + if (i + 1 != cand.mTokenizedCandidate.size()) { + o << " "; + } + } + std::string mungedCandidate = o.str(); + pcw.impl()->StampTimecard("Done looking up mDNS name"); + pcw.impl()->mTransportHandler->AddIceCandidate( + cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr); + } + } else { + pcw.impl()->StampTimecard("Failed looking up mDNS name"); + } + pcw.impl()->mQueriedMDNSHostnames.erase(itor); + } +} + +void PeerConnectionImpl::StunAddrsHandler::OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) { + CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__, + (int)addrs.Length()); + PeerConnectionWrapper pcw(mPcHandle); + if (!pcw.impl()) { + return; + } + pcw.impl()->mStunAddrs = addrs.Clone(); + pcw.impl()->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + pcw.impl()->FlushIceCtxOperationQueueIfReady(); + // If parent process returns 0 STUN addresses, change ICE connection + // state to failed. + if (!pcw.impl()->mStunAddrs.Length()) { + pcw.impl()->IceConnectionStateChange(dom::RTCIceConnectionState::Failed); + } +} + +void PeerConnectionImpl::InitLocalAddrs() { + if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) { + return; + } + if (mStunAddrsRequest) { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING; + mStunAddrsRequest->SendGetStunAddrs(); + } else { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + } +} + +bool PeerConnectionImpl::ShouldForceProxy() const { + if (Preferences::GetBool("media.peerconnection.ice.proxy_only", false)) { + return true; + } + + bool isPBM = false; + // This complicated null check is being extra conservative to avoid + // introducing crashes. It may not be needed. + if (mWindow && mWindow->GetExtantDoc() && + mWindow->GetExtantDoc()->GetPrincipal() && + mWindow->GetExtantDoc() + ->GetPrincipal() + ->OriginAttributesRef() + .mPrivateBrowsingId > 0) { + isPBM = true; + } + + if (isPBM && Preferences::GetBool( + "media.peerconnection.ice.proxy_only_if_pbmode", false)) { + return true; + } + + if (!Preferences::GetBool( + "media.peerconnection.ice.proxy_only_if_behind_proxy", false)) { + return false; + } + + // Ok, we're supposed to be proxy_only, but only if a proxy is configured. + // Let's just see if the document was loaded via a proxy. + + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel(); + if (!httpChannelInternal) { + return false; + } + + bool proxyUsed = false; + Unused << httpChannelInternal->GetIsProxyUsed(&proxyUsed); + return proxyUsed; +} + +void PeerConnectionImpl::EnsureTransports(const JsepSession& aSession) { + mJsepSession->ForEachTransceiver([this, + self = RefPtr<PeerConnectionImpl>(this)]( + const JsepTransceiver& transceiver) { + if (transceiver.HasOwnTransport()) { + mTransportHandler->EnsureProvisionalTransport( + transceiver.mTransport.mTransportId, + transceiver.mTransport.mLocalUfrag, transceiver.mTransport.mLocalPwd, + transceiver.mTransport.mComponents); + } + }); + + GatherIfReady(); +} + +void PeerConnectionImpl::UpdateRTCDtlsTransports(bool aMarkAsStable) { + mJsepSession->ForEachTransceiver( + [this, self = RefPtr<PeerConnectionImpl>(this)]( + const JsepTransceiver& jsepTransceiver) { + std::string transportId = jsepTransceiver.mTransport.mTransportId; + if (transportId.empty()) { + return; + } + if (!mTransportIdToRTCDtlsTransport.count(transportId)) { + mTransportIdToRTCDtlsTransport.emplace( + transportId, new RTCDtlsTransport(GetParentObject())); + } + }); + + for (auto& transceiver : mTransceivers) { + std::string transportId = transceiver->GetTransportId(); + if (transportId.empty()) { + continue; + } + if (mTransportIdToRTCDtlsTransport.count(transportId)) { + transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId], + aMarkAsStable); + } + } + + // Spec says we only update the RTCSctpTransport when negotiation completes +} + +void PeerConnectionImpl::RollbackRTCDtlsTransports() { + for (auto& transceiver : mTransceivers) { + transceiver->RollbackToStableDtlsTransport(); + } +} + +void PeerConnectionImpl::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 PeerConnectionImpl::UpdateTransports(const JsepSession& aSession, + const bool forceIceTcp) { + std::set<std::string> finalTransports; + Maybe<std::string> sctpTransport; + mJsepSession->ForEachTransceiver( + [&, this, self = RefPtr<PeerConnectionImpl>(this)]( + const JsepTransceiver& transceiver) { + if (transceiver.GetMediaType() == SdpMediaSection::kApplication && + transceiver.HasTransport()) { + sctpTransport = Some(transceiver.mTransport.mTransportId); + } + + 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(); + } + + if (sctpTransport.isSome()) { + auto it = mTransportIdToRTCDtlsTransport.find(*sctpTransport); + if (it == mTransportIdToRTCDtlsTransport.end()) { + // What? + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + if (!mDataConnection) { + // What? + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + RefPtr<RTCDtlsTransport> dtlsTransport = it->second; + // Why on earth does the spec use a floating point for this? + double maxMessageSize = + static_cast<double>(mDataConnection->GetMaxMessageSize()); + Nullable<uint16_t> maxChannels; + + if (!mSctpTransport) { + mSctpTransport = new RTCSctpTransport(GetParentObject(), *dtlsTransport, + maxMessageSize, maxChannels); + } else { + mSctpTransport->SetTransport(*dtlsTransport); + mSctpTransport->SetMaxMessageSize(maxMessageSize); + mSctpTransport->SetMaxChannels(maxChannels); + } + } else { + mSctpTransport = nullptr; + } + + return NS_OK; +} + +void PeerConnectionImpl::UpdateTransport(const JsepTransceiver& aTransceiver, + bool aForceIceTcp) { + std::string ufrag; + std::string pwd; + std::vector<std::string> candidates; + size_t components = 0; + + const JsepTransport& transport = aTransceiver.mTransport; + unsigned level = aTransceiver.GetLevel(); + + CSFLogDebug(LOGTAG, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u", + mHandle.c_str(), (unsigned)level, + (unsigned)transport.mComponents); + + ufrag = transport.mIce->GetUfrag(); + pwd = transport.mIce->GetPassword(); + candidates = transport.mIce->GetCandidates(); + components = transport.mComponents; + if (aForceIceTcp) { + candidates.erase( + std::remove_if(candidates.begin(), candidates.end(), + [](const std::string& s) { + return s.find(" UDP ") != std::string::npos || + s.find(" udp ") != std::string::npos; + }), + candidates.end()); + } + + nsTArray<uint8_t> keyDer; + nsTArray<uint8_t> certDer; + nsresult rv = Identity()->Serialize(&keyDer, &certDer); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Failed to serialize DTLS identity: %d", + __FUNCTION__, (int)rv); + return; + } + + DtlsDigestList digests; + for (const auto& fingerprint : + transport.mDtls->GetFingerprints().mFingerprints) { + 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, Identity()->auth_type(), + transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests, + PrivacyRequested()); + + for (auto& candidate : candidates) { + AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag); + } +} + +nsresult PeerConnectionImpl::UpdateMediaPipelines() { + for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) { + transceiver->ResetSync(); + } + + for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) { + if (!transceiver->IsVideo()) { + nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers); + if (NS_FAILED(rv)) { + return rv; + } + } + + transceiver->UpdatePrincipalPrivacy(PrivacyRequested() + ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate); + + nsresult rv = transceiver->UpdateConduit(); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +void PeerConnectionImpl::StartIceChecks(const JsepSession& aSession) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCanRegisterMDNSHostnamesDirectly) { + for (auto& pair : mMDNSHostnamesToRegister) { + mRegisteredMDNSHostnames.insert(pair.first); + mStunAddrsRequest->SendRegisterMDNSHostname( + nsCString(pair.first.c_str()), nsCString(pair.second.c_str())); + } + mMDNSHostnamesToRegister.clear(); + mCanRegisterMDNSHostnamesDirectly = true; + } + + std::vector<std::string> attributes; + if (aSession.RemoteIsIceLite()) { + attributes.push_back("ice-lite"); + } + + if (!aSession.GetIceOptions().empty()) { + attributes.push_back("ice-options:"); + for (const auto& option : aSession.GetIceOptions()) { + attributes.back() += option + ' '; + } + } + + nsCOMPtr<nsIRunnable> runnable( + WrapRunnable(mTransportHandler, &MediaTransportHandler::StartIceChecks, + aSession.IsIceControlling(), attributes)); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const { + MOZ_ASSERT(NS_IsMainThread()); + + uint64_t winId = mWindow->WindowID(); + + bool default_address_only = Preferences::GetBool( + "media.peerconnection.ice.default_address_only", false); + default_address_only |= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + return default_address_only; +} + +bool PeerConnectionImpl::GetPrefObfuscateHostAddresses() const { + MOZ_ASSERT(NS_IsMainThread()); + + uint64_t winId = mWindow->WindowID(); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + obfuscate_host_addresses &= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + obfuscate_host_addresses &= !PeerConnectionImpl::HostnameInPref( + "media.peerconnection.ice.obfuscate_host_addresses.blocklist", mHostname); + obfuscate_host_addresses &= XRE_IsContentProcess(); + + return obfuscate_host_addresses; +} + +PeerConnectionImpl::SignalHandler::SignalHandler(PeerConnectionImpl* aPc, + MediaTransportHandler* aSource) + : mHandle(aPc->GetHandle()), + mSource(aSource), + mSTSThread(aPc->GetSTSThread()) { + ConnectSignals(); +} + +PeerConnectionImpl::SignalHandler::~SignalHandler() { + ASSERT_ON_THREAD(mSTSThread); +} + +void PeerConnectionImpl::SignalHandler::ConnectSignals() { + mSource->SignalGatheringStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s); + mSource->SignalConnectionStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s); + mSource->SignalCandidate.connect( + this, &PeerConnectionImpl::SignalHandler::OnCandidateFound_s); + mSource->SignalAlpnNegotiated.connect( + this, &PeerConnectionImpl::SignalHandler::AlpnNegotiated_s); + mSource->SignalStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s); + mSource->SignalRtcpStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s); +} + +void PeerConnectionImpl::AddIceCandidate(const std::string& aCandidate, + const std::string& aTransportId, + const std::string& aUfrag) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aTransportId.empty()); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + + if (obfuscate_host_addresses && !RelayOnly()) { + std::vector<std::string> tokens; + TokenizeCandidate(aCandidate, tokens); + + if (tokens.size() > 4) { + std::string addr = tokens[4]; + + // Check for address ending with .local + size_t nPeriods = std::count(addr.begin(), addr.end(), '.'); + size_t dotLocalLength = 6; // length of ".local" + + if (nPeriods == 1 && + addr.rfind(".local") + dotLocalLength == addr.length()) { + if (mStunAddrsRequest) { + PendingIceCandidate cand; + cand.mTokenizedCandidate = std::move(tokens); + cand.mTransportId = aTransportId; + cand.mUfrag = aUfrag; + mQueriedMDNSHostnames[addr].push_back(cand); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "PeerConnectionImpl::SendQueryMDNSHostname", + [self = RefPtr<PeerConnectionImpl>(this), addr]() mutable { + if (self->mStunAddrsRequest) { + self->StampTimecard("Look up mDNS name"); + self->mStunAddrsRequest->SendQueryMDNSHostname( + nsCString(nsAutoCString(addr.c_str()))); + } + NS_ReleaseOnMainThread( + "PeerConnectionImpl::SendQueryMDNSHostname", self.forget()); + })); + } + // TODO: Bug 1535690, we don't want to tell the ICE context that remote + // trickle is done if we are waiting to resolve a mDNS candidate. + return; + } + } + } + + mTransportHandler->AddIceCandidate(aTransportId, aCandidate, aUfrag, ""); +} + +void PeerConnectionImpl::UpdateNetworkState(bool online) { + if (mTransportHandler) { + mTransportHandler->UpdateNetworkState(online); + } +} + +void PeerConnectionImpl::FlushIceCtxOperationQueueIfReady() { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsIceCtxReady()) { + for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) { + queuedIceCtxOperation->Run(); + } + mQueuedIceCtxOperations.clear(); + } +} + +void PeerConnectionImpl::PerformOrEnqueueIceCtxOperation( + nsIRunnable* runnable) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsIceCtxReady()) { + runnable->Run(); + } else { + mQueuedIceCtxOperations.push_back(runnable); + } +} + +void PeerConnectionImpl::GatherIfReady() { + MOZ_ASSERT(NS_IsMainThread()); + + // Init local addrs here so that if we re-gather after an ICE restart + // resulting from changing WiFi networks, we get new local addrs. + // Otherwise, we would reuse the addrs from the original WiFi network + // and the ICE restart will fail. + if (!mStunAddrs.Length()) { + InitLocalAddrs(); + } + + // If we had previously queued gathering or ICE start, unqueue them + mQueuedIceCtxOperations.clear(); + nsCOMPtr<nsIRunnable> runnable(WrapRunnable( + RefPtr<PeerConnectionImpl>(this), &PeerConnectionImpl::EnsureIceGathering, + GetPrefDefaultAddressOnly(), GetPrefObfuscateHostAddresses())); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +already_AddRefed<nsIHttpChannelInternal> PeerConnectionImpl::GetChannel() + const { + Document* doc = mWindow->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + NS_WARNING("Unable to get document from window"); + return nullptr; + } + + if (!doc->GetDocumentURI()->SchemeIs("file")) { + nsIChannel* channel = doc->GetChannel(); + if (!channel) { + NS_WARNING("Unable to get channel from document"); + return nullptr; + } + + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(channel); + if (NS_WARN_IF(!httpChannelInternal)) { + CSFLogInfo(LOGTAG, "%s: Document does not have an HTTP channel", + __FUNCTION__); + return nullptr; + } + return httpChannelInternal.forget(); + } + return nullptr; +} + +nsresult PeerConnectionImpl::SetTargetForDefaultLocalAddressLookup() { + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel(); + if (!httpChannelInternal) { + return NS_OK; + } + + nsCString remoteIp; + nsresult rv = httpChannelInternal->GetRemoteAddress(remoteIp); + if (NS_FAILED(rv) || remoteIp.IsEmpty()) { + CSFLogError(LOGTAG, "%s: Failed to get remote IP address: %d", __FUNCTION__, + (int)rv); + return rv; + } + + int32_t remotePort; + rv = httpChannelInternal->GetRemotePort(&remotePort); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Failed to get remote port number: %d", + __FUNCTION__, (int)rv); + return rv; + } + + mTransportHandler->SetTargetForDefaultLocalAddressLookup(remoteIp.get(), + remotePort); + + return NS_OK; +} + +void PeerConnectionImpl::EnsureIceGathering(bool aDefaultRouteOnly, + bool aObfuscateHostAddresses) { + if (!mTargetForDefaultLocalAddressLookupIsSet) { + nsresult rv = SetTargetForDefaultLocalAddressLookup(); + if (NS_FAILED(rv)) { + NS_WARNING("Unable to set target for default local address lookup"); + } + mTargetForDefaultLocalAddressLookupIsSet = true; + } + + // Make sure we don't call StartIceGathering if we're in e10s mode + // and we received no STUN addresses from the parent process. In the + // absence of previously provided STUN addresses, StartIceGathering will + // attempt to gather them (as in non-e10s mode), and this will cause a + // sandboxing exception in e10s mode. + if (!mStunAddrs.Length() && XRE_IsContentProcess()) { + CSFLogInfo(LOGTAG, "%s: No STUN addresses returned from parent process", + __FUNCTION__); + return; + } + + mTransportHandler->StartIceGathering(aDefaultRouteOnly, + aObfuscateHostAddresses, mStunAddrs); +} + +already_AddRefed<dom::RTCRtpTransceiver> PeerConnectionImpl::CreateTransceiver( + const std::string& aId, bool aIsVideo, const RTCRtpTransceiverInit& aInit, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) { + PeerConnectionCtx* ctx = PeerConnectionCtx::GetInstance(); + if (!mCall) { + mCall = WebrtcCallWrapper::Create( + GetTimestampMaker(), + media::ShutdownBlockingTicket::Create( + u"WebrtcCallWrapper shutdown blocker"_ns, + NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__), + ctx->GetSharedWebrtcState()); + } + + if (aAddTrackMagic) { + mJsepSession->ApplyToTransceiver(aId, [](JsepTransceiver& aTransceiver) { + aTransceiver.SetAddTrackMagic(); + }); + } + + RefPtr<RTCRtpTransceiver> transceiver = new RTCRtpTransceiver( + mWindow, PrivacyRequested(), this, mTransportHandler, mJsepSession.get(), + aId, aIsVideo, mSTSThread.get(), aSendTrack, mCall.get(), mIdGenerator); + + transceiver->Init(aInit, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (aSendTrack) { + // implement checking for peerIdentity (where failure == black/silence) + Document* doc = mWindow->GetExtantDoc(); + if (doc) { + transceiver->Sender()->GetPipeline()->UpdateSinkIdentity( + doc->NodePrincipal(), GetPeerIdentity()); + } else { + MOZ_CRASH(); + aRv = NS_ERROR_FAILURE; + return nullptr; // Don't remove this till we know it's safe. + } + } + + return transceiver.forget(); +} + +std::string PeerConnectionImpl::GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const { + for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) { + if (transceiver->Sender()->HasTrack(&aTrack)) { + return transceiver->GetTransportId(); + } + } + return std::string(); +} + +void PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s( + dom::RTCIceGatheringState aState) { + ASSERT_ON_THREAD(mSTSThread); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->IceGatheringStateChange( + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s( + dom::RTCIceConnectionState aState) { + ASSERT_ON_THREAD(mSTSThread); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->IceConnectionStateChange( + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::OnCandidateFound_s( + const std::string& aTransportId, const CandidateInfo& aCandidateInfo) { + ASSERT_ON_THREAD(mSTSThread); + CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str()); + + MOZ_ASSERT(!aCandidateInfo.mUfrag.empty()); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aTransportId, aCandidateInfo] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnCandidateFound( + aTransportId, aCandidateInfo); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::AlpnNegotiated_s( + const std::string& aAlpn, bool aPrivacyRequested) { + MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested); + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aPrivacyRequested] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnAlpnNegotiated( + aPrivacyRequested); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::ConnectionStateChange_s( + const std::string& aTransportId, TransportLayer::State aState) { + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aTransportId, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnDtlsStateChange(aTransportId, + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +/** + * Tells you if any local track is isolated to a specific peer identity. + * Obviously, we want all the tracks to be isolated equally so that they can + * all be sent or not. We check once when we are setting a local description + * and that determines if we flip the "privacy requested" bit on. Once the bit + * is on, all media originating from this peer connection is isolated. + * + * @returns true if any track has a peerIdentity set on it + */ +bool PeerConnectionImpl::AnyLocalTrackHasPeerIdentity() const { + MOZ_ASSERT(NS_IsMainThread()); + + for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) { + if (transceiver->Sender()->GetTrack() && + transceiver->Sender()->GetTrack()->GetPeerIdentity()) { + return true; + } + } + return false; +} + +bool PeerConnectionImpl::AnyCodecHasPluginID(uint64_t aPluginID) { + for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) { + if (transceiver->ConduitHasPluginID(aPluginID)) { + return true; + } + } + return false; +} + +std::unique_ptr<NrSocketProxyConfig> PeerConnectionImpl::GetProxyConfig() + const { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mForceProxy && + Preferences::GetBool("media.peerconnection.disable_http_proxy", false)) { + return nullptr; + } + + nsCString alpn = "webrtc,c-webrtc"_ns; + auto* browserChild = BrowserChild::GetFrom(mWindow); + if (!browserChild) { + // Android doesn't have browser child apparently... + return nullptr; + } + + Document* doc = mWindow->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + NS_WARNING("Unable to get document from window"); + return nullptr; + } + + TabId id = browserChild->GetTabId(); + 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))); +} + +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..66af1aa0e9 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h @@ -0,0 +1,969 @@ +/* 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" +#include "mozilla/Attributes.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 "jsapi/PacketDumper.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move? +#include "mozilla/dom/PeerConnectionImplBinding.h" // ChainedOperation +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "PrincipalChangeObserver.h" +#include "mozilla/dom/PromiseNativeHandler.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" + +#include "mozilla/net/StunAddrsRequestChild.h" +#include "MediaTransportHandler.h" +#include "nsIHttpChannelInternal.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpTransceiver.h" + +namespace test { +#ifdef USE_FAKE_PCOBSERVER +class AFakePCObserver; +#endif +} // namespace test + +class nsDOMDataChannel; +class nsIPrincipal; + +namespace mozilla { +struct CandidateInfo; +class DataChannel; +class DtlsIdentity; +class MediaPipeline; +class MediaPipelineReceive; +class MediaPipelineTransmit; +enum class PrincipalPrivacy : uint8_t; +class SharedWebrtcState; + +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 RemoteSourceStreamInfo; + +// Uuid Generator +class PCUuidGenerator : public mozilla::JsepUuidGenerator { + public: + virtual bool Generate(std::string* idp) override; + virtual mozilla::JsepUuidGenerator* Clone() const override { + return new PCUuidGenerator(*this); + } + + 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()), mUsedAV(false){}; + void RegisterConnection(); + void UnregisterConnection(bool aContainedAV); + bool IsStopped(); + + private: + int64_t mRefCnt; + TimeStamp mStart; + bool mUsedAV; +}; + +// 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 nsWrapperCache, + public mozilla::DataChannelConnection::DataConnectionListener { + struct Internal; // Avoid exposing c includes to bindings + + public: + explicit PeerConnectionImpl( + const mozilla::dom::GlobalObject* aGlobal = nullptr); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl) + + struct RtpExtensionHeader { + JsepMediaType mMediaType; + SdpDirectionAttribute::Direction direction; + std::string extensionname; + }; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() const; + + 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; + + void NotifyDataChannelOpen(DataChannel*) override; + + void NotifyDataChannelClosed(DataChannel*) override; + + void NotifySctpConnected() override; + + void NotifySctpClosed() override; + + const RefPtr<MediaTransportHandler> GetTransportHandler() const; + + // 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 STS thread + nsISerialEventTarget* GetSTSThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mSTSThread; + } + + nsresult Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow); + + // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint) + void Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, 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<dom::RTCRtpTransceiver> AddTransceiver( + const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv); + + bool CheckNegotiationNeeded(); + bool CreatedSender(const dom::RTCRtpSender& aSender) const; + + // 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 mRequestedPrivacy.valueOr(PrincipalPrivacy::NonPrivate) == + PrincipalPrivacy::Private; + } + + 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 ConnectionState(mozilla::dom::RTCPeerConnectionState* aState); + + mozilla::dom::RTCPeerConnectionState ConnectionState() { + mozilla::dom::RTCPeerConnectionState state; + ConnectionState(&state); + return state; + } + + NS_IMETHODIMP Close(); + + void Close(ErrorResult& rv) { rv = Close(); } + + // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + MOZ_CAN_RUN_SCRIPT_BOUNDARY bool PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName); + + NS_IMETHODIMP_TO_ERRORRESULT(SetConfiguration, ErrorResult& rv, + const RTCConfiguration& aConfiguration) { + rv = SetConfiguration(aConfiguration); + } + + dom::RTCSctpTransport* GetSctp() const; + + void RestartIce(); + void RestartIceNoRenegotiationNeeded(); + + 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); + + // Base class for chained operations. Necessary right now because some + // operations come from JS (in the form of dom::ChainedOperation), and others + // come from c++ (dom::ChainedOperation is very unwieldy and arcane to build + // in c++). Once we stop using JSImpl, we should be able to simplify this. + class Operation : public dom::PromiseNativeHandler { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(Operation) + Operation(PeerConnectionImpl* aPc, ErrorResult& aError); + MOZ_CAN_RUN_SCRIPT + void Call(ErrorResult& aError); + dom::Promise* GetPromise() { return mPromise; } + MOZ_CAN_RUN_SCRIPT + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + protected: + MOZ_CAN_RUN_SCRIPT + virtual RefPtr<dom::Promise> CallImpl(ErrorResult& aError) = 0; + virtual ~Operation(); + // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain + // This will be a content promise, since we return this to the caller of + // Chain. + RefPtr<dom::Promise> mPromise; + RefPtr<PeerConnectionImpl> mPc; + }; + + class JSOperation final : public Operation { + public: + JSOperation(PeerConnectionImpl* aPc, dom::ChainedOperation& aOp, + ErrorResult& aError); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSOperation, Operation) + + private: + MOZ_CAN_RUN_SCRIPT + RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override; + ~JSOperation() = default; + RefPtr<dom::ChainedOperation> mOperation; + }; + + MOZ_CAN_RUN_SCRIPT + already_AddRefed<dom::Promise> Chain(dom::ChainedOperation& aOperation, + ErrorResult& aError); + MOZ_CAN_RUN_SCRIPT + already_AddRefed<dom::Promise> Chain(const RefPtr<Operation>& aOperation, + ErrorResult& aError); + already_AddRefed<dom::Promise> MakePromise(ErrorResult& aError) const; + + void UpdateNegotiationNeeded(); + + void GetTransceivers( + nsTArray<RefPtr<dom::RTCRtpTransceiver>>& aTransceiversOut) { + aTransceiversOut = mTransceivers.Clone(); + } + + // Gets the RTC Signaling State of the JSEP session + dom::RTCSignalingState GetSignalingState() const; + + already_AddRefed<dom::Promise> OnSetDescriptionSuccess( + dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError); + + void OnSetDescriptionError(); + + bool IsClosed() const; + + // called when DTLS connects; we only need this once + nsresult OnAlpnNegotiated(bool aPrivacyRequested); + + void OnDtlsStateChange(const std::string& aTransportId, + TransportLayer::State aState); + void UpdateConnectionState(); + dom::RTCPeerConnectionState GetNewConnectionState() const; + + // initialize telemetry for when calls start + void StartCallTelem(); + + // Gets all codec stats for all transports, coalesced to transport level. + nsTArray<dom::RTCCodecStats> GetCodecStats(DOMHighResTimeStamp aNow); + + RefPtr<dom::RTCStatsReportPromise> GetStats(dom::MediaStreamTrack* aSelector, + bool aInternalStats); + + void CollectConduitTelemetryData(); + + void OnMediaError(const std::string& aError); + + 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, const nsCString& aHostName); + + void StampTimecard(const char* aEvent); + + bool RelayOnly() const { + return mJsConfiguration.mIceTransportPolicy.WasPassed() && + mJsConfiguration.mIceTransportPolicy.Value() == + dom::RTCIceTransportPolicy::Relay; + } + + RefPtr<PacketDumper> GetPacketDumper() { + if (!mPacketDumper) { + mPacketDumper = new PacketDumper(mHandle); + } + + return mPacketDumper; + } + + nsString GenerateUUID() const { + std::string result; + if (!mUuidGen->Generate(&result)) { + MOZ_CRASH(); + } + return NS_ConvertUTF8toUTF16(result.c_str()); + } + + bool ShouldAllowOldSetParameters() const { return mAllowOldSetParameters; } + + nsCString GetHostname() const { return mHostname; } + nsCString GetEffectiveTLDPlus1() const { return mEffectiveTLDPlus1; } + + void SendWarningToConsole(const nsCString& aWarning); + + const UniquePtr<dom::RTCStatsReportInternal>& GetFinalStats() const { + return mFinalStats; + } + + void DisableLongTermStats() { mDisableLongTermStats = true; } + + bool LongTermStatsIsDisabled() const { return mDisableLongTermStats; } + + static void GetDefaultVideoCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs, + bool aUseRtx); + + static void GetDefaultAudioCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs); + + static void GetDefaultRtpExtensions( + std::vector<RtpExtensionHeader>& aRtpExtensions); + + static void GetCapabilities(const nsAString& aKind, + dom::Nullable<dom::RTCRtpCapabilities>& aResult, + sdp::Direction aDirection); + static void SetupPreferredCodecs( + std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs); + + static void SetupPreferredRtpExtensions( + std::vector<RtpExtensionHeader>& aPreferredheaders); + + private: + virtual ~PeerConnectionImpl(); + PeerConnectionImpl(const PeerConnectionImpl& rhs); + PeerConnectionImpl& operator=(PeerConnectionImpl); + + 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 CheckApiState(bool assert_ice_ready) const; + void StoreFinalStats(UniquePtr<dom::RTCStatsReportInternal>&& report); + void CheckThread() const { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread"); } + + // test-only: called from AddRIDExtension and AddRIDFilter + // for simulcast mochitests. + RefPtr<MediaPipeline> GetMediaPipelineForTrack( + dom::MediaStreamTrack& aRecvTrack); + + 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(JsepTransceiver& transceiver); + + void RecordIceRestartStatistics(JsepSdpType type); + + void StoreConfigurationForAboutWebrtc(const RTCConfiguration& aConfig); + + dom::Sequence<dom::RTCSdpParsingErrorInternal> GetLastSdpParsingErrors() + const; + + MOZ_CAN_RUN_SCRIPT + void RunNextOperation(ErrorResult& aError); + + void SyncToJsep(); + void SyncFromJsep(); + + void DoSetDescriptionSuccessPostProcessing(dom::RTCSdpType aSdpType, + bool aRemote, + const RefPtr<dom::Promise>& aP); + + // 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; + + mozilla::dom::RTCPeerConnectionState mConnectionState; + + 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<PrincipalPrivacy> mRequestedPrivacy; + + // 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; + nsCString mHostname; + nsCString mEffectiveTLDPlus1; + + // The target to run stuff on + nsCOMPtr<nsISerialEventTarget> mSTSThread; + + // DataConnection that's used to get all the DataChannels + RefPtr<mozilla::DataChannelConnection> mDataConnection; + unsigned int mDataChannelsOpened = 0; + unsigned int mDataChannelsClosed = 0; + + bool mForceIceTcp; + RefPtr<MediaTransportHandler> mTransportHandler; + + // The JSEP negotiation session. + mozilla::UniquePtr<PCUuidGenerator> mUuidGen; + mozilla::UniquePtr<mozilla::JsepSession> mJsepSession; + // There are lots of error cases where we want to abandon an sRD/sLD _after_ + // it has already been applied to the JSEP engine, and revert back to the + // previous state. We also want to ensure that the various modifications + // to the JSEP engine are not exposed to JS until the sRD/sLD completes, + // which is why we have a new "uncommitted" JSEP engine. + mozilla::UniquePtr<mozilla::JsepSession> mUncommittedJsepSession; + unsigned long mIceRestartCount; + unsigned long mIceRollbackCount; + + // The following are used for Telemetry: + bool mCallTelemStarted = false; + bool mCallTelemEnded = false; + + // We _could_ make mFinalStatsQuery be an RTCStatsReportPromise, but that + // would require RTCStatsReportPromise to no longer be exclusive, which is + // a bit of a hassle, and not very performant. + RefPtr<GenericNonExclusivePromise> mFinalStatsQuery; + UniquePtr<dom::RTCStatsReportInternal> mFinalStats; + bool mDisableLongTermStats = 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]; + + // 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); + + 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(PeerConnectionImpl* aPc) + : mPcHandle(aPc->GetHandle()) {} + + void OnMDNSQueryComplete(const nsCString& hostname, + const Maybe<nsCString>& address) override; + + void OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) override; + + private: + // This class is not cycle-collected, so we must avoid grabbing a strong + // reference. + const std::string mPcHandle; + virtual ~StunAddrsHandler() {} + }; + + // 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; + + bool IsIceCtxReady() const { + return mLocalAddrsRequestState == STUN_ADDR_REQUEST_COMPLETE; + } + + // 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 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. + nsresult UpdateMediaPipelines(); + + already_AddRefed<dom::RTCRtpTransceiver> CreateTransceiver( + const std::string& aId, bool aIsVideo, + const dom::RTCRtpTransceiverInit& aInit, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv); + + std::string GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const; + + // this determines if any track is peerIdentity constrained + bool AnyLocalTrackHasPeerIdentity() const; + + bool AnyCodecHasPluginID(uint64_t aPluginID); + + already_AddRefed<nsIHttpChannelInternal> GetChannel() const; + + void BreakCycles(); + + bool HasPendingSetParameters() const; + void InvalidateLastReturnedParameters(); + + RefPtr<WebrtcCallWrapper> mCall; + + // See Bug 1642419, this can be removed when all sites are working with RTX. + bool mRtxIsAllowed = true; + + nsTArray<RefPtr<Operation>> mOperations; + bool mChainingOperation = false; + bool mUpdateNegotiationNeededFlagOnEmptyChain = false; + bool mNegotiationNeeded = false; + std::set<std::pair<std::string, std::string>> mLocalIceCredentialsToReplace; + + nsTArray<RefPtr<dom::RTCRtpTransceiver>> mTransceivers; + std::map<std::string, RefPtr<dom::RTCDtlsTransport>> + mTransportIdToRTCDtlsTransport; + RefPtr<dom::RTCSctpTransport> mSctpTransport; + + // 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 = false; + + // 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 = STUN_ADDR_REQUEST_NONE; + + // 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 = false; + + // 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; + + // web-compat stopgap + bool mAllowOldSetParameters = false; + + // 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; + + // Connecting PCImpl to sigslot is not safe, because sigslot takes strong + // references without any reference counting, and JS holds refcounted strong + // references to PCImpl (meaning JS can cause PCImpl to be destroyed). This + // is not ref-counted (since sigslot holds onto non-refcounted strong refs) + // Must be destroyed on STS. Holds a weak reference to PCImpl. + class SignalHandler : public sigslot::has_slots<> { + public: + SignalHandler(PeerConnectionImpl* aPc, MediaTransportHandler* aSource); + virtual ~SignalHandler(); + + 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 AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested); + void ConnectionStateChange_s(const std::string& aTransportId, + TransportLayer::State aState); + + private: + const std::string mHandle; + RefPtr<MediaTransportHandler> mSource; + RefPtr<nsISerialEventTarget> mSTSThread; + }; + + mozilla::UniquePtr<SignalHandler> mSignalHandler; + + // Make absolutely sure our refcount does not go to 0 before Close() is called + // This is because Close does a stats query, which needs the + // PeerConnectionImpl to stick around until the query is done. + RefPtr<PeerConnectionImpl> mKungFuDeathGrip; + RefPtr<PacketDumper> mPacketDumper; + + 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/RTCDTMFSender.cpp b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp new file mode 100644 index 0000000000..30355aca26 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp @@ -0,0 +1,159 @@ +/* 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 "RTCRtpTransceiver.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_ENTRY(nsINamed) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +LazyLogModule gDtmfLog("RTCDTMFSender"); + +RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow, + RTCRtpTransceiver* aTransceiver) + : DOMEventTargetHelper(aWindow), mTransceiver(aTransceiver) {} + +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::SetPayloadType(int32_t aPayloadType, + int32_t aPayloadFrequency) { + MOZ_ASSERT(NS_IsMainThread()); + mPayloadType = Some(aPayloadType); + mPayloadFrequency = Some(aPayloadFrequency); +} + +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); + mDtmfEvent.Notify(DtmfEvent(mPayloadType.ref(), mPayloadFrequency.ref(), + tone, mDuration)); + } + } + + RefPtr<RTCDTMFToneChangeEvent> event = + RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init); + DispatchTrustedEvent(event); + + return NS_OK; +} + +nsresult RTCDTMFSender::GetName(nsACString& aName) { + aName.AssignLiteral("RTCDTMFSender"); + 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..14be7eb6ee --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.h @@ -0,0 +1,78 @@ +/* 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 "MediaEventSource.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "nsITimer.h" + +class nsPIDOMWindowInner; +class nsITimer; + +namespace mozilla { +class AudioSessionConduit; + +struct DtmfEvent { + DtmfEvent(int aPayloadType, int aPayloadFrequency, int aEventCode, + int aLengthMs) + : mPayloadType(aPayloadType), + mPayloadFrequency(aPayloadFrequency), + mEventCode(aEventCode), + mLengthMs(aLengthMs) {} + const int mPayloadType; + const int mPayloadFrequency; + const int mEventCode; + const int mLengthMs; +}; + +namespace dom { +class RTCRtpTransceiver; + +class RTCDTMFSender : public DOMEventTargetHelper, + public nsITimerCallback, + public nsINamed { + public: + RTCDTMFSender(nsPIDOMWindowInner* aWindow, RTCRtpTransceiver* aTransceiver); + + // nsISupports + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDTMFSender, DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + void SetPayloadType(int32_t aPayloadType, int32_t aPayloadFrequency); + void InsertDTMF(const nsAString& aTones, uint32_t aDuration, + uint32_t aInterToneGap, ErrorResult& aRv); + void GetToneBuffer(nsAString& aOutToneBuffer); + IMPL_EVENT_HANDLER(tonechange) + + void StopPlayout(); + + MediaEventSource<DtmfEvent>& OnDtmfEvent() { return mDtmfEvent; } + + private: + virtual ~RTCDTMFSender() = default; + + void StartPlayout(uint32_t aDelay); + + RefPtr<RTCRtpTransceiver> mTransceiver; + MediaEventProducer<DtmfEvent> mDtmfEvent; + Maybe<int32_t> mPayloadType; + Maybe<int32_t> mPayloadFrequency; + 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..74a4b5f618 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.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 _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::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 mozilla::dom +#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..136aa5142f --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp @@ -0,0 +1,942 @@ +/* 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 "PeerConnectionImpl.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "transport/logging.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "mozilla/dom/Promise.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 "PeerConnectionCtx.h" +#include "RTCRtpTransceiver.h" +#include "libwebrtcglue/AudioConduit.h" +#include "call/call.h" + +namespace mozilla::dom { + +LazyLogModule gReceiverLog("RTCRtpReceiver"); + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpReceiver) + // We do not do anything here, we wait for BreakCycles to be called + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpReceiver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mTransceiver, mTrack, + mTrackSource) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +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 + +static PrincipalHandle GetPrincipalHandle(nsPIDOMWindowInner* aWindow, + PrincipalPrivacy aPrivacy) { + // 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 (aPrivacy == PrincipalPrivacy::Private) { + principal = NullPrincipal::CreateWithInheritedAttributes(principal); + } + return MakePrincipalHandle(principal); +} + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, \ + "RTCRtpReceiver::" #name " (Canonical)") + +RTCRtpReceiver::RTCRtpReceiver( + nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy, + PeerConnectionImpl* aPc, MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver, + const TrackingId& aTrackingId) + : mWatchManager(this, AbstractThread::MainThread()), + mWindow(aWindow), + mPc(aPc), + mCallThread(aCallThread), + mStsThread(aStsThread), + mTransportHandler(aTransportHandler), + mTransceiver(aTransceiver), + INIT_CANONICAL(mSsrc, 0), + INIT_CANONICAL(mVideoRtxSsrc, 0), + INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()), + INIT_CANONICAL(mAudioCodecs, std::vector<AudioCodecConfig>()), + INIT_CANONICAL(mVideoCodecs, std::vector<VideoCodecConfig>()), + INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()), + INIT_CANONICAL(mReceiving, false) { + PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacy); + const bool isAudio = aConduit->type() == MediaSessionConduit::AUDIO; + + MediaTrackGraph* graph = MediaTrackGraph::GetInstance( + isAudio ? MediaTrackGraph::AUDIO_THREAD_DRIVER + : MediaTrackGraph::SYSTEM_THREAD_DRIVER, + aWindow, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, + MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); + + if (isAudio) { + auto* source = graph->CreateSourceTrack(MediaSegment::AUDIO); + mTrackSource = MakeAndAddRef<RemoteTrackSource>( + source, this, principalHandle, u"remote audio"_ns, aTrackingId); + mTrack = MakeAndAddRef<AudioStreamTrack>(aWindow, source, mTrackSource); + mPipeline = MakeAndAddRef<MediaPipelineReceiveAudio>( + mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(), + *aConduit->AsAudioSessionConduit(), mTrackSource->Stream(), aTrackingId, + principalHandle, aPrivacy); + } else { + auto* source = graph->CreateSourceTrack(MediaSegment::VIDEO); + mTrackSource = MakeAndAddRef<RemoteTrackSource>( + source, this, principalHandle, u"remote video"_ns, aTrackingId); + mTrack = MakeAndAddRef<VideoStreamTrack>(aWindow, source, mTrackSource); + mPipeline = MakeAndAddRef<MediaPipelineReceiveVideo>( + mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(), + *aConduit->AsVideoSessionConduit(), mTrackSource->Stream(), aTrackingId, + principalHandle, aPrivacy); + } + + mPipeline->InitControl(this); + + // Spec says remote tracks start out muted. + mTrackSource->SetMuted(true); + + // 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)) { + mRtcpByeListener = aConduit->RtcpByeEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpBye); + mRtcpTimeoutListener = aConduit->RtcpTimeoutEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpTimeout); + } + + mWatchManager.Watch(mReceiveTrackMute, + &RTCRtpReceiver::UpdateReceiveTrackMute); +} + +#undef INIT_CANONICAL + +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 (!mTransceiver) { + return nullptr; + } + return mTransceiver->GetDtlsTransport(); +} + +void RTCRtpReceiver::GetCapabilities( + const GlobalObject&, const nsAString& aKind, + Nullable<dom::RTCRtpCapabilities>& aResult) { + PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kRecv); +} + +already_AddRefed<Promise> RTCRtpReceiver::GetStats(ErrorResult& aError) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + RefPtr<Promise> promise = Promise::Create(global, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + if (NS_WARN_IF(!mTransceiver)) { + // TODO(bug 1056433): When we stop nulling this out when the PC is closed + // (or when the transceiver is stopped), we can remove this code. We + // resolve instead of reject in order to make this eventual change in + // behavior a little smaller. + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise); + return promise.forget(); +} + +nsTArray<RefPtr<RTCStatsPromise>> RTCRtpReceiver::GetStatsInternal( + bool aSkipIceStats) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray<RefPtr<RTCStatsPromise>> promises(3); + + if (!mPipeline) { + return promises; + } + + if (!mHaveStartedReceiving) { + return promises; + } + + nsString recvTrackId; + MOZ_ASSERT(mTrack); + if (mTrack) { + mTrack->GetId(recvTrackId); + } + + { + // Add bandwidth estimation stats + promises.AppendElement(InvokeAsync( + mCallThread, __func__, + [conduit = mPipeline->mConduit, recvTrackId]() mutable { + auto report = MakeUnique<dom::RTCStatsCollection>(); + const Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats(); + stats.apply([&](const auto& aStats) { + dom::RTCBandwidthEstimationInternal bw; + bw.mTrackIdentifier = recvTrackId; + bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8); + bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8); + bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8); + bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms); + if (aStats.rtt_ms >= 0) { + bw.mRttMs.Construct(aStats.rtt_ms); + } + if (!report->mBandwidthEstimations.AppendElement(std::move(bw), + fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + } + + promises.AppendElement( + InvokeAsync( + mCallThread, __func__, + [pipeline = mPipeline, recvTrackId] { + auto report = MakeUnique<dom::RTCStatsCollection>(); + auto asAudio = pipeline->mConduit->AsAudioSessionConduit(); + auto asVideo = pipeline->mConduit->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast<uint32_t>(pipeline->Level())); + + Maybe<uint32_t> ssrc = pipeline->mConduit->GetRemoteSSRC(); + + // Add frame history + asVideo.apply([&](const auto& conduit) { + if (conduit->AddFrameHistory(&report->mVideoFrameHistories)) { + auto& history = report->mVideoFrameHistories.LastElement(); + history.mTrackIdentifier = recvTrackId; + } + }); + + // 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; + + auto constructCommonRemoteOutboundRtpStats = + [&](RTCRemoteOutboundRtpStreamStats& aRemote, + const DOMHighResTimeStamp& aTimestamp) { + remoteId = u"inbound_rtcp_"_ns + idstr; + aRemote.mTimestamp.Construct(aTimestamp); + aRemote.mId.Construct(remoteId); + aRemote.mType.Construct(RTCStatsType::Remote_outbound_rtp); + ssrc.apply([&](uint32_t aSsrc) { aRemote.mSsrc = aSsrc; }); + aRemote.mKind = kind; + aRemote.mMediaType.Construct( + kind); // mediaType is the old name for kind. + aRemote.mLocalId.Construct(localId); + }; + + auto constructCommonInboundRtpStats = + [&](RTCInboundRtpStreamStats& aLocal) { + aLocal.mTrackIdentifier = recvTrackId; + aLocal.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aLocal.mId.Construct(localId); + aLocal.mType.Construct(RTCStatsType::Inbound_rtp); + ssrc.apply([&](uint32_t aSsrc) { aLocal.mSsrc = aSsrc; }); + aLocal.mKind = kind; + aLocal.mMediaType.Construct( + kind); // mediaType is the old name for kind. + if (remoteId.Length()) { + aLocal.mRemoteId.Construct(remoteId); + } + }; + + asAudio.apply([&](auto& aConduit) { + Maybe<webrtc::AudioReceiveStreamInterface::Stats> audioStats = + aConduit->GetReceiverStats(); + if (audioStats.isNothing()) { + return; + } + + if (!audioStats->last_packet_received_timestamp_ms) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp sender data, if present. + if (audioStats->last_sender_report_timestamp_ms) { + RTCRemoteOutboundRtpStreamStats remote; + constructCommonRemoteOutboundRtpStats( + remote, + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *audioStats->last_sender_report_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + remote.mPacketsSent.Construct( + audioStats->sender_reports_packets_sent); + remote.mBytesSent.Construct( + audioStats->sender_reports_bytes_sent); + remote.mRemoteTimestamp.Construct( + *audioStats->last_sender_report_remote_timestamp_ms); + if (!report->mRemoteOutboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCInboundRtpStreamStats local; + constructCommonInboundRtpStats(local); + local.mJitter.Construct(audioStats->jitter_ms / 1000.0); + local.mPacketsLost.Construct(audioStats->packets_lost); + local.mPacketsReceived.Construct(audioStats->packets_rcvd); + local.mPacketsDiscarded.Construct(audioStats->packets_discarded); + local.mBytesReceived.Construct(audioStats->payload_bytes_rcvd); + // Always missing from libwebrtc stats + // if (audioStats->estimated_playout_ntp_timestamp_ms) { + // local.mEstimatedPlayoutTimestamp.Construct( + // RTCStatsTimestamp::FromNtp( + // aConduit->GetTimestampMaker(), + // webrtc::Timestamp::Millis( + // *audioStats->estimated_playout_ntp_timestamp_ms)) + // .ToDom()); + // } + local.mJitterBufferDelay.Construct( + audioStats->jitter_buffer_delay_seconds); + local.mJitterBufferEmittedCount.Construct( + audioStats->jitter_buffer_emitted_count); + local.mTotalSamplesReceived.Construct( + audioStats->total_samples_received); + local.mConcealedSamples.Construct(audioStats->concealed_samples); + local.mSilentConcealedSamples.Construct( + audioStats->silent_concealed_samples); + if (audioStats->last_packet_received_timestamp_ms) { + local.mLastPacketReceivedTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *audioStats->last_packet_received_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + } + local.mHeaderBytesReceived.Construct( + audioStats->header_and_padding_bytes_rcvd); + local.mFecPacketsReceived.Construct( + audioStats->fec_packets_received); + local.mFecPacketsDiscarded.Construct( + audioStats->fec_packets_discarded); + local.mConcealmentEvents.Construct( + audioStats->concealment_events); + + local.mInsertedSamplesForDeceleration.Construct( + audioStats->inserted_samples_for_deceleration); + local.mRemovedSamplesForAcceleration.Construct( + audioStats->removed_samples_for_acceleration); + if (audioStats->audio_level >= 0 && + audioStats->audio_level <= 32767) { + local.mAudioLevel.Construct(audioStats->audio_level / 32767.0); + } + local.mTotalAudioEnergy.Construct( + audioStats->total_output_energy); + local.mTotalSamplesDuration.Construct( + audioStats->total_output_duration); + + if (!report->mInboundRtpStreamStats.AppendElement( + std::move(local), fallible)) { + mozalloc_handle_oom(0); + } + }); + + asVideo.apply([&](auto& aConduit) { + Maybe<webrtc::VideoReceiveStreamInterface::Stats> videoStats = + aConduit->GetReceiverStats(); + if (videoStats.isNothing()) { + return; + } + + if (!videoStats->rtp_stats.last_packet_received_timestamp_ms) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp sender data, if present. + if (videoStats->rtcp_sender_ntp_timestamp_ms) { + RTCRemoteOutboundRtpStreamStats remote; + constructCommonRemoteOutboundRtpStats( + remote, RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + videoStats->rtcp_sender_ntp_timestamp_ms)) + .ToDom()); + remote.mPacketsSent.Construct( + videoStats->rtcp_sender_packets_sent); + remote.mBytesSent.Construct( + videoStats->rtcp_sender_octets_sent); + remote.mRemoteTimestamp.Construct( + (webrtc::TimeDelta::Millis( + videoStats->rtcp_sender_remote_ntp_timestamp_ms) - + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ms()); + if (!report->mRemoteOutboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCInboundRtpStreamStats local; + constructCommonInboundRtpStats(local); + local.mJitter.Construct( + static_cast<double>(videoStats->rtp_stats.jitter) / + webrtc::kVideoPayloadTypeFrequency); + local.mPacketsLost.Construct(videoStats->rtp_stats.packets_lost); + local.mPacketsReceived.Construct( + videoStats->rtp_stats.packet_counter.packets); + local.mPacketsDiscarded.Construct(videoStats->packets_discarded); + local.mDiscardedPackets.Construct(videoStats->packets_discarded); + local.mBytesReceived.Construct( + videoStats->rtp_stats.packet_counter.payload_bytes); + + // Fill in packet type statistics + local.mNackCount.Construct( + videoStats->rtcp_packet_type_counts.nack_packets); + local.mFirCount.Construct( + videoStats->rtcp_packet_type_counts.fir_packets); + local.mPliCount.Construct( + videoStats->rtcp_packet_type_counts.pli_packets); + + // Lastly, fill in video decoder stats + local.mFramesDecoded.Construct(videoStats->frames_decoded); + + local.mFramesPerSecond.Construct(videoStats->decode_frame_rate); + local.mFrameWidth.Construct(videoStats->width); + local.mFrameHeight.Construct(videoStats->height); + // XXX: key_frames + delta_frames may undercount frames because + // they were dropped in FrameBuffer::InsertFrame. (bug 1766553) + local.mFramesReceived.Construct( + videoStats->frame_counts.key_frames + + videoStats->frame_counts.delta_frames); + local.mJitterBufferDelay.Construct( + videoStats->jitter_buffer_delay_seconds); + local.mJitterBufferEmittedCount.Construct( + videoStats->jitter_buffer_emitted_count); + + if (videoStats->qp_sum) { + local.mQpSum.Construct(videoStats->qp_sum.value()); + } + local.mTotalDecodeTime.Construct( + double(videoStats->total_decode_time.ms()) / 1000); + local.mTotalInterFrameDelay.Construct( + videoStats->total_inter_frame_delay); + local.mTotalSquaredInterFrameDelay.Construct( + videoStats->total_squared_inter_frame_delay); + if (videoStats->rtp_stats.last_packet_received_timestamp_ms) { + local.mLastPacketReceivedTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *videoStats->rtp_stats + .last_packet_received_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + } + local.mHeaderBytesReceived.Construct( + videoStats->rtp_stats.packet_counter.header_bytes + + videoStats->rtp_stats.packet_counter.padding_bytes); + local.mTotalProcessingDelay.Construct( + videoStats->total_processing_delay.seconds<double>()); + /* + * Potential new stats that are now available upstream + .if (videoStats->estimated_playout_ntp_timestamp_ms) { + local.mEstimatedPlayoutTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *videoStats->estimated_playout_ntp_timestamp_ms)) + .ToDom()); + } + */ + // Not including frames dropped in the rendering pipe, which + // is not of webrtc's concern anyway?! + local.mFramesDropped.Construct(videoStats->frames_dropped); + if (!report->mInboundRtpStreamStats.AppendElement( + std::move(local), fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), + __func__); + }) + ->Then( + mStsThread, __func__, + [pipeline = mPipeline](UniquePtr<RTCStatsCollection> aReport) { + // Fill in Contributing Source statistics + if (!aReport->mInboundRtpStreamStats.IsEmpty() && + aReport->mInboundRtpStreamStats[0].mId.WasPassed()) { + pipeline->GetContributingSourceStats( + aReport->mInboundRtpStreamStats[0].mId.Value(), + aReport->mRtpContributingSourceStats); + } + return RTCStatsPromise::CreateAndResolve(std::move(aReport), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + })); + + if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) { + promises.AppendElement(mTransportHandler->GetIceStats( + GetJsepTransceiver().mTransport.mTransportId, + mPipeline->GetTimestampMaker().GetNow().ToDom())); + } + + return promises; +} + +void RTCRtpReceiver::SetJitterBufferTarget( + const Nullable<DOMHighResTimeStamp>& aTargetMs, ErrorResult& aError) { + // Spec says jitter buffer target cannot be negative or larger than 4000 + // milliseconds and to throw RangeError if it is. If an invalid value is + // received we return early to preserve the current JitterBufferTarget + // internal slot and jitter buffer values. + if (mPipeline && mPipeline->mConduit) { + if (!aTargetMs.IsNull() && + (aTargetMs.Value() < 0.0 || aTargetMs.Value() > 4000.0)) { + aError.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("jitterBufferTarget"); + return; + } + + mJitterBufferTarget.reset(); + + if (!aTargetMs.IsNull()) { + mJitterBufferTarget = Some(aTargetMs.Value()); + } + // If aJitterBufferTarget is null then we are resetting the jitter buffer so + // pass the default target of 0.0. + mPipeline->mConduit->SetJitterBufferTarget( + mJitterBufferTarget.valueOr(0.0)); + } +} + +void RTCRtpReceiver::GetContributingSources( + nsTArray<RTCRtpContributingSource>& aSources) { + // Duplicate code... + if (mPipeline && mPipeline->mConduit) { + nsTArray<dom::RTCRtpSourceEntry> sources; + mPipeline->mConduit->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->mConduit) { + nsTArray<dom::RTCRtpSourceEntry> sources; + mPipeline->mConduit->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() { + MOZ_ASSERT(NS_IsMainThread()); + mWatchManager.Shutdown(); + if (mPipeline) { + mPipeline->Shutdown(); + mPipeline = nullptr; + } + if (mTrackSource) { + mTrackSource->Destroy(); + } + mCallThread = nullptr; + mRtcpByeListener.DisconnectIfExists(); + mRtcpTimeoutListener.DisconnectIfExists(); + mUnmuteListener.DisconnectIfExists(); +} + +void RTCRtpReceiver::BreakCycles() { + mWindow = nullptr; + mPc = nullptr; + mTrack = nullptr; + mTrackSource = nullptr; +} + +void RTCRtpReceiver::UpdateTransport() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHaveSetupTransport) { + mPipeline->SetLevel(GetJsepTransceiver().GetLevel()); + mHaveSetupTransport = true; + } + + UniquePtr<MediaPipelineFilter> filter; + + auto const& details = GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails(); + if (GetJsepTransceiver().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 : GetJsepTransceiver().mRecvTrack.GetSsrcs()) { + filter->AddRemoteSSRC(ssrc); + } + for (uint32_t ssrc : GetJsepTransceiver().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 = GetJsepTransceiver() + .mRecvTrack.GetNegotiatedDetails() + ->GetUniquePayloadTypes(); + for (unsigned char& uniquePt : uniquePts) { + filter->AddUniquePT(uniquePt); + } + } + + mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId, + std::move(filter)); +} + +void RTCRtpReceiver::UpdateConduit() { + if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) { + UpdateVideoConduit(); + } else { + UpdateAudioConduit(); + } + + if ((mReceiving = mTransceiver->IsReceiving())) { + mHaveStartedReceiving = true; + } +} + +void RTCRtpReceiver::UpdateVideoConduit() { + RefPtr<VideoSessionConduit> conduit = + *mPipeline->mConduit->AsVideoSessionConduit(); + + // 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 (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__, + GetJsepTransceiver().mRecvTrack.GetSsrcs().front())); + uint32_t rtxSsrc = + GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().empty() + ? 0 + : GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().front(); + mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front(); + mVideoRtxSsrc = 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. + // In any case, do not disable SSRC changes if no SSRCs were negotiated + if (GetJsepTransceiver().HasBundleLevel() && + (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() || + !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt( + webrtc::RtpExtension::kMidUri))) { + mCallThread->Dispatch( + NewRunnableMethod("VideoSessionConduit::DisableSsrcChanges", conduit, + &VideoSessionConduit::DisableSsrcChanges)); + } + } + + if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mRecvTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()); + + { + std::vector<webrtc::RtpExtension> extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + mLocalRtpExtensions = extmaps; + } + + std::vector<VideoCodecConfig> configs; + RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s No video codecs were negotiated (recv).", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return; + } + + mVideoCodecs = configs; + mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig()); + } +} + +void RTCRtpReceiver::UpdateAudioConduit() { + RefPtr<AudioSessionConduit> conduit = + *mPipeline->mConduit->AsAudioSessionConduit(); + + if (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__, + GetJsepTransceiver().mRecvTrack.GetSsrcs().front())); + mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front(); + + // 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. + // In any case, do not disable SSRC changes if no SSRCs were negotiated + if (GetJsepTransceiver().HasBundleLevel() && + (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() || + !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt( + webrtc::RtpExtension::kMidUri))) { + mCallThread->Dispatch( + NewRunnableMethod("AudioSessionConduit::DisableSsrcChanges", conduit, + &AudioSessionConduit::DisableSsrcChanges)); + } + } + + if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mRecvTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()); + std::vector<AudioCodecConfig> configs; + RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s No audio codecs were negotiated (recv)", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return; + } + + // Ensure conduit knows about extensions prior to creating streams + { + std::vector<webrtc::RtpExtension> extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + mLocalRtpExtensions = extmaps; + } + + mAudioCodecs = configs; + } +} + +void RTCRtpReceiver::Stop() { + MOZ_ASSERT(mTransceiver->Stopped()); + mReceiving = false; +} + +bool RTCRtpReceiver::HasTrack(const dom::MediaStreamTrack* aTrack) const { + return !aTrack || (mTrack == aTrack); +} + +void RTCRtpReceiver::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) { + if (!mPipeline) { + return; + } + + // Spec says we set [[Receptive]] to true on sLD(sendrecv/recvonly), and to + // false on sRD(recvonly/inactive), sLD(sendonly/inactive), or when stop() + // is called. + bool wasReceptive = mReceptive; + mReceptive = aJsepTransceiver.mRecvTrack.GetReceptive(); + if (!wasReceptive && mReceptive) { + mUnmuteListener = mPipeline->mConduit->RtpPacketEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtpPacket); + } else if (wasReceptive && !mReceptive) { + mUnmuteListener.DisconnectIfExists(); + } +} + +void RTCRtpReceiver::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {} + +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( + GetJsepTransceiver().mRecvTrack.GetStreamIds().begin(), + GetJsepTransceiver().mRecvTrack.GetStreamIds().end()); + MOZ_ASSERT(GetJsepTransceiver().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 : GetJsepTransceiver().mRecvTrack.GetStreamIds()) { + if (!oldIds.count(id)) { + needsTrackEvent = true; + aChanges->mStreamAssociationsAdded.push_back({mTrack, id}); + } + } + + mStreamIds = GetJsepTransceiver().mRecvTrack.GetStreamIds(); + + if (mRemoteSetSendBit != + GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit()) { + mRemoteSetSendBit = GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit(); + if (mRemoteSetSendBit) { + needsTrackEvent = true; + } else { + aChanges->mReceiversToMute.push_back(this); + } + } + + if (needsTrackEvent) { + aChanges->mTrackEvents.push_back({this, mStreamIds}); + } +} + +void RTCRtpReceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) { + if (!mPipeline) { + return; + } + + if (aPrivacy != PrincipalPrivacy::Private) { + return; + } + + mPipeline->SetPrivatePrincipal(GetPrincipalHandle(mWindow, aPrivacy)); +} + +// 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->mConduit) { + return; + } + mPipeline->mConduit->InsertAudioLevelForContributingSource( + aSource, aTimestamp, aRtpTimestamp, aHasLevel, aLevel); +} + +void RTCRtpReceiver::OnRtcpBye() { mReceiveTrackMute = true; } + +void RTCRtpReceiver::OnRtcpTimeout() { mReceiveTrackMute = true; } + +void RTCRtpReceiver::SetTrackMuteFromRemoteSdp() { + MOZ_ASSERT(!mReceptive, + "PeerConnectionImpl should have blocked unmute events prior to " + "firing mute"); + mReceiveTrackMute = true; + // Set the mute state (and fire the mute event) synchronously. Unmute is + // handled asynchronously after receiving RTP packets. + UpdateReceiveTrackMute(); + MOZ_ASSERT(mTrack->Muted(), "Muted state was indeed set synchronously"); +} + +void RTCRtpReceiver::OnRtpPacket() { + MOZ_ASSERT(mReceptive, "We should not be registered unless this is set!"); + // We should be registered since we're currently getting a callback. + mUnmuteListener.Disconnect(); + if (mReceptive) { + mReceiveTrackMute = false; + } +} + +void RTCRtpReceiver::UpdateReceiveTrackMute() { + if (!mTrack) { + return; + } + if (!mTrackSource) { + return; + } + // This sets the muted state for mTrack and all its clones. + // Idempotent -- only reacts to changes. + mTrackSource->SetMuted(mReceiveTrackMute); +} + +std::string RTCRtpReceiver::GetMid() const { + return mTransceiver->GetMidAscii(); +} + +JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() { + MOZ_ASSERT(mTransceiver); + return mTransceiver->GetJsepTransceiver(); +} + +const JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() const { + MOZ_ASSERT(mTransceiver); + return mTransceiver->GetJsepTransceiver(); +} + +} // 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..2c050bceb1 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h @@ -0,0 +1,198 @@ +/* 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/StateMirroring.h" +#include "mozilla/Maybe.h" +#include "js/RootingAPI.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "PerformanceRecorder.h" +#include "RTCStatsReport.h" +#include "transportbridge/MediaPipeline.h" +#include <vector> + +class nsPIDOMWindowInner; + +namespace mozilla { +class MediaSessionConduit; +class MediaTransportHandler; +class JsepTransceiver; +class PeerConnectionImpl; +enum class PrincipalPrivacy : uint8_t; +class RemoteTrackSource; + +namespace dom { +class MediaStreamTrack; +class Promise; +class RTCDtlsTransport; +struct RTCRtpCapabilities; +struct RTCRtpContributingSource; +struct RTCRtpSynchronizationSource; +class RTCRtpTransceiver; + +class RTCRtpReceiver : public nsISupports, + public nsWrapperCache, + public MediaPipelineReceiveControlInterface { + public: + RTCRtpReceiver(nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy, + PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver, + const TrackingId& aTrackingId); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver) + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // webidl + MediaStreamTrack* Track() const { return mTrack; } + RTCDtlsTransport* GetTransport() const; + static void GetCapabilities(const GlobalObject&, const nsAString& aKind, + Nullable<dom::RTCRtpCapabilities>& aResult); + already_AddRefed<Promise> GetStats(ErrorResult& aError); + void GetContributingSources( + nsTArray<dom::RTCRtpContributingSource>& aSources); + void GetSynchronizationSources( + nsTArray<dom::RTCRtpSynchronizationSource>& aSources); + // 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( + bool aSkipIceStats = false); + Nullable<DOMHighResTimeStamp> GetJitterBufferTarget( + ErrorResult& aError) const { + return mJitterBufferTarget.isSome() ? Nullable(mJitterBufferTarget.value()) + : Nullable<DOMHighResTimeStamp>(); + } + void SetJitterBufferTarget(const Nullable<DOMHighResTimeStamp>& aTargetMs, + ErrorResult& aError); + + void Shutdown(); + void BreakCycles(); + // Terminal state, reached through stopping RTCRtpTransceiver. + void Stop(); + bool HasTrack(const dom::MediaStreamTrack* aTrack) const; + void SyncToJsep(JsepTransceiver& aJsepTransceiver) const; + void SyncFromJsep(const JsepTransceiver& aJsepTransceiver); + const std::vector<std::string>& GetStreamIds() const { return mStreamIds; } + + struct StreamAssociation { + RefPtr<MediaStreamTrack> mTrack; + std::string mStreamId; + }; + + struct TrackEventInfo { + RefPtr<RTCRtpReceiver> mReceiver; + std::vector<std::string> mStreamIds; + }; + + struct StreamAssociationChanges { + std::vector<RefPtr<RTCRtpReceiver>> mReceiversToMute; + 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(); + void UpdateConduit(); + + // This is called when we set a remote description; may be an offer or answer. + void UpdateStreams(StreamAssociationChanges* aChanges); + + // Called when the privacy-needed state changes on the fly, as a result of + // ALPN negotiation. + void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy); + + void OnRtcpBye(); + void OnRtcpTimeout(); + + void SetTrackMuteFromRemoteSdp(); + void OnRtpPacket(); + void UpdateUnmuteBlockingState(); + void UpdateReceiveTrackMute(); + + AbstractCanonical<Ssrc>* CanonicalSsrc() { return &mSsrc; } + AbstractCanonical<Ssrc>* CanonicalVideoRtxSsrc() { return &mVideoRtxSsrc; } + AbstractCanonical<RtpExtList>* CanonicalLocalRtpExtensions() { + return &mLocalRtpExtensions; + } + + AbstractCanonical<std::vector<AudioCodecConfig>>* CanonicalAudioCodecs() { + return &mAudioCodecs; + } + + AbstractCanonical<std::vector<VideoCodecConfig>>* CanonicalVideoCodecs() { + return &mVideoCodecs; + } + AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRtpRtcpConfig() { + return &mVideoRtpRtcpConfig; + } + AbstractCanonical<bool>* CanonicalReceiving() override { return &mReceiving; } + + private: + virtual ~RTCRtpReceiver(); + + void UpdateVideoConduit(); + void UpdateAudioConduit(); + + std::string GetMid() const; + JsepTransceiver& GetJsepTransceiver(); + const JsepTransceiver& GetJsepTransceiver() const; + + WatchManager<RTCRtpReceiver> mWatchManager; + nsCOMPtr<nsPIDOMWindowInner> mWindow; + RefPtr<PeerConnectionImpl> mPc; + bool mHaveStartedReceiving = false; + bool mHaveSetupTransport = false; + RefPtr<AbstractThread> mCallThread; + nsCOMPtr<nsISerialEventTarget> mStsThread; + RefPtr<dom::MediaStreamTrack> mTrack; + RefPtr<RemoteTrackSource> mTrackSource; + RefPtr<MediaPipelineReceive> mPipeline; + RefPtr<MediaTransportHandler> mTransportHandler; + RefPtr<RTCRtpTransceiver> mTransceiver; + // 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; + Watchable<bool> mReceiveTrackMute{true, "RTCRtpReceiver::mReceiveTrackMute"}; + // This corresponds to the [[Receptive]] slot on RTCRtpTransceiver. + // Its only purpose is suppressing unmute events if true. + bool mReceptive = false; + // This is the [[JitterBufferTarget]] internal slot. + Maybe<DOMHighResTimeStamp> mJitterBufferTarget; + + MediaEventListener mRtcpByeListener; + MediaEventListener mRtcpTimeoutListener; + MediaEventListener mUnmuteListener; + + Canonical<Ssrc> mSsrc; + Canonical<Ssrc> mVideoRtxSsrc; + Canonical<RtpExtList> mLocalRtpExtensions; + Canonical<std::vector<AudioCodecConfig>> mAudioCodecs; + Canonical<std::vector<VideoCodecConfig>> mVideoCodecs; + Canonical<Maybe<RtpRtcpConfig>> mVideoRtpRtcpConfig; + Canonical<bool> mReceiving; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCRtpReceiver_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp new file mode 100644 index 0000000000..568a83e8d1 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp @@ -0,0 +1,1654 @@ +/* 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 "RTCRtpSender.h" +#include "transport/logging.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/glean/GleanMetrics.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" +#include "mozilla/dom/VideoStreamTrack.h" +#include "jsep/JsepTransceiver.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "RTCStatsReport.h" +#include "mozilla/Preferences.h" +#include "RTCRtpTransceiver.h" +#include "PeerConnectionImpl.h" +#include "libwebrtcglue/AudioConduit.h" +#include <vector> +#include "call/call.h" + +namespace mozilla::dom { + +LazyLogModule gSenderLog("RTCRtpSender"); + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender) + // We do not do anything here, we wait for BreakCycles to be called + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver, + mStreams, mDtmf) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)") + +RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, + nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, + dom::MediaStreamTrack* aTrack, + const Sequence<RTCRtpEncodingParameters>& aEncodings, + RTCRtpTransceiver* aTransceiver) + : mWatchManager(this, AbstractThread::MainThread()), + mWindow(aWindow), + mPc(aPc), + mSenderTrack(aTrack), + mTransportHandler(aTransportHandler), + mTransceiver(aTransceiver), + INIT_CANONICAL(mSsrcs, Ssrcs()), + INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()), + INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()), + INIT_CANONICAL(mAudioCodec, Nothing()), + INIT_CANONICAL(mVideoCodec, Nothing()), + INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()), + INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo), + INIT_CANONICAL(mCname, std::string()), + INIT_CANONICAL(mTransmitting, false) { + mPipeline = new MediaPipelineTransmit( + mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread, + aConduit->type() == MediaSessionConduit::VIDEO, aConduit); + mPipeline->InitControl(this); + + if (aConduit->type() == MediaSessionConduit::AUDIO) { + mDtmf = new RTCDTMFSender(aWindow, mTransceiver); + } + mPipeline->SetTrack(mSenderTrack); + + mozilla::glean::rtcrtpsender::count.Add(1); + + if (mPc->ShouldAllowOldSetParameters()) { + mAllowOldSetParameters = true; + mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1); + } + + if (aEncodings.Length()) { + // This sender was created by addTransceiver with sendEncodings. + mParameters.mEncodings = aEncodings; + mSimulcastEnvelopeSet = true; + mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1); + } else { + // This sender was created by addTrack, sRD(offer), or addTransceiver + // without sendEncodings. + RTCRtpEncodingParameters defaultEncoding; + defaultEncoding.mActive = true; + if (aConduit->type() == MediaSessionConduit::VIDEO) { + defaultEncoding.mScaleResolutionDownBy.Construct(1.0f); + } + Unused << mParameters.mEncodings.AppendElement(defaultEncoding, fallible); + UpdateRestorableEncodings(mParameters.mEncodings); + MaybeGetJsepRids(); + } + + if (mDtmf) { + mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender); + } +} + +#undef INIT_CANONICAL + +RTCRtpSender::~RTCRtpSender() = default; + +JSObject* RTCRtpSender::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto); +} + +RTCDtlsTransport* RTCRtpSender::GetTransport() const { + if (!mTransceiver) { + return nullptr; + } + return mTransceiver->GetDtlsTransport(); +} + +RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; } + +already_AddRefed<Promise> RTCRtpSender::GetStats(ErrorResult& aError) { + RefPtr<Promise> promise = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + if (NS_WARN_IF(!mPipeline)) { + // TODO(bug 1056433): When we stop nulling this out when the PC is closed + // (or when the transceiver is stopped), we can remove this code. We + // resolve instead of reject in order to make this eventual change in + // behavior a little smaller. + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + if (!mSenderTrack) { + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise); + return promise.forget(); +} + +nsTArray<RefPtr<dom::RTCStatsPromise>> RTCRtpSender::GetStatsInternal( + bool aSkipIceStats) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray<RefPtr<RTCStatsPromise>> promises(2); + if (!mSenderTrack || !mPipeline) { + return promises; + } + + nsAutoString trackName; + if (auto track = mPipeline->GetTrack()) { + track->GetId(trackName); + } + + { + // Add bandwidth estimation stats + promises.AppendElement(InvokeAsync( + mPipeline->mCallThread, __func__, + [conduit = mPipeline->mConduit, trackName]() mutable { + auto report = MakeUnique<dom::RTCStatsCollection>(); + Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats(); + stats.apply([&](const auto aStats) { + dom::RTCBandwidthEstimationInternal bw; + bw.mTrackIdentifier = trackName; + bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8); + bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8); + bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8); + bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms); + if (aStats.rtt_ms >= 0) { + bw.mRttMs.Construct(aStats.rtt_ms); + } + if (!report->mBandwidthEstimations.AppendElement(std::move(bw), + fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + } + + promises.AppendElement(InvokeAsync( + mPipeline->mCallThread, __func__, [pipeline = mPipeline, trackName] { + auto report = MakeUnique<dom::RTCStatsCollection>(); + auto asAudio = pipeline->mConduit->AsAudioSessionConduit(); + auto asVideo = pipeline->mConduit->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast<uint32_t>(pipeline->Level())); + + for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) { + nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns; + localId.AppendInt(ssrc); + nsString remoteId; + Maybe<uint16_t> base_seq = + pipeline->mConduit->RtpSendBaseSeqFor(ssrc); + + auto constructCommonRemoteInboundRtpStats = + [&](RTCRemoteInboundRtpStreamStats& aRemote, + const webrtc::ReportBlockData& aRtcpData) { + remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns; + remoteId.AppendInt(ssrc); + aRemote.mTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + pipeline->GetTimestampMaker(), + webrtc::Timestamp::Micros( + aRtcpData.report_block_timestamp_utc_us()) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + aRemote.mId.Construct(remoteId); + aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp); + aRemote.mSsrc = ssrc; + aRemote.mKind = kind; + aRemote.mMediaType.Construct( + kind); // mediaType is the old name for kind. + aRemote.mLocalId.Construct(localId); + if (base_seq) { + if (aRtcpData.report_block() + .extended_highest_sequence_number < *base_seq) { + aRemote.mPacketsReceived.Construct(0); + } else { + aRemote.mPacketsReceived.Construct( + aRtcpData.report_block() + .extended_highest_sequence_number - + aRtcpData.report_block().packets_lost - *base_seq + 1); + } + } + }; + + auto constructCommonOutboundRtpStats = + [&](RTCOutboundRtpStreamStats& aLocal) { + aLocal.mSsrc = ssrc; + aLocal.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aLocal.mId.Construct(localId); + aLocal.mType.Construct(RTCStatsType::Outbound_rtp); + aLocal.mKind = kind; + aLocal.mMediaType.Construct( + kind); // mediaType is the old name for kind. + if (remoteId.Length()) { + aLocal.mRemoteId.Construct(remoteId); + } + }; + + asAudio.apply([&](auto& aConduit) { + Maybe<webrtc::AudioSendStream::Stats> audioStats = + aConduit->GetSenderStats(); + if (audioStats.isNothing()) { + return; + } + + if (audioStats->packets_sent == 0) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp receiver data, if present. + // ReceiverReports have less information than SenderReports, so fill + // in what we can. + Maybe<webrtc::ReportBlockData> reportBlockData; + { + if (const auto remoteSsrc = aConduit->GetRemoteSSRC(); + remoteSsrc) { + for (auto& data : audioStats->report_block_datas) { + if (data.report_block().source_ssrc == ssrc && + data.report_block().sender_ssrc == *remoteSsrc) { + reportBlockData.emplace(data); + break; + } + } + } + } + reportBlockData.apply([&](auto& aReportBlockData) { + RTCRemoteInboundRtpStreamStats remote; + constructCommonRemoteInboundRtpStats(remote, aReportBlockData); + if (audioStats->jitter_ms >= 0) { + remote.mJitter.Construct(audioStats->jitter_ms / 1000.0); + } + if (audioStats->packets_lost >= 0) { + remote.mPacketsLost.Construct(audioStats->packets_lost); + } + if (audioStats->rtt_ms >= 0) { + remote.mRoundTripTime.Construct( + static_cast<double>(audioStats->rtt_ms) / 1000.0); + } + remote.mFractionLost.Construct(audioStats->fraction_lost); + remote.mTotalRoundTripTime.Construct( + double(aReportBlockData.sum_rtt_ms()) / 1000); + remote.mRoundTripTimeMeasurements.Construct( + aReportBlockData.num_rtts()); + if (!report->mRemoteInboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + }); + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCOutboundRtpStreamStats local; + constructCommonOutboundRtpStats(local); + local.mPacketsSent.Construct(audioStats->packets_sent); + local.mBytesSent.Construct(audioStats->payload_bytes_sent); + local.mNackCount.Construct( + audioStats->rtcp_packet_type_counts.nack_packets); + local.mHeaderBytesSent.Construct( + audioStats->header_and_padding_bytes_sent); + local.mRetransmittedPacketsSent.Construct( + audioStats->retransmitted_packets_sent); + local.mRetransmittedBytesSent.Construct( + audioStats->retransmitted_bytes_sent); + /* + * Potential new stats that are now available upstream. + * Note: when we last tried exposing this we were getting + * targetBitrate for audio was ending up as 0. We did not + * investigate why. + local.mTargetBitrate.Construct(audioStats->target_bitrate_bps); + */ + if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), + fallible)) { + mozalloc_handle_oom(0); + } + }); + + asVideo.apply([&](auto& aConduit) { + Maybe<webrtc::VideoSendStream::Stats> videoStats = + aConduit->GetSenderStats(); + if (videoStats.isNothing()) { + return; + } + + Maybe<webrtc::VideoSendStream::StreamStats> streamStats; + auto kv = videoStats->substreams.find(ssrc); + if (kv != videoStats->substreams.end()) { + streamStats = Some(kv->second); + } + + if (!streamStats) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply( + [&](const auto rtxSsrc) { + auto kv = videoStats->substreams.find(rtxSsrc); + if (kv != videoStats->substreams.end()) { + streamStats->rtp_stats.Add(kv->second.rtp_stats); + } + }); + + if (streamStats->rtp_stats.first_packet_time_ms == -1) { + return; + } + + // First, fill in remote stat with rtcp receiver data, if present. + // ReceiverReports have less information than SenderReports, so fill + // in what we can. + if (streamStats->report_block_data) { + const webrtc::ReportBlockData& rtcpReportData = + *streamStats->report_block_data; + RTCRemoteInboundRtpStreamStats remote; + remote.mJitter.Construct( + static_cast<double>(rtcpReportData.report_block().jitter) / + webrtc::kVideoPayloadTypeFrequency); + remote.mPacketsLost.Construct( + rtcpReportData.report_block().packets_lost); + if (rtcpReportData.has_rtt()) { + remote.mRoundTripTime.Construct( + static_cast<double>(rtcpReportData.last_rtt_ms()) / 1000.0); + } + constructCommonRemoteInboundRtpStats(remote, rtcpReportData); + remote.mTotalRoundTripTime.Construct( + streamStats->report_block_data->sum_rtt_ms() / 1000.0); + remote.mFractionLost.Construct( + static_cast<float>( + rtcpReportData.report_block().fraction_lost) / + (1 << 8)); + remote.mRoundTripTimeMeasurements.Construct( + streamStats->report_block_data->num_rtts()); + if (!report->mRemoteInboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCOutboundRtpStreamStats local; + constructCommonOutboundRtpStats(local); + local.mPacketsSent.Construct( + streamStats->rtp_stats.transmitted.packets); + local.mBytesSent.Construct( + streamStats->rtp_stats.transmitted.payload_bytes); + local.mNackCount.Construct( + streamStats->rtcp_packet_type_counts.nack_packets); + local.mFirCount.Construct( + streamStats->rtcp_packet_type_counts.fir_packets); + local.mPliCount.Construct( + streamStats->rtcp_packet_type_counts.pli_packets); + local.mFramesEncoded.Construct(streamStats->frames_encoded); + if (streamStats->qp_sum) { + local.mQpSum.Construct(*streamStats->qp_sum); + } + local.mHeaderBytesSent.Construct( + streamStats->rtp_stats.transmitted.header_bytes + + streamStats->rtp_stats.transmitted.padding_bytes); + local.mRetransmittedPacketsSent.Construct( + streamStats->rtp_stats.retransmitted.packets); + local.mRetransmittedBytesSent.Construct( + streamStats->rtp_stats.retransmitted.payload_bytes); + local.mTotalEncodedBytesTarget.Construct( + videoStats->total_encoded_bytes_target); + local.mFrameWidth.Construct(streamStats->width); + local.mFrameHeight.Construct(streamStats->height); + local.mFramesSent.Construct(streamStats->frames_encoded); + local.mHugeFramesSent.Construct(streamStats->huge_frames_sent); + local.mTotalEncodeTime.Construct( + double(streamStats->total_encode_time_ms) / 1000.); + /* + * Potential new stats that are now available upstream. + local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps); + */ + if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), + fallible)) { + mozalloc_handle_oom(0); + } + }); + } + + auto constructCommonMediaSourceStats = + [&](RTCMediaSourceStats& aStats) { + nsString id = u"mediasource_"_ns + idstr + trackName; + aStats.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aStats.mId.Construct(id); + aStats.mType.Construct(RTCStatsType::Media_source); + aStats.mTrackIdentifier = trackName; + aStats.mKind = kind; + }; + + // TODO(bug 1804678): Use RTCAudioSourceStats/RTCVideoSourceStats + RTCMediaSourceStats mediaSourceStats; + constructCommonMediaSourceStats(mediaSourceStats); + if (!report->mMediaSourceStats.AppendElement( + std::move(mediaSourceStats), fallible)) { + mozalloc_handle_oom(0); + } + + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + + if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) { + promises.AppendElement(mTransportHandler->GetIceStats( + GetJsepTransceiver().mTransport.mTransportId, + mPipeline->GetTimestampMaker().GetNow().ToDom())); + } + + return promises; +} + +void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind, + Nullable<dom::RTCRtpCapabilities>& aResult) { + PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend); +} + +void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) { + nsCString warning( + "WARNING! Invalid setParameters call detected! The good news? Firefox " + "supports sendEncodings in addTransceiver now, so we ask that you switch " + "over to using the parameters code you use for other browsers. Thank you " + "for your patience and support. The specific error was: "); + warning += aError; + mPc->SendWarningToConsole(warning); +} + +nsCString RTCRtpSender::GetEffectiveTLDPlus1() const { + return mPc->GetEffectiveTLDPlus1(); +} + +already_AddRefed<Promise> RTCRtpSender::SetParameters( + const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) { + dom::RTCRtpSendParameters paramsCopy(aParameters); + // When the setParameters method is called, the user agent MUST run the + // following steps: + // Let parameters be the method's first argument. + // Let sender be the RTCRtpSender object on which setParameters is invoked. + // Let transceiver be the RTCRtpTransceiver object associated with sender + // (i.e.sender is transceiver.[[Sender]]). + + RefPtr<dom::Promise> p = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + if (mPc->IsClosed()) { + p->MaybeRejectWithInvalidStateError("Peer connection is closed"); + return p.forget(); + } + + // If transceiver.[[Stopped]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (mTransceiver->Stopped()) { + p->MaybeRejectWithInvalidStateError("This sender's transceiver is stopped"); + return p.forget(); + } + + // If sender.[[LastReturnedParameters]] is null, return a promise rejected + // with a newly created InvalidStateError. + if (!mLastReturnedParameters.isSome()) { + nsCString error( + "Cannot call setParameters without first calling getParameters"); + if (mAllowOldSetParameters) { + if (!mHaveWarnedBecauseNoGetParameters) { + mHaveWarnedBecauseNoGetParameters = true; + mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_no_getparameters + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else { + if (!mHaveFailedBecauseNoGetParameters) { + mHaveFailedBecauseNoGetParameters = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidStateError(error); + return p.forget(); + } + } + + // According to the spec, our consistency checking is based on + // [[LastReturnedParameters]], but if we're letting + // [[LastReturnedParameters]]==null slide, we still want to do + // consistency checking on _something_ so we can warn implementers if they + // are messing that up also. Just find something, _anything_, to do that + // checking with. + // TODO(bug 1803388): Remove this stuff once it is no longer needed. + // TODO(bug 1803389): Remove the glean errors once they are no longer needed. + Maybe<RTCRtpSendParameters> oldParams; + if (mAllowOldSetParameters) { + if (mLastReturnedParameters.isSome()) { + oldParams = mLastReturnedParameters; + } else if (mPendingParameters.isSome()) { + oldParams = mPendingParameters; + } else { + oldParams = Some(mParameters); + } + MOZ_ASSERT(oldParams.isSome()); + } else { + oldParams = mLastReturnedParameters; + } + MOZ_ASSERT(oldParams.isSome()); + + // Validate parameters by running the following steps: + // Let encodings be parameters.encodings. + // Let codecs be parameters.codecs. + // Let N be the number of RTCRtpEncodingParameters stored in + // sender.[[SendEncodings]]. + // If any of the following conditions are met, + // return a promise rejected with a newly created InvalidModificationError: + + bool pendingRidChangeFromCompatMode = false; + // encodings.length is different from N. + if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) { + nsCString error("Cannot change the number of encodings with setParameters"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseEncodingCountChange) { + mHaveFailedBecauseEncodingCountChange = true; + mozilla::glean::rtcrtpsender_setparameters::fail_length_changed + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + // Make sure we don't use the old rids in SyncToJsep while we wait for the + // queued task below to update mParameters. + pendingRidChangeFromCompatMode = true; + mSimulcastEnvelopeSet = true; + if (!mHaveWarnedBecauseEncodingCountChange) { + mHaveWarnedBecauseEncodingCountChange = true; + mozilla::glean::rtcrtpsender_setparameters::warn_length_changed + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_length_changed + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else { + // encodings has been re-ordered. + for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) { + const auto& oldEncoding = oldParams->mEncodings[i]; + const auto& newEncoding = paramsCopy.mEncodings[i]; + if (oldEncoding.mRid != newEncoding.mRid) { + nsCString error("Cannot change rid, or reorder encodings"); + if (!mHaveFailedBecauseRidChange) { + mHaveFailedBecauseRidChange = true; + mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + } + } + + // TODO(bug 1803388): Handle this in webidl, once we stop allowing the old + // setParameters style. + if (!paramsCopy.mTransactionId.WasPassed()) { + nsCString error("transactionId is not set!"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseNoTransactionId) { + mHaveFailedBecauseNoTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid + .AddToNumerator(1); + } + p->MaybeRejectWithTypeError(error); + return p.forget(); + } + if (!mHaveWarnedBecauseNoTransactionId) { + mHaveWarnedBecauseNoTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_no_transactionid + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else if (oldParams->mTransactionId != paramsCopy.mTransactionId) { + // Any parameter in parameters is marked as a Read-only parameter (such as + // RID) and has a value that is different from the corresponding parameter + // value in sender.[[LastReturnedParameters]]. Note that this also applies + // to transactionId. + nsCString error( + "Cannot change transaction id: call getParameters, modify the result, " + "and then call setParameters"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseStaleTransactionId) { + mHaveFailedBecauseStaleTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + if (!mHaveWarnedBecauseStaleTransactionId) { + mHaveWarnedBecauseStaleTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::warn_stale_transactionid + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_stale_transactionid + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } + + // This could conceivably happen if we are allowing the old setParameters + // behavior. + if (!paramsCopy.mEncodings.Length()) { + nsCString error("Cannot set an empty encodings array"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseNoEncodings) { + mHaveFailedBecauseNoEncodings = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings + .AddToNumerator(1); + } + + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + // TODO: Add some warning telemetry here + WarnAboutBadSetParameters(error); + // Just don't do this; it's stupid. + paramsCopy.mEncodings = oldParams->mEncodings; + } + + // TODO: Verify remaining read-only parameters + // headerExtensions (bug 1765851) + // rtcp (bug 1765852) + // codecs (bug 1534687) + + // CheckAndRectifyEncodings handles the following steps: + // If transceiver kind is "audio", remove the scaleResolutionDownBy member + // from all encodings that contain one. + // + // If transceiver kind is "video", and any encoding in encodings contains a + // scaleResolutionDownBy member whose value is less than 1.0, return a + // promise rejected with a newly created RangeError. + // + // Verify that each encoding in encodings has a maxFramerate member whose + // value is greater than or equal to 0.0. If one of the maxFramerate values + // does not meet this requirement, return a promise rejected with a newly + // created RangeError. + ErrorResult rv; + CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), rv); + if (rv.Failed()) { + if (!mHaveFailedBecauseOtherError) { + mHaveFailedBecauseOtherError = true; + mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1); + } + p->MaybeReject(std::move(rv)); + return p.forget(); + } + + // If transceiver kind is "video", then for each encoding in encodings that + // doesn't contain a scaleResolutionDownBy member, add a + // scaleResolutionDownBy member with the value 1.0. + if (mTransceiver->IsVideo()) { + for (auto& encoding : paramsCopy.mEncodings) { + if (!encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Construct(1.0); + } + } + } + + // Let p be a new promise. (see above) + + // In parallel, configure the media stack to use parameters to transmit + // sender.[[SenderTrack]]. + // Right now this is infallible. That may change someday. + + // We need to put this in a member variable, since MaybeUpdateConduit needs it + // This also allows PeerConnectionImpl to detect when there is a pending + // setParameters, which has implcations for the handling of + // setRemoteDescription. + mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode; + mPendingParameters = Some(paramsCopy); + uint32_t serialNumber = ++mNumSetParametersCalls; + MaybeUpdateConduit(); + + // If the media stack is successfully configured with parameters, + // queue a task to run the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr<RTCRtpSender>(this), p, paramsCopy, serialNumber] { + // Set sender.[[LastReturnedParameters]] to null. + mLastReturnedParameters = Nothing(); + // Set sender.[[SendEncodings]] to parameters.encodings. + mParameters = paramsCopy; + UpdateRestorableEncodings(mParameters.mEncodings); + // Only clear mPendingParameters if it matches; there could have been + // back-to-back calls to setParameters, and we only want to clear this + // if no subsequent setParameters is pending. + if (serialNumber == mNumSetParametersCalls) { + mPendingParameters = Nothing(); + // Ok, nothing has called SyncToJsep while this async task was + // pending. No need for special handling anymore. + mPendingRidChangeFromCompatMode = false; + } + MOZ_ASSERT(mParameters.mEncodings.Length()); + // Resolve p with undefined. + p->MaybeResolveWithUndefined(); + })); + + // Return p. + return p.forget(); +} + +// static +void RTCRtpSender::CheckAndRectifyEncodings( + Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo, + ErrorResult& aRv) { + // If any encoding contains a rid member whose value does not conform to the + // grammar requirements specified in Section 10 of [RFC8851], throw a + // TypeError. + for (const auto& encoding : aEncodings) { + if (encoding.mRid.WasPassed()) { + std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); + std::string error; + if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) { + aRv.ThrowTypeError(nsCString(error)); + return; + } + if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) { + std::ostringstream ss; + ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength + << " characters long (due to internal limitations)"; + aRv.ThrowTypeError(nsCString(ss.str())); + return; + } + } + } + + if (aEncodings.Length() > 1) { + // If some but not all encodings contain a rid member, throw a TypeError. + // rid must be set if there is more than one encoding + // NOTE: Since rid is read-only, and the number of encodings cannot grow, + // this should never happen in setParameters. + for (const auto& encoding : aEncodings) { + if (!encoding.mRid.WasPassed()) { + aRv.ThrowTypeError("Missing rid"); + return; + } + } + + // If any encoding contains a rid member whose value is the same as that of + // a rid contained in another encoding in sendEncodings, throw a TypeError. + // NOTE: Since rid is read-only, and the number of encodings cannot grow, + // this should never happen in setParameters. + std::set<nsString> uniqueRids; + for (const auto& encoding : aEncodings) { + if (uniqueRids.count(encoding.mRid.Value())) { + aRv.ThrowTypeError("Duplicate rid"); + return; + } + uniqueRids.insert(encoding.mRid.Value()); + } + } + // TODO: ptime/adaptivePtime validation (bug 1733647) + + // If kind is "audio", remove the scaleResolutionDownBy member from all + // encodings that contain one. + if (!aVideo) { + for (auto& encoding : aEncodings) { + if (encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Reset(); + } + if (encoding.mMaxFramerate.WasPassed()) { + encoding.mMaxFramerate.Reset(); + } + } + } + + // If any encoding contains a scaleResolutionDownBy member whose value is + // less than 1.0, throw a RangeError. + for (const auto& encoding : aEncodings) { + if (encoding.mScaleResolutionDownBy.WasPassed()) { + if (encoding.mScaleResolutionDownBy.Value() < 1.0f) { + aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0"); + return; + } + } + } + + // Verify that the value of each maxFramerate member in sendEncodings that is + // defined is greater than 0.0. If one of the maxFramerate values does not + // meet this requirement, throw a RangeError. + for (const auto& encoding : aEncodings) { + if (encoding.mMaxFramerate.WasPassed()) { + if (encoding.mMaxFramerate.Value() < 0.0f) { + aRv.ThrowRangeError("maxFramerate must be non-negative"); + return; + } + } + } +} + +void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) { + MOZ_ASSERT(mParameters.mEncodings.Length()); + // If sender.[[LastReturnedParameters]] is not null, return + // sender.[[LastReturnedParameters]], and abort these steps. + if (mLastReturnedParameters.isSome()) { + aParameters = *mLastReturnedParameters; + return; + } + + // Let result be a new RTCRtpSendParameters dictionary constructed as follows: + + // transactionId is set to a new unique identifier + aParameters.mTransactionId.Construct(mPc->GenerateUUID()); + + // encodings is set to the value of the [[SendEncodings]] internal slot. + aParameters.mEncodings = mParameters.mEncodings; + + // The headerExtensions sequence is populated based on the header extensions + // that have been negotiated for sending + // TODO(bug 1765851): We do not support this yet + // aParameters.mHeaderExtensions.Construct(); + + // codecs is set to the value of the [[SendCodecs]] internal slot + // TODO(bug 1534687): We do not support this yet + + // rtcp.cname is set to the CNAME of the associated RTCPeerConnection. + // rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated + // for sending, and false otherwise. + // TODO(bug 1765852): We do not support this yet + aParameters.mRtcp.Construct(); + aParameters.mRtcp.Value().mCname.Construct(); + aParameters.mRtcp.Value().mReducedSize.Construct(false); + aParameters.mHeaderExtensions.Construct(); + aParameters.mCodecs.Construct(); + + // Set sender.[[LastReturnedParameters]] to result. + mLastReturnedParameters = Some(aParameters); + + // Queue a task that sets sender.[[LastReturnedParameters]] to null. + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr<RTCRtpSender>(this)] { + mLastReturnedParameters = Nothing(); + })); +} + +bool operator==(const RTCRtpEncodingParameters& a1, + const RTCRtpEncodingParameters& a2) { + // webidl does not generate types that are equality comparable + return a1.mActive == a2.mActive && a1.mFec == a2.mFec && + a1.mMaxBitrate == a2.mMaxBitrate && + a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority && + a1.mRid == a2.mRid && a1.mRtx == a2.mRtx && + a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy && + a1.mSsrc == a2.mSsrc; +} + +// static +void RTCRtpSender::ApplyJsEncodingToConduitEncoding( + const RTCRtpEncodingParameters& aJsEncoding, + VideoCodecConfig::Encoding* aConduitEncoding) { + aConduitEncoding->active = aJsEncoding.mActive; + if (aJsEncoding.mMaxBitrate.WasPassed()) { + aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value(); + } + if (aJsEncoding.mMaxFramerate.WasPassed()) { + aConduitEncoding->constraints.maxFps = + Some(aJsEncoding.mMaxFramerate.Value()); + } + if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) { + // Optional does not have a valueOr, despite being based on Maybe + // :( + aConduitEncoding->constraints.scaleDownBy = + aJsEncoding.mScaleResolutionDownBy.Value(); + } else { + aConduitEncoding->constraints.scaleDownBy = 1.0f; + } +} + +void RTCRtpSender::UpdateRestorableEncodings( + const Sequence<RTCRtpEncodingParameters>& aEncodings) { + MOZ_ASSERT(aEncodings.Length()); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { + // Once initial negotiation completes, we are no longer allowed to restore + // the unicast encoding. + mUnicastEncoding.reset(); + } else if (mParameters.mEncodings.Length() == 1 && + !mParameters.mEncodings[0].mRid.WasPassed()) { + // If we have not completed the initial negotiation, and we currently are + // ridless unicast, we need to save our unicast encoding in case a + // rollback occurs. + mUnicastEncoding = Some(mParameters.mEncodings[0]); + } +} + +Sequence<RTCRtpEncodingParameters> RTCRtpSender::ToSendEncodings( + const std::vector<std::string>& aRids) const { + MOZ_ASSERT(!aRids.empty()); + + Sequence<RTCRtpEncodingParameters> result; + // If sendEncodings is given as input to this algorithm, and is non-empty, + // set the [[SendEncodings]] slot to sendEncodings. + for (const auto& rid : aRids) { + MOZ_ASSERT(!rid.empty()); + RTCRtpEncodingParameters encoding; + encoding.mActive = true; + encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); + Unused << result.AppendElement(encoding, fallible); + } + + // If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy + // to 2^(length of sendEncodings - encoding index - 1). + if (mTransceiver->IsVideo()) { + double scale = 1.0f; + for (auto it = result.rbegin(); it != result.rend(); ++it) { + it->mScaleResolutionDownBy.Construct(scale); + scale *= 2; + } + } + + return result; +} + +void RTCRtpSender::MaybeGetJsepRids() { + MOZ_ASSERT(!mSimulcastEnvelopeSet); + MOZ_ASSERT(mParameters.mEncodings.Length()); + + auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids(); + if (!jsepRids.empty()) { + UpdateRestorableEncodings(mParameters.mEncodings); + if (jsepRids.size() != 1 || !jsepRids[0].empty()) { + // JSEP is using at least one rid. Stomp our single ridless encoding + mParameters.mEncodings = ToSendEncodings(jsepRids); + } + mSimulcastEnvelopeSet = true; + mSimulcastEnvelopeSetByJSEP = true; + } +} + +Sequence<RTCRtpEncodingParameters> RTCRtpSender::GetMatchingEncodings( + const std::vector<std::string>& aRids) const { + Sequence<RTCRtpEncodingParameters> result; + + if (!aRids.empty() && !aRids[0].empty()) { + // Simulcast, or unicast with rid + for (const auto& encoding : mParameters.mEncodings) { + for (const auto& rid : aRids) { + auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str()); + if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) { + auto encodingCopy(encoding); + if (!encodingCopy.mRid.WasPassed()) { + encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); + } + Unused << result.AppendElement(encodingCopy, fallible); + break; + } + } + } + } + + // If we're allowing the old setParameters behavior, we _might_ be able to + // get into this situation even if there were rids above. Be extra careful. + // Under normal circumstances, this just handles the ridless case. + if (!result.Length()) { + // Unicast with no specified rid. Restore mUnicastEncoding, if + // it exists, otherwise pick the first encoding. + if (mUnicastEncoding.isSome()) { + Unused << result.AppendElement(*mUnicastEncoding, fallible); + } else { + Unused << result.AppendElement(mParameters.mEncodings[0], fallible); + } + } + + return result; +} + +void RTCRtpSender::SetStreams( + const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, ErrorResult& aRv) { + if (mPc->IsClosed()) { + aRv.ThrowInvalidStateError( + "Cannot call setStreams if the peer connection is closed"); + return; + } + + SetStreamsImpl(aStreams); + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpSender::SetStreamsImpl( + const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) { + mStreams.Clear(); + std::set<nsString> ids; + for (const auto& stream : aStreams) { + nsString id; + stream->GetId(id); + if (!ids.count(id)) { + ids.insert(id); + mStreams.AppendElement(stream); + } + } +} + +void RTCRtpSender::GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams) { + aStreams = mStreams.Clone(); +} + +class ReplaceTrackOperation final : public PeerConnectionImpl::Operation { + public: + ReplaceTrackOperation(PeerConnectionImpl* aPc, + const RefPtr<RTCRtpTransceiver>& aTransceiver, + const RefPtr<MediaStreamTrack>& aTrack, + ErrorResult& aError); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation, + PeerConnectionImpl::Operation) + + private: + MOZ_CAN_RUN_SCRIPT + RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override; + ~ReplaceTrackOperation() = default; + RefPtr<RTCRtpTransceiver> mTransceiver; + RefPtr<MediaStreamTrack> mNewTrack; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation, + PeerConnectionImpl::Operation, mTransceiver, + mNewTrack) + +NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) +NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation) +NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation) + +ReplaceTrackOperation::ReplaceTrackOperation( + PeerConnectionImpl* aPc, const RefPtr<RTCRtpTransceiver>& aTransceiver, + const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aError) + : PeerConnectionImpl::Operation(aPc, aError), + mTransceiver(aTransceiver), + mNewTrack(aTrack) {} + +RefPtr<dom::Promise> ReplaceTrackOperation::CallImpl(ErrorResult& aError) { + RefPtr<RTCRtpSender> sender = mTransceiver->Sender(); + // If transceiver.[[Stopped]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (mTransceiver->Stopped()) { + RefPtr<dom::Promise> error = sender->MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s Cannot call replaceTrack when transceiver is stopped", + __FUNCTION__)); + error->MaybeRejectWithInvalidStateError( + "Cannot call replaceTrack when transceiver is stopped"); + return error; + } + + // Let p be a new promise. + RefPtr<dom::Promise> p = sender->MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + if (!sender->SeamlessTrackSwitch(mNewTrack)) { + MOZ_LOG(gSenderLog, LogLevel::Info, + ("%s Could not seamlessly replace track", __FUNCTION__)); + p->MaybeRejectWithInvalidModificationError( + "Could not seamlessly replace track"); + return p; + } + + // Queue a task that runs the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { + // If connection.[[IsClosed]] is true, abort these steps. + // Set sender.[[SenderTrack]] to withTrack. + if (sender->SetSenderTrackWithClosedCheck(track)) { + // Resolve p with undefined. + p->MaybeResolveWithUndefined(); + } + })); + + // Return p. + return p; +} + +already_AddRefed<dom::Promise> RTCRtpSender::ReplaceTrack( + dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) { + // If withTrack is non-null and withTrack.kind differs from the transceiver + // kind of transceiver, return a promise rejected with a newly created + // TypeError. + if (aWithTrack) { + nsString newKind; + aWithTrack->GetKind(newKind); + nsString oldKind; + mTransceiver->GetKind(oldKind); + if (newKind != oldKind) { + RefPtr<dom::Promise> error = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + error->MaybeRejectWithTypeError( + "Cannot replaceTrack with a different kind!"); + return error.forget(); + } + } + + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(), + __FUNCTION__, mSenderTrack.get(), aWithTrack)); + + // Return the result of chaining the following steps to connection's + // operations chain: + RefPtr<PeerConnectionImpl::Operation> op = + new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError); + if (aError.Failed()) { + return nullptr; + } + // Static analysis forces us to use a temporary. + auto pc = mPc; + return pc->Chain(op, aError); +} + +nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; } + +already_AddRefed<dom::Promise> RTCRtpSender::MakePromise( + ErrorResult& aError) const { + return mPc->MakePromise(aError); +} + +bool RTCRtpSender::SeamlessTrackSwitch( + const RefPtr<MediaStreamTrack>& aWithTrack) { + // We do not actually update mSenderTrack here! Spec says that happens in a + // queued task after this is done (this happens in + // SetSenderTrackWithClosedCheck). + + mPipeline->SetTrack(aWithTrack); + + MaybeUpdateConduit(); + + // There may eventually be cases where a renegotiation is necessary to switch. + return true; +} + +void RTCRtpSender::SetTrack(const RefPtr<MediaStreamTrack>& aTrack) { + // Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack + mSenderTrack = aTrack; + SeamlessTrackSwitch(aTrack); + if (aTrack) { + // RFC says (in the section on remote rollback): + // However, an RtpTransceiver MUST NOT be removed if a track was attached + // to the RtpTransceiver via the addTrack method. + mAddTrackCalled = true; + } +} + +bool RTCRtpSender::SetSenderTrackWithClosedCheck( + const RefPtr<MediaStreamTrack>& aTrack) { + if (!mPc->IsClosed()) { + mSenderTrack = aTrack; + return true; + } + + return false; +} + +void RTCRtpSender::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + mWatchManager.Shutdown(); + mPipeline->Shutdown(); + mPipeline = nullptr; +} + +void RTCRtpSender::BreakCycles() { + mWindow = nullptr; + mPc = nullptr; + mSenderTrack = nullptr; + mTransceiver = nullptr; + mStreams.Clear(); + mDtmf = nullptr; +} + +void RTCRtpSender::UpdateTransport() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHaveSetupTransport) { + mPipeline->SetLevel(GetJsepTransceiver().GetLevel()); + mHaveSetupTransport = true; + } + + mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId, + nullptr); +} + +void RTCRtpSender::MaybeUpdateConduit() { + // 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 (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) { + MOZ_ASSERT( + false, + "No local ssrcs! This is a bug in the jsep engine, and should never " + "happen!"); + return; + } + + if (!mPipeline) { + return; + } + + bool wasTransmitting = mTransmitting; + + if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) { + Maybe<VideoConfig> newConfig = GetNewVideoConfig(); + if (newConfig.isSome()) { + ApplyVideoConfig(*newConfig); + } + } else { + Maybe<AudioConfig> newConfig = GetNewAudioConfig(); + if (newConfig.isSome()) { + ApplyAudioConfig(*newConfig); + } + } + + if (!mSenderTrack && !wasTransmitting && mTransmitting) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s Starting transmit conduit without send track!", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + } +} + +void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) { + if (!mSimulcastEnvelopeSet) { + // JSEP is establishing the simulcast envelope for the first time, right now + // This is the addTrack (or addTransceiver without sendEncodings) case. + MaybeGetJsepRids(); + } else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() || + !aJsepTransceiver.mSendTrack.IsInHaveRemote()) { + // Spec says that we do not update our encodings until we're in stable, + // _unless_ this is the first negotiation. + std::vector<std::string> rids = aJsepTransceiver.mSendTrack.GetRids(); + if (mSimulcastEnvelopeSetByJSEP && rids.empty()) { + // JSEP previously set the simulcast envelope, but now it has no opinion + // regarding unicast/simulcast. This can only happen on rollback of the + // initial remote offer. + mParameters.mEncodings = GetMatchingEncodings(rids); + MOZ_ASSERT(mParameters.mEncodings.Length()); + mSimulcastEnvelopeSetByJSEP = false; + mSimulcastEnvelopeSet = false; + } else if (!rids.empty()) { + // JSEP has an opinion on the simulcast envelope, which trumps anything + // we have already. + mParameters.mEncodings = GetMatchingEncodings(rids); + MOZ_ASSERT(mParameters.mEncodings.Length()); + } + } + + MaybeUpdateConduit(); +} + +void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const { + std::vector<std::string> streamIds; + for (const auto& stream : mStreams) { + nsString wideStreamId; + stream->GetId(wideStreamId); + std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get(); + MOZ_ASSERT(!streamId.empty()); + streamIds.push_back(streamId); + } + + aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds); + + if (mSimulcastEnvelopeSet) { + std::vector<std::string> rids; + Maybe<RTCRtpSendParameters> parameters; + if (mPendingRidChangeFromCompatMode) { + // *sigh* If we have just let a setParameters change our rids, but we have + // not yet updated mParameters because the queued task hasn't run yet, + // we want to set the _new_ rids on the JsepTrack. So, we are forced to + // grab them from mPendingParameters. + parameters = mPendingParameters; + } else { + parameters = Some(mParameters); + } + for (const auto& encoding : parameters->mEncodings) { + if (encoding.mRid.WasPassed()) { + rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get()); + } else { + rids.push_back(""); + } + } + + aJsepTransceiver.mSendTrack.SetRids(rids); + } + + if (mTransceiver->IsVideo()) { + aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams); + } else { + aJsepTransceiver.mSendTrack.SetMaxEncodings(1); + } + + if (mAddTrackCalled) { + aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false); + } +} + +Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() { + // 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 (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { + return Nothing(); + } + + VideoConfig oldConfig; + oldConfig.mSsrcs = mSsrcs; + oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; + oldConfig.mCname = mCname; + oldConfig.mTransmitting = mTransmitting; + oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs; + oldConfig.mVideoCodec = mVideoCodec; + oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig; + oldConfig.mVideoCodecMode = mVideoCodecMode; + + VideoConfig newConfig(oldConfig); + + UpdateBaseConfig(&newConfig); + + newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs(); + + const JsepTrackNegotiatedDetails details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + + if (mSenderTrack) { + RefPtr<mozilla::dom::VideoStreamTrack> videotrack = + mSenderTrack->AsVideoStreamTrack(); + + if (!videotrack) { + MOZ_CRASH( + "In ConfigureVideoCodecMode, mSenderTrack is not video! This should " + "never happen!"); + } + + dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); + switch (source) { + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Window: + case dom::MediaSourceEnum::Application: + newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing; + break; + + case dom::MediaSourceEnum::Camera: + case dom::MediaSourceEnum::Other: + // Other is used by canvas capture, which we treat as realtime video. + // This seems debatable, but we've been doing it this way for a long + // time, so this is likely fine. + newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo; + break; + + case dom::MediaSourceEnum::Microphone: + case dom::MediaSourceEnum::AudioCapture: + case dom::MediaSourceEnum::EndGuard_: + MOZ_ASSERT(false); + break; + } + } + + std::vector<VideoCodecConfig> configs; + RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs); + + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gSenderLog, LogLevel::Error, + ("%s[%s]: %s No video codecs were negotiated (send).", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + newConfig.mVideoCodec = Some(configs[0]); + // Spec says that we start using new parameters right away, _before_ we + // update the parameters that are visible to JS (ie; mParameters). + const RTCRtpSendParameters& parameters = + mPendingParameters.isSome() ? *mPendingParameters : mParameters; + for (VideoCodecConfig::Encoding& conduitEncoding : + newConfig.mVideoCodec->mEncodings) { + for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) { + std::string rid; + if (jsEncoding.mRid.WasPassed()) { + rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get(); + } + if (conduitEncoding.rid == rid) { + ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding); + break; + } + } + } + + newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig()); + + if (newConfig == oldConfig) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + if (newConfig.mVideoCodec.isSome()) { + MOZ_ASSERT(newConfig.mSsrcs.size() == + newConfig.mVideoCodec->mEncodings.size()); + } + return Some(newConfig); +} + +Maybe<RTCRtpSender::AudioConfig> RTCRtpSender::GetNewAudioConfig() { + AudioConfig oldConfig; + oldConfig.mSsrcs = mSsrcs; + oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; + oldConfig.mCname = mCname; + oldConfig.mTransmitting = mTransmitting; + oldConfig.mAudioCodec = mAudioCodec; + + AudioConfig newConfig(oldConfig); + + UpdateBaseConfig(&newConfig); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mSendTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + + std::vector<AudioCodecConfig> configs; + RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gSenderLog, LogLevel::Error, + ("%s[%s]: %s No audio codecs were negotiated (send)", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + std::vector<AudioCodecConfig> dtmfConfigs; + std::copy_if( + configs.begin(), configs.end(), std::back_inserter(dtmfConfigs), + [](const auto& value) { return value.mName == "telephone-event"; }); + + const AudioCodecConfig& sendCodec = configs[0]; + + if (!dtmfConfigs.empty()) { + // There is at least one telephone-event codec. + // We primarily choose the codec whose frequency matches the send codec. + // Secondarily we choose the one with the lowest frequency. + auto dtmfIterator = + std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(), + [&sendCodec](const auto& dtmfCodec) { + return dtmfCodec.mFreq == sendCodec.mFreq; + }); + if (dtmfIterator == dtmfConfigs.end()) { + dtmfIterator = std::min_element( + dtmfConfigs.begin(), dtmfConfigs.end(), + [](const auto& a, const auto& b) { return a.mFreq < b.mFreq; }); + } + MOZ_ASSERT(dtmfIterator != dtmfConfigs.end()); + newConfig.mDtmfPt = dtmfIterator->mType; + newConfig.mDtmfFreq = dtmfIterator->mFreq; + } + + newConfig.mAudioCodec = Some(sendCodec); + } + + if (newConfig == oldConfig) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + return Some(newConfig); +} + +void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) { + aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs(); + aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME(); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mSendTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + { + std::vector<webrtc::RtpExtension> extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + aConfig->mLocalRtpExtensions = extmaps; + } + } + // RTCRtpTransceiver::IsSending is updated after negotiation completes, in a + // queued task (which we may be in right now). Don't use + // JsepTrack::GetActive, because that updates before the queued task, which + // is too early for some of the things we interact with here (eg; + // RTCDTMFSender). + aConfig->mTransmitting = mTransceiver->IsSending(); +} + +void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) { + if (aConfig.mVideoCodec.isSome()) { + MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size()); + } + + mSsrcs = aConfig.mSsrcs; + mCname = aConfig.mCname; + mLocalRtpExtensions = aConfig.mLocalRtpExtensions; + + mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs; + mVideoCodec = aConfig.mVideoCodec; + mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig; + mVideoCodecMode = aConfig.mVideoCodecMode; + + mTransmitting = aConfig.mTransmitting; +} + +void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) { + mTransmitting = false; + + mSsrcs = aConfig.mSsrcs; + mCname = aConfig.mCname; + mLocalRtpExtensions = aConfig.mLocalRtpExtensions; + + mAudioCodec = aConfig.mAudioCodec; + + if (aConfig.mDtmfPt >= 0) { + mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq); + } + + mTransmitting = aConfig.mTransmitting; +} + +void RTCRtpSender::Stop() { + MOZ_ASSERT(mTransceiver->Stopped()); + mTransmitting = false; +} + +bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const { + if (!mSenderTrack) { + return false; + } + + if (!aTrack) { + return true; + } + + return mSenderTrack.get() == aTrack; +} + +RefPtr<MediaPipelineTransmit> RTCRtpSender::GetPipeline() const { + return mPipeline; +} + +std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); } + +JsepTransceiver& RTCRtpSender::GetJsepTransceiver() { + return mTransceiver->GetJsepTransceiver(); +} + +void RTCRtpSender::UpdateDtmfSender() { + if (!mDtmf) { + return; + } + + if (mTransmitting) { + return; + } + + mDtmf->StopPlayout(); +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.h b/dom/media/webrtc/jsapi/RTCRtpSender.h new file mode 100644 index 0000000000..0c1282e0db --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpSender.h @@ -0,0 +1,260 @@ +/* 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 _RTCRtpSender_h_ +#define _RTCRtpSender_h_ + +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StateMirroring.h" +#include "mozilla/Maybe.h" +#include "js/RootingAPI.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCRtpParametersBinding.h" +#include "RTCStatsReport.h" +#include "jsep/JsepTrack.h" +#include "transportbridge/MediaPipeline.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +class MediaSessionConduit; +class MediaTransportHandler; +class JsepTransceiver; +class PeerConnectionImpl; +class DOMMediaStream; + +namespace dom { +class MediaStreamTrack; +class Promise; +class RTCDtlsTransport; +class RTCDTMFSender; +struct RTCRtpCapabilities; +class RTCRtpTransceiver; + +class RTCRtpSender : public nsISupports, + public nsWrapperCache, + public MediaPipelineTransmitControlInterface { + public: + RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, dom::MediaStreamTrack* aTrack, + const Sequence<RTCRtpEncodingParameters>& aEncodings, + RTCRtpTransceiver* aTransceiver); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender) + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // webidl + MediaStreamTrack* GetTrack() const { return mSenderTrack; } + RTCDtlsTransport* GetTransport() const; + RTCDTMFSender* GetDtmf() const; + MOZ_CAN_RUN_SCRIPT + already_AddRefed<Promise> ReplaceTrack(MediaStreamTrack* aWithTrack, + ErrorResult& aError); + already_AddRefed<Promise> GetStats(ErrorResult& aError); + static void GetCapabilities(const GlobalObject&, const nsAString& kind, + Nullable<dom::RTCRtpCapabilities>& result); + already_AddRefed<Promise> SetParameters( + const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError); + // Not a simple getter, so not const + // See https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters + void GetParameters(RTCRtpSendParameters& aParameters); + + static void CheckAndRectifyEncodings( + Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo, + ErrorResult& aRv); + + nsPIDOMWindowInner* GetParentObject() const; + nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal( + bool aSkipIceStats = false); + + void SetStreams(const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, + ErrorResult& aRv); + // ChromeOnly webidl + void GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams); + // ChromeOnly webidl + void SetStreamsImpl(const Sequence<OwningNonNull<DOMMediaStream>>& aStreams); + // ChromeOnly webidl + void SetTrack(const RefPtr<MediaStreamTrack>& aTrack); + void Shutdown(); + void BreakCycles(); + // Terminal state, reached through stopping RTCRtpTransceiver. + void Stop(); + bool HasTrack(const dom::MediaStreamTrack* aTrack) const; + bool IsMyPc(const PeerConnectionImpl* aPc) const { return mPc.get() == aPc; } + RefPtr<MediaPipelineTransmit> GetPipeline() const; + already_AddRefed<dom::Promise> MakePromise(ErrorResult& aError) const; + bool SeamlessTrackSwitch(const RefPtr<MediaStreamTrack>& aWithTrack); + bool SetSenderTrackWithClosedCheck(const RefPtr<MediaStreamTrack>& aTrack); + + // This is called when we set an answer (ie; when the transport is finalized). + void UpdateTransport(); + void SyncToJsep(JsepTransceiver& aJsepTransceiver) const; + void SyncFromJsep(const JsepTransceiver& aJsepTransceiver); + void MaybeUpdateConduit(); + + AbstractCanonical<Ssrcs>* CanonicalSsrcs() { return &mSsrcs; } + AbstractCanonical<Ssrcs>* CanonicalVideoRtxSsrcs() { return &mVideoRtxSsrcs; } + AbstractCanonical<RtpExtList>* CanonicalLocalRtpExtensions() { + return &mLocalRtpExtensions; + } + + AbstractCanonical<Maybe<AudioCodecConfig>>* CanonicalAudioCodec() { + return &mAudioCodec; + } + + AbstractCanonical<Maybe<VideoCodecConfig>>* CanonicalVideoCodec() { + return &mVideoCodec; + } + AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRtpRtcpConfig() { + return &mVideoRtpRtcpConfig; + } + AbstractCanonical<webrtc::VideoCodecMode>* CanonicalVideoCodecMode() { + return &mVideoCodecMode; + } + AbstractCanonical<std::string>* CanonicalCname() { return &mCname; } + AbstractCanonical<bool>* CanonicalTransmitting() override { + return &mTransmitting; + } + + bool HasPendingSetParameters() const { return mPendingParameters.isSome(); } + void InvalidateLastReturnedParameters() { + mLastReturnedParameters = Nothing(); + } + + private: + virtual ~RTCRtpSender(); + + std::string GetMid() const; + JsepTransceiver& GetJsepTransceiver(); + static void ApplyJsEncodingToConduitEncoding( + const RTCRtpEncodingParameters& aJsEncoding, + VideoCodecConfig::Encoding* aConduitEncoding); + void UpdateRestorableEncodings( + const Sequence<RTCRtpEncodingParameters>& aEncodings); + Sequence<RTCRtpEncodingParameters> GetMatchingEncodings( + const std::vector<std::string>& aRids) const; + Sequence<RTCRtpEncodingParameters> ToSendEncodings( + const std::vector<std::string>& aRids) const; + void MaybeGetJsepRids(); + void UpdateDtmfSender(); + + void WarnAboutBadSetParameters(const nsCString& aError); + nsCString GetEffectiveTLDPlus1() const; + + WatchManager<RTCRtpSender> mWatchManager; + nsCOMPtr<nsPIDOMWindowInner> mWindow; + RefPtr<PeerConnectionImpl> mPc; + RefPtr<dom::MediaStreamTrack> mSenderTrack; + bool mAddTrackCalled = false; + RTCRtpSendParameters mParameters; + Maybe<RTCRtpSendParameters> mPendingParameters; + uint32_t mNumSetParametersCalls = 0; + // When JSEP goes from simulcast to unicast without a rid, and we started out + // as unicast without a rid, we are supposed to restore that unicast encoding + // from before. + Maybe<RTCRtpEncodingParameters> mUnicastEncoding; + bool mSimulcastEnvelopeSet = false; + bool mSimulcastEnvelopeSetByJSEP = false; + bool mPendingRidChangeFromCompatMode = false; + Maybe<RTCRtpSendParameters> mLastReturnedParameters; + RefPtr<MediaPipelineTransmit> mPipeline; + RefPtr<MediaTransportHandler> mTransportHandler; + RefPtr<RTCRtpTransceiver> mTransceiver; + nsTArray<RefPtr<DOMMediaStream>> mStreams; + bool mHaveSetupTransport = false; + // TODO(bug 1803388): Remove this stuff once it is no longer needed. + bool mAllowOldSetParameters = false; + + // TODO(bug 1803388): Remove the glean warnings once they are no longer needed + bool mHaveWarnedBecauseNoGetParameters = false; + bool mHaveWarnedBecauseEncodingCountChange = false; + bool mHaveWarnedBecauseNoTransactionId = false; + bool mHaveWarnedBecauseStaleTransactionId = false; + // TODO(bug 1803389): Remove the glean errors once they are no longer needed. + bool mHaveFailedBecauseNoGetParameters = false; + bool mHaveFailedBecauseEncodingCountChange = false; + bool mHaveFailedBecauseRidChange = false; + bool mHaveFailedBecauseNoTransactionId = false; + bool mHaveFailedBecauseStaleTransactionId = false; + bool mHaveFailedBecauseNoEncodings = false; + bool mHaveFailedBecauseOtherError = false; + + RefPtr<dom::RTCDTMFSender> mDtmf; + + class BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const BaseConfig& aOther) const { + return mSsrcs == aOther.mSsrcs && + mLocalRtpExtensions == aOther.mLocalRtpExtensions && + mCname == aOther.mCname && mTransmitting == aOther.mTransmitting; + } + Ssrcs mSsrcs; + RtpExtList mLocalRtpExtensions; + std::string mCname; + bool mTransmitting = false; + }; + + class VideoConfig : public BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const VideoConfig& aOther) const { + return BaseConfig::operator==(aOther) && + mVideoRtxSsrcs == aOther.mVideoRtxSsrcs && + mVideoCodec == aOther.mVideoCodec && + mVideoRtpRtcpConfig == aOther.mVideoRtpRtcpConfig && + mVideoCodecMode == aOther.mVideoCodecMode; + } + Ssrcs mVideoRtxSsrcs; + Maybe<VideoCodecConfig> mVideoCodec; + Maybe<RtpRtcpConfig> mVideoRtpRtcpConfig; + webrtc::VideoCodecMode mVideoCodecMode = + webrtc::VideoCodecMode::kRealtimeVideo; + }; + + class AudioConfig : public BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const AudioConfig& aOther) const { + return BaseConfig::operator==(aOther) && + mAudioCodec == aOther.mAudioCodec && mDtmfPt == aOther.mDtmfPt && + mDtmfFreq == aOther.mDtmfFreq; + } + Maybe<AudioCodecConfig> mAudioCodec; + int32_t mDtmfPt = -1; + int32_t mDtmfFreq = 0; + }; + + Maybe<VideoConfig> GetNewVideoConfig(); + Maybe<AudioConfig> GetNewAudioConfig(); + void UpdateBaseConfig(BaseConfig* aConfig); + void ApplyVideoConfig(const VideoConfig& aConfig); + void ApplyAudioConfig(const AudioConfig& aConfig); + + Canonical<Ssrcs> mSsrcs; + Canonical<Ssrcs> mVideoRtxSsrcs; + Canonical<RtpExtList> mLocalRtpExtensions; + + Canonical<Maybe<AudioCodecConfig>> mAudioCodec; + Canonical<Maybe<VideoCodecConfig>> mVideoCodec; + Canonical<Maybe<RtpRtcpConfig>> mVideoRtpRtcpConfig; + Canonical<webrtc::VideoCodecMode> mVideoCodecMode; + Canonical<std::string> mCname; + Canonical<bool> mTransmitting; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCRtpSender_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp new file mode 100644 index 0000000000..bf4f74dd5d --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp @@ -0,0 +1,1080 @@ +/* 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/RTCRtpTransceiver.h" +#include "mozilla/UniquePtr.h" +#include <algorithm> +#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/Promise.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpReceiver.h" +#include "RTCRtpSender.h" +#include "RTCDTMFSender.h" +#include "systemservices/MediaUtils.h" +#include "libwebrtcglue/WebrtcCallWrapper.h" +#include "libwebrtcglue/WebrtcGmpVideoCodec.h" +#include "utils/PerformanceRecorder.h" + +namespace mozilla { + +using namespace dom; + +namespace { +struct ConduitControlState : public AudioConduitControlInterface, + public VideoConduitControlInterface { + ConduitControlState(RTCRtpTransceiver* aTransceiver, RTCRtpSender* aSender, + RTCRtpReceiver* aReceiver) + : mTransceiver(new nsMainThreadPtrHolder<RTCRtpTransceiver>( + "ConduitControlState::mTransceiver", aTransceiver, false)), + mSender(new nsMainThreadPtrHolder<dom::RTCRtpSender>( + "ConduitControlState::mSender", aSender, false)), + mReceiver(new nsMainThreadPtrHolder<dom::RTCRtpReceiver>( + "ConduitControlState::mReceiver", aReceiver, false)) {} + + const nsMainThreadPtrHandle<RTCRtpTransceiver> mTransceiver; + const nsMainThreadPtrHandle<RTCRtpSender> mSender; + const nsMainThreadPtrHandle<RTCRtpReceiver> mReceiver; + + // MediaConduitControlInterface + AbstractCanonical<bool>* CanonicalReceiving() override { + return mReceiver->CanonicalReceiving(); + } + AbstractCanonical<bool>* CanonicalTransmitting() override { + return mSender->CanonicalTransmitting(); + } + AbstractCanonical<Ssrcs>* CanonicalLocalSsrcs() override { + return mSender->CanonicalSsrcs(); + } + AbstractCanonical<std::string>* CanonicalLocalCname() override { + return mSender->CanonicalCname(); + } + AbstractCanonical<std::string>* CanonicalMid() override { + return mTransceiver->CanonicalMid(); + } + AbstractCanonical<Ssrc>* CanonicalRemoteSsrc() override { + return mReceiver->CanonicalSsrc(); + } + AbstractCanonical<std::string>* CanonicalSyncGroup() override { + return mTransceiver->CanonicalSyncGroup(); + } + AbstractCanonical<RtpExtList>* CanonicalLocalRecvRtpExtensions() override { + return mReceiver->CanonicalLocalRtpExtensions(); + } + AbstractCanonical<RtpExtList>* CanonicalLocalSendRtpExtensions() override { + return mSender->CanonicalLocalRtpExtensions(); + } + + // AudioConduitControlInterface + AbstractCanonical<Maybe<AudioCodecConfig>>* CanonicalAudioSendCodec() + override { + return mSender->CanonicalAudioCodec(); + } + AbstractCanonical<std::vector<AudioCodecConfig>>* CanonicalAudioRecvCodecs() + override { + return mReceiver->CanonicalAudioCodecs(); + } + MediaEventSource<DtmfEvent>& OnDtmfEvent() override { + return mSender->GetDtmf()->OnDtmfEvent(); + } + + // VideoConduitControlInterface + AbstractCanonical<Ssrcs>* CanonicalLocalVideoRtxSsrcs() override { + return mSender->CanonicalVideoRtxSsrcs(); + } + AbstractCanonical<Ssrc>* CanonicalRemoteVideoRtxSsrc() override { + return mReceiver->CanonicalVideoRtxSsrc(); + } + AbstractCanonical<Maybe<VideoCodecConfig>>* CanonicalVideoSendCodec() + override { + return mSender->CanonicalVideoCodec(); + } + AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoSendRtpRtcpConfig() + override { + return mSender->CanonicalVideoRtpRtcpConfig(); + } + AbstractCanonical<std::vector<VideoCodecConfig>>* CanonicalVideoRecvCodecs() + override { + return mReceiver->CanonicalVideoCodecs(); + } + AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRecvRtpRtcpConfig() + override { + return mReceiver->CanonicalVideoRtpRtcpConfig(); + } + AbstractCanonical<webrtc::VideoCodecMode>* CanonicalVideoCodecMode() + override { + return mSender->CanonicalVideoCodecMode(); + } +}; +} // namespace + +MOZ_MTLOG_MODULE("RTCRtpTransceiver") + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpTransceiver) + if (tmp->mHandlingUnlink) { + tmp->BreakCycles(); + tmp->mHandlingUnlink = false; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpTransceiver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSendTrack, mReceiver, + mSender, mDtlsTransport, + mLastStableDtlsTransport) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpTransceiver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpTransceiver) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpTransceiver) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, \ + "RTCRtpTransceiver::" #name " (Canonical)") + +RTCRtpTransceiver::RTCRtpTransceiver( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession, + const std::string& aTransceiverId, bool aIsVideo, + nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack, + WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator) + : mWindow(aWindow), + mPc(aPc), + mTransportHandler(aTransportHandler), + mTransceiverId(aTransceiverId), + mJsepTransceiver(*aJsepSession->GetTransceiver(mTransceiverId)), + mStsThread(aStsThread), + mCallWrapper(aCallWrapper), + mSendTrack(aSendTrack), + mIdGenerator(aIdGenerator), + mPrincipalPrivacy(aPrivacyNeeded ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate), + mIsVideo(aIsVideo), + INIT_CANONICAL(mMid, std::string()), + INIT_CANONICAL(mSyncGroup, std::string()) {} + +#undef INIT_CANONICAL + +RTCRtpTransceiver::~RTCRtpTransceiver() = default; + +SdpDirectionAttribute::Direction ToSdpDirection( + RTCRtpTransceiverDirection aDirection) { + switch (aDirection) { + case dom::RTCRtpTransceiverDirection::Sendrecv: + return SdpDirectionAttribute::Direction::kSendrecv; + case dom::RTCRtpTransceiverDirection::Sendonly: + return SdpDirectionAttribute::Direction::kSendonly; + case dom::RTCRtpTransceiverDirection::Recvonly: + return SdpDirectionAttribute::Direction::kRecvonly; + case dom::RTCRtpTransceiverDirection::Inactive: + return SdpDirectionAttribute::Direction::kInactive; + case dom::RTCRtpTransceiverDirection::EndGuard_:; + } + MOZ_CRASH("Invalid transceiver direction!"); +} + +static uint32_t sRemoteSourceId = 0; + +// TODO(bug 1401592): Once we implement the sendEncodings stuff, there will +// need to be validation code in here. +void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit, + ErrorResult& aRv) { + TrackingId trackingId(TrackingId::Source::RTCRtpReceiver, sRemoteSourceId++, + TrackingId::TrackAcrossProcesses::Yes); + if (IsVideo()) { + InitVideo(trackingId); + } else { + InitAudio(); + } + + if (!IsValid()) { + aRv = NS_ERROR_UNEXPECTED; + return; + } + + mReceiver = new RTCRtpReceiver(mWindow, mPrincipalPrivacy, mPc, + mTransportHandler, mCallWrapper->mCallThread, + mStsThread, mConduit, this, trackingId); + + mSender = new RTCRtpSender(mWindow, mPc, mTransportHandler, + mCallWrapper->mCallThread, mStsThread, mConduit, + mSendTrack, aInit.mSendEncodings, this); + + if (mConduit) { + InitConduitControl(); + } + + mSender->SetStreamsImpl(aInit.mStreams); + mDirection = aInit.mDirection; +} + +void RTCRtpTransceiver::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, + bool aStable) { + mDtlsTransport = aDtlsTransport; + if (aStable) { + mLastStableDtlsTransport = mDtlsTransport; + } +} + +void RTCRtpTransceiver::RollbackToStableDtlsTransport() { + mDtlsTransport = mLastStableDtlsTransport; +} + +void RTCRtpTransceiver::InitAudio() { + mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << ": Failed to create AudioSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void RTCRtpTransceiver::InitVideo(const TrackingId& aRecvTrackingId) { + VideoSessionConduit::Options options; + options.mVideoLatencyTestEnable = + Preferences::GetBool("media.video.test_latency", false); + options.mMinBitrate = std::max( + 0, + Preferences::GetInt("media.peerconnection.video.min_bitrate", 0) * 1000); + options.mStartBitrate = std::max( + 0, Preferences::GetInt("media.peerconnection.video.start_bitrate", 0) * + 1000); + options.mPrefMaxBitrate = std::max( + 0, + Preferences::GetInt("media.peerconnection.video.max_bitrate", 0) * 1000); + if (options.mMinBitrate != 0 && + options.mMinBitrate < kViEMinCodecBitrate_bps) { + options.mMinBitrate = kViEMinCodecBitrate_bps; + } + if (options.mStartBitrate < options.mMinBitrate) { + options.mStartBitrate = options.mMinBitrate; + } + if (options.mPrefMaxBitrate && + options.mStartBitrate > options.mPrefMaxBitrate) { + options.mStartBitrate = options.mPrefMaxBitrate; + } + // XXX We'd love if this was a live param for testing adaptation/etc + // in automation + options.mMinBitrateEstimate = + std::max(0, Preferences::GetInt( + "media.peerconnection.video.min_bitrate_estimate", 0) * + 1000); + options.mSpatialLayers = std::max( + 1, Preferences::GetInt("media.peerconnection.video.svc.spatial", 0)); + options.mTemporalLayers = std::max( + 1, Preferences::GetInt("media.peerconnection.video.svc.temporal", 0)); + options.mDenoising = + Preferences::GetBool("media.peerconnection.video.denoising", false); + options.mLockScaling = + Preferences::GetBool("media.peerconnection.video.lock_scaling", false); + + mConduit = + VideoSessionConduit::Create(mCallWrapper, mStsThread, std::move(options), + mPc->GetHandle(), aRecvTrackingId); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << ": Failed to create VideoSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void RTCRtpTransceiver::InitConduitControl() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mConduit); + ConduitControlState control(this, mSender, mReceiver); + mCallWrapper->mCallThread->Dispatch(NS_NewRunnableFunction( + __func__, [conduit = mConduit, control = std::move(control)]() mutable { + conduit->AsVideoSessionConduit().apply( + [&](VideoSessionConduit* aConduit) { + aConduit->InitControl(&control); + }); + conduit->AsAudioSessionConduit().apply( + [&](AudioSessionConduit* aConduit) { + aConduit->InitControl(&control); + }); + })); +} + +void RTCRtpTransceiver::Close() { + // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia -> + // PCMedia::SelfDestruct. Satisfies step 7 of + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close + mShutdown = true; + if (mDtlsTransport) { + mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED); + } + StopImpl(); +} + +void RTCRtpTransceiver::BreakCycles() { + mSender->BreakCycles(); + mReceiver->BreakCycles(); + mWindow = nullptr; + mSendTrack = nullptr; + mSender = nullptr; + mReceiver = nullptr; + mDtlsTransport = nullptr; + mLastStableDtlsTransport = nullptr; + mPc = nullptr; +} + +// TODO: Only called from one place in PeerConnectionImpl, synchronously, when +// the JSEP engine has successfully completed an offer/answer exchange. This is +// a bit squirrely, since identity validation happens asynchronously in +// PeerConnection.jsm. This probably needs to happen once all the "in parallel" +// steps have succeeded, but before we queue the task for JS observable state +// updates. +nsresult RTCRtpTransceiver::UpdateTransport() { + if (!mHasTransport) { + return NS_OK; + } + + mReceiver->UpdateTransport(); + mSender->UpdateTransport(); + return NS_OK; +} + +nsresult RTCRtpTransceiver::UpdateConduit() { + if (mStopped) { + return NS_OK; + } + + mReceiver->UpdateConduit(); + mSender->MaybeUpdateConduit(); + + return NS_OK; +} + +void RTCRtpTransceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) { + if (mPrincipalPrivacy == aPrivacy) { + return; + } + + mPrincipalPrivacy = aPrivacy; + mReceiver->UpdatePrincipalPrivacy(mPrincipalPrivacy); +} + +void RTCRtpTransceiver::ResetSync() { mSyncGroup = std::string(); } + +// TODO: Only called from one place in PeerConnectionImpl, synchronously, when +// the JSEP engine has successfully completed an offer/answer exchange. This is +// a bit squirrely, since identity validation happens asynchronously in +// PeerConnection.jsm. This probably needs to happen once all the "in parallel" +// steps have succeeded, but before we queue the task for JS observable state +// updates. +nsresult RTCRtpTransceiver::SyncWithMatchingVideoConduits( + nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers) { + if (mStopped) { + return NS_OK; + } + + if (IsVideo()) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " called when transceiver is not " + "video! This should never happen."); + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + std::set<std::string> myReceiveStreamIds; + myReceiveStreamIds.insert(mReceiver->GetStreamIds().begin(), + mReceiver->GetStreamIds().end()); + + for (RefPtr<RTCRtpTransceiver>& 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->Receiver()->GetStreamIds()) { + if (myReceiveStreamIds.count(streamId)) { + // Ok, we have one video, one non-video - cross the streams! + mSyncGroup = streamId; + transceiver->mSyncGroup = streamId; + + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __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 RTCRtpTransceiver::ConduitHasPluginID(uint64_t aPluginID) { + return mConduit && mConduit->HasCodecPluginID(aPluginID); +} + +void RTCRtpTransceiver::SyncFromJsep(const JsepSession& aSession) { + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " Syncing from JSEP transceiver"); + if (mShutdown) { + // Shutdown_m has already been called, probably due to pc.close(). Just + // nod and smile. + return; + } + + mJsepTransceiver = *aSession.GetTransceiver(mTransceiverId); + + // Transceivers can stop due to JSEP negotiation, so we need to check that + if (mJsepTransceiver.IsStopped()) { + StopImpl(); + } + + mReceiver->SyncFromJsep(mJsepTransceiver); + mSender->SyncFromJsep(mJsepTransceiver); + + // mid from JSEP + if (mJsepTransceiver.IsAssociated()) { + mMid = mJsepTransceiver.GetMid(); + } else { + mMid = std::string(); + } + + // currentDirection from JSEP, but not if "this transceiver has never been + // represented in an offer/answer exchange" + if (mJsepTransceiver.HasLevel() && mJsepTransceiver.IsNegotiated()) { + if (mJsepTransceiver.mRecvTrack.GetActive()) { + if (mJsepTransceiver.mSendTrack.GetActive()) { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendrecv); + mHasBeenUsedToSend = true; + } else { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Recvonly); + } + } else { + if (mJsepTransceiver.mSendTrack.GetActive()) { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendonly); + mHasBeenUsedToSend = true; + } else { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Inactive); + } + } + } + + mShouldRemove = mJsepTransceiver.IsRemoved(); + mHasTransport = mJsepTransceiver.HasLevel() && !mJsepTransceiver.IsStopped(); +} + +void RTCRtpTransceiver::SyncToJsep(JsepSession& aSession) const { + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " Syncing to JSEP transceiver"); + + aSession.ApplyToTransceiver( + mTransceiverId, [this, self = RefPtr<const RTCRtpTransceiver>(this)]( + JsepTransceiver& aTransceiver) { + mReceiver->SyncToJsep(aTransceiver); + mSender->SyncToJsep(aTransceiver); + aTransceiver.mJsDirection = ToSdpDirection(mDirection); + if (mStopped) { + aTransceiver.Stop(); + } + }); +} + +void RTCRtpTransceiver::GetKind(nsAString& aKind) const { + // The transceiver kind of an RTCRtpTransceiver is defined by the kind of the + // associated RTCRtpReceiver's MediaStreamTrack object. + MOZ_ASSERT(mReceiver && mReceiver->Track()); + mReceiver->Track()->GetKind(aKind); +} + +void RTCRtpTransceiver::GetMid(nsAString& aMid) const { + if (!mMid.Ref().empty()) { + aMid = NS_ConvertUTF8toUTF16(mMid.Ref()); + } else { + aMid.SetIsVoid(true); + } +} + +std::string RTCRtpTransceiver::GetMidAscii() const { + if (mMid.Ref().empty()) { + return std::string(); + } + + return mMid.Ref(); +} + +void RTCRtpTransceiver::SetDirection(RTCRtpTransceiverDirection aDirection, + ErrorResult& aRv) { + if (mStopped) { + aRv.ThrowInvalidStateError("Transceiver is stopped!"); + return; + } + + if (aDirection == mDirection) { + return; + } + + SetDirectionInternal(aDirection); + + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpTransceiver::SetDirectionInternal( + RTCRtpTransceiverDirection aDirection) { + // We do not update the direction on the JsepTransceiver until sync + mDirection = aDirection; +} + +bool RTCRtpTransceiver::ShouldRemove() const { return mShouldRemove; } + +bool RTCRtpTransceiver::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() || !mSender->GetTrack()) { + return false; + } + + // Ok, it looks like the connection is up and sending. Did we negotiate + // telephone-event? + const 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* RTCRtpTransceiver::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return dom::RTCRtpTransceiver_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* RTCRtpTransceiver::GetParentObject() const { + return mWindow; +} + +static void JsepCodecDescToAudioCodecConfig( + const JsepAudioCodecDescription& aCodec, Maybe<AudioCodecConfig>* aConfig) { + uint16_t pt; + + // TODO(bug 1761272): Getting the pt for a JsepAudioCodecDescription should be + // infallible. + if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt); + MOZ_ASSERT(false); + return; + } + + // libwebrtc crashes if we attempt to configure a mono recv codec + bool sendMono = aCodec.mForceMono && aCodec.mDirection == sdp::kSend; + + *aConfig = Some(AudioCodecConfig( + pt, aCodec.mName, static_cast<int>(aCodec.mClock), + sendMono ? 1 : static_cast<int>(aCodec.mChannels), aCodec.mFECEnabled)); + (*aConfig)->mMaxPlaybackRate = static_cast<int>(aCodec.mMaxPlaybackRate); + (*aConfig)->mDtmfEnabled = aCodec.mDtmfEnabled; + (*aConfig)->mDTXEnabled = aCodec.mDTXEnabled; + (*aConfig)->mMaxAverageBitrate = aCodec.mMaxAverageBitrate; + (*aConfig)->mFrameSizeMs = aCodec.mFrameSizeMs; + (*aConfig)->mMinFrameSizeMs = aCodec.mMinFrameSizeMs; + (*aConfig)->mMaxFrameSizeMs = aCodec.mMaxFrameSizeMs; + (*aConfig)->mCbrEnabled = aCodec.mCbrEnabled; +} + +// TODO: This and the next function probably should move to JsepTransceiver +Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&> +RTCRtpTransceiver::GetNegotiatedSendCodecs() const { + if (!mJsepTransceiver.mSendTrack.GetActive()) { + return Nothing(); + } + + const auto* details = mJsepTransceiver.mSendTrack.GetNegotiatedDetails(); + if (!details) { + return Nothing(); + } + + if (details->GetEncodingCount() == 0) { + return Nothing(); + } + + return SomeRef(details->GetEncoding(0).GetCodecs()); +} + +Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&> +RTCRtpTransceiver::GetNegotiatedRecvCodecs() const { + if (!mJsepTransceiver.mRecvTrack.GetActive()) { + return Nothing(); + } + + const auto* details = mJsepTransceiver.mRecvTrack.GetNegotiatedDetails(); + if (!details) { + return Nothing(); + } + + if (details->GetEncodingCount() == 0) { + return Nothing(); + } + + return SomeRef(details->GetEncoding(0).GetCodecs()); +} + +// TODO: Maybe move this someplace else? +/*static*/ +void RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<AudioCodecConfig>* aConfigs) { + Maybe<AudioCodecConfig> telephoneEvent; + + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + if (NS_WARN_IF(codec->Type() != SdpMediaSection::kAudio)) { + MOZ_ASSERT(false, "Codec is not audio! This is a JSEP bug."); + return; + } + Maybe<AudioCodecConfig> config; + const JsepAudioCodecDescription& audio = + static_cast<const JsepAudioCodecDescription&>(*codec); + JsepCodecDescToAudioCodecConfig(audio, &config); + 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)); + } +} + +auto RTCRtpTransceiver::GetActivePayloadTypes() const + -> RefPtr<ActivePayloadTypesPromise> { + if (!mConduit) { + return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(), + __func__); + } + + if (!mCallWrapper) { + return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(), + __func__); + } + + return InvokeAsync(mCallWrapper->mCallThread, __func__, + [conduit = mConduit]() { + PayloadTypes pts; + pts.mSendPayloadType = conduit->ActiveSendPayloadType(); + pts.mRecvPayloadType = conduit->ActiveRecvPayloadType(); + return ActivePayloadTypesPromise::CreateAndResolve( + std::move(pts), __func__); + }); +} + +static void JsepCodecDescToVideoCodecConfig( + const JsepVideoCodecDescription& aCodec, Maybe<VideoCodecConfig>* aConfig) { + uint16_t pt; + + // TODO(bug 1761272): Getting the pt for a JsepVideoCodecDescription should be + // infallible. + if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt); + MOZ_ASSERT(false); + return; + } + + UniquePtr<VideoCodecConfigH264> h264Config; + + if (aCodec.mName == "H264") { + h264Config = MakeUnique<VideoCodecConfigH264>(); + size_t spropSize = sizeof(h264Config->sprop_parameter_sets); + strncpy(h264Config->sprop_parameter_sets, + aCodec.mSpropParameterSets.c_str(), spropSize); + h264Config->sprop_parameter_sets[spropSize - 1] = '\0'; + h264Config->packetization_mode = + static_cast<int>(aCodec.mPacketizationMode); + h264Config->profile_level_id = static_cast<int>(aCodec.mProfileLevelId); + h264Config->tias_bw = 0; // TODO(bug 1403206) + } + + *aConfig = Some(VideoCodecConfig(pt, aCodec.mName, aCodec.mConstraints, + h264Config.get())); + + (*aConfig)->mAckFbTypes = aCodec.mAckFbTypes; + (*aConfig)->mNackFbTypes = aCodec.mNackFbTypes; + (*aConfig)->mCcmFbTypes = aCodec.mCcmFbTypes; + (*aConfig)->mRembFbSet = aCodec.RtcpFbRembIsSet(); + (*aConfig)->mFECFbSet = aCodec.mFECEnabled; + (*aConfig)->mTransportCCFbSet = aCodec.RtcpFbTransportCCIsSet(); + if (aCodec.mFECEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(aCodec.mREDPayloadType, &pt)) { + (*aConfig)->mREDPayloadType = pt; + } + if (SdpHelper::GetPtAsInt(aCodec.mULPFECPayloadType, &pt)) { + (*aConfig)->mULPFECPayloadType = pt; + } + } + if (aCodec.mRtxEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(aCodec.mRtxPayloadType, &pt)) { + (*aConfig)->mRTXPayloadType = pt; + } + } +} + +// TODO: Maybe move this someplace else? +/*static*/ +void RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<VideoCodecConfig>* aConfigs) { + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + if (NS_WARN_IF(codec->Type() != SdpMediaSection::kVideo)) { + MOZ_ASSERT(false, "Codec is not video! This is a JSEP bug."); + return; + } + Maybe<VideoCodecConfig> config; + const JsepVideoCodecDescription& video = + static_cast<const JsepVideoCodecDescription&>(*codec); + + JsepCodecDescToVideoCodecConfig(video, &config); + + config->mTias = aDetails.GetTias(); + + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(video.mDefaultPt)) { + VideoCodecConfig::Encoding encoding; + encoding.rid = jsepEncoding.mRid; + config->mEncodings.push_back(encoding); + } + } + + aConfigs->push_back(std::move(*config)); + } + } +} + +void RTCRtpTransceiver::Stop(ErrorResult& aRv) { + if (mPc->IsClosed()) { + aRv.ThrowInvalidStateError("Peer connection is closed"); + return; + } + + StopImpl(); + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpTransceiver::StopImpl() { + if (mStopped) { + return; + } + + if (mCallWrapper) { + auto conduit = std::move(mConduit); + (conduit ? conduit->Shutdown() + : GenericPromise::CreateAndResolve(true, __func__)) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [sender = mSender, receiver = mReceiver]() mutable { + // Shutdown pipelines when conduits are guaranteed shut down, + // so that all packets sent from conduits can be delivered. + sender->Shutdown(); + receiver->Shutdown(); + }); + mCallWrapper = nullptr; + } + mStopped = true; + mCurrentDirection.SetNull(); + + mSender->Stop(); + mReceiver->Stop(); + + auto self = nsMainThreadPtrHandle<RTCRtpTransceiver>( + new nsMainThreadPtrHolder<RTCRtpTransceiver>( + "RTCRtpTransceiver::StopImpl::self", this, false)); + mStsThread->Dispatch(NS_NewRunnableFunction( + __func__, [self] { self->mTransportHandler = nullptr; })); +} + +bool RTCRtpTransceiver::IsVideo() const { return mIsVideo; } + +bool RTCRtpTransceiver::IsSending() const { + return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendonly) || + mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv); +} + +bool RTCRtpTransceiver::IsReceiving() const { + return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Recvonly) || + mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv); +} + +void RTCRtpTransceiver::ChainToDomPromiseWithCodecStats( + nsTArray<RefPtr<RTCStatsPromise>> aStats, + const RefPtr<dom::Promise>& aDomPromise) { + nsTArray<RTCCodecStats> codecStats = + mPc->GetCodecStats(mPc->GetTimestampMaker().GetNow().ToDom()); + + AutoTArray< + std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>, + 1> + statsPromises; + statsPromises.AppendElement(std::make_tuple( + this, RTCStatsPromise::All(GetMainThreadSerialEventTarget(), aStats))); + + ApplyCodecStats(std::move(codecStats), std::move(statsPromises)) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aDomPromise, window = mWindow, + idGen = mIdGenerator](UniquePtr<RTCStatsCollection> aStats) mutable { + // Rewrite ids and merge stats collections into the final report. + AutoTArray<UniquePtr<RTCStatsCollection>, 1> stats; + stats.AppendElement(std::move(aStats)); + + RTCStatsCollection opaqueStats; + idGen->RewriteIds(std::move(stats), &opaqueStats); + + RefPtr<RTCStatsReport> report(new RTCStatsReport(window)); + report->Incorporate(opaqueStats); + + aDomPromise->MaybeResolve(std::move(report)); + }, + [aDomPromise](nsresult aError) { + aDomPromise->MaybeReject(NS_ERROR_FAILURE); + }); +} + +RefPtr<RTCStatsPromise> RTCRtpTransceiver::ApplyCodecStats( + nsTArray<RTCCodecStats> aCodecStats, + nsTArray< + std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>> + aTransceiverStatsPromises) { + MOZ_ASSERT(NS_IsMainThread()); + // The process here is roughly: + // - Gather all inputs to the codec filtering process, including: + // - Each transceiver's transportIds + // - Each transceiver's active payload types (resolved) + // - Each transceiver's resolved stats + // + // Waiting (async) for multiple promises of different types is not supported + // by the MozPromise API (bug 1752318), so we are a bit finicky here. We + // create media::Refcountables of the types we want to resolve, and let + // these be shared across Then-functions through RefPtrs. + // + // - For each active payload type in a transceiver: + // - Register the codec stats for this payload type and transport if we + // haven't already done so + // - If it was a send payload type, assign the codec stats id for this + // payload type and transport to the transceiver's outbound-rtp and + // remote-inbound-rtp stats as codecId + // - If it was a recv payload type, assign the codec stats id for this + // payload type and transport to the transceiver's inbound-rtp and + // remote-outbound-rtp stats as codecId + // + // - Flatten all transceiver stats collections into one, and set the + // registered codec stats on it + + // Wrap codec stats in a Refcountable<> to allow sharing across promise + // handlers. + auto codecStats = MakeRefPtr<media::Refcountable<nsTArray<RTCCodecStats>>>(); + *codecStats = std::move(aCodecStats); + + struct IdComparator { + bool operator()(const RTCCodecStats& aA, const RTCCodecStats& aB) const { + return aA.mId.Value() < aB.mId.Value(); + } + }; + + // Stores distinct codec stats by id; to avoid dupes within a transport. + auto finalCodecStats = + MakeRefPtr<media::Refcountable<std::set<RTCCodecStats, IdComparator>>>(); + + // All the transceiver rtp stream stats in a single array. These stats will, + // when resolved, contain codecIds. + nsTArray<RefPtr<RTCStatsPromise>> promises( + aTransceiverStatsPromises.Length()); + + for (const auto& [transceiver, allPromise] : aTransceiverStatsPromises) { + // Per transceiver, gather up what we need to assign codecId to this + // transceiver's rtp stream stats. Register codec stats while we're at it. + auto payloadTypes = + MakeRefPtr<media::Refcountable<RTCRtpTransceiver::PayloadTypes>>(); + promises.AppendElement( + transceiver->GetActivePayloadTypes() + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [payloadTypes, allPromise = allPromise]( + RTCRtpTransceiver::PayloadTypes aPayloadTypes) { + // Forward active payload types to the next Then-handler. + *payloadTypes = std::move(aPayloadTypes); + return allPromise; + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::AllPromiseType::CreateAndReject( + NS_ERROR_UNEXPECTED, __func__); + }) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [codecStats, finalCodecStats, payloadTypes, + transportId = + NS_ConvertASCIItoUTF16(transceiver->GetTransportId())]( + nsTArray<UniquePtr<RTCStatsCollection>> + aTransceiverStats) mutable { + // We have all the data we need to register codec stats and + // assign codecIds for this transceiver's rtp stream stats. + + auto report = MakeUnique<RTCStatsCollection>(); + FlattenStats(std::move(aTransceiverStats), report.get()); + + // Find the codec stats we are looking for, based on the + // transportId and the active payload types. + Maybe<RTCCodecStats&> sendCodec; + Maybe<RTCCodecStats&> recvCodec; + for (auto& codec : *codecStats) { + if (payloadTypes->mSendPayloadType.isSome() == + sendCodec.isSome() && + payloadTypes->mRecvPayloadType.isSome() == + recvCodec.isSome()) { + // We have found all the codec stats we were looking for. + break; + } + if (codec.mTransportId != transportId) { + continue; + } + if (payloadTypes->mSendPayloadType && + *payloadTypes->mSendPayloadType == + static_cast<int>(codec.mPayloadType) && + (!codec.mCodecType.WasPassed() || + codec.mCodecType.Value() == RTCCodecType::Encode)) { + MOZ_ASSERT(!sendCodec, + "At most one send codec stat per transceiver"); + sendCodec = SomeRef(codec); + } + if (payloadTypes->mRecvPayloadType && + *payloadTypes->mRecvPayloadType == + static_cast<int>(codec.mPayloadType) && + (!codec.mCodecType.WasPassed() || + codec.mCodecType.Value() == RTCCodecType::Decode)) { + MOZ_ASSERT(!recvCodec, + "At most one recv codec stat per transceiver"); + recvCodec = SomeRef(codec); + } + } + + // Register and assign codecIds for the found codec stats. + if (sendCodec) { + finalCodecStats->insert(*sendCodec); + for (auto& stat : report->mOutboundRtpStreamStats) { + stat.mCodecId.Construct(sendCodec->mId.Value()); + } + for (auto& stat : report->mRemoteInboundRtpStreamStats) { + stat.mCodecId.Construct(sendCodec->mId.Value()); + } + } + if (recvCodec) { + finalCodecStats->insert(*recvCodec); + for (auto& stat : report->mInboundRtpStreamStats) { + stat.mCodecId.Construct(recvCodec->mId.Value()); + } + for (auto& stat : report->mRemoteOutboundRtpStreamStats) { + stat.mCodecId.Construct(recvCodec->mId.Value()); + } + } + + return RTCStatsPromise::CreateAndResolve(std::move(report), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + })); + } + + return RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [finalCodecStats = std::move(finalCodecStats)]( + nsTArray<UniquePtr<RTCStatsCollection>> aStats) mutable { + auto finalStats = MakeUnique<RTCStatsCollection>(); + FlattenStats(std::move(aStats), finalStats.get()); + MOZ_ASSERT(finalStats->mCodecStats.IsEmpty()); + if (!finalStats->mCodecStats.SetCapacity(finalCodecStats->size(), + fallible)) { + mozalloc_handle_oom(0); + } + while (!finalCodecStats->empty()) { + auto node = finalCodecStats->extract(finalCodecStats->begin()); + if (!finalStats->mCodecStats.AppendElement( + std::move(node.value()), fallible)) { + mozalloc_handle_oom(0); + } + } + return RTCStatsPromise::CreateAndResolve(std::move(finalStats), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + }); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.h b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h new file mode 100644 index 0000000000..4830e465a0 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h @@ -0,0 +1,243 @@ +/* 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/StateMirroring.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISerialEventTarget.h" +#include "nsTArray.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "ErrorList.h" +#include "jsep/JsepSession.h" +#include "transport/transportlayer.h" // For TransportLayer::State +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "RTCStatsReport.h" + +class nsIPrincipal; + +namespace mozilla { +class PeerIdentity; +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 RTCStatsIdGenerator; +class WebrtcCallWrapper; +class JsepTrackNegotiatedDetails; +class PeerConnectionImpl; +enum class PrincipalPrivacy : uint8_t; + +namespace dom { +class RTCDtlsTransport; +class RTCDTMFSender; +class RTCRtpTransceiver; +struct RTCRtpSourceEntry; +class RTCRtpReceiver; +class RTCRtpSender; + +/** + * 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 RTCRtpTransceiver : public nsISupports, public nsWrapperCache { + public: + /** + * |aSendTrack| might or might not be set. + */ + RTCRtpTransceiver( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession, + const std::string& aTransceiverId, bool aIsVideo, + nsISerialEventTarget* aStsThread, MediaStreamTrack* aSendTrack, + WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator); + + void Init(const RTCRtpTransceiverInit& aInit, ErrorResult& aRv); + + bool IsValid() const { return !!mConduit; } + + nsresult UpdateTransport(); + + nsresult UpdateConduit(); + + void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy); + + void ResetSync(); + + nsresult SyncWithMatchingVideoConduits( + nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers); + + void Close(); + + void BreakCycles(); + + bool ConduitHasPluginID(uint64_t aPluginID); + + // for webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() const; + RTCRtpReceiver* Receiver() const { return mReceiver; } + RTCRtpSender* Sender() const { return mSender; } + RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; } + void GetKind(nsAString& aKind) const; + void GetMid(nsAString& aMid) const; + RTCRtpTransceiverDirection Direction() const { return mDirection; } + void SetDirection(RTCRtpTransceiverDirection aDirection, ErrorResult& aRv); + Nullable<RTCRtpTransceiverDirection> GetCurrentDirection() { + return mCurrentDirection; + } + void Stop(ErrorResult& aRv); + void SetDirectionInternal(RTCRtpTransceiverDirection aDirection); + bool HasBeenUsedToSend() const { return mHasBeenUsedToSend; } + + bool CanSendDTMF() const; + bool Stopped() const { return mStopped; } + void SyncToJsep(JsepSession& aSession) const; + void SyncFromJsep(const JsepSession& aSession); + std::string GetMidAscii() const; + + void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport, bool aStable); + void RollbackToStableDtlsTransport(); + + std::string GetTransportId() const { + return mJsepTransceiver.mTransport.mTransportId; + } + + JsepTransceiver& GetJsepTransceiver() { return mJsepTransceiver; } + + bool IsVideo() const; + + bool IsSending() const; + + bool IsReceiving() const; + + bool ShouldRemove() const; + + Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&> + GetNegotiatedSendCodecs() const; + + Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&> + GetNegotiatedRecvCodecs() const; + + struct PayloadTypes { + Maybe<int> mSendPayloadType; + Maybe<int> mRecvPayloadType; + }; + using ActivePayloadTypesPromise = MozPromise<PayloadTypes, nsresult, true>; + RefPtr<ActivePayloadTypesPromise> GetActivePayloadTypes() const; + + MediaSessionConduit* GetConduit() const { return mConduit; } + + const std::string& GetJsepTransceiverId() const { return mTransceiverId; } + + void SetRemovedFromPc() { mHandlingUnlink = true; } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver) + + static void NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<AudioCodecConfig>* aConfigs); + + static void NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector<VideoCodecConfig>* aConfigs); + + /* Returns a promise that will contain the stats in aStats, along with the + * codec stats (which is a PC-wide thing) */ + void ChainToDomPromiseWithCodecStats(nsTArray<RefPtr<RTCStatsPromise>> aStats, + const RefPtr<Promise>& aDomPromise); + + /** + * Takes a set of codec stats (per-peerconnection) and a set of + * transceiver/transceiver-stats-promise tuples. Filters out all referenced + * codec stats based on the transceiver's transport and rtp stream stats. + * Finally returns the flattened stats containing the filtered codec stats and + * all given per-transceiver-stats. + */ + static RefPtr<RTCStatsPromise> ApplyCodecStats( + nsTArray<RTCCodecStats> aCodecStats, + nsTArray<std::tuple<RTCRtpTransceiver*, + RefPtr<RTCStatsPromise::AllPromiseType>>> + aTransceiverStatsPromises); + + AbstractCanonical<std::string>* CanonicalMid() { return &mMid; } + AbstractCanonical<std::string>* CanonicalSyncGroup() { return &mSyncGroup; } + + private: + virtual ~RTCRtpTransceiver(); + void InitAudio(); + void InitVideo(const TrackingId& aRecvTrackingId); + void InitConduitControl(); + void StopImpl(); + + nsCOMPtr<nsPIDOMWindowInner> mWindow; + RefPtr<PeerConnectionImpl> mPc; + RefPtr<MediaTransportHandler> mTransportHandler; + const std::string mTransceiverId; + // Copy of latest from the JSEP engine. + JsepTransceiver mJsepTransceiver; + nsCOMPtr<nsISerialEventTarget> mStsThread; + // state for webrtc.org that is shared between all transceivers + RefPtr<WebrtcCallWrapper> mCallWrapper; + RefPtr<MediaStreamTrack> mSendTrack; + RefPtr<RTCStatsIdGenerator> mIdGenerator; + RefPtr<MediaSessionConduit> mConduit; + // 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<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<RTCDtlsTransport> mLastStableDtlsTransport; + RefPtr<RTCRtpReceiver> mReceiver; + RefPtr<RTCRtpSender> mSender; + RTCRtpTransceiverDirection mDirection = RTCRtpTransceiverDirection::Sendrecv; + Nullable<RTCRtpTransceiverDirection> mCurrentDirection; + bool mStopped = false; + bool mShutdown = false; + bool mHasBeenUsedToSend = false; + PrincipalPrivacy mPrincipalPrivacy; + bool mShouldRemove = false; + bool mHasTransport = false; + bool mIsVideo; + // This is really nasty. Most of the time, PeerConnectionImpl needs to be in + // charge of unlinking each RTCRtpTransceiver, because it needs to perform + // stats queries on its way out, which requires all of the RTCRtpTransceivers + // (and their transitive dependencies) to stick around until those stats + // queries are finished. However, when an RTCRtpTransceiver is removed from + // the PeerConnectionImpl due to negotiation, the PeerConnectionImpl + // releases its reference, which means the PeerConnectionImpl cannot be in + // charge of the unlink anymore. We cannot do the unlink when this reference + // is released either, because RTCRtpTransceiver might have some work it needs + // to do first. Also, JS may be maintaining a reference to the + // RTCRtpTransceiver (or one of its dependencies), which means it must remain + // fully functional after it is removed (meaning it cannot release any of its + // dependencies, or vice versa). + bool mHandlingUnlink = false; + std::string mTransportId; + + Canonical<std::string> mMid; + Canonical<std::string> mSyncGroup; +}; + +} // namespace dom + +} // namespace mozilla + +#endif // _TRANSCEIVERIMPL_H_ diff --git a/dom/media/webrtc/jsapi/RTCSctpTransport.cpp b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp new file mode 100644 index 0000000000..63424968ae --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RTCSctpTransport.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/RTCSctpTransportBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCSctpTransport, DOMEventTargetHelper, + mDtlsTransport) + +NS_IMPL_ADDREF_INHERITED(RTCSctpTransport, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCSctpTransport, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCSctpTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +RTCSctpTransport::RTCSctpTransport(nsPIDOMWindowInner* aWindow, + RTCDtlsTransport& aDtlsTransport, + double aMaxMessageSize, + const Nullable<uint16_t>& aMaxChannels) + : DOMEventTargetHelper(aWindow), + mState(RTCSctpTransportState::Connecting), + mDtlsTransport(&aDtlsTransport), + mMaxMessageSize(aMaxMessageSize), + mMaxChannels(aMaxChannels) {} + +JSObject* RTCSctpTransport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return RTCSctpTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCSctpTransport::UpdateState(RTCSctpTransportState aState) { + if (mState == RTCSctpTransportState::Closed || mState == aState) { + return; + } + + mState = aState; + + 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/RTCSctpTransport.h b/dom/media/webrtc/jsapi/RTCSctpTransport.h new file mode 100644 index 0000000000..8e21db5e73 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCSctpTransport.h @@ -0,0 +1,65 @@ +/* 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 _RTCSctpTransport_h_ +#define _RTCSctpTransport_h_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "RTCDtlsTransport.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +enum class RTCSctpTransportState : uint8_t; + +class RTCSctpTransport : public DOMEventTargetHelper { + public: + explicit RTCSctpTransport(nsPIDOMWindowInner* aWindow, + RTCDtlsTransport& aDtlsTransport, + double aMaxMessageSize, + const Nullable<uint16_t>& aMaxChannels); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCSctpTransport, + DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + IMPL_EVENT_HANDLER(statechange) + + RTCDtlsTransport* Transport() const { return mDtlsTransport; } + RTCSctpTransportState State() const { return mState; } + double MaxMessageSize() const { return mMaxMessageSize; } + Nullable<uint16_t> GetMaxChannels() const { return mMaxChannels; } + + void SetTransport(RTCDtlsTransport& aTransport) { + mDtlsTransport = &aTransport; + } + + void SetMaxMessageSize(double aMaxMessageSize) { + mMaxMessageSize = aMaxMessageSize; + } + + void SetMaxChannels(const Nullable<uint16_t>& aMaxChannels) { + mMaxChannels = aMaxChannels; + } + + void UpdateState(RTCSctpTransportState aState); + + private: + virtual ~RTCSctpTransport() = default; + + RTCSctpTransportState mState; + RefPtr<RTCDtlsTransport> mDtlsTransport; + double mMaxMessageSize; + Nullable<uint16_t> mMaxChannels; +}; + +} // namespace mozilla::dom +#endif // _RTCSctpTransport_h_ diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp new file mode 100644 index 0000000000..8b0462e223 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp @@ -0,0 +1,90 @@ +/* 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 <iostream> + +#include "mozilla/RandomNum.h" +#include "RTCStatsReport.h" +#include "WebrtcGlobal.h" + +namespace mozilla { + +RTCStatsIdGenerator::RTCStatsIdGenerator() + : mSalt(RandomUint64().valueOr(0xa5a5a5a5)), mCounter(0) {} + +void RTCStatsIdGenerator::RewriteIds( + nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats, + dom::RTCStatsCollection* aIntoReport) { + // Rewrite an Optional id + auto rewriteId = [&](dom::Optional<nsString>& id) { + if (id.WasPassed()) { + id.Value() = Id(id.Value()); + } + }; + + auto rewriteIds = [&](auto& aList, auto... aParam) { + for (auto& stat : aList) { + (rewriteId(stat.*aParam), ...); + } + }; + + // Involves a lot of copying, since webidl dictionaries don't have + // move semantics. Oh well. + + // Create a temporary to avoid double-rewriting any stats already in + // aIntoReport. + auto stats = MakeUnique<dom::RTCStatsCollection>(); + dom::FlattenStats(std::move(aFromStats), stats.get()); + + using S = dom::RTCStats; + using ICPS = dom::RTCIceCandidatePairStats; + using RSS = dom::RTCRtpStreamStats; + using IRSS = dom::RTCInboundRtpStreamStats; + using ORSS = dom::RTCOutboundRtpStreamStats; + using RIRSS = dom::RTCRemoteInboundRtpStreamStats; + using RORSS = dom::RTCRemoteOutboundRtpStreamStats; + + rewriteIds(stats->mIceCandidatePairStats, &S::mId, &ICPS::mLocalCandidateId, + &ICPS::mRemoteCandidateId); + rewriteIds(stats->mIceCandidateStats, &S::mId); + rewriteIds(stats->mInboundRtpStreamStats, &S::mId, &IRSS::mRemoteId, + &RSS::mCodecId); + rewriteIds(stats->mOutboundRtpStreamStats, &S::mId, &ORSS::mRemoteId, + &RSS::mCodecId); + rewriteIds(stats->mRemoteInboundRtpStreamStats, &S::mId, &RIRSS::mLocalId, + &RSS::mCodecId); + rewriteIds(stats->mRemoteOutboundRtpStreamStats, &S::mId, &RORSS::mLocalId, + &RSS::mCodecId); + rewriteIds(stats->mCodecStats, &S::mId); + rewriteIds(stats->mRtpContributingSourceStats, &S::mId); + rewriteIds(stats->mTrickledIceCandidateStats, &S::mId); + rewriteIds(stats->mDataChannelStats, &S::mId); + + dom::MergeStats(std::move(stats), aIntoReport); +} + +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..1391c029ad --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.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/. */ +/* -*- 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 <map> + +#include "mozilla/Atomics.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { +struct RTCStatsCollection; +} // namespace dom + +class RTCStatsIdGenerator { + public: + RTCStatsIdGenerator(); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RTCStatsIdGenerator); + + void RewriteIds(nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats, + dom::RTCStatsCollection* aIntoReport); + + private: + virtual ~RTCStatsIdGenerator(){}; + nsString Id(const nsString& aKey); + 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..9f39eb3865 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.cpp @@ -0,0 +1,213 @@ +/* -*- 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 "libwebrtcglue/SystemTime.h" +#include "mozilla/dom/Performance.h" +#include "nsRFPService.h" +#include "WebrtcGlobal.h" + +namespace mozilla::dom { + +RTCStatsTimestampState::RTCStatsTimestampState() + : mRandomTimelineSeed(0), + mStartDomRealtime(WebrtcSystemTimeBase()), + mStartRealtime( + WebrtcSystemTime() - + webrtc::TimeDelta::Micros( + (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())), + mRTPCallerType(RTPCallerType::Normal), + mStartWallClockRaw( + PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {} + +RTCStatsTimestampState::RTCStatsTimestampState(Performance& aPerformance) + : mRandomTimelineSeed(aPerformance.GetRandomTimelineSeed()), + mStartDomRealtime(aPerformance.CreationTimeStamp()), + mStartRealtime( + WebrtcSystemTime() - + webrtc::TimeDelta::Micros( + (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())), + mRTPCallerType(aPerformance.GetRTPCallerType()), + mStartWallClockRaw( + PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {} + +TimeStamp RTCStatsTimestamp::ToMozTime() const { return mMozTime; } + +webrtc::Timestamp RTCStatsTimestamp::ToRealtime() const { + return ToDomRealtime() + + webrtc::TimeDelta::Micros(mState.mStartRealtime.us()); +} + +webrtc::Timestamp RTCStatsTimestamp::To1Jan1970() const { + return ToDomRealtime() + webrtc::TimeDelta::Millis(mState.mStartWallClockRaw); +} + +webrtc::Timestamp RTCStatsTimestamp::ToNtp() const { + return To1Jan1970() + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970); +} + +webrtc::Timestamp RTCStatsTimestamp::ToDomRealtime() const { + return webrtc::Timestamp::Micros( + (mMozTime - mState.mStartDomRealtime).ToMicroseconds()); +} + +DOMHighResTimeStamp RTCStatsTimestamp::ToDom() 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 realtime = ToDomRealtime().ms<double>(); + // mRandomTimelineSeed is not set in the unit-tests. + if (mState.mRandomTimelineSeed) { + realtime = nsRFPService::ReduceTimePrecisionAsMSecs( + realtime, mState.mRandomTimelineSeed, mState.mRTPCallerType); + } + + // 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 + DOMHighResTimeStamp start = nsRFPService::ReduceTimePrecisionAsMSecs( + mState.mStartWallClockRaw, 0, mState.mRTPCallerType); + + return start + realtime; +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromMozTime( + const RTCStatsTimestampMaker& aMaker, TimeStamp aMozTime) { + return RTCStatsTimestamp(aMaker.mState, aMozTime); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromRealtime( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aRealtime) { + return FromDomRealtime( + aMaker, + aRealtime - webrtc::TimeDelta::Micros(aMaker.mState.mStartRealtime.us())); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::From1Jan1970( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp a1Jan1970) { + const auto& state = aMaker.mState; + return FromDomRealtime( + aMaker, a1Jan1970 - webrtc::TimeDelta::Millis(state.mStartWallClockRaw)); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromNtp( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aNtpTime) { + const auto& state = aMaker.mState; + const auto domRealtime = aNtpTime - + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970) - + webrtc::TimeDelta::Millis(state.mStartWallClockRaw); + // Ntp times exposed by libwebrtc to stats are always **rounded** to + // milliseconds. That means they can jump up to half a millisecond into the + // future. We compensate for that here so that things seem consistent to js. + return FromDomRealtime(aMaker, domRealtime - webrtc::TimeDelta::Micros(500)); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromDomRealtime( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aDomRealtime) { + return RTCStatsTimestamp(aMaker.mState, aMaker.mState.mStartDomRealtime + + TimeDuration::FromMicroseconds( + aDomRealtime.us<double>())); +} + +RTCStatsTimestamp::RTCStatsTimestamp(RTCStatsTimestampState aState, + TimeStamp aMozTime) + : mState(aState), mMozTime(aMozTime) {} + +RTCStatsTimestampMaker::RTCStatsTimestampMaker(RTCStatsTimestampState aState) + : mState(aState) {} + +/* static */ +RTCStatsTimestampMaker RTCStatsTimestampMaker::Create( + nsPIDOMWindowInner* aWindow /* = nullptr */) { + if (!aWindow) { + return RTCStatsTimestampMaker(RTCStatsTimestampState()); + } + if (Performance* p = aWindow->GetPerformance()) { + return RTCStatsTimestampMaker(RTCStatsTimestampState(*p)); + } + return RTCStatsTimestampMaker(RTCStatsTimestampState()); +} + +RTCStatsTimestamp RTCStatsTimestampMaker::GetNow() const { + return RTCStatsTimestamp::FromMozTime(*this, TimeStamp::Now()); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCStatsReport, mParent) + +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) { + ForAllPublicRTCStatsCollectionMembers( + aStats, [&](auto... aMember) { (SetRTCStats(aMember), ...); }); +} + +void RTCStatsReport::Set(const nsAString& aKey, JS::Handle<JSObject*> aValue, + ErrorResult& aRv) { + RTCStatsReport_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv); +} + +namespace { +template <size_t I, typename... Ts> +bool MoveInto(std::tuple<Ts...>& aFrom, std::tuple<Ts*...>& aInto) { + return std::get<I>(aInto)->AppendElements(std::move(std::get<I>(aFrom)), + fallible); +} + +template <size_t... Is, typename... Ts> +bool MoveInto(std::tuple<Ts...>&& aFrom, std::tuple<Ts*...>& aInto, + std::index_sequence<Is...>) { + return (... && MoveInto<Is>(aFrom, aInto)); +} + +template <typename... Ts> +bool MoveInto(std::tuple<Ts...>&& aFrom, std::tuple<Ts*...>& aInto) { + return MoveInto(std::move(aFrom), aInto, std::index_sequence_for<Ts...>()); +} +} // namespace + +void MergeStats(UniquePtr<RTCStatsCollection> aFromStats, + RTCStatsCollection* aIntoStats) { + auto fromTuple = ForAllRTCStatsCollectionMembers( + *aFromStats, + [&](auto&... aMember) { return std::make_tuple(std::move(aMember)...); }); + auto intoTuple = ForAllRTCStatsCollectionMembers( + *aIntoStats, + [&](auto&... aMember) { return std::make_tuple(&aMember...); }); + if (!MoveInto(std::move(fromTuple), intoTuple)) { + mozalloc_handle_oom(0); + } +} + +void FlattenStats(nsTArray<UniquePtr<RTCStatsCollection>> aFromStats, + RTCStatsCollection* aIntoStats) { + for (auto& stats : aFromStats) { + MergeStats(std::move(stats), aIntoStats); + } +} + +} // 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..97bf3daa52 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.h @@ -0,0 +1,205 @@ +/* -*- 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 "api/units/timestamp.h" // webrtc::Timestamp +#include "js/RootingAPI.h" // JS::Rooted +#include "js/Value.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/PerformanceService.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // RTCStatsCollection +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIGlobalObject.h" +#include "nsPIDOMWindow.h" // nsPIDOMWindowInner +#include "nsContentUtils.h" +#include "nsWrapperCache.h" +#include "prtime.h" // PR_Now + +namespace mozilla { + +extern TimeStamp WebrtcSystemTimeBase(); + +namespace dom { + +/** + * Keeps the state needed to convert RTCStatsTimestamps. + */ +struct RTCStatsTimestampState { + RTCStatsTimestampState(); + explicit RTCStatsTimestampState(Performance& aPerformance); + + RTCStatsTimestampState(const RTCStatsTimestampState&) = default; + + // These members are sampled when a non-copy constructor is called. + + // Performance's random timeline seed. + const uint64_t mRandomTimelineSeed; + // TimeStamp::Now() when the members were sampled. This is equivalent to time + // 0 in DomRealtime. + const TimeStamp mStartDomRealtime; + // WebrtcSystemTime() when the members were sampled. This represents the same + // point in time as mStartDomRealtime, but as a webrtc timestamp. + const webrtc::Timestamp mStartRealtime; + // Performance's RTPCallerType. + const RTPCallerType mRTPCallerType; + // Performance.timeOrigin for mStartDomRealtime when the members were sampled. + const DOMHighResTimeStamp mStartWallClockRaw; +}; + +/** + * Classes that facilitate creating timestamps for webrtc stats by mimicking + * dom::Performance, as well as getting and converting timestamps for libwebrtc + * and our integration with it. + * + * They use the same clock to avoid drift and inconsistencies, base on + * mozilla::TimeStamp, and convert to and from these time bases: + * - Moz : Monotonic, unspecified (but constant) and inaccessible epoch, + * as implemented by mozilla::TimeStamp + * - Realtime : Monotonic, unspecified (but constant) epoch. + * - 1Jan1970 : Monotonic, unix epoch (00:00:00 UTC on 1 January 1970). + * - Ntp : Monotonic, ntp epoch (00:00:00 UTC on 1 January 1900). + * - Dom : Monotonic, milliseconds since unix epoch, as the timestamps + * defined by webrtc-pc. Corresponds to Performance.timeOrigin + + * Performance.now(). Has reduced precision. + * - DomRealtime: Like Dom, but with full precision. + * - WallClock : Non-monotonic, unix epoch. Not used here since it is + * non-monotonic and cannot be correlated to the other time + * bases. + */ +class RTCStatsTimestampMaker; +class RTCStatsTimestamp { + public: + TimeStamp ToMozTime() const; + webrtc::Timestamp ToRealtime() const; + webrtc::Timestamp To1Jan1970() const; + webrtc::Timestamp ToNtp() const; + webrtc::Timestamp ToDomRealtime() const; + DOMHighResTimeStamp ToDom() const; + + static RTCStatsTimestamp FromMozTime(const RTCStatsTimestampMaker& aMaker, + TimeStamp aMozTime); + static RTCStatsTimestamp FromRealtime(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp From1Jan1970(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp FromNtp(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp FromDomRealtime(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aDomRealtime); + // There is on purpose no conversion functions from DOMHighResTimeStamp + // because of the loss in precision of a floating point to integer conversion. + + private: + RTCStatsTimestamp(RTCStatsTimestampState aState, TimeStamp aMozTime); + + const RTCStatsTimestampState mState; + const TimeStamp mMozTime; +}; + +class RTCStatsTimestampMaker { + public: + static RTCStatsTimestampMaker Create(nsPIDOMWindowInner* aWindow = nullptr); + + RTCStatsTimestamp GetNow() const; + + const RTCStatsTimestampState mState; + + private: + explicit RTCStatsTimestampMaker(RTCStatsTimestampState aState); +}; + +// 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_NATIVE_WRAPPERCACHE_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; +}; + +void MergeStats(UniquePtr<dom::RTCStatsCollection> aFromStats, + dom::RTCStatsCollection* aIntoStats); + +void FlattenStats(nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats, + dom::RTCStatsCollection* aIntoStats); + +} // namespace dom +} // namespace mozilla + +#endif // RTCStatsReport_h_ diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.cpp b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp new file mode 100644 index 0000000000..41c679e431 --- /dev/null +++ b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp @@ -0,0 +1,73 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +#include "RemoteTrackSource.h" + +#include "MediaStreamError.h" +#include "MediaTrackGraph.h" +#include "RTCRtpReceiver.h" + +namespace mozilla { + +NS_IMPL_ADDREF_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource) +NS_IMPL_RELEASE_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoteTrackSource) +NS_INTERFACE_MAP_END_INHERITING(dom::MediaStreamTrackSource) +NS_IMPL_CYCLE_COLLECTION_CLASS(RemoteTrackSource) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + tmp->Destroy(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceiver) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +RemoteTrackSource::RemoteTrackSource(SourceMediaTrack* aStream, + dom::RTCRtpReceiver* aReceiver, + nsIPrincipal* aPrincipal, + const nsString& aLabel, + TrackingId aTrackingId) + : dom::MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)), + mStream(aStream), + mReceiver(aReceiver) {} + +RemoteTrackSource::~RemoteTrackSource() { Destroy(); } + +void RemoteTrackSource::Destroy() { + if (mStream) { + MOZ_ASSERT(!mStream->IsDestroyed()); + mStream->End(); + mStream->Destroy(); + mStream = nullptr; + + GetMainThreadSerialEventTarget()->Dispatch(NewRunnableMethod( + "RemoteTrackSource::ForceEnded", this, &RemoteTrackSource::ForceEnded)); + } +} + +auto RemoteTrackSource::ApplyConstraints( + const dom::MediaTrackConstraints& aConstraints, dom::CallerType aCallerType) + -> RefPtr<ApplyConstraintsPromise> { + return ApplyConstraintsPromise::CreateAndReject( + MakeRefPtr<MediaMgrError>( + dom::MediaStreamError::Name::OverconstrainedError, ""), + __func__); +} + +void RemoteTrackSource::SetPrincipal(nsIPrincipal* aPrincipal) { + mPrincipal = aPrincipal; + PrincipalChanged(); +} + +void RemoteTrackSource::SetMuted(bool aMuted) { MutedChanged(aMuted); } + +void RemoteTrackSource::ForceEnded() { OverrideEnded(); } + +SourceMediaTrack* RemoteTrackSource::Stream() const { return mStream; } + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.h b/dom/media/webrtc/jsapi/RemoteTrackSource.h new file mode 100644 index 0000000000..be730f0030 --- /dev/null +++ b/dom/media/webrtc/jsapi/RemoteTrackSource.h @@ -0,0 +1,64 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ +#define DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ + +#include "MediaStreamTrack.h" + +namespace mozilla { + +namespace dom { +class RTCRtpReceiver; +} + +class SourceMediaTrack; + +class RemoteTrackSource : public dom::MediaStreamTrackSource { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + + RemoteTrackSource(SourceMediaTrack* aStream, dom::RTCRtpReceiver* aReceiver, + nsIPrincipal* aPrincipal, const nsString& aLabel, + TrackingId aTrackingId); + + void Destroy() override; + + dom::MediaSourceEnum GetMediaSource() const override { + return dom::MediaSourceEnum::Other; + } + + RefPtr<ApplyConstraintsPromise> ApplyConstraints( + const dom::MediaTrackConstraints& aConstraints, + dom::CallerType aCallerType) override; + + 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); + void SetMuted(bool aMuted); + void ForceEnded(); + + SourceMediaTrack* Stream() const; + + private: + virtual ~RemoteTrackSource(); + + RefPtr<SourceMediaTrack> mStream; + RefPtr<dom::RTCRtpReceiver> mReceiver; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalChild.h b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h new file mode 100644 index 0000000000..147b4b44f0 --- /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::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 nsAString& 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; + + static WebrtcGlobalChild* GetOrSet(const Maybe<WebrtcGlobalChild*>& aChild); + + public: + virtual ~WebrtcGlobalChild(); + static WebrtcGlobalChild* Get(); +}; + +} // namespace mozilla::dom + +#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..7d0a9e64b1 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp @@ -0,0 +1,829 @@ +/* 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 "WebrtcGlobalStatsHistory.h" +#include "mozilla/Assertions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/PWebrtcGlobal.h" +#include "mozilla/dom/PWebrtcGlobalChild.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 "nsISupports.h" +#include "nsITimer.h" +#include "nsLiteralString.h" +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "nsXULAppAPI.h" +#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 "nsString.h" +#include "transport/runnable_utils.h" +#include "MediaTransportHandler.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" + +#ifdef XP_WIN +# include <process.h> +#endif + +namespace mozilla::dom { + +using StatsRequestCallback = + nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback>; + +using LogRequestCallback = nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback>; + +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; + } + + WebrtcContentParents() = delete; + WebrtcContentParents(const WebrtcContentParents&) = delete; + WebrtcContentParents& operator=(const WebrtcContentParents&) = delete; + + private: + static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents; +}; + +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 nsTArray<dom::RTCStatsReportInternal>& GetWebrtcGlobalStatsStash() { + static StaticAutoPtr<nsTArray<dom::RTCStatsReportInternal>> sStash; + if (!sStash) { + sStash = new nsTArray<dom::RTCStatsReportInternal>(); + ClearOnShutdown(&sStash); + } + return *sStash; +} + +static RefPtr<PWebrtcGlobalParent::GetStatsPromise> +GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) { + nsTArray<RefPtr<dom::RTCStatsReportPromise>> promises; + + std::set<nsString> pcids; + if (auto* ctx = GetPeerConnectionCtx()) { + // Grab stats for PCs that still exist + ctx->ForEachPeerConnection([&](PeerConnectionImpl* aPc) { + if (!aPcIdFilter.IsEmpty() && + !aPcIdFilter.EqualsASCII(aPc->GetIdAsAscii().c_str())) { + return; + } + if (!aPc->IsClosed() || !aPc->LongTermStatsIsDisabled()) { + nsString id; + aPc->GetId(id); + pcids.insert(id); + promises.AppendElement(aPc->GetStats(nullptr, true)); + } + }); + + // Grab previously stashed stats, if they aren't dupes, and ensure they + // are marked closed. (In a content process, this should already have + // happened, but in the parent process, the stash will contain the last + // observed stats from the content processes. From the perspective of the + // parent process, these are assumed closed unless we see new stats from the + // content process that say otherwise.) + for (auto& report : GetWebrtcGlobalStatsStash()) { + report.mClosed = true; + if ((aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) && + !pcids.count(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 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 ClearLongTermStats() { + if (!NS_IsMainThread()) { + MOZ_ASSERT(NS_IsMainThread()); + return; + } + + GetWebrtcGlobalStatsStash().Clear(); + if (XRE_IsParentProcess()) { + WebrtcGlobalStatsHistory::Clear(); + } + if (auto* ctx = GetPeerConnectionCtx()) { + ctx->ClearClosedStats(); + } +} + +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 + ClearLongTermStats(); +} + +void WebrtcGlobalInformation::GetStatsHistoryPcIds( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback, + ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + IgnoredErrorResult rv; + aPcIdsCallback.Call(WebrtcGlobalStatsHistory::PcIds(), rv); + aRv = NS_OK; +} + +void WebrtcGlobalInformation::GetStatsHistorySince( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryCallback& aStatsCallback, + const nsAString& pcIdFilter, const Optional<DOMHighResTimeStamp>& aAfter, + const Optional<DOMHighResTimeStamp>& aSdpAfter, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + WebrtcGlobalStatisticsReport history; + + auto statsAfter = aAfter.WasPassed() ? Some(aAfter.Value()) : Nothing(); + auto sdpAfter = aSdpAfter.WasPassed() ? Some(aSdpAfter.Value()) : Nothing(); + + WebrtcGlobalStatsHistory::GetHistory(pcIdFilter).apply([&](auto& hist) { + if (!history.mReports.AppendElements(hist->Since(statsAfter), fallible)) { + mozalloc_handle_oom(0); + } + if (!history.mSdpHistories.AppendElement(hist->SdpSince(sdpAfter), + fallible)) { + mozalloc_handle_oom(0); + } + }); + + IgnoredErrorResult rv; + aStatsCallback.Call(history, rv); + aRv = NS_OK; +} + +using StatsPromiseArray = + nsTArray<RefPtr<PWebrtcGlobalParent::GetStatsPromise>>; + +void WebrtcGlobalInformation::GatherHistory() { + const nsString emptyFilter; + if (!NS_IsMainThread()) { + MOZ_ASSERT(NS_IsMainThread()); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + using StatsPromise = PWebrtcGlobalParent::GetStatsPromise; + auto resolveThenAppendStatsHistory = [](RefPtr<StatsPromise>&& promise) { + auto AppendStatsHistory = [](StatsPromise::ResolveOrRejectValue&& result) { + if (result.IsReject()) { + return; + } + for (const auto& report : result.ResolveValue()) { + WebrtcGlobalStatsHistory::Record( + MakeUnique<RTCStatsReportInternal>(report)); + } + }; + promise->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(AppendStatsHistory)); + }; + for (const auto& cp : WebrtcContentParents::GetAll()) { + resolveThenAppendStatsHistory(cp->SendGetStats(emptyFilter)); + } + resolveThenAppendStatsHistory(GetStatsPromiseForThisProcess(emptyFilter)); +} + +void WebrtcGlobalInformation::GetAllStats( + const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& aPcIdFilter, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + StatsPromiseArray statsPromises; + + nsString filter; + if (aPcIdFilter.WasPassed()) { + filter = aPcIdFilter.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!"); + // Flatten stats from content processes and parent process. + // The stats from the parent process (which will come last) might + // contain some stale content-process stats, so skip those. + for (auto& processResult : aResult.ResolveValue()) { + // TODO: Report rejection on individual content processes someday? + if (processResult.IsResolve()) { + for (auto& pcStats : processResult.ResolveValue()) { + if (!pcids.count(pcStats.mPcid)) { + pcids.insert(pcStats.mPcid); + if (!flattened.mReports.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + } + } + } + } + + if (filter.IsEmpty()) { + // Unfiltered is simple; the flattened result becomes the new stash. + GetWebrtcGlobalStatsStash() = flattened.mReports; + } else if (!flattened.mReports.IsEmpty()) { + // Update our stash with the single result. + MOZ_ASSERT(flattened.mReports.Length() == 1); + StashStats(flattened.mReports[0]); + } + + 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()); + + nsAutoString pattern(aPattern); + + // 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)); +} + +/*static*/ +void WebrtcGlobalInformation::StashStats( + const dom::RTCStatsReportInternal& aReport) { + // Remove previous report, if present + // TODO: Make this a map instead of an array? + for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) { + auto& pcStats = GetWebrtcGlobalStatsStash()[i]; + if (pcStats.mPcid == aReport.mPcid) { + GetWebrtcGlobalStatsStash().RemoveElementAt(i); + break; + } + ++i; + } + // Stash final stats + GetWebrtcGlobalStatsStash().AppendElement(aReport); +} + +void WebrtcGlobalInformation::AdjustTimerReferences( + PcTrackingUpdate&& aUpdate) { + static StaticRefPtr<nsITimer> sHistoryTimer; + static StaticAutoPtr<nsTHashSet<nsString>> sPcids; + + MOZ_ASSERT(NS_IsMainThread()); + + auto HandleAdd = [&](nsString&& aPcid, bool aIsLongTermStatsDisabled) { + if (!sPcids) { + sPcids = new nsTHashSet<nsString>(); + ClearOnShutdown(&sPcids); + } + sPcids->EnsureInserted(aPcid); + // Reserve a stats history + WebrtcGlobalStatsHistory::InitHistory(nsString(aPcid), + aIsLongTermStatsDisabled); + if (!sHistoryTimer) { + sHistoryTimer = NS_NewTimer(GetMainThreadSerialEventTarget()); + if (sHistoryTimer) { + sHistoryTimer->InitWithNamedFuncCallback( + [](nsITimer* aTimer, void* aClosure) { + if (WebrtcGlobalStatsHistory::Pref::Enabled()) { + WebrtcGlobalInformation::GatherHistory(); + } + }, + nullptr, WebrtcGlobalStatsHistory::Pref::PollIntervalMs(), + nsITimer::TYPE_REPEATING_SLACK, + "WebrtcGlobalInformation::GatherHistory"); + } + ClearOnShutdown(&sHistoryTimer); + } + }; + + auto HandleRemove = [&](const nsString& aRemoved) { + WebrtcGlobalStatsHistory::CloseHistory(nsString(aRemoved)); + if (!sPcids || !sPcids->Count()) { + return; + } + if (!sPcids->Contains(aRemoved)) { + return; + } + sPcids->Remove(aRemoved); + if (!sPcids->Count() && sHistoryTimer) { + sHistoryTimer->Cancel(); + sHistoryTimer = nullptr; + } + }; + + switch (aUpdate.Type()) { + case PcTrackingUpdate::Type::Add: { + HandleAdd(std::move(aUpdate.mPcid), + aUpdate.mLongTermStatsDisabled.valueOrFrom([&]() { + MOZ_ASSERT(aUpdate.mLongTermStatsDisabled.isNothing()); + return true; + })); + return; + } + case PcTrackingUpdate::Type::Remove: { + HandleRemove(aUpdate.mPcid); + return; + } + default: { + MOZ_ASSERT(false, "Invalid PcCount operation"); + } + } +} + +WebrtcGlobalParent* WebrtcGlobalParent::Alloc() { + return WebrtcContentParents::Alloc(); +} + +bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) { + WebrtcContentParents::Dealloc(aActor); + return true; +} + +void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + mShutdown = true; + for (const auto& pcId : mPcids) { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(nsString(pcId)); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionCreated( + const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) { + if (mShutdown) { + return IPC_OK(); + } + mPcids.EnsureInserted(aPcId); + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Add(nsString(aPcId), aIsLongTermStatsDisabled); + WebrtcGlobalInformation::PeerConnectionTracking(update); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionDestroyed( + const nsAString& aPcId) { + mPcids.EnsureRemoved(aPcId); + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(nsString(aPcId)); + WebrtcGlobalStatsHistory::CloseHistory(aPcId); + WebrtcGlobalInformation::PeerConnectionTracking(update); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionFinalStats( + const RTCStatsReportInternal& aFinalStats) { + auto finalStats = MakeUnique<RTCStatsReportInternal>(aFinalStats); + WebrtcGlobalStatsHistory::Record(std::move(finalStats)); + WebrtcGlobalStatsHistory::CloseHistory(aFinalStats.mPcid); + 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 nsAString& 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(); + } + + ClearLongTermStats(); + 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::GetOrSet( + const Maybe<WebrtcGlobalChild*>& aChild) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsContentProcess()); + static WebrtcGlobalChild* sChild; + if (!sChild && !aChild) { + sChild = static_cast<WebrtcGlobalChild*>( + ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor()); + } + aChild.apply([](auto* child) { sChild = child; }); + return sChild; +} + +WebrtcGlobalChild* WebrtcGlobalChild::Get() { return GetOrSet(Nothing()); } + +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); + GetOrSet(Some(nullptr)); +} + +} // 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..d43013bb43 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h @@ -0,0 +1,102 @@ +/* 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 <tuple> +#include "mozilla/dom/WebrtcGlobalInformationBinding.h" +#include "nsString.h" +#include "mozilla/dom/BindingDeclarations.h" // for Optional +#include "nsDOMNavigationTiming.h" +#include "WebrtcGlobalStatsHistory.h" + +namespace mozilla { +class PeerConnectionImpl; +class ErrorResult; + +namespace dom { + +class GlobalObject; +class WebrtcGlobalStatisticsCallback; +class WebrtcGlobalStatisticsHistoryPcIdsCallback; +class WebrtcGlobalLoggingCallback; +struct RTCStatsReportInternal; + +class WebrtcGlobalInformation { + public: + MOZ_CAN_RUN_SCRIPT + static void GetAllStats(const GlobalObject& aGlobal, + WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional<nsAString>& aPcIdFilter, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT + static void GetStatsHistoryPcIds( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT + static void GetStatsHistorySince( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryCallback& aStatsCallback, + const nsAString& aPcIdFilter, const Optional<DOMHighResTimeStamp>& aAfter, + const Optional<DOMHighResTimeStamp>& aSdpAfter, ErrorResult& aRv); + + static void GatherHistory(); + + 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 StashStats(const RTCStatsReportInternal& aReport); + + WebrtcGlobalInformation() = delete; + WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete; + WebrtcGlobalInformation& operator=(const WebrtcGlobalInformation& aRhs) = + delete; + + struct PcTrackingUpdate { + static PcTrackingUpdate Add(const nsString& aPcid, + const bool& aLongTermStatsDisabled) { + return PcTrackingUpdate{aPcid, Some(aLongTermStatsDisabled)}; + } + static PcTrackingUpdate Remove(const nsString& aPcid) { + return PcTrackingUpdate{aPcid, Nothing()}; + } + nsString mPcid; + Maybe<bool> mLongTermStatsDisabled; + enum class Type { + Add, + Remove, + }; + Type Type() const { + return mLongTermStatsDisabled ? Type::Add : Type::Remove; + } + }; + static void PeerConnectionTracking(PcTrackingUpdate& aUpdate) { + AdjustTimerReferences(std::move(aUpdate)); + } + + private: + static void AdjustTimerReferences(PcTrackingUpdate&& aUpdate); +}; + +} // 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..8372c4182f --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h @@ -0,0 +1,52 @@ +/* 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::dom { + +class WebrtcParents; + +class WebrtcGlobalParent : public PWebrtcGlobalParent { + friend class ContentParent; + friend class WebrtcGlobalInformation; + friend class WebrtcContentParents; + + bool mShutdown; + nsTHashSet<nsString> mPcids; + + MOZ_IMPLICIT WebrtcGlobalParent(); + + static WebrtcGlobalParent* Alloc(); + static bool Dealloc(WebrtcGlobalParent* aActor); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual mozilla::ipc::IPCResult Recv__delete__() override; + // Notification that a PeerConnection exists, and stats polling can begin + // if it hasn't already begun due to a previously created PeerConnection. + virtual mozilla::ipc::IPCResult RecvPeerConnectionCreated( + const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) override; + // Notification that a PeerConnection no longer exists, and stats polling + // can end if there are no other PeerConnections. + virtual mozilla::ipc::IPCResult RecvPeerConnectionDestroyed( + const nsAString& aPcid) override; + // Ditto but we have final stats + virtual mozilla::ipc::IPCResult RecvPeerConnectionFinalStats( + const RTCStatsReportInternal& aFinalStats) override; + virtual ~WebrtcGlobalParent(); + + public: + NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent) + + bool IsActive() { return !mShutdown; } +}; + +} // namespace mozilla::dom + +#endif // _WEBRTC_GLOBAL_PARENT_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp new file mode 100644 index 0000000000..1d576b5dca --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp @@ -0,0 +1,282 @@ +/* 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 "WebrtcGlobalStatsHistory.h" +#include <memory> + +#include "domstubs.h" +#include "mozilla/LinkedList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/fallible.h" +#include "mozilla/mozalloc_oom.h" +#include "nsDOMNavigationTiming.h" + +namespace mozilla::dom { + +constexpr auto SEC_TO_MS(const DOMHighResTimeStamp sec) -> DOMHighResTimeStamp { + return sec * 1000.0; +} + +constexpr auto MIN_TO_MS(const DOMHighResTimeStamp min) -> DOMHighResTimeStamp { + return SEC_TO_MS(min * 60.0); +} + +// Prefs +auto WebrtcGlobalStatsHistory::Pref::Enabled() -> bool { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_enabled(); +} + +auto WebrtcGlobalStatsHistory::Pref::PollIntervalMs() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_poll_interval_ms(); +} + +auto WebrtcGlobalStatsHistory::Pref::StorageWindowS() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_storage_window_s(); +} + +auto WebrtcGlobalStatsHistory::Pref::PruneAfterM() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_prune_after_m(); +} + +auto WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_closed_stats_to_retain(); +} + +auto WebrtcGlobalStatsHistory::Get() -> WebrtcGlobalStatsHistory::StatsMap& { + static StaticAutoPtr<StatsMap> sHist; + if (!sHist) { + sHist = new StatsMap(); + ClearOnShutdown(&sHist); + } + return *sHist; +} + +auto WebrtcGlobalStatsHistory::Entry::ReportElement::Timestamp() const + -> DOMHighResTimeStamp { + return report->mTimestamp; +} + +auto WebrtcGlobalStatsHistory::Entry::SdpElement::Timestamp() const + -> DOMHighResTimeStamp { + return sdp.mTimestamp; +} + +auto WebrtcGlobalStatsHistory::Entry::MakeReportElement( + UniquePtr<RTCStatsReportInternal> aReport) + -> WebrtcGlobalStatsHistory::Entry::ReportElement* { + auto* elem = new ReportElement(); + elem->report = std::move(aReport); + // We don't want to store a copy of the SDP history with each stats entry. + // SDP History is stored seperately, see MakeSdpElements. + elem->report->mSdpHistory.Clear(); + return elem; +} + +auto WebrtcGlobalStatsHistory::Entry::MakeSdpElementsSince( + Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory, + const Maybe<DOMHighResTimeStamp>& aSdpAfter) + -> AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> { + AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> result; + for (auto& sdpHist : aSdpHistory) { + if (!aSdpAfter || aSdpAfter.value() < sdpHist.mTimestamp) { + auto* element = new SdpElement(); + element->sdp = sdpHist; + result.insertBack(element); + } + } + return result; +} + +template <typename T> +auto FindFirstEntryAfter(const T* first, + const Maybe<DOMHighResTimeStamp>& aAfter) -> const T* { + const auto* current = first; + while (aAfter && current && current->Timestamp() <= aAfter.value()) { + current = current->getNext(); + } + return current; +} + +template <typename T> +auto CountElementsToEndInclusive(const LinkedListElement<T>* elem) -> size_t { + size_t count = 0; + const auto* cursor = elem; + while (cursor && cursor->isInList()) { + count++; + cursor = cursor->getNext(); + } + return count; +} + +auto WebrtcGlobalStatsHistory::Entry::Since( + const Maybe<DOMHighResTimeStamp>& aAfter) const + -> nsTArray<RTCStatsReportInternal> { + nsTArray<RTCStatsReportInternal> results; + const auto* cursor = FindFirstEntryAfter(mReports.getFirst(), aAfter); + const auto count = CountElementsToEndInclusive(cursor); + if (!results.SetCapacity(count, fallible)) { + mozalloc_handle_oom(0); + } + while (cursor) { + results.AppendElement(RTCStatsReportInternal(*cursor->report)); + cursor = cursor->getNext(); + } + return results; +} + +auto WebrtcGlobalStatsHistory::Entry::SdpSince( + const Maybe<DOMHighResTimeStamp>& aAfter) const -> RTCSdpHistoryInternal { + RTCSdpHistoryInternal results; + results.mPcid = mPcid; + // If no timestamp was passed copy the entire history + const auto* cursor = FindFirstEntryAfter(mSdp.getFirst(), aAfter); + const auto count = CountElementsToEndInclusive(cursor); + if (!results.mSdpHistory.SetCapacity(count, fallible)) { + mozalloc_handle_oom(0); + } + while (cursor) { + if (!results.mSdpHistory.AppendElement( + RTCSdpHistoryEntryInternal(cursor->sdp), fallible)) { + mozalloc_handle_oom(0); + } + cursor = cursor->getNext(); + } + return results; +} + +auto WebrtcGlobalStatsHistory::Entry::Prune(const DOMHighResTimeStamp aBefore) + -> void { + // Clear everything in the case that we don't keep stats + if (mIsLongTermStatsDisabled) { + mReports.clear(); + } + // Clear everything before the cutoff + for (auto* element = mReports.getFirst(); + element && element->report->mTimestamp < aBefore; + element = mReports.getFirst()) { + delete mReports.popFirst(); + } + // I don't think we should prune SDPs but if we did it would look like this: + // Note: we always keep the most recent SDP +} + +auto WebrtcGlobalStatsHistory::InitHistory(const nsAString& aPcId, + const bool aIsLongTermStatsDisabled) + -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + if (WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId)) { + return; + } + WebrtcGlobalStatsHistory::Get().GetOrInsertNew(aPcId, nsString(aPcId), + aIsLongTermStatsDisabled); +}; + +auto WebrtcGlobalStatsHistory::Record(UniquePtr<RTCStatsReportInternal> aReport) + -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + // Use the report timestamp as "now" for determining time depth + // based pruning. + const auto now = aReport->mTimestamp; + const auto earliest = now - SEC_TO_MS(Pref::StorageWindowS()); + + // Store closed state before moving the report + const auto closed = aReport->mClosed; + const auto pcId = aReport->mPcid; + + auto history = WebrtcGlobalStatsHistory::GetHistory(aReport->mPcid); + if (history && Pref::Enabled()) { + auto entry = history.value(); + // Remove expired entries + entry->Prune(earliest); + // Find new SDP entries + auto sdpAfter = Maybe<DOMHighResTimeStamp>(Nothing()); + if (auto* lastSdp = entry->mSdp.getLast(); lastSdp) { + sdpAfter = Some(lastSdp->Timestamp()); + } + entry->mSdp.extendBack( + Entry::MakeSdpElementsSince(std::move(aReport->mSdpHistory), sdpAfter)); + // Reports must be in ascending order by mTimestamp + const auto* latest = entry->mReports.getLast(); + // Maintain sorted order + if (!latest || latest->report->mTimestamp < aReport->mTimestamp) { + entry->mReports.insertBack(Entry::MakeReportElement(std::move(aReport))); + } + } + // Close the history if needed + if (closed) { + CloseHistory(pcId); + } +} + +auto WebrtcGlobalStatsHistory::CloseHistory(const nsAString& aPcId) -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + auto maybeHist = WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId); + if (!maybeHist) { + return; + } + { + auto&& hist = maybeHist.value(); + hist->mIsClosed = true; + + if (hist->mIsLongTermStatsDisabled) { + WebrtcGlobalStatsHistory::Get().Remove(aPcId); + return; + } + } + size_t remainingClosedStatsToRetain = + WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain(); + WebrtcGlobalStatsHistory::Get().RemoveIf([&](auto& iter) { + auto& entry = iter.Data(); + if (!entry->mIsClosed) { + return false; + } + if (entry->mIsLongTermStatsDisabled) { + return true; + } + if (remainingClosedStatsToRetain > 0) { + remainingClosedStatsToRetain -= 1; + return false; + } + return true; + }); +} + +auto WebrtcGlobalStatsHistory::Clear() -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + WebrtcGlobalStatsHistory::Get().RemoveIf([](auto& aIter) { + // First clear all the closed histories. + if (aIter.Data()->mIsClosed) { + return true; + } + // For all remaining histories clear their stored reports + aIter.Data()->mReports.clear(); + // As an optimization we don't clear the SDP, because that would + // be reconstitued in the very next stats gathering polling period. + // Those are potentially large allocations which we can skip. + return false; + }); +} + +auto WebrtcGlobalStatsHistory::PcIds() -> dom::Sequence<nsString> { + MOZ_ASSERT(XRE_IsParentProcess()); + dom::Sequence<nsString> pcIds; + for (const auto& pcId : WebrtcGlobalStatsHistory::Get().Keys()) { + if (!pcIds.AppendElement(pcId, fallible)) { + mozalloc_handle_oom(0); + } + } + return pcIds; +} + +auto WebrtcGlobalStatsHistory::GetHistory(const nsAString& aPcId) + -> Maybe<RefPtr<Entry> > { + MOZ_ASSERT(XRE_IsParentProcess()); + const auto pcid = NS_ConvertUTF16toUTF8(aPcId); + + return WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId); +} +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h new file mode 100644 index 0000000000..aa820e787d --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h @@ -0,0 +1,88 @@ +/* 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/. */ + +#pragma once + +#include <memory> +#include "domstubs.h" +#include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsDOMNavigationTiming.h" +#include "nsHashKeys.h" +#include "nsTHashMap.h" + +namespace mozilla::dom { +class WebrtcGlobalStatisticsHistoryCallback; +struct RTCStatsReportInternal; + +struct WebrtcGlobalStatsHistory { + // History preferences + struct Pref { + static auto Enabled() -> bool; + static auto PollIntervalMs() -> uint32_t; + static auto StorageWindowS() -> uint32_t; + static auto PruneAfterM() -> uint32_t; + static auto ClosedStatsToRetain() -> uint32_t; + Pref() = delete; + ~Pref() = delete; + }; + + struct Entry { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Entry) + // We need to wrap the report in an element + struct ReportElement : public LinkedListElement<ReportElement> { + UniquePtr<RTCStatsReportInternal> report; + auto Timestamp() const -> DOMHighResTimeStamp; + virtual ~ReportElement() = default; + }; + // And likewise for the SDP history + struct SdpElement : public LinkedListElement<SdpElement> { + RTCSdpHistoryEntryInternal sdp; + auto Timestamp() const -> DOMHighResTimeStamp; + virtual ~SdpElement() = default; + }; + + explicit Entry(const nsString& aPcid, const bool aIsLongTermStatsDisabled) + : mPcid(aPcid), mIsLongTermStatsDisabled(aIsLongTermStatsDisabled) {} + + nsString mPcid; + AutoCleanLinkedList<ReportElement> mReports; + AutoCleanLinkedList<SdpElement> mSdp; + bool mIsLongTermStatsDisabled; + bool mIsClosed = false; + + auto Since(const Maybe<DOMHighResTimeStamp>& aAfter) const + -> nsTArray<RTCStatsReportInternal>; + auto SdpSince(const Maybe<DOMHighResTimeStamp>& aAfter) const + -> RTCSdpHistoryInternal; + + static auto MakeReportElement(UniquePtr<RTCStatsReportInternal> aReport) + -> ReportElement*; + static auto MakeSdpElementsSince( + Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory, + const Maybe<DOMHighResTimeStamp>& aSdpAfter) + -> AutoCleanLinkedList<SdpElement>; + auto Prune(const DOMHighResTimeStamp aBefore) -> void; + + private: + virtual ~Entry() = default; + }; + using StatsMap = nsTHashMap<nsStringHashKey, RefPtr<Entry> >; + static auto InitHistory(const nsAString& aPcId, + const bool aIsLongTermStatsDisabled) -> void; + static auto Record(UniquePtr<RTCStatsReportInternal> aReport) -> void; + static auto CloseHistory(const nsAString& aPcId) -> void; + static auto GetHistory(const nsAString& aPcId) -> Maybe<RefPtr<Entry> >; + static auto Clear() -> void; + static auto PcIds() -> dom::Sequence<nsString>; + + WebrtcGlobalStatsHistory() = delete; + + private: + static auto Get() -> StatsMap&; +}; +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build new file mode 100644 index 0000000000..2c1dbe79f1 --- /dev/null +++ b/dom/media/webrtc/jsapi/moz.build @@ -0,0 +1,51 @@ +# -*- 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/dns", # For nsDNSService2.h + "/third_party/libsrtp/src/include", + "/third_party/libwebrtc", + "/third_party/libwebrtc/third_party/abseil-cpp", +] + +UNIFIED_SOURCES += [ + "MediaTransportHandler.cpp", + "MediaTransportHandlerIPC.cpp", + "MediaTransportParent.cpp", + "PacketDumper.cpp", + "PeerConnectionCtx.cpp", + "PeerConnectionImpl.cpp", + "RemoteTrackSource.cpp", + "RTCDtlsTransport.cpp", + "RTCDTMFSender.cpp", + "RTCRtpReceiver.cpp", + "RTCRtpSender.cpp", + "RTCRtpTransceiver.cpp", + "RTCSctpTransport.cpp", + "RTCStatsIdGenerator.cpp", + "RTCStatsReport.cpp", + "WebrtcGlobalInformation.cpp", + "WebrtcGlobalStatsHistory.cpp", +] + +EXPORTS.mozilla.dom += [ + "RTCDtlsTransport.h", + "RTCDTMFSender.h", + "RTCRtpReceiver.h", + "RTCRtpSender.h", + "RTCRtpTransceiver.h", + "RTCSctpTransport.h", + "RTCStatsReport.h", +] + +FINAL_LIBRARY = "xul" |