/* 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/. */

// Original author: ekr@rtfm.com

#include "transportlayerdtls.h"

#include <algorithm>
#include <iomanip>
#include <sstream>

#include "dtlsidentity.h"
#include "keyhi.h"
#include "logging.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/glean/DomMediaWebrtcMetrics.h"
#include "nsCOMPtr.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
#include "sslexp.h"
#include "sslproto.h"

namespace mozilla {

MOZ_MTLOG_MODULE("mtransport")

static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER;

// TODO: Implement a mode for this where
// the channel is not ready until confirmed externally
// (e.g., after cert check).

#define UNIMPLEMENTED                                                     \
  MOZ_MTLOG(ML_ERROR, "Call to unimplemented function " << __FUNCTION__); \
  MOZ_ASSERT(false);                                                      \
  PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0)

#define MAX_ALPN_LENGTH 255

// We need to adapt the NSPR/libssl model to the TransportFlow model.
// The former wants pull semantics and TransportFlow wants push.
//
// - A TransportLayerDtls assumes it is sitting on top of another
//   TransportLayer, which means that events come in asynchronously.
// - NSS (libssl) wants to sit on top of a PRFileDesc and poll.
// - The TransportLayerNSPRAdapter is a PRFileDesc containing a
//   FIFO.
// - When TransportLayerDtls.PacketReceived() is called, we insert
//   the packets in the FIFO and then do a PR_Recv() on the NSS
//   PRFileDesc, which eventually reads off the FIFO.
//
// All of this stuff is assumed to happen solely in a single thread
// (generally the SocketTransportService thread)

void TransportLayerNSPRAdapter::PacketReceived(MediaPacket& packet) {
  if (enabled_) {
    input_.push(new MediaPacket(std::move(packet)));
  }
}

int32_t TransportLayerNSPRAdapter::Recv(void* buf, int32_t buflen) {
  if (input_.empty()) {
    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
    return -1;
  }

  MediaPacket* front = input_.front();
  int32_t count = static_cast<int32_t>(front->len());

  if (buflen < count) {
    MOZ_ASSERT(false, "Not enough buffer space to receive into");
    PR_SetError(PR_BUFFER_OVERFLOW_ERROR, 0);
    return -1;
  }

  memcpy(buf, front->data(), count);

  input_.pop();
  delete front;

  return count;
}

int32_t TransportLayerNSPRAdapter::Write(const void* buf, int32_t length) {
  if (!enabled_) {
    MOZ_MTLOG(ML_WARNING, "Writing to disabled transport layer");
    return -1;
  }

  MediaPacket packet;
  // Copies. Oh well.
  packet.Copy(static_cast<const uint8_t*>(buf), static_cast<size_t>(length));
  packet.SetType(MediaPacket::DTLS);

  TransportResult r = output_->SendPacket(packet);
  if (r >= 0) {
    return r;
  }

  if (r == TE_WOULDBLOCK) {
    PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
  } else {
    PR_SetError(PR_IO_ERROR, 0);
  }

  return -1;
}

// Implementation of NSPR methods
static PRStatus TransportLayerClose(PRFileDesc* f) {
  f->dtor(f);
  return PR_SUCCESS;
}

static int32_t TransportLayerRead(PRFileDesc* f, void* buf, int32_t length) {
  UNIMPLEMENTED;
  return -1;
}

static int32_t TransportLayerWrite(PRFileDesc* f, const void* buf,
                                   int32_t length) {
  TransportLayerNSPRAdapter* io =
      reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
  return io->Write(buf, length);
}

static int32_t TransportLayerAvailable(PRFileDesc* f) {
  UNIMPLEMENTED;
  return -1;
}

int64_t TransportLayerAvailable64(PRFileDesc* f) {
  UNIMPLEMENTED;
  return -1;
}

static PRStatus TransportLayerSync(PRFileDesc* f) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static int32_t TransportLayerSeek(PRFileDesc* f, int32_t offset,
                                  PRSeekWhence how) {
  UNIMPLEMENTED;
  return -1;
}

static int64_t TransportLayerSeek64(PRFileDesc* f, int64_t offset,
                                    PRSeekWhence how) {
  UNIMPLEMENTED;
  return -1;
}

static PRStatus TransportLayerFileInfo(PRFileDesc* f, PRFileInfo* info) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static PRStatus TransportLayerFileInfo64(PRFileDesc* f, PRFileInfo64* info) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static int32_t TransportLayerWritev(PRFileDesc* f, const PRIOVec* iov,
                                    int32_t iov_size, PRIntervalTime to) {
  UNIMPLEMENTED;
  return -1;
}

static PRStatus TransportLayerConnect(PRFileDesc* f, const PRNetAddr* addr,
                                      PRIntervalTime to) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static PRFileDesc* TransportLayerAccept(PRFileDesc* sd, PRNetAddr* addr,
                                        PRIntervalTime to) {
  UNIMPLEMENTED;
  return nullptr;
}

static PRStatus TransportLayerBind(PRFileDesc* f, const PRNetAddr* addr) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static PRStatus TransportLayerListen(PRFileDesc* f, int32_t depth) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static PRStatus TransportLayerShutdown(PRFileDesc* f, int32_t how) {
  // This is only called from NSS when we are the server and the client refuses
  // to provide a certificate.  In this case, the handshake is destined for
  // failure, so we will just let this pass.
  TransportLayerNSPRAdapter* io =
      reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
  io->SetEnabled(false);
  return PR_SUCCESS;
}

// This function does not support peek, or waiting until `to`
static int32_t TransportLayerRecv(PRFileDesc* f, void* buf, int32_t buflen,
                                  int32_t flags, PRIntervalTime to) {
  MOZ_ASSERT(flags == 0);
  if (flags != 0) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return -1;
  }

  TransportLayerNSPRAdapter* io =
      reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
  return io->Recv(buf, buflen);
}

// Note: this is always nonblocking and assumes a zero timeout.
static int32_t TransportLayerSend(PRFileDesc* f, const void* buf,
                                  int32_t amount, int32_t flags,
                                  PRIntervalTime to) {
  int32_t written = TransportLayerWrite(f, buf, amount);
  return written;
}

static int32_t TransportLayerRecvfrom(PRFileDesc* f, void* buf, int32_t amount,
                                      int32_t flags, PRNetAddr* addr,
                                      PRIntervalTime to) {
  UNIMPLEMENTED;
  return -1;
}

static int32_t TransportLayerSendto(PRFileDesc* f, const void* buf,
                                    int32_t amount, int32_t flags,
                                    const PRNetAddr* addr, PRIntervalTime to) {
  UNIMPLEMENTED;
  return -1;
}

static int16_t TransportLayerPoll(PRFileDesc* f, int16_t in_flags,
                                  int16_t* out_flags) {
  UNIMPLEMENTED;
  return -1;
}

static int32_t TransportLayerAcceptRead(PRFileDesc* sd, PRFileDesc** nd,
                                        PRNetAddr** raddr, void* buf,
                                        int32_t amount, PRIntervalTime t) {
  UNIMPLEMENTED;
  return -1;
}

static int32_t TransportLayerTransmitFile(PRFileDesc* sd, PRFileDesc* f,
                                          const void* headers, int32_t hlen,
                                          PRTransmitFileFlags flags,
                                          PRIntervalTime t) {
  UNIMPLEMENTED;
  return -1;
}

static PRStatus TransportLayerGetpeername(PRFileDesc* f, PRNetAddr* addr) {
  // TODO: Modify to return unique names for each channel
  // somehow, as opposed to always the same static address. The current
  // implementation messes up the session cache, which is why it's off
  // elsewhere
  addr->inet.family = PR_AF_INET;
  addr->inet.port = 0;
  addr->inet.ip = 0;

  return PR_SUCCESS;
}

static PRStatus TransportLayerGetsockname(PRFileDesc* f, PRNetAddr* addr) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static PRStatus TransportLayerGetsockoption(PRFileDesc* f,
                                            PRSocketOptionData* opt) {
  switch (opt->option) {
    case PR_SockOpt_Nonblocking:
      opt->value.non_blocking = PR_TRUE;
      return PR_SUCCESS;
    default:
      UNIMPLEMENTED;
      break;
  }

  return PR_FAILURE;
}

// Imitate setting socket options. These are mostly noops.
static PRStatus TransportLayerSetsockoption(PRFileDesc* f,
                                            const PRSocketOptionData* opt) {
  switch (opt->option) {
    case PR_SockOpt_Nonblocking:
      return PR_SUCCESS;
    case PR_SockOpt_NoDelay:
      return PR_SUCCESS;
    default:
      UNIMPLEMENTED;
      break;
  }

  return PR_FAILURE;
}

static int32_t TransportLayerSendfile(PRFileDesc* out, PRSendFileData* in,
                                      PRTransmitFileFlags flags,
                                      PRIntervalTime to) {
  UNIMPLEMENTED;
  return -1;
}

static PRStatus TransportLayerConnectContinue(PRFileDesc* f, int16_t flags) {
  UNIMPLEMENTED;
  return PR_FAILURE;
}

static int32_t TransportLayerReserved(PRFileDesc* f) {
  UNIMPLEMENTED;
  return -1;
}

static const struct PRIOMethods TransportLayerMethods = {
    PR_DESC_LAYERED,
    TransportLayerClose,
    TransportLayerRead,
    TransportLayerWrite,
    TransportLayerAvailable,
    TransportLayerAvailable64,
    TransportLayerSync,
    TransportLayerSeek,
    TransportLayerSeek64,
    TransportLayerFileInfo,
    TransportLayerFileInfo64,
    TransportLayerWritev,
    TransportLayerConnect,
    TransportLayerAccept,
    TransportLayerBind,
    TransportLayerListen,
    TransportLayerShutdown,
    TransportLayerRecv,
    TransportLayerSend,
    TransportLayerRecvfrom,
    TransportLayerSendto,
    TransportLayerPoll,
    TransportLayerAcceptRead,
    TransportLayerTransmitFile,
    TransportLayerGetsockname,
    TransportLayerGetpeername,
    TransportLayerReserved,
    TransportLayerReserved,
    TransportLayerGetsockoption,
    TransportLayerSetsockoption,
    TransportLayerSendfile,
    TransportLayerConnectContinue,
    TransportLayerReserved,
    TransportLayerReserved,
    TransportLayerReserved,
    TransportLayerReserved};

TransportLayerDtls::~TransportLayerDtls() {
  // Destroy the NSS instance first so it can still send out an alert before
  // we disable the nspr_io_adapter_.
  ssl_fd_ = nullptr;
  nspr_io_adapter_->SetEnabled(false);
  if (timer_) {
    timer_->Cancel();
  }
}

nsresult TransportLayerDtls::InitInternal() {
  // Get the transport service as an event target
  nsresult rv;
  target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);

  if (NS_FAILED(rv)) {
    MOZ_MTLOG(ML_ERROR, "Couldn't get socket transport service");
    return rv;
  }

  timer_ = NS_NewTimer();
  if (!timer_) {
    MOZ_MTLOG(ML_ERROR, "Couldn't get timer");
    return rv;
  }

  return NS_OK;
}

void TransportLayerDtls::WasInserted() {
  // Connect to the lower layers
  if (!Setup()) {
    mErrorDescription = "Internal error";
    TL_SET_STATE(TS_ERROR);
  }
}

// Set the permitted and default ALPN identifiers.
// The default is here to allow for peers that don't want to negotiate ALPN
// in that case, the default string will be reported from GetNegotiatedAlpn().
// Setting the default to the empty string causes the transport layer to fail
// if ALPN is not negotiated.
// Note: we only support Unicode strings here, which are encoded into UTF-8,
// even though ALPN ostensibly allows arbitrary octet sequences.
nsresult TransportLayerDtls::SetAlpn(const std::set<std::string>& alpn_allowed,
                                     const std::string& alpn_default) {
  alpn_allowed_ = alpn_allowed;
  alpn_default_ = alpn_default;

  return NS_OK;
}

nsresult TransportLayerDtls::SetVerificationAllowAll() {
  // Defensive programming
  if (verification_mode_ != VERIFY_UNSET) return NS_ERROR_ALREADY_INITIALIZED;

  verification_mode_ = VERIFY_ALLOW_ALL;

  return NS_OK;
}

nsresult TransportLayerDtls::SetVerificationDigest(const DtlsDigest& digest) {
  // Defensive programming
  if (verification_mode_ != VERIFY_UNSET &&
      verification_mode_ != VERIFY_DIGEST) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }

  digests_.push_back(digest);
  verification_mode_ = VERIFY_DIGEST;
  return NS_OK;
}

void TransportLayerDtls::SetMinMaxVersion(Version min_version,
                                          Version max_version) {
  if (min_version < Version::DTLS_1_0 || min_version > Version::DTLS_1_3 ||
      max_version < Version::DTLS_1_0 || max_version > Version::DTLS_1_3 ||
      min_version > max_version || max_version < min_version) {
    return;
  }
  minVersion_ = min_version;
  maxVersion_ = max_version;
}

// TODO: make sure this is called from STS. Otherwise
// we have thread safety issues
bool TransportLayerDtls::Setup() {
  CheckThread();
  SECStatus rv;

  if (!downward_) {
    MOZ_MTLOG(ML_ERROR, "DTLS layer with nothing below. This is useless");
    return false;
  }
  nspr_io_adapter_ = MakeUnique<TransportLayerNSPRAdapter>(downward_);

  if (!identity_) {
    MOZ_MTLOG(ML_ERROR, "Can't start DTLS without an identity");
    return false;
  }

  if (verification_mode_ == VERIFY_UNSET) {
    MOZ_MTLOG(ML_ERROR,
              "Can't start DTLS without specifying a verification mode");
    return false;
  }

  if (transport_layer_identity == PR_INVALID_IO_LAYER) {
    transport_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
  }

  UniquePRFileDesc pr_fd(
      PR_CreateIOLayerStub(transport_layer_identity, &TransportLayerMethods));
  MOZ_ASSERT(pr_fd != nullptr);
  if (!pr_fd) return false;
  pr_fd->secret = reinterpret_cast<PRFilePrivate*>(nspr_io_adapter_.get());

  UniquePRFileDesc ssl_fd(DTLS_ImportFD(nullptr, pr_fd.get()));
  MOZ_ASSERT(ssl_fd != nullptr);  // This should never happen
  if (!ssl_fd) {
    return false;
  }

  (void)pr_fd.release();  // ownership transfered to ssl_fd;

  if (role_ == CLIENT) {
    MOZ_MTLOG(ML_INFO, "Setting up DTLS as client");
    rv = SSL_GetClientAuthDataHook(ssl_fd.get(), GetClientAuthDataHook, this);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
      return false;
    }

    if (maxVersion_ >= Version::DTLS_1_3) {
      MOZ_MTLOG(ML_INFO, "Setting DTLS1.3 supported_versions workaround");
      rv = SSL_SetDtls13VersionWorkaround(ssl_fd.get(), PR_TRUE);
      if (rv != SECSuccess) {
        MOZ_MTLOG(ML_ERROR, "Couldn't set DTLS1.3 workaround");
        return false;
      }
    }
  } else {
    MOZ_MTLOG(ML_INFO, "Setting up DTLS as server");
    // Server side
    rv = SSL_ConfigSecureServer(ssl_fd.get(), identity_->cert().get(),
                                identity_->privkey().get(),
                                identity_->auth_type());
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
      return false;
    }

    UniqueCERTCertList zero_certs(CERT_NewCertList());
    rv = SSL_SetTrustAnchors(ssl_fd.get(), zero_certs.get());
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, "Couldn't set trust anchors");
      return false;
    }

    // Insist on a certificate from the client
    rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUEST_CERTIFICATE, PR_TRUE);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, "Couldn't request certificate");
      return false;
    }

    rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUIRE_CERTIFICATE, PR_TRUE);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, "Couldn't require certificate");
      return false;
    }
  }

  SSLVersionRange version_range = {static_cast<PRUint16>(minVersion_),
                                   static_cast<PRUint16>(maxVersion_)};

  rv = SSL_VersionRangeSet(ssl_fd.get(), &version_range);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Can't disable SSLv3");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable session tickets");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_CACHE, PR_TRUE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable session caching");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_DEFLATE, PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable deflate");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_RENEGOTIATION,
                     SSL_RENEGOTIATE_NEVER);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable renegotiation");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_FALSE_START, PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable false start");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_LOCKS, PR_TRUE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable locks");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable ECDHE key reuse");
    return false;
  }

  if (!SetupCipherSuites(ssl_fd)) {
    return false;
  }

  // If the version is DTLS1.3 and pq in enabled, we will indicate support for
  // ML-KEM
  bool enable_mlkem = maxVersion_ >= Version::DTLS_1_3 &&
                      StaticPrefs::media_webrtc_enable_pq_hybrid_kex();

  bool send_mlkem = StaticPrefs::media_webrtc_send_mlkem_keyshare();

  if (!enable_mlkem && send_mlkem) {
    MOZ_MTLOG(ML_NOTICE,
              "The PQ preferences are inconsistent. ML-KEM support will not be "
              "advertised, nor will the ML-KEM key share be sent.");
  }

  std::vector<SSLNamedGroup> namedGroups;

  if (enable_mlkem && send_mlkem) {
    // RFC 8446: client_shares:  A list of offered KeyShareEntry values in
    // descending order of client preference.
    // {ssl_grp_kem_mlkem768x25519}, {ssl_grp_ec_curve2551} key shared to be
    // sent ML-KEM has the highest preference, so it's sent first.
    namedGroups = {ssl_grp_kem_mlkem768x25519, ssl_grp_ec_curve25519,
                   ssl_grp_ec_secp256r1,       ssl_grp_ec_secp384r1,
                   ssl_grp_ffdhe_2048,         ssl_grp_ffdhe_3072};

    if (SECSuccess != SSL_SendAdditionalKeyShares(ssl_fd.get(), 1)) {
      MOZ_MTLOG(ML_ERROR, "Couldn't set up additional key shares");
      return false;
    }
  }
  // Else we don't send any additional key share
  else if (enable_mlkem && !send_mlkem) {
    // Here the order of the namedGroups is different than in the first if
    // {ssl_grp_ec_curve25519} is first because it's the key share
    // that will be sent by default
    namedGroups = {ssl_grp_ec_curve25519, ssl_grp_kem_mlkem768x25519,
                   ssl_grp_ec_secp256r1,  ssl_grp_ec_secp384r1,
                   ssl_grp_ffdhe_2048,    ssl_grp_ffdhe_3072};
  }
  // ml_kem is disabled
  // {ssl_grp_ec_curve25519} will be send as a default key_share
  else {
    namedGroups = {ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1,
                   ssl_grp_ec_secp384r1, ssl_grp_ffdhe_2048,
                   ssl_grp_ffdhe_3072};
  }

  rv = SSL_NamedGroupConfig(ssl_fd.get(), namedGroups.data(),
                            std::size(namedGroups));

  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't set named groups");
    return false;
  }

  // Certificate validation
  rv = SSL_AuthCertificateHook(ssl_fd.get(), AuthCertificateHook,
                               reinterpret_cast<void*>(this));
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't set certificate validation hook");
    return false;
  }

  if (!SetupAlpn(ssl_fd)) {
    return false;
  }

  // Now start the handshake
  rv = SSL_ResetHandshake(ssl_fd.get(), role_ == SERVER ? PR_TRUE : PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't reset handshake");
    return false;
  }
  ssl_fd_ = std::move(ssl_fd);

  // Finally, get ready to receive data
  downward_->SignalStateChange.connect(this, &TransportLayerDtls::StateChange);
  downward_->SignalPacketReceived.connect(this,
                                          &TransportLayerDtls::PacketReceived);

  if (downward_->state() == TS_OPEN) {
    TL_SET_STATE(TS_CONNECTING);
    Handshake();
  }

  return true;
}

bool TransportLayerDtls::SetupAlpn(UniquePRFileDesc& ssl_fd) const {
  if (alpn_allowed_.empty()) {
    return true;
  }

  SECStatus rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_NPN, PR_FALSE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't disable NPN");
    return false;
  }

  rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_ALPN, PR_TRUE);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't enable ALPN");
    return false;
  }

  unsigned char buf[MAX_ALPN_LENGTH];
  size_t offset = 0;
  for (const auto& tag : alpn_allowed_) {
    if ((offset + 1 + tag.length()) >= sizeof(buf)) {
      MOZ_MTLOG(ML_ERROR, "ALPN too long");
      return false;
    }
    buf[offset++] = tag.length();
    memcpy(buf + offset, tag.c_str(), tag.length());
    offset += tag.length();
  }
  rv = SSL_SetNextProtoNego(ssl_fd.get(), buf, offset);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN string");
    return false;
  }
  return true;
}

// Ciphers we need to enable.  These are on by default in standard firefox
// builds, but can be disabled with prefs and they aren't on in our unit tests
// since that uses NSS default configuration.
//
// Only override prefs to comply with MUST statements in the security-arch
// doc. Anything outside this list is governed by the usual combination of
// policy and user preferences.
static const uint32_t EnabledCiphers[] = {
    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA};

// Disable all NSS suites modes without PFS or with old and rusty
// ciphersuites. Anything outside this list is governed by the usual
// combination of policy and user preferences.
static const uint32_t DisabledCiphers[] = {
    // Bug 1310061: disable all SHA384 ciphers until fixed
    TLS_AES_256_GCM_SHA384,
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
    TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
    TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,

    TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
    TLS_DHE_RSA_WITH_AES_256_CBC_SHA,

    TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
    TLS_ECDHE_RSA_WITH_RC4_128_SHA,

    TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
    TLS_DHE_DSS_WITH_RC4_128_SHA,

    TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
    TLS_ECDH_RSA_WITH_RC4_128_SHA,

    TLS_RSA_WITH_AES_128_GCM_SHA256,
    TLS_RSA_WITH_AES_256_GCM_SHA384,
    TLS_RSA_WITH_AES_128_CBC_SHA,
    TLS_RSA_WITH_AES_128_CBC_SHA256,
    TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
    TLS_RSA_WITH_AES_256_CBC_SHA,
    TLS_RSA_WITH_AES_256_CBC_SHA256,
    TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
    TLS_RSA_WITH_SEED_CBC_SHA,
    TLS_RSA_WITH_3DES_EDE_CBC_SHA,
    TLS_RSA_WITH_RC4_128_SHA,
    TLS_RSA_WITH_RC4_128_MD5,

    TLS_DHE_RSA_WITH_DES_CBC_SHA,
    TLS_DHE_DSS_WITH_DES_CBC_SHA,
    TLS_RSA_WITH_DES_CBC_SHA,

    TLS_ECDHE_ECDSA_WITH_NULL_SHA,
    TLS_ECDHE_RSA_WITH_NULL_SHA,
    TLS_ECDH_ECDSA_WITH_NULL_SHA,
    TLS_ECDH_RSA_WITH_NULL_SHA,
    TLS_RSA_WITH_NULL_SHA,
    TLS_RSA_WITH_NULL_SHA256,
    TLS_RSA_WITH_NULL_MD5,
};

bool TransportLayerDtls::SetupCipherSuites(UniquePRFileDesc& ssl_fd) {
  SECStatus rv;

  // Set the SRTP ciphers
  if (!enabled_srtp_ciphers_.empty()) {
    rv = SSL_InstallExtensionHooks(ssl_fd.get(), ssl_use_srtp_xtn,
                                   TransportLayerDtls::WriteSrtpXtn, this,
                                   TransportLayerDtls::HandleSrtpXtn, this);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, LAYER_INFO << "unable to set SRTP extension handler");
      return false;
    }
  }

  rv = SSL_AlertSentCallback(ssl_fd.get(),
                             TransportLayerDtls::SentAlertCallback, this);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Unable to register alert sent callback");
  }

  rv = SSL_AlertReceivedCallback(
      ssl_fd.get(), TransportLayerDtls::ReceivedAlertCallback, this);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR,
              LAYER_INFO << "Unable to register alert received callback");
  }

  for (const auto& cipher : EnabledCiphers) {
    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << cipher);
    rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_TRUE);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Unable to enable suite: " << cipher);
      return false;
    }
  }

  for (const auto& cipher : DisabledCiphers) {
    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << cipher);

    PRBool enabled = false;
    rv = SSL_CipherPrefGet(ssl_fd.get(), cipher, &enabled);
    if (rv != SECSuccess) {
      MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Unable to check if suite is enabled: "
                                      << cipher);
      return false;
    }
    if (enabled) {
      rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_FALSE);
      if (rv != SECSuccess) {
        MOZ_MTLOG(ML_NOTICE,
                  LAYER_INFO << "Unable to disable suite: " << cipher);
        return false;
      }
    }
  }

  return true;
}

nsresult TransportLayerDtls::GetChannelInfo(SSLChannelInfo* info) const {
  CheckThread();
  if (state_ != TS_OPEN) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  if (SSL_GetChannelInfo(ssl_fd_.get(), info, sizeof(*info)) != SECSuccess) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsTArray<nsTArray<uint8_t>> TransportLayerDtls::GetPeerCertChainDer() const {
  CheckThread();
  nsTArray<nsTArray<uint8_t>> result;
  UniqueCERTCertList chain(SSL_PeerCertificateChain(ssl_fd_.get()));
  if (!chain) {
    MOZ_MTLOG(ML_NOTICE,
              LAYER_INFO << "SSL_PeerCertificateChain returned no certs");
    return result;
  }
  for (CERTCertListNode* node = CERT_LIST_HEAD(chain);
       !CERT_LIST_END(node, chain); node = CERT_LIST_NEXT(node)) {
    const SECItem& der = node->cert->derCert;
    nsTArray<uint8_t> bytes;
    bytes.AppendElements(der.data, der.len);
    result.AppendElement(std::move(bytes));
  }
  return result;
}

nsTArray<uint8_t> TransportLayerDtls::GetLocalCertDer() const {
  CheckThread();
  nsTArray<uint8_t> result;
  if (!identity_) {
    return result;
  }
  const UniqueCERTCertificate& cert = identity_->cert();
  if (!cert) {
    return result;
  }
  const SECItem& der = cert->derCert;
  result.AppendElements(der.data, der.len);
  return result;
}

nsresult TransportLayerDtls::GetCipherSuite(uint16_t* cipherSuite) const {
  if (!cipherSuite) {
    MOZ_MTLOG(ML_ERROR, LAYER_INFO << "GetCipherSuite passed a nullptr");
    return NS_ERROR_NULL_POINTER;
  }
  SSLChannelInfo info;
  nsresult rv = GetChannelInfo(&info);
  if (NS_FAILED(rv)) {
    if (rv == NS_ERROR_FAILURE) {
      MOZ_MTLOG(ML_NOTICE,
                LAYER_INFO << "GetCipherSuite can't get channel info");
    }
    return rv;
  }
  *cipherSuite = info.cipherSuite;
  return NS_OK;
}

std::vector<uint16_t> TransportLayerDtls::GetDefaultSrtpCiphers() {
  std::vector<uint16_t> ciphers;

  ciphers.push_back(kDtlsSrtpAeadAes128Gcm);
  // Since we don't support DTLS 1.3 or SHA384 ciphers (see bug 1312976)
  // we don't really enough entropy to prefer this over 128 bit
  ciphers.push_back(kDtlsSrtpAeadAes256Gcm);
  ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_80);
#ifndef NIGHTLY_BUILD
  // To support bug 1491583 lets try to find out if we get bug reports if we
  // no longer offer this in Nightly builds.
  ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_32);
#endif

  return ciphers;
}

const char* TransportLayerDtls::GetSrtpCipherName(uint16_t cipher) {
  switch (cipher) {
    case kDtlsSrtpAes128CmHmacSha1_80:
      return "SRTP_AES128_CM_HMAC_SHA1_80";
    case kDtlsSrtpAes128CmHmacSha1_32:
      return "SRTP_AES128_CM_HMAC_SHA1_32";
    case kDtlsSrtpAeadAes128Gcm:
      return "SRTP_AEAD_AES_128_GCM";
    case kDtlsSrtpAeadAes256Gcm:
      return "SRTP_AEAD_AES_256_GCM";
  }
  return nullptr;
}

void TransportLayerDtls::StateChange(TransportLayer* layer, State state) {
  switch (state) {
    case TS_NONE:
      MOZ_ASSERT(false);  // Can't happen
      break;

    case TS_INIT:
      MOZ_MTLOG(ML_ERROR,
                LAYER_INFO << "State change of lower layer to INIT forbidden");
      mErrorDescription = "Internal Error";
      TL_SET_STATE(TS_ERROR);
      break;

    case TS_CONNECTING:
      MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is connecting.");
      break;

    case TS_OPEN:
      if (timer_) {
        MOZ_MTLOG(ML_INFO,
                  LAYER_INFO << "Lower layer is now open; starting TLS");
        timer_->Cancel();
        timer_->SetTarget(target_);
        // Async, since the ICE layer might need to send a STUN response, and
        // we don't want the handshake to start until that is sent.
        timer_->InitWithNamedFuncCallback(
            TimerCallback, this, 0, nsITimer::TYPE_ONE_SHOT,
            "TransportLayerDtls::TimerCallback"_ns);
        TL_SET_STATE(TS_CONNECTING);
      } else {
        // We have already completed DTLS. Can happen if the ICE layer failed
        // due to a loss of network, and then recovered.
        TL_SET_STATE(TS_OPEN);
      }
      break;

    case TS_CLOSED:
      MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is now closed");
      TL_SET_STATE(TS_CLOSED);
      break;

    case TS_ERROR:
      MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower layer experienced an error");
      mErrorDescription = "ICE encountered an error";
      TL_SET_STATE(TS_ERROR);
      break;
  }
}

void TransportLayerDtls::Handshake() {
  if (!timer_) {
    // We are done with DTLS, regardless of the state changes of lower layers
    return;
  }

  if (!handshakeTelemetryRecorded) {
    RecordStartedHandshakeTelemetry();
    handshakeTelemetryRecorded = true;
  }

  // Clear the retransmit timer
  timer_->Cancel();

  MOZ_ASSERT(state_ == TS_CONNECTING);

  SECStatus rv = SSL_ForceHandshake(ssl_fd_.get());

  if (rv == SECSuccess) {
    MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "****** SSL handshake completed ******");
    if (!cert_ok_) {
      MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred");
      RecordHandshakeCompletionTelemetry("CERT_FAILURE");
      mErrorDescription = "Internal error";
      TL_SET_STATE(TS_ERROR);
      return;
    }
    if (!CheckAlpn()) {
      // Despite connecting, the connection doesn't have a valid ALPN label.
      // Forcibly close the connection so that the peer isn't left hanging
      // (assuming the close_notify isn't dropped).
      ssl_fd_ = nullptr;
      RecordHandshakeCompletionTelemetry("ALPN_FAILURE");
      // CheckAlpn sets mErrorDescription
      TL_SET_STATE(TS_ERROR);
      return;
    }

    RecordHandshakeCompletionTelemetry("SUCCESS");
    TL_SET_STATE(TS_OPEN);

    RecordTlsTelemetry();
    timer_ = nullptr;
  } else {
    int32_t err = PR_GetError();
    switch (err) {
      case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
        MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed DTLS message; ignoring");
        // If this were TLS (and not DTLS), this would be fatal, but
        // here we're required to ignore bad messages, so fall through
        [[fallthrough]];
      case PR_WOULD_BLOCK_ERROR:
        MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Handshake would have blocked");
        PRIntervalTime timeout;
        rv = DTLS_GetHandshakeTimeout(ssl_fd_.get(), &timeout);
        if (rv == SECSuccess) {
          uint32_t timeout_ms = PR_IntervalToMilliseconds(timeout);

          MOZ_MTLOG(ML_DEBUG,
                    LAYER_INFO << "Setting DTLS timeout to " << timeout_ms);
          timer_->SetTarget(target_);
          timer_->InitWithNamedFuncCallback(
              TimerCallback, this, timeout_ms, nsITimer::TYPE_ONE_SHOT,
              "TransportLayerDtls::TimerCallback"_ns);
        }
        break;
      default:
        const char* err_msg = PR_ErrorToName(err);
        MOZ_MTLOG(ML_ERROR, LAYER_INFO << "DTLS handshake error " << err << " ("
                                       << err_msg << ")");
        RecordHandshakeCompletionTelemetry(err_msg);
        mErrorDescription = "DTLS handshake failure";
        TL_SET_STATE(TS_ERROR);
        break;
    }
  }
}

// Checks if ALPN was negotiated correctly and returns false if it wasn't.
// After this returns successfully, alpn_ will be set to the negotiated
// protocol.
bool TransportLayerDtls::CheckAlpn() {
  if (alpn_allowed_.empty()) {
    return true;
  }

  SSLNextProtoState alpnState;
  char chosenAlpn[MAX_ALPN_LENGTH];
  unsigned int chosenAlpnLen;
  SECStatus rv = SSL_GetNextProto(ssl_fd_.get(), &alpnState,
                                  reinterpret_cast<unsigned char*>(chosenAlpn),
                                  &chosenAlpnLen, sizeof(chosenAlpn));
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, LAYER_INFO << "ALPN error");
    mErrorDescription = "Internal error";
    return false;
  }
  switch (alpnState) {
    case SSL_NEXT_PROTO_SELECTED:
    case SSL_NEXT_PROTO_NEGOTIATED:
      break;  // OK

    case SSL_NEXT_PROTO_NO_SUPPORT:
      MOZ_MTLOG(ML_NOTICE,
                LAYER_INFO << "ALPN not negotiated, "
                           << (alpn_default_.empty() ? "failing"
                                                     : "selecting default"));
      alpn_ = alpn_default_;
      if (alpn_.empty()) {
        mErrorDescription = "No ALPN";
      }
      return !alpn_.empty();

    case SSL_NEXT_PROTO_NO_OVERLAP:
      // This only happens if there is a custom NPN/ALPN callback installed
      // and that callback doesn't properly handle ALPN.
      MOZ_MTLOG(ML_ERROR, LAYER_INFO << "error in ALPN selection callback");
      mErrorDescription = "Internal error";
      return false;

    case SSL_NEXT_PROTO_EARLY_VALUE:
      MOZ_CRASH("Unexpected 0-RTT ALPN value");
      mErrorDescription = "Internal error";
      return false;
  }

  // Warning: NSS won't null terminate the ALPN string for us.
  std::string chosen(chosenAlpn, chosenAlpnLen);
  MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Selected ALPN string: " << chosen);
  if (alpn_allowed_.find(chosen) == alpn_allowed_.end()) {
    // Maybe our peer chose a protocol we didn't offer (when we are client),
    // or something is seriously wrong.
    std::ostringstream ss;
    for (auto i = alpn_allowed_.begin(); i != alpn_allowed_.end(); ++i) {
      ss << (i == alpn_allowed_.begin() ? " '" : ", '") << *i << "'";
    }
    MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Bad ALPN string: '" << chosen
                                   << "'; permitted:" << ss.str());
    mErrorDescription = fmt::format("Invalid ALPN: {}", chosen);
    return false;
  }
  alpn_ = std::move(chosen);
  return true;
}

void TransportLayerDtls::PacketReceived(TransportLayer* layer,
                                        MediaPacket& packet) {
  CheckThread();
  MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << packet.len() << ")");

  if (state_ != TS_CONNECTING && state_ != TS_OPEN) {
    MOZ_MTLOG(ML_DEBUG,
              LAYER_INFO << "Discarding packet in inappropriate state");
    return;
  }

  if (!packet.data()) {
    // Something ate this, probably the SRTP layer
    return;
  }

  if (packet.type() != MediaPacket::DTLS) {
    return;
  }

  nspr_io_adapter_->PacketReceived(packet);
  GetDecryptedPackets();
}

void TransportLayerDtls::GetDecryptedPackets() {
  // If we're still connecting, try to handshake
  if (state_ == TS_CONNECTING) {
    Handshake();
  }

  // Now try a recv if we're open, since there might be data left
  if (state_ == TS_OPEN) {
    int32_t rv;
    // One packet might contain several DTLS packets
    do {
      // nICEr uses a 9216 bytes buffer to allow support for jumbo frames
      // Can we peek to get a better idea of the actual size?
      static const size_t kBufferSize = 9216;
      auto buffer = MakeUnique<uint8_t[]>(kBufferSize);
      rv = PR_Recv(ssl_fd_.get(), buffer.get(), kBufferSize, 0,
                   PR_INTERVAL_NO_WAIT);
      if (rv > 0) {
        // We have data
        MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS");
        MediaPacket packet;
        packet.SetType(MediaPacket::SCTP);
        packet.Take(std::move(buffer), static_cast<size_t>(rv));
        SignalPacketReceived(this, packet);
      } else if (rv == 0) {
        TL_SET_STATE(TS_CLOSED);
      } else {
        int32_t err = PR_GetError();

        if (err == PR_WOULD_BLOCK_ERROR) {
          // This gets ignored
          MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Receive would have blocked");
        } else {
          MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err);
          mErrorDescription = "DTLS receive failed";
          TL_SET_STATE(TS_ERROR);
        }
      }
    } while (rv > 0);
  }
}

void TransportLayerDtls::SetState(State state, const char* file,
                                  unsigned line) {
  if (timer_) {
    switch (state) {
      case TS_NONE:
      case TS_INIT:
        MOZ_ASSERT(false);
        break;
      case TS_CONNECTING:
        break;
      case TS_OPEN:
      case TS_CLOSED:
      case TS_ERROR:
        timer_->Cancel();
        break;
    }
  }

  TransportLayer::SetState(state, file, line);
}

TransportResult TransportLayerDtls::SendPacket(MediaPacket& packet) {
  CheckThread();
  if (state_ != TS_OPEN) {
    MOZ_MTLOG(ML_ERROR,
              LAYER_INFO << "Can't call SendPacket() in state " << state_);
    return TE_ERROR;
  }

  int32_t rv = PR_Send(ssl_fd_.get(), packet.data(), packet.len(), 0,
                       PR_INTERVAL_NO_WAIT);

  if (rv > 0) {
    // We have data
    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Wrote " << rv << " bytes to SSL Layer");
    return rv;
  }

  if (rv == 0) {
    TL_SET_STATE(TS_CLOSED);
    return 0;
  }

  int32_t err = PR_GetError();

  if (err == PR_WOULD_BLOCK_ERROR) {
    // This gets ignored
    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Send would have blocked");
    return TE_WOULDBLOCK;
  }

  MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err);
  mErrorDescription = "DTLS send failed";
  TL_SET_STATE(TS_ERROR);
  return TE_ERROR;
}

SECStatus TransportLayerDtls::GetClientAuthDataHook(
    void* arg, PRFileDesc* fd, CERTDistNames* caNames,
    CERTCertificate** pRetCert, SECKEYPrivateKey** pRetKey) {
  MOZ_MTLOG(ML_DEBUG, "Server requested client auth");

  TransportLayerDtls* stream = reinterpret_cast<TransportLayerDtls*>(arg);
  stream->CheckThread();

  if (!stream->identity_) {
    MOZ_MTLOG(ML_ERROR, "No identity available");
    PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
    return SECFailure;
  }

  *pRetCert = CERT_DupCertificate(stream->identity_->cert().get());
  if (!*pRetCert) {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    return SECFailure;
  }

  *pRetKey = SECKEY_CopyPrivateKey(stream->identity_->privkey().get());
  if (!*pRetKey) {
    CERT_DestroyCertificate(*pRetCert);
    *pRetCert = nullptr;
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    return SECFailure;
  }

  return SECSuccess;
}

nsresult TransportLayerDtls::SetSrtpCiphers(
    const std::vector<uint16_t>& ciphers) {
  enabled_srtp_ciphers_ = std::move(ciphers);
  return NS_OK;
}

nsresult TransportLayerDtls::GetSrtpCipher(uint16_t* cipher) const {
  CheckThread();
  if (srtp_cipher_ == 0) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  *cipher = srtp_cipher_;
  return NS_OK;
}

static uint8_t* WriteUint16(uint8_t* cursor, uint16_t v) {
  *cursor++ = v >> 8;
  *cursor++ = v & 0xff;
  return cursor;
}

static SSLHandshakeType SrtpXtnServerMessage(PRFileDesc* fd) {
  SSLPreliminaryChannelInfo preinfo;
  SECStatus rv = SSL_GetPreliminaryChannelInfo(fd, &preinfo, sizeof(preinfo));
  if (rv != SECSuccess) {
    MOZ_ASSERT(false, "Can't get version info");
    return ssl_hs_client_hello;
  }
  return (preinfo.protocolVersion >= SSL_LIBRARY_VERSION_TLS_1_3)
             ? ssl_hs_encrypted_extensions
             : ssl_hs_server_hello;
}

/* static */
PRBool TransportLayerDtls::WriteSrtpXtn(PRFileDesc* fd,
                                        SSLHandshakeType message, uint8_t* data,
                                        unsigned int* len, unsigned int max_len,
                                        void* arg) {
  auto self = reinterpret_cast<TransportLayerDtls*>(arg);

  // ClientHello: send all supported versions.
  if (message == ssl_hs_client_hello) {
    MOZ_ASSERT(self->role_ == CLIENT);
    MOZ_ASSERT(self->enabled_srtp_ciphers_.size(), "Haven't enabled SRTP");
    // We will take 2 octets for each cipher, plus a 2 octet length and 1
    // octet for the length of the empty MKI.
    if (max_len < self->enabled_srtp_ciphers_.size() * 2 + 3) {
      MOZ_ASSERT(false, "Not enough space to send SRTP extension");
      return false;
    }
    uint8_t* cursor = WriteUint16(data, self->enabled_srtp_ciphers_.size() * 2);
    for (auto cs : self->enabled_srtp_ciphers_) {
      cursor = WriteUint16(cursor, cs);
    }
    *cursor++ = 0;  // MKI is empty
    *len = cursor - data;
    return true;
  }

  if (message == SrtpXtnServerMessage(fd)) {
    MOZ_ASSERT(self->role_ == SERVER);
    if (!self->srtp_cipher_) {
      // Not negotiated. Definitely bad, but the connection can fail later.
      return false;
    }
    if (max_len < 5) {
      MOZ_ASSERT(false, "Not enough space to send SRTP extension");
      return false;
    }

    uint8_t* cursor = WriteUint16(data, 2);  // Length = 2.
    cursor = WriteUint16(cursor, self->srtp_cipher_);
    *cursor++ = 0;  // No MKI
    *len = cursor - data;
    return true;
  }

  return false;
}

class TlsParser {
 public:
  TlsParser(const uint8_t* data, size_t len) : cursor_(data), remaining_(len) {}

  bool error() const { return error_; }
  size_t remaining() const { return remaining_; }

  template <typename T, class = std::enable_if_t<std::is_unsigned_v<T>>>
  void Read(T* v, size_t sz = sizeof(T)) {
    MOZ_ASSERT(sz <= sizeof(T),
               "Type is too small to hold the value requested");
    if (remaining_ < sz) {
      error_ = true;
      return;
    }

    T result = 0;
    for (size_t i = 0; i < sz; ++i) {
      result = (result << 8) | *cursor_++;
      remaining_--;
    }
    *v = result;
  }

  template <typename T, class = std::enable_if_t<std::is_unsigned_v<T>>>
  void ReadVector(std::vector<T>* v, size_t w) {
    MOZ_ASSERT(v->empty(), "vector needs to be empty");

    uint32_t len;
    Read(&len, w);
    if (error_ || len % sizeof(T) != 0 || len > remaining_) {
      error_ = true;
      return;
    }

    size_t count = len / sizeof(T);
    v->reserve(count);
    for (T i = 0; !error_ && i < count; ++i) {
      T item;
      Read(&item);
      if (!error_) {
        v->push_back(item);
      }
    }
  }

  void Skip(size_t n) {
    if (remaining_ < n) {
      error_ = true;
    } else {
      cursor_ += n;
      remaining_ -= n;
    }
  }

  size_t SkipVector(size_t w) {
    uint32_t len = 0;
    Read(&len, w);
    Skip(len);
    return len;
  }

 private:
  const uint8_t* cursor_;
  size_t remaining_;
  bool error_ = false;
};

/* static */
SECStatus TransportLayerDtls::HandleSrtpXtn(
    PRFileDesc* fd, SSLHandshakeType message, const uint8_t* data,
    unsigned int len, SSLAlertDescription* alert, void* arg) {
  static const uint8_t kTlsAlertHandshakeFailure = 40;
  static const uint8_t kTlsAlertIllegalParameter = 47;
  static const uint8_t kTlsAlertDecodeError = 50;
  static const uint8_t kTlsAlertUnsupportedExtension = 110;

  auto self = reinterpret_cast<TransportLayerDtls*>(arg);

  // Parse the extension.
  TlsParser parser(data, len);
  std::vector<uint16_t> advertised;
  parser.ReadVector(&advertised, 2);
  size_t mki_len = parser.SkipVector(1);
  if (parser.error() || parser.remaining() > 0) {
    *alert = kTlsAlertDecodeError;
    return SECFailure;
  }

  if (message == ssl_hs_client_hello) {
    MOZ_ASSERT(self->role_ == SERVER);
    if (self->enabled_srtp_ciphers_.empty()) {
      // We don't have SRTP enabled, which is probably bad, but no sense in
      // having the handshake fail at this point, let the client decide if
      // this is a problem.
      return SECSuccess;
    }

    for (auto supported : self->enabled_srtp_ciphers_) {
      auto it = std::find(advertised.begin(), advertised.end(), supported);
      if (it != advertised.end()) {
        self->srtp_cipher_ = supported;
        return SECSuccess;
      }
    }

    // No common cipher.
    *alert = kTlsAlertHandshakeFailure;
    return SECFailure;
  }

  if (message == SrtpXtnServerMessage(fd)) {
    MOZ_ASSERT(self->role_ == CLIENT);
    if (advertised.size() != 1 || mki_len > 0) {
      *alert = kTlsAlertIllegalParameter;
      return SECFailure;
    }
    self->srtp_cipher_ = advertised[0];
    return SECSuccess;
  }

  *alert = kTlsAlertUnsupportedExtension;
  return SECFailure;
}

static constexpr uint8_t kAlertFatal = 2;

/* static */
void TransportLayerDtls::SentAlertCallback(const PRFileDesc* fd, void* arg,
                                           const SSLAlert* alert) {
  auto self = reinterpret_cast<TransportLayerDtls*>(arg);
  if (alert->level == kAlertFatal && !self->mSentAlert.isSome()) {
    self->mSentAlert = Some(alert->description);
  }
}

/* static */
void TransportLayerDtls::ReceivedAlertCallback(const PRFileDesc* fd, void* arg,
                                               const SSLAlert* alert) {
  auto self = reinterpret_cast<TransportLayerDtls*>(arg);
  if (alert->level == kAlertFatal && !self->mReceivedAlert.isSome()) {
    self->mReceivedAlert = Some(alert->description);
  }
}

nsresult TransportLayerDtls::ExportKeyingMaterial(const std::string& label,
                                                  bool use_context,
                                                  const std::string& context,
                                                  unsigned char* out,
                                                  unsigned int outlen) {
  CheckThread();
  if (state_ != TS_OPEN) {
    MOZ_ASSERT(false, "Transport must be open for ExportKeyingMaterial");
    return NS_ERROR_NOT_AVAILABLE;
  }
  SECStatus rv = SSL_ExportKeyingMaterial(
      ssl_fd_.get(), label.c_str(), label.size(), use_context,
      reinterpret_cast<const unsigned char*>(context.c_str()), context.size(),
      out, outlen);
  if (rv != SECSuccess) {
    MOZ_MTLOG(ML_ERROR, "Couldn't export SSL keying material");
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

SECStatus TransportLayerDtls::AuthCertificateHook(void* arg, PRFileDesc* fd,
                                                  PRBool checksig,
                                                  PRBool isServer) {
  TransportLayerDtls* stream = reinterpret_cast<TransportLayerDtls*>(arg);
  stream->CheckThread();
  return stream->AuthCertificateHook(fd, checksig, isServer);
}

SECStatus TransportLayerDtls::CheckDigest(
    const DtlsDigest& digest, UniqueCERTCertificate& peer_cert) const {
  DtlsDigest computed_digest(digest.algorithm_);

  MOZ_MTLOG(ML_DEBUG,
            LAYER_INFO << "Checking digest, algorithm=" << digest.algorithm_);
  nsresult res = DtlsIdentity::ComputeFingerprint(peer_cert, &computed_digest);
  if (NS_FAILED(res)) {
    MOZ_MTLOG(ML_ERROR, "Could not compute peer fingerprint for digest "
                            << digest.algorithm_);
    // Go to end
    PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
    return SECFailure;
  }

  if (computed_digest != digest) {
    MOZ_MTLOG(ML_ERROR, "Digest does not match");
    PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
    return SECFailure;
  }

  return SECSuccess;
}

SECStatus TransportLayerDtls::AuthCertificateHook(PRFileDesc* fd,
                                                  PRBool checksig,
                                                  PRBool isServer) {
  CheckThread();
  UniqueCERTCertificate peer_cert(SSL_PeerCertificate(fd));

  // We are not set up to take this being called multiple
  // times. Change this if we ever add renegotiation.
  MOZ_ASSERT(!auth_hook_called_);
  if (auth_hook_called_) {
    PR_SetError(PR_UNKNOWN_ERROR, 0);
    return SECFailure;
  }
  auth_hook_called_ = true;

  MOZ_ASSERT(verification_mode_ != VERIFY_UNSET);

  switch (verification_mode_) {
    case VERIFY_UNSET:
      // Break out to error exit
      PR_SetError(PR_UNKNOWN_ERROR, 0);
      break;

    case VERIFY_ALLOW_ALL:
      cert_ok_ = true;
      return SECSuccess;

    case VERIFY_DIGEST: {
      MOZ_ASSERT(!digests_.empty());
      // Check all the provided digests

      // Checking functions call PR_SetError()
      SECStatus rv = SECFailure;
      for (const auto& digest : digests_) {
        rv = CheckDigest(digest, peer_cert);

        // Matches a digest, we are good to go
        if (rv == SECSuccess) {
          cert_ok_ = true;
          return SECSuccess;
        }
      }
    } break;
    default:
      MOZ_CRASH();  // Can't happen
  }

  mHasFingerprintError = true;
  return SECFailure;
}

void TransportLayerDtls::TimerCallback(nsITimer* timer, void* arg) {
  TransportLayerDtls* dtls = reinterpret_cast<TransportLayerDtls*>(arg);

  MOZ_MTLOG(ML_DEBUG, "DTLS timer expired");

  dtls->Handshake();
}

void TransportLayerDtls::RecordHandshakeCompletionTelemetry(
    const char* aResult) {
  if (role_ == CLIENT) {
    mozilla::glean::webrtcdtls::client_handshake_result.Get(nsCString(aResult))
        .Add(1);
  } else {
    mozilla::glean::webrtcdtls::server_handshake_result.Get(nsCString(aResult))
        .Add(1);
  }
}

void TransportLayerDtls::RecordStartedHandshakeTelemetry() {
  if (role_ == CLIENT) {
    mozilla::glean::webrtcdtls::client_handshake_started_counter.Add(1);
  } else {
    mozilla::glean::webrtcdtls::server_handshake_started_counter.Add(1);
  }
}

void TransportLayerDtls::RecordTlsTelemetry() {
  MOZ_ASSERT(state_ == TS_OPEN);
  SSLChannelInfo info;
  nsresult rv = GetChannelInfo(&info);
  if (NS_FAILED(rv)) {
    if (rv == NS_ERROR_FAILURE) {
      MOZ_MTLOG(ML_NOTICE,
                LAYER_INFO << "RecordTlsTelemetry failed to get channel info");
    }
    return;
  }

  switch (info.protocolVersion) {
    case SSL_LIBRARY_VERSION_TLS_1_1:
      mozilla::glean::webrtcdtls::protocol_version.Get("1.0"_ns).Add(1);
      break;
    case SSL_LIBRARY_VERSION_TLS_1_2:
      mozilla::glean::webrtcdtls::protocol_version.Get("1.2"_ns).Add(1);
      break;
    case SSL_LIBRARY_VERSION_TLS_1_3:
      mozilla::glean::webrtcdtls::protocol_version.Get("1.3"_ns).Add(1);
      break;
    default:
      MOZ_CRASH("Unknown SSL version");
  }

  {
    std::ostringstream oss;
    // Record TLS cipher-suite ID as a string (eg;
    // TLS_DHE_RSA_WITH_AES_128_CBC_SHA is 0x0033)
    oss << "0x" << std::setfill('0') << std::setw(4) << std::hex
        << info.cipherSuite;
    mozilla::glean::webrtcdtls::cipher.Get(nsCString(oss.str().c_str())).Add(1);
    MOZ_MTLOG(ML_DEBUG, "cipher: " << oss.str());
  }

  // Record Key Exchange Algorithm Type
  // keyExchange null=0, rsa=1, dh=2, ecdh=4, ecdh_hybrid=8
  mozilla::glean::webrtcdtls::key_exchange_algorithm.AccumulateSingleSample(
      info.keaType);

  uint16_t cipher;
  rv = GetSrtpCipher(&cipher);

  if (NS_FAILED(rv)) {
    MOZ_MTLOG(ML_DEBUG, "No SRTP cipher suite");
    return;
  }

  {
    std::ostringstream oss;
    // Record SRTP cipher-suite ID as a string (eg;
    // SRTP_AES128_CM_HMAC_SHA1_80 is 0x0001)
    oss << "0x" << std::setfill('0') << std::setw(4) << std::hex << cipher;
    mozilla::glean::webrtcdtls::srtp_cipher.Get(nsCString(oss.str().c_str()))
        .Add(1);
    MOZ_MTLOG(ML_DEBUG, "srtp cipher: " << oss.str());
  }
}

}  // namespace mozilla
