summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/jsapi
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc/jsapi')
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.cpp1727
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.h167
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp414
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h96
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportParent.cpp240
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.cpp124
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.h52
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.cpp650
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.h194
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.cpp4640
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.h969
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.cpp159
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.h78
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.cpp69
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.h43
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.cpp942
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.h198
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpSender.cpp1654
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpSender.h260
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp1080
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpTransceiver.h243
-rw-r--r--dom/media/webrtc/jsapi/RTCSctpTransport.cpp53
-rw-r--r--dom/media/webrtc/jsapi/RTCSctpTransport.h65
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp90
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.h42
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.cpp213
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.h205
-rw-r--r--dom/media/webrtc/jsapi/RemoteTrackSource.cpp73
-rw-r--r--dom/media/webrtc/jsapi/RemoteTrackSource.h64
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalChild.h42
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp829
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.h102
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalParent.h52
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp282
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h88
-rw-r--r--dom/media/webrtc/jsapi/moz.build51
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"