/* 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/dtlsidentity.h"
#include "transport/nricemediastream.h"
#include "transport/nriceresolver.h"
#include "transport/sigslot.h"
#include "transport/transportflow.h"
#include "transport/transportlayerdtls.h"
#include "transport/transportlayerice.h"
#include "transport/transportlayersrtp.h"

// Config stuff
#include "mozilla/IceServerParser.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/dom/RTCConfigurationBinding.h"

// Logging stuff
#include "common/browser_logging/CSFLog.h"

// For fetching ICE logging
#include "transport/rlogconnector.h"

// DTLS
#include <map>
#include <string>
#include <vector>

#include "mozilla/Base64.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/PublicSSL.h"  // For psm::InitializeCipherSuite
#include "mozilla/ReverseIterator.h"
#include "mozilla/dom/RTCStatsReportBinding.h"
#include "nsDNSService2.h"
#include "nsFmtString.h"
#include "nsISocketTransportService.h"
#include "nss.h"  // For NSS_NoDB_Init
#include "sdp/SdpAttribute.h"
#include "transport/runnable_utils.h"
#define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket)              \
  PROFILER_MARKER_TEXT("WebRTC Packet Received", MEDIA_RT, {},        \
                       ProfilerString8View::WrapNullTerminatedString( \
                           MediaPacket::EnumValueToString((aPacket).type())));

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();

  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 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;
    // Counts of the (decrypted) payload that traverses this transport, used to
    // populate RTCTransportStats. These exclude STUN connectivity checks and
    // DTLS/SRTP protection overhead, both of which are added below this point.
    uint64_t mBytesSent = 0;
    uint64_t mBytesReceived = 0;
    uint64_t mPacketsSent = 0;
    uint64_t mPacketsReceived = 0;
  };

  using MediaTransportHandler::OnAlpnNegotiated;
  using MediaTransportHandler::OnCandidate;
  using MediaTransportHandler::OnCandidateError;
  using MediaTransportHandler::OnConnectionStateChange;
  using MediaTransportHandler::OnEncryptedSending;
  using MediaTransportHandler::OnGatheringStateChange;
  using MediaTransportHandler::OnPacketReceived;
  using MediaTransportHandler::OnRtcpStateChange;
  using MediaTransportHandler::OnStateChange;

  void OnGatheringStateChange(const std::string& aTransportId,
                              NrIceMediaStream::GatheringState aState);
  void OnConnectionStateChange(NrIceMediaStream* aIceStream,
                               NrIceCtx::ConnectionState aState);
  void OnCandidateFound(NrIceMediaStream* aStream,
                        const std::string& aCandidate,
                        const std::string& aUfrag, const std::string& aMDNSAddr,
                        const std::string& aActualAddr);
  void OnCandidateError(NrIceMediaStream* aStream, const std::string& aAddress,
                        uint16_t aPort, const std::string& aUrl,
                        uint16_t aErrorCode, const std::string& aErrorText);
  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,
                   dom::RTCTransportStats& aTransport) 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;
  bool mAllowLoopback = false;
  bool mAllowLinkLocal = 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() {
  RefPtr<MediaTransportHandler> result;
  if (XRE_IsContentProcess() &&
      Preferences::GetBool("media.peerconnection.mtransport_process") &&
      StaticPrefs::network_process_enabled()) {
    result = MakeRefPtr<MediaTransportHandlerIPC>();
  } else {
    result = MakeRefPtr<MediaTransportHandlerSTS>();
  }
  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 =
        MakeRefPtr<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() {
  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;
}

static NrIceCtx::GlobalConfig GetGlobalConfig() {
  NrIceCtx::GlobalConfig config;
  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);
  int network_delay_ms = Preferences::GetInt(
      "media.peerconnection.nat_simulator.network_delay_ms", 0);

  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());
    CSFLogDebug(LOGTAG, "NAT network delay: %d", network_delay_ms);
    NrIceCtx::NatSimulatorConfig natConfig;
    natConfig.mBlockUdp = block_udp;
    natConfig.mBlockTcp = block_tcp;
    natConfig.mBlockTls = block_tls;
    natConfig.mErrorCodeForDrop = error_code_for_drop;
    natConfig.mFilteringType = std::move(filtering_type);
    natConfig.mMappingType = std::move(mapping_type);
    natConfig.mNetworkDelayMs = network_delay_ms;
    if (redirect_address.Length()) {
      CSFLogDebug(LOGTAG, "Redirect address: %s", redirect_address.get());
      CSFLogDebug(LOGTAG, "Redirect targets: %s", redirect_targets.get());
      natConfig.mRedirectAddress = std::move(redirect_address);
      std::stringstream str(redirect_targets.get());
      std::string target;
      while (getline(str, target, ',')) {
        CSFLogDebug(LOGTAG, "Adding target: %s", target.c_str());
        natConfig.mRedirectTargets.AppendElement(std::move(target));
      }
    }
    return Some(std::move(natConfig));
  }
  return Nothing();
}

void MediaTransportHandlerSTS::CreateIceCtx(const std::string& aName) {
  mInitPromise = InvokeAsync(
      GetMainThreadSerialEventTarget(), __func__,
      [=, this, 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();
        mAllowLoopback =
            Preferences::GetBool("media.peerconnection.ice.loopback", false);
        mAllowLinkLocal =
            Preferences::GetBool("media.peerconnection.ice.link_local", false);

        MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance());
        STSShutdownHandler::Instance()->Register(this);

        return InvokeAsync(
            mStsThread, __func__,
            [=, this, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
              mIceCtx = NrIceCtx::Create(aName);
              if (!mIceCtx) {
                return InitPromise::CreateAndReject("NrIceCtx::Create failed",
                                                    __func__);
              }

              mIceCtx->SignalConnectionStateChange.connect(
                  this, &MediaTransportHandlerSTS::OnConnectionStateChange);

              mDNSResolver = MakeRefPtr<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__);
            });
      });
}

using ParsedIceServer = IceServerParser::ParsedIceServer;

nsresult MediaTransportHandlerSTS::SetIceConfig(
    const nsTArray<dom::RTCIceServer>& aIceServers,
    dom::RTCIceTransportPolicy aIcePolicy) {
  auto result = IceServerParser::Parse(aIceServers);
  if (result.isErr()) {
    // Discard the detailed ErrorResult; callers at this level use nsresult.
    result.unwrapErr().SuppressException();
    return NS_ERROR_FAILURE;
  }

  nsTArray<ParsedIceServer> entries = result.unwrap();

  MOZ_RELEASE_ASSERT(mInitPromise);

  mInitPromise->Then(
      mStsThread, __func__,
      [this, aIcePolicy, entries = std::move(entries),
       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.mAllowLoopback = mAllowLoopback;
        config.mAllowLinkLocal = mAllowLinkLocal;
        config.mNatSimulatorConfig = mNatConfig;

        nsresult rv;

        if (NS_FAILED(rv = mIceCtx->SetIceServers(entries, mTurnDisabled))) {
          CSFLogError(LOGTAG, "%s: Failed to set ICE servers", __FUNCTION__);
          return;
        }
        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__);
  // 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.
  nsresult rv = mStsThread->Dispatch(
      NewNonOwningRunnableMethod("MediaTransportHandlerSTS::DestroyFinal", this,
                                 &MediaTransportHandlerSTS::DestroyFinal));
  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::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__,
      [=, this, 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);
          stream->SignalCandidateError.connect(
              this, &MediaTransportHandlerSTS::OnCandidateError);
          stream->SignalGatheringStateChange.connect(
              this, &MediaTransportHandlerSTS::OnGatheringStateChange);
        }

        // 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__,
      [=, this, 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__,
      [=, this, 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__,
      [=, this, 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__,
      [=, this, 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__,
      [=, this, 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__,
      [=, this, 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__,
      [=, this, 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)) {
            OnStateChange(transportId, TransportLayer::TS_CLOSED, {});
            OnRtcpStateChange(transportId, TransportLayer::TS_CLOSED);
            // 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 (int error = layer->SendPacket(aPacket); error < 0) {
          CSFLogError(LOGTAG,
                      "%s: Transport flow (%s) failed to send packet. error=%d",
                      mIceCtx->name().c_str(), aTransportId.c_str(), error);
        } else if (auto it = mTransports.find(aTransportId);
                   it != mTransports.end()) {
          // On success the layer returns the number of (unencrypted) payload
          // bytes it was handed.
          it->second.mBytesSent += error;
          it->second.mPacketsSent += 1;
        }
      },
      [](const std::string& aError) {});
}

TransportLayer::State MediaTransportHandler::GetState(
    const std::string& aTransportId, bool aRtcp) const {
  MutexAutoLock lock(mStateCacheMutex);
  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,
                                        CandidateInfo&& aCandidateInfo) {
  mCandidateGathered.Notify(aTransportId, std::move(aCandidateInfo));
}

void MediaTransportHandler::OnCandidateError(
    IceCandidateErrorInfo&& aErrorInfo) {
  mCandidateError.Notify(std::move(aErrorInfo));
}

void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) {
  const bool privacyRequested = aAlpn == "c-webrtc";
  mAlpnNegotiated.Notify(aAlpn, privacyRequested);
}

void MediaTransportHandler::OnGatheringStateChange(
    const std::string& aTransportId, dom::RTCIceGathererState aState) {
  mGatheringStateChange.Notify(aTransportId, aState);
}

void MediaTransportHandler::OnConnectionStateChange(
    const std::string& aTransportId, dom::RTCIceTransportState aState) {
  mConnectionStateChange.Notify(aTransportId, aState);
}

void MediaTransportHandler::OnPacketReceived(std::string&& aTransportId,
                                             MediaPacket&& aPacket) {
  switch (aPacket.type()) {
    case MediaPacket::UNCLASSIFIED:
    case MediaPacket::DTLS:
    case MediaPacket::SRTP:
    case MediaPacket::SRTCP:
      // Shouldn't happen, and nothing would care if it did
      break;
    case MediaPacket::RTP:
    case MediaPacket::RTCP:
      mRtpPacketReceived.Notify(std::forward<std::string>(aTransportId),
                                std::forward<MediaPacket>(aPacket));
      break;
    case MediaPacket::SCTP:
      mSctpPacketReceived.Notify(std::forward<std::string>(aTransportId),
                                 std::forward<MediaPacket>(aPacket));
      break;
  }
}

void MediaTransportHandler::OnEncryptedSending(const std::string& aTransportId,
                                               MediaPacket&& aPacket) {
  mEncryptedSending.Notify(aTransportId, std::move(aPacket));
}

void MediaTransportHandler::OnStateChange(
    const std::string& aTransportId, TransportLayer::State aState,
    nsTArray<nsTArray<uint8_t>>&& aRemoteCerts,
    Maybe<dom::RTCErrorParams> aError) {
  {
    MutexAutoLock lock(mStateCacheMutex);
    mStateCache[aTransportId] = aState;
  }
  mStateChange.Notify(aTransportId, aState, std::move(aRemoteCerts), aError);
}

void MediaTransportHandler::OnRtcpStateChange(
    const std::string& aTransportId, TransportLayer::State aState,
    Maybe<dom::RTCErrorParams> aError) {
  {
    MutexAutoLock lock(mStateCacheMutex);
    mRtcpStateCache[aTransportId] = aState;
  }
  mRtcpStateChange.Notify(aTransportId, aState, aError);
}

static uint16_t ToDtlsWireVersion(uint16_t aProtocolVersion) {
  switch (aProtocolVersion) {
    case SSL_LIBRARY_VERSION_DTLS_1_0:
      return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
    case SSL_LIBRARY_VERSION_DTLS_1_2:
      return SSL_LIBRARY_VERSION_DTLS_1_2_WIRE;
    case SSL_LIBRARY_VERSION_DTLS_1_3:
      return SSL_LIBRARY_VERSION_DTLS_1_3_WIRE;
    default:
      return 0;
  }
}

// BuildCertificateStats returns the issuerCertificateId.
// https://w3c.github.io/webrtc-stats/#dom-rtccertificatestats-issuercertificateid
// "The issuerCertificateId refers to the stats object that contains the next
// certificate in the certificate chain."
static nsString BuildCertificateStats(const nsTArray<uint8_t>& aDerCert,
                                      const nsAString& aIssuerId,
                                      DOMHighResTimeStamp aNow,
                                      dom::RTCStatsCollection* aStats) {
  if (aDerCert.IsEmpty()) {
    return nsString();
  }

  DtlsDigest digest(DtlsIdentity::DEFAULT_HASH_ALGORITHM);
  if (NS_FAILED(DtlsIdentity::ComputeFingerprint(aDerCert.Elements(),
                                                 aDerCert.Length(), &digest))) {
    return nsString();
  }
  NS_ConvertUTF8toUTF16 fingerprint(
      SdpFingerprintAttributeList::FormatFingerprint(digest.value_).c_str());

  nsFmtString id(u"certificate_{}", fingerprint);

  for (const auto& existing : aStats->mCertificateStats) {
    if (existing.mId.WasPassed() && existing.mId.Value() == id) {
      return id;
    }
  }

  nsCString base64Cert;
  if (NS_FAILED(Base64Encode(reinterpret_cast<const char*>(aDerCert.Elements()),
                             aDerCert.Length(), base64Cert))) {
    return nsString();
  }

  dom::RTCCertificateStats cert;
  cert.mId.Construct(id);
  cert.mTimestamp.Construct(aNow);
  cert.mType.Construct(dom::RTCStatsType::Certificate);
  cert.mFingerprint = fingerprint;
  cert.mFingerprintAlgorithm = NS_ConvertUTF8toUTF16(digest.algorithm_);
  cert.mBase64Certificate = NS_ConvertUTF8toUTF16(base64Cert);
  if (!aIssuerId.IsEmpty()) {
    cert.mIssuerCertificateId.Construct(aIssuerId);
  }

  if (!aStats->mCertificateStats.AppendElement(cert, fallible)) {
    mozalloc_handle_oom(0);
  }
  return id;
}

RefPtr<dom::RTCStatsPromise> MediaTransportHandlerSTS::GetIceStats(
    const std::string& aTransportId, DOMHighResTimeStamp aNow) {
  MOZ_RELEASE_ASSERT(mInitPromise);

  return mInitPromise->Then(
      mStsThread, __func__, [=, this, self = RefPtr(this)]() {
        auto stats = MakeUnique<dom::RTCStatsCollection>();
        if (mIceCtx) {
          dom::RTCIceRole iceRole =
              mIceCtx->GetControlling() == NrIceCtx::ICE_CONTROLLING
                  ? dom::RTCIceRole::Controlling
                  : dom::RTCIceRole::Controlled;
          for (const auto& stream : mIceCtx->GetStreams()) {
            if (aTransportId.empty() || aTransportId == stream->GetId()) {
              dom::RTCTransportStats transport;
              transport.mId.Construct(
                  NS_ConvertASCIItoUTF16(stream->GetId().c_str()));
              transport.mTimestamp.Construct(aNow);
              transport.mType.Construct(dom::RTCStatsType::Transport);
              transport.mIceRole.Construct(iceRole);
              std::string ufrag = stream->GetUfrag();
              if (!ufrag.empty()) {
                transport.mIceLocalUsernameFragment.Construct(
                    NS_ConvertASCIItoUTF16(ufrag.c_str()));
              }
              switch (stream->state()) {
                case NrIceMediaStream::ICE_CONNECTING:
                  transport.mIceState.Construct(
                      dom::RTCIceTransportState::Checking);
                  break;
                case NrIceMediaStream::ICE_OPEN:
                  transport.mIceState.Construct(
                      dom::RTCIceTransportState::Connected);
                  break;
                case NrIceMediaStream::ICE_CLOSED:
                  transport.mIceState.Construct(
                      dom::RTCIceTransportState::Closed);
                  break;
              }
              // XXX(Bug 1225723) Determine if dtlsState should be `required`.
              transport.mDtlsState = dom::RTCDtlsTransportState::New;
              auto transportIt = mTransports.find(stream->GetId());
              if (transportIt != mTransports.end() &&
                  transportIt->second.mFlow) {
                if (auto* dtlsLayer = static_cast<TransportLayerDtls*>(
                        transportIt->second.mFlow->GetLayer(
                            TransportLayerDtls::ID()))) {
                  transport.mDtlsRole.Construct(
                      dtlsLayer->role() == TransportLayerDtls::CLIENT
                          ? dom::RTCDtlsRole::Client
                          : dom::RTCDtlsRole::Server);
                  switch (dtlsLayer->state()) {
                    case TransportLayer::TS_NONE:
                    case TransportLayer::TS_INIT:
                      transport.mDtlsState = dom::RTCDtlsTransportState::New;
                      break;
                    case TransportLayer::TS_CONNECTING:
                      transport.mDtlsState =
                          dom::RTCDtlsTransportState::Connecting;
                      break;
                    case TransportLayer::TS_OPEN:
                      transport.mDtlsState =
                          dom::RTCDtlsTransportState::Connected;
                      break;
                    case TransportLayer::TS_CLOSED:
                      transport.mDtlsState = dom::RTCDtlsTransportState::Closed;
                      break;
                    case TransportLayer::TS_ERROR:
                      transport.mDtlsState = dom::RTCDtlsTransportState::Failed;
                      break;
                  }
                  uint16_t srtpCipher = 0;
                  if (NS_SUCCEEDED(dtlsLayer->GetSrtpCipher(&srtpCipher))) {
                    const char* name =
                        TransportLayerDtls::GetSrtpCipherName(srtpCipher);
                    if (name) {
                      transport.mSrtpCipher.Construct(
                          NS_ConvertASCIItoUTF16(name));
                    }
                  }
                  SSLChannelInfo channelInfo;
                  if (NS_SUCCEEDED(dtlsLayer->GetChannelInfo(&channelInfo))) {
                    if (uint16_t v =
                            ToDtlsWireVersion(channelInfo.protocolVersion)) {
                      transport.mTlsVersion.Construct(
                          nsFmtString(u"{:04X}", v));
                    }
                    SSLCipherSuiteInfo info;
                    if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &info,
                                               sizeof(info)) == SECSuccess &&
                        info.cipherSuiteName) {
                      transport.mDtlsCipher.Construct(
                          NS_ConvertASCIItoUTF16(info.cipherSuiteName));
                    }
                  }

                  if (dtlsLayer->state() == TransportLayer::TS_OPEN) {
                    {
                      nsString localId =
                          BuildCertificateStats(dtlsLayer->GetLocalCertDer(),
                                                u""_ns, aNow, stats.get());
                      if (!localId.IsEmpty()) {
                        transport.mLocalCertificateId.Construct(localId);
                      }
                    }

                    {
                      nsTArray<nsTArray<uint8_t>> remoteChain =
                          dtlsLayer->GetPeerCertChainDer();
                      nsString issuerId;
                      // The chain is leaf-first. Here we start with the root;
                      // for the root the issuerId is empty. In WebRTC the chain
                      // often consists of a single certificate, i.e. it is
                      // self-signed:
                      // https://w3c.github.io/webrtc-stats/#dom-rtccertificatestats-issuercertificateid
                      // "If the current certificate is at the end of the chain
                      // (i.e. a self-signed certificate), this will not be
                      // set."
                      for (const auto& der : Reversed(remoteChain)) {
                        issuerId = BuildCertificateStats(der, issuerId, aNow,
                                                         stats.get());
                      }
                      // Having walked the chain root-first, issuerId now holds
                      // the leaf certificate's id.
                      if (!issuerId.IsEmpty()) {
                        transport.mRemoteCertificateId.Construct(issuerId);
                      }
                    }
                  }
                }
                transport.mBytesSent.Construct(transportIt->second.mBytesSent);
                transport.mBytesReceived.Construct(
                    transportIt->second.mBytesReceived);
                transport.mPacketsSent.Construct(
                    transportIt->second.mPacketsSent);
                transport.mPacketsReceived.Construct(
                    transportIt->second.mPacketsReceived);
              }
              // XXX(Bug 2037532) Fill missing fields on the transport.
              GetIceStats(*stream, aNow, stats.get(), transport);

              // 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.
              if (!stats->mTransportStats.AppendElement(transport, fallible)) {
                mozalloc_handle_oom(0);
              }
            }
          }
        }
        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 = 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.mUsernameFragment.Construct(
        NS_ConvertASCIItoUTF16(candidate.username_fragment.c_str()));
    // Foundation is not set for peer-reflexive candidates.
    if (candidate.type != NrIceCandidate::ICE_PEER_REFLEXIVE) {
      cand.mFoundation.Construct(
          NS_ConvertASCIItoUTF16(candidate.foundation.c_str()));
    }
    if (candidate.tcp_type == NrIceCandidate::ICE_ACTIVE) {
      cand.mTcpType.Construct(dom::RTCIceTcpCandidateType::Active);
    } else if (candidate.tcp_type == NrIceCandidate::ICE_PASSIVE) {
      cand.mTcpType.Construct(dom::RTCIceTcpCandidateType::Passive);
    }
    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, dom::RTCTransportStats& aTransport) 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 = 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.mPacketsSent.Construct(candPair.packets_sent);
    s.mPacketsReceived.Construct(candPair.packets_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.mResponsesReceived.Construct(candPair.responses_recvd);
    s.mCurrentRoundTripTime.Construct(candPair.current_rtt_ms / 1000.0);
    s.mTotalRoundTripTime.Construct(candPair.total_rtt_ms / 1000.0);
    s.mComponentId.Construct(candPair.component_id);
    if (candPair.selected && candPair.component_id == 1) {
      aTransport.mSelectedCandidatePairId.Construct(codeword);
    }
    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 flow = MakeRefPtr<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::RTCIceGathererState toDomIceGathererState(
    NrIceMediaStream::GatheringState aState) {
  switch (aState) {
    case NrIceMediaStream::ICE_STREAM_GATHER_INIT:
      return dom::RTCIceGathererState::New;
    case NrIceMediaStream::ICE_STREAM_GATHER_STARTED:
      return dom::RTCIceGathererState::Gathering;
    case NrIceMediaStream::ICE_STREAM_GATHER_COMPLETE:
      return dom::RTCIceGathererState::Complete;
  }
  MOZ_CRASH();
}

void MediaTransportHandlerSTS::OnGatheringStateChange(
    const std::string& aTransportId, NrIceMediaStream::GatheringState aState) {
  OnGatheringStateChange(aTransportId, toDomIceGathererState(aState));
}

static mozilla::dom::RTCIceTransportState toDomIceTransportState(
    NrIceCtx::ConnectionState aState) {
  switch (aState) {
    case NrIceCtx::ICE_CTX_INIT:
      return dom::RTCIceTransportState::New;
    case NrIceCtx::ICE_CTX_CHECKING:
      return dom::RTCIceTransportState::Checking;
    case NrIceCtx::ICE_CTX_CONNECTED:
      return dom::RTCIceTransportState::Connected;
    case NrIceCtx::ICE_CTX_COMPLETED:
      return dom::RTCIceTransportState::Completed;
    case NrIceCtx::ICE_CTX_FAILED:
      return dom::RTCIceTransportState::Failed;
    case NrIceCtx::ICE_CTX_DISCONNECTED:
      return dom::RTCIceTransportState::Disconnected;
    case NrIceCtx::ICE_CTX_CLOSED:
      return dom::RTCIceTransportState::Closed;
  }
  MOZ_CRASH();
}

void MediaTransportHandlerSTS::OnConnectionStateChange(
    NrIceMediaStream* aIceStream, NrIceCtx::ConnectionState aState) {
  OnConnectionStateChange(aIceStream->GetId(), toDomIceTransportState(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(), std::move(info));
}

void MediaTransportHandlerSTS::OnCandidateError(NrIceMediaStream* aStream,
                                                const std::string& aAddress,
                                                uint16_t aPort,
                                                const std::string& aUrl,
                                                uint16_t aErrorCode,
                                                const std::string& aErrorText) {
  IceCandidateErrorInfo info;
  info.mAddress = aAddress;
  info.mPort = aPort;
  info.mUrl = aUrl;
  info.mErrorCode = aErrorCode;
  info.mErrorText = aErrorText;
  OnCandidateError(std::move(info));
}

dom::RTCErrorParams GetErrorInfo(const TransportLayerDtls& aDtlsLayer) {
  dom::RTCErrorInit error;
  if (aDtlsLayer.HasFingerprintError()) {
    // We might have sent an alert for this, but webrtc-pc says sendAlert is
    // only set when the error detail is "dtls-failure".
    error.mErrorDetail = dom::RTCErrorDetailType::Fingerprint_failure;
  } else {
    error.mErrorDetail = dom::RTCErrorDetailType::Dtls_failure;
    // Spec says these cannot be set in the "fingerprint-failure" case
    aDtlsLayer.GetSentAlert().apply(
        [&](auto value) { error.mSentAlert.Construct(value); });
    aDtlsLayer.GetReceivedAlert().apply(
        [&](auto value) { error.mReceivedAlert.Construct(value); });
  }

  return dom::RTCErrorParams{error, aDtlsLayer.GetErrorDescription()};
}

void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer,
                                             TransportLayer::State aState) {
  nsTArray<nsTArray<uint8_t>> remoteCerts;

  MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
  Maybe<dom::RTCErrorParams> error;
  TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
  if (aState == TransportLayer::TS_OPEN) {
    OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn());
    remoteCerts = dtlsLayer->GetPeerCertChainDer();
  } else if (aState == TransportLayer::TS_ERROR) {
    error = Some(GetErrorInfo(*dtlsLayer));
  }

  // 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,
                                       std::move(remoteCerts), error);
}

void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer,
                                                 TransportLayer::State aState) {
  MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
  Maybe<dom::RTCErrorParams> error;
  TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
  if (aState == TransportLayer::TS_ERROR) {
    error = Some(GetErrorInfo(*dtlsLayer));
  }

  MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState, error);
}

void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer,
                                              MediaPacket& aPacket) {
  MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket);
  if (auto it = mTransports.find(aLayer->flow_id()); it != mTransports.end()) {
    it->second.mBytesReceived += aPacket.len();
    it->second.mPacketsReceived += 1;
  }
  OnPacketReceived(std::string(aLayer->flow_id()), std::move(aPacket));
}

void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer,
                                                      MediaPacket& aPacket) {
  OnEncryptedSending(aLayer->flow_id(), std::move(aPacket));
}

}  // namespace mozilla

#undef MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED
