Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/netwerk/sctp/datachannel/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 59 kB image not shown  

Quelle  DataChannel.cpp

  Sprache: C
 

/* 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 <algorithm>
#include <stdio.h>

#ifdef XP_WIN
#  include <winsock.h>  // for htonl, htons, ntohl, ntohs
#endif

#include "nsIInputStream.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "mozilla/Sprintf.h"
#include "nsProxyRelease.h"
#include "nsThread.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "mozilla/Components.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/RTCDataChannel.h"
#include "mozilla/dom/RTCDataChannelBinding.h"
#ifdef MOZ_PEERCONNECTION
#  include "transport/runnable_utils.h"
#  include "jsapi/MediaTransportHandler.h"
#  include "mediapacket.h"
#endif

#include "DataChannel.h"
#include "DataChannelDcSctp.h"
#include "DataChannelUsrsctp.h"
#include "DataChannelLog.h"
#include "DataChannelProtocol.h"

namespace mozilla {

LazyLogModule gDataChannelLog("DataChannel");

OutgoingMsg::OutgoingMsg(nsACString&& aData,
                         const DataChannelMessageMetadata& aMetadata)
    : mData(std::move(aData)), mMetadata(aMetadata) {}

void OutgoingMsg::Advance(size_t offset) {
  mPos += offset;
  if (mPos > mData.Length()) {
    mPos = mData.Length();
  }
}

DataChannelConnection::~DataChannelConnection() {
  DC_INFO(("%p: Deleting DataChannelConnection", this));
  // This may die on the MainThread, or on the STS thread, or on an
  // sctp thread if we were in a callback when the DOM side shut things down.
  MOZ_ASSERT(mState == DataChannelConnectionState::Closed);
  MOZ_ASSERT(mPending.empty());

  if (!mSTS->IsOnCurrentThread()) {
    // We may be on MainThread *or* on an sctp thread (being called from
    // receive_cb() or SendSctpPacket())
    if (mInternalIOThread) {
      // Avoid spinning the event thread from here (which if we're mainthread
      // is in the event loop already)
      nsCOMPtr<nsIRunnable> r = WrapRunnable(
          nsCOMPtr<nsIThread>(mInternalIOThread), &nsIThread::AsyncShutdown);
      mSTS->Dispatch(r.forget(), NS_DISPATCH_FALLIBLE);
    }
  } else {
    // on STS, safe to call shutdown
    if (mInternalIOThread) {
      mInternalIOThread->Shutdown();
    }
  }
}

void DataChannelConnection::Destroy() {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO(("%p: Destroying DataChannelConnection", this));
  CloseAll();
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  MOZ_DIAGNOSTIC_ASSERT(mSTS);
#endif
  mListener = nullptr;
  mSTS->Dispatch(
      NS_NewCancelableRunnableFunction(
          __func__,
          [this, self = RefPtr<DataChannelConnection>(this)]() {
            mPacketReceivedListener.DisconnectIfExists();
            mStateChangeListener.DisconnectIfExists();
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
            mShutdown = true;
            DC_INFO(("Shutting down connection %p, id %p", this, (void*)mId));
#endif
          }),
      NS_DISPATCH_FALLIBLE);
}

Maybe<RefPtr<DataChannelConnection>> DataChannelConnection::Create(
    DataChannelConnection::DataConnectionListener* aListener,
    nsISerialEventTarget* aTarget, MediaTransportHandler* aHandler,
    const uint16_t aLocalPort, const uint16_t aNumStreams) {
  MOZ_ASSERT(NS_IsMainThread());

  RefPtr<DataChannelConnection> connection;
  if (Preferences::GetBool("media.peerconnection.sctp.use_dcsctp"false)) {
    connection = new DataChannelConnectionDcSctp(aListener, aTarget,
                                                 aHandler);  // Walks into a bar
  } else {
    connection = new DataChannelConnectionUsrsctp(
        aListener, aTarget, aHandler);  // Walks into a bar
  }
  return connection->Init(aLocalPort, aNumStreams) ? Some(connection)
                                                   : Nothing();
}

DataChannelConnection::DataChannelConnection(
    DataChannelConnection::DataConnectionListener* aListener,
    nsISerialEventTarget* aTarget, MediaTransportHandler* aHandler)
    : NeckoTargetHolder(aTarget),
      mListener(aListener),
      mTransportHandler(aHandler) {
  MOZ_ASSERT(NS_IsMainThread());
  DC_VERBOSE(
      ("%p: DataChannelConnection c'tor, listener=%p", this, mListener.get()));

  // XXX FIX! make this a global we get once
  // Find the STS thread
  nsresult rv;
  mSTS = mozilla::components::SocketTransport::Service(&rv);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  mShutdown = false;
#endif
}

// Only called on MainThread, mMaxMessageSize is read on other threads
void DataChannelConnection::SetMaxMessageSize(uint64_t aMaxMessageSize) {
  MOZ_ASSERT(NS_IsMainThread());

  mMaxMessageSize = aMaxMessageSize;

  nsresult rv;
  nsCOMPtr<nsIPrefService> prefs;
  prefs = mozilla::components::Preferences::Service(&rv);
  if (!NS_WARN_IF(NS_FAILED(rv))) {
    nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);

    if (branch) {
      int32_t temp;
      if (!NS_FAILED(branch->GetIntPref(
              "media.peerconnection.sctp.force_maximum_message_size", &temp))) {
        if (temp > 0 && (uint64_t)temp < mMaxMessageSize) {
          mMaxMessageSize = (uint64_t)temp;
        }
      }
    }
  }

  // Fix remote MMS. This code exists, so future implementations of
  // RTCSctpTransport.maxMessageSize can simply provide that value from
  // GetMaxMessageSize.

  // TODO: Bug 1382779, once resolved, can be increased to
  // min(Uint8ArrayMaxSize, UINT32_MAX)
  // TODO: Bug 1381146, once resolved, can be increased to whatever we support
  // then (hopefully
  //       SIZE_MAX)
  if (mMaxMessageSize == 0 ||
      mMaxMessageSize > WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE) {
    mMaxMessageSize = WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE;
  }

  DC_DEBUG(("%p: Maximum message size (outgoing data): %" PRIu64
            " (enforced=%s)",
            this, mMaxMessageSize,
            aMaxMessageSize != mMaxMessageSize ? "yes" : "no"));

  for (auto& channel : mChannels.GetAll()) {
    channel->SetMaxMessageSize(GetMaxMessageSize());
  }
}

double DataChannelConnection::GetMaxMessageSize() {
  MOZ_ASSERT(NS_IsMainThread());
  if (mMaxMessageSize) {
    return static_cast<double>(mMaxMessageSize);
  }

  return std::numeric_limits<double>::infinity();
}

RefPtr<DataChannelConnection::StatsPromise> DataChannelConnection::GetStats(
    const DOMHighResTimeStamp aTimestamp) const {
  MOZ_ASSERT(NS_IsMainThread());
  nsTArray<RefPtr<DataChannelStatsPromise>> statsPromises;
  for (const RefPtr<DataChannel>& chan : mChannels.GetAll()) {
    if (chan) {
      RefPtr<DataChannelStatsPromise> statsPromise(chan->GetStats(aTimestamp));
      if (statsPromise) {
        statsPromises.AppendElement(std::move(statsPromise));
      }
    }
  }

  return DataChannelStatsPromise::All(GetMainThreadSerialEventTarget(),
                                      statsPromises);
}

RefPtr<DataChannelStatsPromise> DataChannel::GetStats(
    const DOMHighResTimeStamp aTimestamp) {
  MOZ_ASSERT(NS_IsMainThread());

  return InvokeAsync(mDomEventTarget, __func__,
                     [this, self = RefPtr<DataChannel>(this), aTimestamp] {
                       if (!GetDomDataChannel()) {
                         // Empty stats object, I guess... too late to
                         // return a nullptr and rejecting will trash stats
                         // promises for all other datachannels.
                         return DataChannelStatsPromise::CreateAndResolve(
                             dom::RTCDataChannelStats(), __func__);
                       }
                       return DataChannelStatsPromise::CreateAndResolve(
                           GetDomDataChannel()->GetStats(aTimestamp), __func__);
                     });
}

bool DataChannelConnection::ConnectToTransport(const std::string& aTransportId,
                                               const bool aClient,
                                               const uint16_t aLocalPort,
                                               const uint16_t aRemotePort) {
  MOZ_ASSERT(NS_IsMainThread());

  static const auto paramString =
      [](const std::string& tId, const Maybe<bool>& client,
         const uint16_t localPort, const uint16_t remotePort) -> std::string {
    std::ostringstream stream;
    stream << "Transport ID: '" << tId << "', Role: '"
           << (client ? (client.value() ? "client" : "server") : "")
           << "', Local Port: '" << localPort << "', Remote Port: '"
           << remotePort << "'";
    return stream.str();
  };

  const auto params =
      paramString(aTransportId, Some(aClient), aLocalPort, aRemotePort);
  DC_INFO(
      ("%p: ConnectToTransport connecting DTLS transport with parameters: %s",
       this, params.c_str()));

  DC_INFO(("%p: New transport parameters: %s", this, params.c_str()));
  if (NS_WARN_IF(aTransportId.empty())) {
    return false;
  }

  if (!mAllocateEven.isSome()) {
    // Do this stuff once.
    mLocalPort = aLocalPort;
    mRemotePort = aRemotePort;
    mAllocateEven = Some(aClient);
    nsTArray<RefPtr<DataChannel>> hasStreamId;
    // Could be faster. Probably doesn't matter.
    while (auto channel = mChannels.Get(INVALID_STREAM)) {
      mChannels.Remove(channel);
      auto id = FindFreeStream();
      if (id != INVALID_STREAM) {
        channel->SetStream(id);
        mChannels.Insert(channel);
        DC_DEBUG(("%p: Inserting auto-selected id %u for channel %p", this,
                  static_cast<unsigned>(id), channel.get()));
        mStreamIds.InsertElementSorted(id);
        hasStreamId.AppendElement(std::move(channel));
      } else {
        DC_WARN(("%p: Could not find id for channel %p, calling AnnounceClosed",
                 this, channel.get()));
        // Spec language is very similar to AnnounceClosed, the differences
        // being a lack of a closed check at the top, a different error event,
        // and no removal of the channel from the [[DataChannels]] slot.
        // We don't support firing errors right now, and we probabaly want the
        // closed check anyway, and we don't really have something equivalent
        // to the [[DataChannels]] slot, so just use AnnounceClosed for now.
        channel->AnnounceClosed();
      }
    }

    mSTS->Dispatch(NS_NewCancelableRunnableFunction(
                       __func__,
                       [this, self = RefPtr<DataChannelConnection>(this),
                        hasStreamId = std::move(hasStreamId)]() {
                         SetState(DataChannelConnectionState::Connecting);
                         for (auto& channel : hasStreamId) {
                           OpenFinish(std::move(channel));
                         }
                       }),
                   NS_DISPATCH_FALLIBLE);
  }

  // We do not check whether this is a new transport id here, that happens on
  // STS.
  RUN_ON_THREAD(mSTS,
                WrapRunnable(RefPtr<DataChannelConnection>(this),
                             &DataChannelConnection::SetSignals, aTransportId),
                NS_DISPATCH_NORMAL);
  return true;
}

void DataChannelConnection::SetSignals(const std::string& aTransportId) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  if (mTransportId == aTransportId) {
    // Nothing to do!
    return;
  }

  mTransportId = aTransportId;

  if (!mConnectedToTransportHandler) {
    mPacketReceivedListener =
        mTransportHandler->GetSctpPacketReceived().Connect(
            mSTS, this, &DataChannelConnection::OnPacketReceived);
    mStateChangeListener = mTransportHandler->GetStateChange().Connect(
        mSTS, this, &DataChannelConnection::TransportStateChange);
    mConnectedToTransportHandler = true;
  }
  // SignalStateChange() doesn't call you with the initial state
  if (mTransportHandler->GetState(mTransportId, false) ==
      TransportLayer::TS_OPEN) {
    DC_DEBUG(("%p: Setting transport signals, dtls already open", this));
    OnTransportReady();
  } else {
    DC_DEBUG(("%p: Setting transport signals, dtls not open yet", this));
  }
}

void DataChannelConnection::TransportStateChange(
    const std::string& aTransportId, TransportLayer::State aState,
    const nsTArray<nsTArray<uint8_t>>&) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  if (aTransportId == mTransportId) {
    if (aState == TransportLayer::TS_OPEN) {
      DC_DEBUG(("%p: Transport is open!", this));
      OnTransportReady();
    } else if (aState == TransportLayer::TS_CLOSED ||
               aState == TransportLayer::TS_NONE ||
               aState == TransportLayer::TS_ERROR) {
      DC_DEBUG(("%p: Transport is closed!", this));
      CloseAll_s();
    }
  }
}

// Process any pending Opens
void DataChannelConnection::ProcessQueuedOpens() {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  std::set<RefPtr<DataChannel>> temp(std::move(mPending));
  // Technically in an unspecified state, although no reasonable impl will leave
  // anything in here.
  mPending.clear();
  for (const auto& channel : temp) {
    DC_DEBUG(("%p: Processing queued open for %p (%u)", this, channel.get(),
              channel->mStream));
    OpenFinish(channel);  // may end up back in mPending
  }
}

void DataChannelConnection::OnPacketReceived(const std::string& aTransportId,
                                             const MediaPacket& packet) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  if (packet.type() == MediaPacket::SCTP && mTransportId == aTransportId) {
    OnSctpPacketReceived(packet);
  }
}

void DataChannelConnection::SendPacket(std::unique_ptr<MediaPacket>&& packet) {
  mSTS->Dispatch(NS_NewCancelableRunnableFunction(
                     "DataChannelConnection::SendPacket",
                     [this, self = RefPtr<DataChannelConnection>(this),
                      packet = std::move(packet)]() mutable {
                       // DC_DEBUG(("%p: SCTP/DTLS sent %ld bytes",
                       // this, len));
                       if (!mTransportId.empty() && mTransportHandler) {
                         mTransportHandler->SendPacket(mTransportId,
                                                       std::move(*packet));
                       }
                     }),
                 NS_DISPATCH_FALLIBLE);
}

already_AddRefed<DataChannel> DataChannelConnection::FindChannelByStream(
    uint16_t stream) {
  return mChannels.Get(stream).forget();
}

uint16_t DataChannelConnection::FindFreeStream() const {
  MOZ_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(mAllocateEven.isSome());
  if (!mAllocateEven.isSome()) {
    return INVALID_STREAM;
  }

  uint16_t i = (*mAllocateEven ? 0 : 1);

  // Find the lowest odd/even id that is not present in mStreamIds
  for (auto id : mStreamIds) {
    if (i >= MAX_NUM_STREAMS) {
      return INVALID_STREAM;
    }

    if (id == i) {
      // i is in use, try the next one
      i += 2;
    } else if (id > i) {
      // i is definitely not in use
      break;
    }
  }

  return i;
}

// Returns a POSIX error code.
int DataChannelConnection::SendControlMessage(DataChannel& aChannel,
                                              const uint8_t* data,
                                              uint32_t len) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  // Create message instance and send
  // Note: Main-thread IO, but doesn't block
#if (UINT32_MAX > SIZE_MAX)
  if (len > SIZE_MAX) {
    return EMSGSIZE;
  }
#endif

  DataChannelMessageMetadata metadata(aChannel.mStream,
                                      DATA_CHANNEL_PPID_CONTROL, false);
  nsCString buffer(reinterpret_cast<const char*>(data), len);
  OutgoingMsg msg(std::move(buffer), metadata);

  return SendMessage(aChannel, std::move(msg));
}

// Returns a POSIX error code.
int DataChannelConnection::SendOpenAckMessage(DataChannel& aChannel) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  DC_INFO(("%p: Sending DataChannel open ack, channel %p", this, &aChannel));
  struct rtcweb_datachannel_ack ack = {};
  ack.msg_type = DATA_CHANNEL_ACK;

  return SendControlMessage(aChannel, (const uint8_t*)&ack, sizeof(ack));
}

// Returns a POSIX error code.
int DataChannelConnection::SendOpenRequestMessage(DataChannel& aChannel) {
  DC_INFO(
      ("%p: Sending DataChannel open request, channel %p", this, &aChannel));
  const nsACString& label = aChannel.mLabel;
  const nsACString& protocol = aChannel.mProtocol;
  const bool unordered = !aChannel.mOrdered;
  const DataChannelReliabilityPolicy prPolicy = aChannel.mPrPolicy;
  const uint32_t prValue = aChannel.mPrValue;

  const size_t label_len = label.Length();     // not including nul
  const size_t proto_len = protocol.Length();  // not including nul
  // careful - request struct include one char for the label
  const size_t req_size = sizeof(struct rtcweb_datachannel_open_request) - 1 +
                          label_len + proto_len;
  UniqueFreePtr<struct rtcweb_datachannel_open_request> req(
      (struct rtcweb_datachannel_open_request*)moz_xmalloc(req_size));

  memset(req.get(), 0, req_size);
  req->msg_type = DATA_CHANNEL_OPEN_REQUEST;
  switch (prPolicy) {
    case DataChannelReliabilityPolicy::Reliable:
      req->channel_type = DATA_CHANNEL_RELIABLE;
      break;
    case DataChannelReliabilityPolicy::LimitedLifetime:
      req->channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_TIMED;
      break;
    case DataChannelReliabilityPolicy::LimitedRetransmissions:
      req->channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT;
      break;
    default:
      return EINVAL;
  }
  if (unordered) {
    // Per the current types, all differ by 0x80 between ordered and unordered
    req->channel_type |=
        0x80;  // NOTE: be careful if new types are added in the future
  }

  req->reliability_param = htonl(prValue);
  req->priority = htons(0); /* XXX: add support */
  req->label_length = htons(label_len);
  req->protocol_length = htons(proto_len);
  memcpy(&req->label[0], PromiseFlatCString(label).get(), label_len);
  memcpy(&req->label[label_len], PromiseFlatCString(protocol).get(), proto_len);

  // TODO: req_size is an int... that looks hairy
  return SendControlMessage(aChannel, (const uint8_t*)req.get(), req_size);
}

// Caller must ensure that length <= SIZE_MAX
void DataChannelConnection::HandleOpenRequestMessage(
    const struct rtcweb_datachannel_open_request* req, uint32_t length,
    uint16_t stream) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  DataChannelReliabilityPolicy prPolicy;

  const size_t requiredLength = (sizeof(*req) - 1) + ntohs(req->label_length) +
                                ntohs(req->protocol_length);
  if (((size_t)length) != requiredLength) {
    if (((size_t)length) < requiredLength) {
      DC_ERROR(
          ("%p: insufficient length: %u, should be %zu. Unable to continue.",
           this, length, requiredLength));
      return;
    }
    DC_WARN(("%p: Inconsistent length: %u, should be %zu", this, length,
             requiredLength));
  }

  DC_DEBUG(("%p: length %u, sizeof(*req) = %zu", this, length, sizeof(*req)));

  switch (req->channel_type) {
    case DATA_CHANNEL_RELIABLE:
    case DATA_CHANNEL_RELIABLE_UNORDERED:
      prPolicy = DataChannelReliabilityPolicy::Reliable;
      break;
    case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
    case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED:
      prPolicy = DataChannelReliabilityPolicy::LimitedRetransmissions;
      break;
    case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
    case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED:
      prPolicy = DataChannelReliabilityPolicy::LimitedLifetime;
      break;
    default:
      DC_ERROR(("%p: Unknown channel type %d", this, req->channel_type));
      /* XXX error handling */
      return;
  }

  if (stream >= mNegotiatedIdLimit) {
    DC_ERROR(
        ("%p: stream %u out of bounds (%u)", this, stream, mNegotiatedIdLimit));
    return;
  }

  const uint32_t prValue = ntohl(req->reliability_param);
  const bool ordered = !(req->channel_type & 0x80);
  const nsCString label(&req->label[0], ntohs(req->label_length));
  const nsCString protocol(&req->label[ntohs(req->label_length)],
                           ntohs(req->protocol_length));

  // Always dispatch this to mainthread; this is a brand new datachannel, which
  // has not had any opportunity to be transferred to a worker.
  Dispatch(
      NS_NewCancelableRunnableFunction(
          "DataChannelConnection::HandleOpenRequestMessage",
          [this, self = RefPtr<DataChannelConnection>(this), stream, prPolicy,
           prValue, ordered, label, protocol]() {
            RefPtr<DataChannel> channel = FindChannelByStream(stream);
            if (channel) {
              if (!channel->mNegotiated) {
                DC_ERROR((
                    "HandleOpenRequestMessage: channel for pre-existing stream "
                    "%u that was not externally negotiated. JS is lying to us, "
                    "or there's an id collision.",
                    stream));
                /* XXX: some error handling */
              } else {
                DC_DEBUG(("Open for externally negotiated channel %u", stream));
                // XXX should also check protocol, maybe label
                if (prPolicy != channel->mPrPolicy ||
                    prValue != channel->mPrValue ||
                    ordered != channel->mOrdered) {
                  DC_WARN(
                      ("external negotiation mismatch with OpenRequest:"
                       "channel %u, policy %s/%s, value %u/%u, ordered %d/%d",
                       stream, ToString(prPolicy), ToString(channel->mPrPolicy),
                       prValue, channel->mPrValue, static_cast<int>(ordered),
                       static_cast<int>(channel->mOrdered)));
                }
              }
              return;
            }
            channel = new DataChannel(this, stream, label, protocol, prPolicy,
                                      prValue, ordered, false);
            mChannels.Insert(channel);
            mStreamIds.InsertElementSorted(stream);

            DC_INFO(("%p: sending ON_CHANNEL_CREATED for %p/%s/%s: %u", this,
                     channel.get(), channel->mLabel.get(),
                     channel->mProtocol.get(), stream));

            // Awkward. If we convert over to using Maybe for this in
            // DataChannel, we won't need to have this extra conversion, since
            // Nullable converts easily to Maybe.
            dom::Nullable<uint16_t> maxLifeTime;
            dom::Nullable<uint16_t> maxRetransmits;
            if (prPolicy == DataChannelReliabilityPolicy::LimitedLifetime) {
              maxLifeTime.SetValue(std::min(
                  std::numeric_limits<uint16_t>::max(), (uint16_t)prValue));
            } else if (prPolicy ==
                       DataChannelReliabilityPolicy::LimitedRetransmissions) {
              maxRetransmits.SetValue(std::min(
                  std::numeric_limits<uint16_t>::max(), (uint16_t)prValue));
            }

            if (mListener) {
              // important to give it an already_AddRefed pointer!
              // TODO(bug 1974443): Have nsDOMDataChannel create the DataChannel
              // object, or have DataChannel take an nsDOMDataChannel, to avoid
              // passing this param list more than once?
              mListener->NotifyDataChannel(do_AddRef(channel), label, ordered,
                                           maxLifeTime, maxRetransmits,
                                           protocol, false);
              // Spec says to queue this in the queued task for ondatachannel
              channel->AnnounceOpen();
            }

            mSTS->Dispatch(
                NS_NewCancelableRunnableFunction(
                    "DataChannelConnection::HandleOpenRequestMessage",
                    [this, self = RefPtr<DataChannelConnection>(this),
                     channel = std::move(channel)]() {
                      // Note that any message can be buffered;
                      // SendOpenAckMessage may error later than this check.
                      const auto error = SendOpenAckMessage(*channel);
                      if (error) {
                        DC_ERROR(
                            ("%p: SendOpenAckMessage failed, channel %p, error "
                             "= %d",
                             this, channel.get(), error));
                        FinishClose_s(channel);
                        return;
                      }
                      channel->mWaitingForAck = false;
                      channel->mSendStreamNeedsReset = true;
                      channel->mRecvStreamNeedsReset = true;
                      OnStreamOpen(channel->mStream);
                    }),
                NS_DISPATCH_FALLIBLE);
          }),
      NS_DISPATCH_FALLIBLE);
}

// Caller must ensure that length <= SIZE_MAX
void DataChannelConnection::HandleOpenAckMessage(
    const struct rtcweb_datachannel_ack* ack, uint32_t length,
    uint16_t stream) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());

  RefPtr<DataChannel> channel = FindChannelByStream(stream);
  if (NS_WARN_IF(!channel)) {
    return;
  }

  DC_INFO(("%p: OpenAck received for channel %p, stream %u, waiting=%d", this,
           channel.get(), stream, channel->mWaitingForAck ? 1 : 0));

  channel->mWaitingForAck = false;

  // Either externally negotiated or we sent Open
  channel->AnnounceOpen();
  OnStreamOpen(stream);
}

// Caller must ensure that length <= SIZE_MAX
void DataChannelConnection::HandleUnknownMessage(uint32_t ppid, uint32_t length,
                                                 uint16_t stream) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  /* XXX: Send an error message? */
  DC_ERROR(("%p: unknown DataChannel message received: %u, len %u on stream %d",
            this, ppid, length, stream));
  // XXX Log to JS error console if possible
}

void DataChannelConnection::HandleDataMessage(IncomingMsg&& aMsg) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());

  RefPtr<DataChannel> channel = FindChannelByStream(aMsg.GetStreamId());
  if (!channel) {
    MOZ_ASSERT(false,
               "Wait until OnStreamOpen is called before calling "
               "HandleDataMessage!");
    return;
  }

  const size_t data_length = aMsg.GetData().Length();
  bool isBinary = false;

  switch (aMsg.GetPpid()) {
    case DATA_CHANNEL_PPID_DOMSTRING:
    case DATA_CHANNEL_PPID_DOMSTRING_PARTIAL:
      DC_DEBUG(
          ("%p: DataChannel: Received string message of length %zu on "
           "channel %u",
           this, data_length, channel->mStream));
      // WebSockets checks IsUTF8() here; we can try to deliver it
      break;

    case DATA_CHANNEL_PPID_DOMSTRING_EMPTY:
      DC_DEBUG(
          ("%p: DataChannel: Received empty string message of length %zu on "
           "channel %u",
           this, data_length, channel->mStream));
      // Just in case.
      aMsg.GetData().Truncate(0);
      break;

    case DATA_CHANNEL_PPID_BINARY:
    case DATA_CHANNEL_PPID_BINARY_PARTIAL:
      DC_DEBUG(
          ("%p: DataChannel: Received binary message of length %zu on "
           "channel id %u",
           this, data_length, channel->mStream));
      isBinary = true;
      break;

    case DATA_CHANNEL_PPID_BINARY_EMPTY:
      DC_DEBUG(
          ("%p: DataChannel: Received empty binary message of length %zu on "
           "channel id %u",
           this, data_length, channel->mStream));
      // Just in case.
      aMsg.GetData().Truncate(0);
      isBinary = true;
      break;

    default:
      NS_ERROR("Unknown data PPID");
      DC_ERROR(("%p: Unknown data PPID %" PRIu32, this, aMsg.GetPpid()));
      return;
  }

  channel->OnMessageReceived(std::move(aMsg.GetData()), isBinary);
}

void DataChannelConnection::HandleDCEPMessage(IncomingMsg&& aMsg) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  const struct rtcweb_datachannel_open_request* req;
  const struct rtcweb_datachannel_ack* ack;

  req = reinterpret_cast<const struct rtcweb_datachannel_open_request*>(
      aMsg.GetData().BeginReading());

  size_t data_length = aMsg.GetLength();

  DC_INFO(("%p: Handling DCEP message of length %zu", this, data_length));

  // Ensure minimum message size (ack is the smallest DCEP message)
  if (data_length < sizeof(*ack)) {
    DC_WARN(("%p: Ignored invalid DCEP message (too short)", this));
    return;
  }

  switch (req->msg_type) {
    case DATA_CHANNEL_OPEN_REQUEST:
      // structure includes a possibly-unused char label[1] (in a packed
      // structure)
      if (NS_WARN_IF(data_length < sizeof(*req) - 1)) {
        return;
      }

      HandleOpenRequestMessage(req, data_length, aMsg.GetStreamId());
      break;
    case DATA_CHANNEL_ACK:
      // >= sizeof(*ack) checked above

      ack = reinterpret_cast<const struct rtcweb_datachannel_ack*>(
          aMsg.GetData().BeginReading());
      HandleOpenAckMessage(ack, data_length, aMsg.GetStreamId());
      break;
    default:
      HandleUnknownMessage(aMsg.GetPpid(), data_length, aMsg.GetStreamId());
      break;
  }
}

bool DataChannelConnection::ReassembleMessageChunk(IncomingMsg& aReassembled,
                                                   const void* buffer,
                                                   size_t length, uint32_t ppid,
                                                   uint16_t stream) {
  // Note: Until we support SIZE_MAX sized messages, we need this check
#if (SIZE_MAX > UINT32_MAX)
  if (length > UINT32_MAX) {
    DC_ERROR(("%p: Cannot handle message of size %zu (max=%u)", this, length,
              UINT32_MAX));
    return false;
  }
#endif

  // Ensure it doesn't blow up our buffer
  // TODO: Change 'WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL' to whatever the
  //       new buffer is capable of holding.
  if (length + aReassembled.GetLength() >
      WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL) {
    DC_ERROR(
        ("%p: Buffered message would become too large to handle, closing "
         "connection",
         this));
    return false;
  }

  if (aReassembled.GetPpid() != ppid) {
    NS_WARNING("DataChannel message aborted by fragment type change!");
    return false;
  }

  aReassembled.Append((uint8_t*)buffer, length);

  return true;
}

void DataChannelConnection::OnStreamsReset(std::vector<uint16_t>&& aStreams) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  for (auto stream : aStreams) {
    DC_INFO(("%p: Received reset request for stream %u", this, stream));
    RefPtr<DataChannel> channel = FindChannelByStream(stream);
    if (channel) {
      channel->mRecvStreamNeedsReset = false;
      if (channel->mSendStreamNeedsReset) {
        // We do not send our own reset yet, we give the RTCDataChannel a chance
        // to finish sending messages first.
        DC_INFO(("%p: Need to send a reset, closing gracefully", this));
        channel->GracefulClose();
      } else {
        DC_INFO(
            ("%p: We've already reset our stream, closing immediately", this));
        FinishClose_s(channel);
      }
    }
  }
}

void DataChannelConnection::OnStreamsResetComplete(
    std::vector<uint16_t>&& aStreams) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  for (auto stream : aStreams) {
    DC_INFO(("%p: Received reset response for stream %u", this, stream));
    RefPtr<DataChannel> channel = FindChannelByStream(stream);
    if (channel) {
      channel->mSendStreamNeedsReset = false;
      if (!channel->mRecvStreamNeedsReset) {
        // The other end has already performed its reset
        DC_INFO(
            ("%p: Remote stream has already been reset, closing immediately",
             this));
        FinishClose_s(channel);
      }
    }
  }
}

already_AddRefed<DataChannel> DataChannelConnection::Open(
    const nsACString& label, const nsACString& protocol,
    DataChannelReliabilityPolicy prPolicy, bool inOrder, uint32_t prValue,
    bool aExternalNegotiated, uint16_t aStream) {
  MOZ_ASSERT(NS_IsMainThread());
  if (!aExternalNegotiated) {
    if (mAllocateEven.isSome()) {
      aStream = FindFreeStream();
      if (aStream == INVALID_STREAM) {
        return nullptr;
      }
    } else {
      // We do not yet know whether we are client or server, and an id has not
      // been chosen for us. We will need to choose later.
      aStream = INVALID_STREAM;
    }
  }

  DC_INFO(
      ("%p: DC Open: label %s/%s, type %s, inorder %d, prValue %u, "
       "external: %s, stream %u",
       this, PromiseFlatCString(label).get(),
       PromiseFlatCString(protocol).get(), ToString(prPolicy), inOrder, prValue,
       aExternalNegotiated ? "true" : "false", aStream));

  if ((prPolicy == DataChannelReliabilityPolicy::Reliable) && (prValue != 0)) {
    return nullptr;
  }

  if (aStream != INVALID_STREAM) {
    if (mStreamIds.ContainsSorted(aStream)) {
      DC_ERROR(("%p: external negotiation of already-open channel %u", this,
                aStream));
      // This is the only place where duplicate id checking is performed. The
      // JSImpl code assumes that any error is due to id-related problems. This
      // probably needs some cleanup.
      return nullptr;
    }

    DC_DEBUG(("%p: Inserting externally-negotiated id %u", this,
              static_cast<unsigned>(aStream)));
    mStreamIds.InsertElementSorted(aStream);
  }

  RefPtr<DataChannel> channel(new DataChannel(this, aStream, label, protocol,
                                              prPolicy, prValue, inOrder,
                                              aExternalNegotiated));
  mChannels.Insert(channel);

  if (aStream != INVALID_STREAM) {
    mSTS->Dispatch(NS_NewCancelableRunnableFunction(
                       "DataChannel::OpenFinish",
                       [this, self = RefPtr<DataChannelConnection>(this),
                        channel]() mutable { OpenFinish(channel); }),
                   NS_DISPATCH_FALLIBLE);
  }

  return channel.forget();
}

// Separate routine so we can also call it to finish up from pending opens
void DataChannelConnection::OpenFinish(RefPtr<DataChannel> aChannel) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());
  const uint16_t stream = aChannel->mStream;

  // Cases we care about:
  // Pre-negotiated:
  //    Not Open:
  //      Doesn't fit:
  //         -> change initial ask or renegotiate after open
  //      -> queue open
  //    Open:
  //      Doesn't fit:
  //         -> RaiseStreamLimitTo && queue
  //      Does fit:
  //         -> open
  // Not negotiated:
  //    Not Open:
  //      -> queue open
  //    Open:
  //      -> Try to get a stream
  //      Doesn't fit:
  //         -> RaiseStreamLimitTo && queue
  //      Does fit:
  //         -> open
  // So the Open cases are basically the same
  // Not Open cases are simply queue for non-negotiated, and
  // either change the initial ask or possibly renegotiate after open.
  DataChannelConnectionState state = GetState();
  if (state != DataChannelConnectionState::Open ||
      stream >= mNegotiatedIdLimit) {
    if (state == DataChannelConnectionState::Open) {
      MOZ_ASSERT(stream != INVALID_STREAM);
      // RaiseStreamLimitTo() limits to MAX_NUM_STREAMS -- allocate extra
      // streams to avoid asking for more every time we want a higher limit.
      uint16_t num_desired = std::min(16 * (stream / 16 + 1), MAX_NUM_STREAMS);
      DC_DEBUG(("%p: Attempting to raise stream limit %u -> %u", this,
                mNegotiatedIdLimit, num_desired));
      if (!RaiseStreamLimitTo(num_desired)) {
        NS_ERROR("Failed to request more streams");
        FinishClose_s(aChannel);
        return;
      }
    }
    DC_INFO(("%p: Queuing channel %p (%u) to finish open", this, aChannel.get(),
             stream));
    mPending.insert(std::move(aChannel));
    return;
  }

  MOZ_ASSERT(state == DataChannelConnectionState::Open);
  MOZ_ASSERT(stream != INVALID_STREAM);
  MOZ_ASSERT(stream < mNegotiatedIdLimit);

  if (!aChannel->mNegotiated) {
    if (!aChannel->mOrdered) {
      // Don't send unordered until this gets cleared.
      aChannel->mWaitingForAck = true;
    }

    const int error = SendOpenRequestMessage(*aChannel);
    if (error) {
      DC_ERROR(("%p: SendOpenRequest failed, error = %d", this, error));
      FinishClose_s(aChannel);
      return;
    }
  }

  // Even if we're in the negotiated case, and will never send an open request,
  // we're supposed to send a stream reset when we tear down.
  aChannel->mSendStreamNeedsReset = true;
  aChannel->mRecvStreamNeedsReset = true;

  if (aChannel->mNegotiated) {
    // Either externally negotiated or we sent Open
    aChannel->AnnounceOpen();
    OnStreamOpen(stream);
  }
}

nsISerialEventTarget* DataChannelConnection::GetIOThread() {
  // Spawn a thread to send the data
  if (!mInternalIOThread) {
    // TODO(bug 1998966): Lazy shutdown once done? Maybe have this live in
    // DataChannel (so we have an IO thread for each channel that sends blobs)?
    NS_NewNamedThread("DataChannel IO", getter_AddRefs(mInternalIOThread));
  }

  return mInternalIOThread.get();
}

void DataChannelConnection::SetState(DataChannelConnectionState aState) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());

  DC_DEBUG(
      ("%p: DataChannelConnection labeled %s switching connection state %s -> "
       "%s",
       this, mTransportId.c_str(), ToString(mState), ToString(aState)));

  if (mState == aState) {
    return;
  }

  mState = aState;

  if (mState == DataChannelConnectionState::Open) {
    Dispatch(NS_NewCancelableRunnableFunction(
                 __func__,
                 [this, self = RefPtr<DataChannelConnection>(this)]() {
                   if (mListener) {
                     mListener->NotifySctpConnected();
                   }
                 }),
             NS_DISPATCH_FALLIBLE);
  } else if (mState == DataChannelConnectionState::Closed) {
    Dispatch(NS_NewCancelableRunnableFunction(
                 __func__,
                 [this, self = RefPtr<DataChannelConnection>(this)]() {
                   if (mListener) {
                     mListener->NotifySctpClosed();
                   }
                 }),
             NS_DISPATCH_FALLIBLE);
  }
}

void DataChannelConnection::SendDataMessage(DataChannel& aChannel,
                                            nsACString&& aMsg, bool aIsBinary) {
  // Could be main, could be a worker

  nsCString temp(std::move(aMsg));

  mSTS->Dispatch(
      NS_NewCancelableRunnableFunction(
          __func__,
          [this, self = RefPtr<DataChannelConnection>(this),
           channel = RefPtr(&aChannel), msg = std::move(temp),
           aIsBinary]() mutable {
            Maybe<uint16_t> maxRetransmissions;
            Maybe<uint16_t> maxLifetimeMs;

            switch (channel->mPrPolicy) {
              case DataChannelReliabilityPolicy::Reliable:
                break;
              case DataChannelReliabilityPolicy::LimitedRetransmissions:
                maxRetransmissions = Some(channel->mPrValue);
                break;
              case DataChannelReliabilityPolicy::LimitedLifetime:
                maxLifetimeMs = Some(channel->mPrValue);
                break;
            }

            uint32_t ppid;
            if (aIsBinary) {
              if (msg.Length()) {
                ppid = DATA_CHANNEL_PPID_BINARY;
              } else {
                ppid = DATA_CHANNEL_PPID_BINARY_EMPTY;
                msg.Append('\0');
              }
            } else {
              if (msg.Length()) {
                ppid = DATA_CHANNEL_PPID_DOMSTRING;
              } else {
                ppid = DATA_CHANNEL_PPID_DOMSTRING_EMPTY;
                msg.Append('\0');
              }
            }

            DataChannelMessageMetadata metadata(
                channel->mStream, ppid,
                !channel->mOrdered && !channel->mWaitingForAck,
                maxRetransmissions, maxLifetimeMs);
            // Create message instance and send
            OutgoingMsg outgoing(std::move(msg), metadata);

            SendMessage(*channel, std::move(outgoing));
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannelConnection::EndOfStream(const RefPtr<DataChannel>& aChannel) {
  mSTS->Dispatch(
      NS_NewCancelableRunnableFunction(
          __func__,
          [this, self = RefPtr<DataChannelConnection>(this), channel = aChannel,
           stream = aChannel->mStream]() {
            if (channel->mSendStreamNeedsReset) {
              if (channel->mEndOfStreamCalled) {
                return;
              }
              channel->mEndOfStreamCalled = true;
              DC_INFO((
                  "%p: Need to send a reset for channel %p, closing gracefully",
                  this, channel.get()));
              nsTArray<uint16_t> temp({stream});
              bool success = ResetStreams(temp);
              if (success) {
                return;
              }
              // We presume that OnStreamResetComplete will not be called in
              // this case, nor will we receive a stream reset from the other
              // end.
              DC_INFO(
                  ("%p: Failed to send a reset for channel %p, closing "
                   "immediately",
                   this, channel.get()));
              channel->mRecvStreamNeedsReset = false;
            }

            if (!channel->mRecvStreamNeedsReset) {
              // Stream is reset in both directions (or never existed in the
              // first place), we're ready to finish tearing down.
              DC_INFO(
                  ("%p: Stream does not need reset in either direction for "
                   "channel %p",
                   this, channel.get()));
              FinishClose_s(channel);
            }
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannel::EndOfStream() {
  // This can happen before mDomEventTarget is actually ready.
  if (mConnection) {
    mConnection->EndOfStream(this);
  }
}

RefPtr<dom::RTCDataChannel> DataChannel::GetDomDataChannel() const {
  MOZ_ASSERT(mDomEventTarget->IsOnCurrentThread());
  if (NS_IsMainThread()) {
    return mMainthreadDomDataChannel;
  }
  return mWorkerDomDataChannel;
}

void DataChannelConnection::FinishClose_s(const RefPtr<DataChannel>& aChannel) {
  MOZ_ASSERT(mSTS->IsOnCurrentThread());

  // We're removing this from all containers, make sure the passed pointer
  // stays valid.
  // It is possible for this to be called twice if both JS and the transport
  // side cause closure at the same time, but this is idempotent so no big deal
  aChannel->mBufferedData.Clear();
  mChannels.Remove(aChannel);
  mPending.erase(aChannel);

  // Close the channel's data transport by following the associated
  // procedure.
  aChannel->AnnounceClosed();
}

void DataChannelConnection::CloseAll_s() {
  // Make sure no more channels will be opened
  SetState(DataChannelConnectionState::Closed);

  nsTArray<uint16_t> streamsToReset;
  // Close current channels
  // If there are runnables, they hold a strong ref and keep the channel
  // and/or connection alive (even if in a CLOSED state)
  for (auto& channel : mChannels.GetAll()) {
    DC_INFO(("%p: closing channel %p, stream %u", this, channel.get(),
             channel->mStream));
    if (channel->mSendStreamNeedsReset) {
      DC_INFO(("%p: channel %p needs to send reset", this, channel.get()));
      channel->mSendStreamNeedsReset = false;
      streamsToReset.AppendElement(channel->mStream);
    }
    // We do not wait for the reset to finish in this case; we won't be around
    // to see the response.
    FinishClose_s(channel);
  }

  // Clean up any pending opens for channels
  std::set<RefPtr<DataChannel>> temp(std::move(mPending));
  // Technically in an unspecified state, although no reasonable impl will leave
  // anything in here.
  mPending.clear();
  for (const auto& channel : temp) {
    DC_INFO(("%p: closing pending channel %p, stream %u", this, channel.get(),
             channel->mStream));
    FinishClose_s(channel);  // also releases the ref on each iteration
  }

  // It's more efficient to let the Resets queue in shutdown and then
  // ResetStreams() here.
  if (!streamsToReset.IsEmpty()) {
    ResetStreams(streamsToReset);
  }
}

void DataChannelConnection::CloseAll() {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO(("%p: Closing all channels", this));

  mSTS->Dispatch(NS_NewCancelableRunnableFunction(
                     "DataChannelConnection::CloseAll",
                     [this, self = RefPtr<DataChannelConnection>(this)]() {
                       CloseAll_s();
                     }),
                 NS_DISPATCH_FALLIBLE);
}

void DataChannelConnection::MarkStreamAvailable(uint16_t aStream) {
  MOZ_ASSERT(NS_IsMainThread());
  mStreamIds.RemoveElementSorted(aStream);
}

bool DataChannelConnection::Channels::IdComparator::Equals(
    const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
  return aChannel->mStream == aId;
}

bool DataChannelConnection::Channels::IdComparator::LessThan(
    const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
  return aChannel->mStream < aId;
}

bool DataChannelConnection::Channels::IdComparator::Equals(
    const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
  return Equals(a1, a2->mStream);
}

bool DataChannelConnection::Channels::IdComparator::LessThan(
    const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
  return LessThan(a1, a2->mStream);
}

void DataChannelConnection::Channels::Insert(
    const RefPtr<DataChannel>& aChannel) {
  DC_DEBUG(("%p: Inserting channel %u : %p", this, aChannel->mStream,
            aChannel.get()));
  MutexAutoLock lock(mMutex);
  if (aChannel->mStream != INVALID_STREAM) {
    MOZ_ASSERT(!mChannels.ContainsSorted(aChannel, IdComparator()));
  }

  MOZ_ASSERT(!mChannels.Contains(aChannel));

  mChannels.InsertElementSorted(aChannel, IdComparator());
}

bool DataChannelConnection::Channels::Remove(
    const RefPtr<DataChannel>& aChannel) {
  DC_DEBUG(("%p: Removing channel %u : %p", this, aChannel->mStream,
            aChannel.get()));
  MutexAutoLock lock(mMutex);
  if (aChannel->mStream == INVALID_STREAM) {
    return mChannels.RemoveElement(aChannel);
  }

  auto index = mChannels.BinaryIndexOf(aChannel->mStream, IdComparator());
  if (index != ChannelArray::NoIndex) {
    if (mChannels[index].get() == aChannel.get()) {
      mChannels.RemoveElementAt(index);
      return true;
    }
  }
  return false;
}

RefPtr<DataChannel> DataChannelConnection::Channels::Get(uint16_t aId) const {
  MutexAutoLock lock(mMutex);
  auto index = mChannels.BinaryIndexOf(aId, IdComparator());
  if (index == ChannelArray::NoIndex) {
    return nullptr;
  }
  return mChannels[index];
}

RefPtr<DataChannel> DataChannelConnection::Channels::GetNextChannel(
    uint16_t aCurrentId) const {
  MutexAutoLock lock(mMutex);
  if (mChannels.IsEmpty()) {
    return nullptr;
  }

  auto index = mChannels.IndexOfFirstElementGt(aCurrentId, IdComparator());
  if (index == mChannels.Length()) {
    index = 0;
  }
  return mChannels[index];
}

DataChannel::DataChannel(DataChannelConnection* connection, uint16_t stream,
                         const nsACString& label, const nsACString& protocol,
                         DataChannelReliabilityPolicy policy, uint32_t value,
                         bool ordered, bool negotiated)
    : mLabel(label),
      mProtocol(protocol),
      mPrPolicy(policy),
      mPrValue(value),
      mNegotiated(negotiated),
      mOrdered(ordered),
      mStream(stream),
      mConnection(connection),
      mDomEventTarget(new StopGapEventTarget) {
  DC_INFO(
      ("%p: Necko DataChannel created, label '%s'. Waiting for RTCDataChannel "
       "to be created.",
       this, mLabel.get()));
  NS_ASSERTION(mConnection, "NULL connection");
}

DataChannel::~DataChannel() {
  DC_INFO(("%p: DataChannel is being destroyed.", this));
}

void DataChannel::SetMainthreadDomDataChannel(dom::RTCDataChannel* aChannel) {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO(
      ("%p: Mainthread RTCDataChannel created(%p). Waiting for confirmation of "
       "event target.",
       this, aChannel));
  mMainthreadDomDataChannel = aChannel;
  SetMaxMessageSize(mConnection->GetMaxMessageSize());
  if (GetStream()) {
    mMainthreadDomDataChannel->SetId(*GetStream());
  }
}

void DataChannel::OnWorkerTransferStarted() {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO((
      "%p: RTCDataChannel is being transferred. Disabling synchronous updates. "
      "Mainthread will not be our event target, waiting to learn worker "
      "thread.",
      this));
  mHasWorkerDomDataChannel = true;
}

void DataChannel::OnWorkerTransferComplete(dom::RTCDataChannel* aChannel) {
  MOZ_ASSERT(!NS_IsMainThread());
  DC_INFO(
      ("%p: Worker RTCDataChannel created(%p). Worker thread is our event "
       "target.",
       this, aChannel));
  mWorkerDomDataChannel = aChannel;
  mDomEventTarget->SetRealEventTarget(GetCurrentSerialEventTarget());
}

void DataChannel::OnWorkerTransferDisabled() {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO(
      ("%p: Mainthread RTCDataChannel is no longer eligible for transfer. "
       "Mainthread is our event target.",
       this));
  mDomEventTarget->SetRealEventTarget(GetCurrentSerialEventTarget());
}

void DataChannel::UnsetMainthreadDomDataChannel() {
  MOZ_ASSERT(NS_IsMainThread());
  DC_INFO(("%p: Mainthread RTCDataChannel is being destroyed(%p).", this,
           mMainthreadDomDataChannel));
  mMainthreadDomDataChannel = nullptr;
  if (mHasWorkerDomDataChannel) {
    DC_INFO(
        ("Mainthread RTCDataChannel is being destroyed. Dispatching task to "
         "inform corresponding worker RTCDataChannel."));
    mDomEventTarget->Dispatch(
        NS_NewCancelableRunnableFunction(
            "DataChannel::UnsetMainthreadDomDataChannel",
            [this, self = RefPtr<DataChannel>(this)] {
              if (mWorkerDomDataChannel) {
                mWorkerDomDataChannel->UnsetWorkerNeedsUs();
              }
            }),
        NS_DISPATCH_FALLIBLE);
  } else {
    DC_INFO(("%p: No worker RTCDataChannel. Closing.", this));
    EndOfStream();
  }
}

void DataChannel::UnsetWorkerDomDataChannel() {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(mDomEventTarget->IsOnCurrentThread());
  DC_INFO(("%p: Worker RTCDataChannel is being destroyed(%p). Closing.", this,
           mWorkerDomDataChannel));
  mWorkerDomDataChannel = nullptr;
  EndOfStream();
}

void DataChannel::DecrementBufferedAmount(size_t aSize) {
  mDomEventTarget->Dispatch(
      NS_NewCancelableRunnableFunction(
          "DataChannel::DecrementBufferedAmount",
          [this, self = RefPtr<DataChannel>(this), aSize] {
            if (GetDomDataChannel()) {
              GetDomDataChannel()->DecrementBufferedAmount(aSize);
            }
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannel::AnnounceOpen() {
  // When an underlying data transport is to be announced (the other peer
  // created a channel with negotiated unset or set to false), the user agent of
  // the peer that did not initiate the creation process MUST queue a task to
  // run the following steps:
  DC_INFO(
      ("%p: DataChannel is open. Queueing AnnounceOpen call to RTCDataChannel.",
       this));

  mDomEventTarget->Dispatch(
      NS_NewCancelableRunnableFunction(
          "DataChannel::AnnounceOpen",
          [this, self = RefPtr<DataChannel>(this)] {
            if (GetDomDataChannel() && !mAnnouncedOpen) {
              mAnnouncedOpen = true;
              DC_INFO(("Calling AnnounceOpen on RTCDataChannel."));
              GetDomDataChannel()->AnnounceOpen();
            }

            // Right now, we're already on mainthread, but this might be a
            // worker someday.
            if (mConnection) {
              GetMainThreadSerialEventTarget()->Dispatch(
                  NS_NewCancelableRunnableFunction(
                      "DataChannel::AnnounceOpen",
                      [this, self = RefPtr<DataChannel>(this),
                       connection = mConnection]() {
                        // Stats stuff
                        // TODO: Can we simplify this?
                        if (!mEverOpened && connection->mListener) {
                          mEverOpened = true;
                          connection->mListener->NotifyDataChannelOpen(this);
                        }
                      }),
                  NS_DISPATCH_FALLIBLE);
            }
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannel::AnnounceClosed() {
  // When an RTCDataChannel object's underlying data transport has been closed,
  // the user agent MUST queue a task to run the following steps:
  DC_INFO(
      ("%p: DataChannel is closed. Queueing AnnounceClosed call to "
       "RTCDataChannel.",
       this));

  GetMainThreadSerialEventTarget()->Dispatch(
      NS_NewCancelableRunnableFunction(
          "DataChannel::AnnounceClosed",
          [this, self = RefPtr<DataChannel>(this), connection = mConnection]() {
            if (mAnnouncedClosed) {
              return;
            }
            mAnnouncedClosed = true;
            // We have to unset this first, and then fire DOM events, so the
            // event handler won't hit an error if it tries to reuse this id.
            if (mStream != INVALID_STREAM) {
              DC_INFO(("%p: Marking stream id %u available", this, mStream));
              connection->MarkStreamAvailable(mStream);
            }

            // Stats stuff
            if (mEverOpened && connection->mListener) {
              connection->mListener->NotifyDataChannelClosed(this);
            }

            DC_INFO(("%p: Dispatching AnnounceClosed to DOM thread", this));
            mDomEventTarget->Dispatch(
                NS_NewCancelableRunnableFunction(
                    "DataChannel::AnnounceClosed",
                    [this, self = RefPtr<DataChannel>(this)] {
                      DC_INFO(("%p: Attempting to call AnnounceClosed.", this));
                      if (GetDomDataChannel()) {
                        DC_INFO(
                            ("%p: Calling AnnounceClosed on RTCDataChannel.",
                             this));
                        GetDomDataChannel()->AnnounceClosed();
                      }
                    }),
                NS_DISPATCH_FALLIBLE);
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannel::GracefulClose() {
  DC_INFO(
      ("%p: DataChannel transport is closing. Queueing GracefulClose call to "
       "RTCDataChannel.",
       this));

  mDomEventTarget->Dispatch(
      NS_NewCancelableRunnableFunction(
          "DataChannel::GracefulClose",
          [this, self = RefPtr<DataChannel>(this)] {
            if (GetDomDataChannel()) {
              DC_INFO(("Calling GracefulClose on RTCDataChannel."));
              GetDomDataChannel()->GracefulClose();
            }
          }),
      NS_DISPATCH_FALLIBLE);
}

void DataChannel::SendMsg(nsCString&& aMsg) {
  SendBuffer(std::move(aMsg), false);
}

void DataChannel::SendBinaryMsg(nsCString&& aMsg) {
  SendBuffer(std::move(aMsg), true);
}

void DataChannel::SendBuffer(nsCString&& aMsg, bool aBinary) {
  MOZ_ASSERT(mDomEventTarget->IsOnCurrentThread());
  if (mMessagesSentPromise) {
    mMessagesSentPromise = mMessagesSentPromise->Then(
        mDomEventTarget, __func__,
        [this, self = RefPtr<DataChannel>(this), msg = std::move(aMsg),
         aBinary](
            const GenericNonExclusivePromise::ResolveOrRejectValue&) mutable {
          if (mConnection) {
            mConnection->SendDataMessage(*this, std::move(msg), aBinary);
            return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
          }
          return GenericNonExclusivePromise::CreateAndResolve(false, __func__);
        });

    UnsetMessagesSentPromiseWhenSettled();
    return;
  }
  mConnection->SendDataMessage(*this, std::move(aMsg), aBinary);
}

void DataChannel::SendBinaryBlob(nsIInputStream* aBlob) {
  MOZ_ASSERT(mDomEventTarget->IsOnCurrentThread());
  if (!mMessagesSentPromise) {
    mMessagesSentPromise =
        GenericNonExclusivePromise::CreateAndResolve(true, __func__);
  }

  mMessagesSentPromise = mMessagesSentPromise->Then(
      mConnection->GetIOThread(), __func__,
      [this, self = RefPtr<DataChannel>(this), blob = RefPtr(aBlob)](
          const GenericNonExclusivePromise::ResolveOrRejectValue&) {
        nsCString data;
        if (NS_SUCCEEDED(NS_ReadInputStreamToString(blob, data, -1))) {
          if (mConnection) {
            // This dispatches to STS, which is when we're supposed to resolve
            mConnection->SendDataMessage(*this, std::move(data), true);
          }
          blob->Close();
          return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
        }
        return GenericNonExclusivePromise::CreateAndResolve(false, __func__);
      });

  UnsetMessagesSentPromiseWhenSettled();
}

void DataChannel::UnsetMessagesSentPromiseWhenSettled() {
  MOZ_ASSERT(mDomEventTarget->IsOnCurrentThread());
  // This is why we are using a non-exclusive promise; we want to null this out
  // when we're done, but only if nothing else has chained off of it.
  mMessagesSentPromise->Then(
      mDomEventTarget, __func__,
      [this, self = RefPtr(this), promise = mMessagesSentPromise]() {
        if (promise == mMessagesSentPromise) {
          mMessagesSentPromise = nullptr;
        }
      });
}

void DataChannel::SetStream(uint16_t aId) {
  MOZ_ASSERT(NS_IsMainThread());
  mStream = aId;

  // This is an inconvenient wrinkle in the spec; if the stream id is discovered
  // on main (for any reason), we update mainthread-homed RTCDataChannel
  // synchronously, but must dispatch for workers. It is possible this will
  // change, but probably not.
  if (mHasWorkerDomDataChannel) {
    DC_INFO(
        ("DataChannel has been allocated a stream ID. Queueing task to inform "
         "worker RTCDataChannel."));
    mDomEventTarget->Dispatch(
        NS_NewCancelableRunnableFunction(
            __func__,
            [this, self = RefPtr<DataChannel>(this), aId] {
              if (mWorkerDomDataChannel) {
                mWorkerDomDataChannel->SetId(aId);
              }
            }),
        NS_DISPATCH_FALLIBLE);
  } else {
    DC_INFO(
        ("%p: DataChannel has been allocated a stream ID. Synchronously "
         "informing mainthread RTCDataChannel.",
         this));
    mMainthreadDomDataChannel->SetId(aId);
  }
}

void DataChannel::SetMaxMessageSize(double aMaxMessageSize) {
  MOZ_ASSERT(NS_IsMainThread());

  if (mHasWorkerDomDataChannel) {
    DC_INFO(
        ("DataChannel has updated its maximum message size. Queueing task to "
         "inform worker RTCDataChannel."));
    mDomEventTarget->Dispatch(
        NS_NewCancelableRunnableFunction(
            __func__,
            [this, self = RefPtr<DataChannel>(this), aMaxMessageSize] {
              if (mWorkerDomDataChannel) {
                mWorkerDomDataChannel->SetMaxMessageSize(aMaxMessageSize);
              }
            }),
        NS_DISPATCH_FALLIBLE);
  } else {
    DC_INFO(
        ("%p: DataChannel has updated its maximum message size. Synchronously "
         "informing mainthread RTCDataChannel.",
         this));
    if (mMainthreadDomDataChannel) {
      mMainthreadDomDataChannel->SetMaxMessageSize(aMaxMessageSize);
    }
  }
}

void DataChannel::OnMessageReceived(nsCString&& aMsg, bool aIsBinary) {
  // Receiving any data implies that the other end has received an OPEN
  // request from us.
  mWaitingForAck = false;

  DC_DEBUG(
      ("%p: received message (%s)", this, aIsBinary ? "binary" : "string"));

  mDomEventTarget->Dispatch(NS_NewCancelableRunnableFunction(
                                "DataChannel::OnMessageReceived",
                                [this, self = RefPtr<DataChannel>(this),
                                 msg = std::move(aMsg), aIsBinary]() {
                                  if (GetDomDataChannel()) {
                                    if (!mAnnouncedOpen) {
                                      mAnnouncedOpen = true;
                                      GetDomDataChannel()->AnnounceOpen();
                                    }
                                    GetDomDataChannel()->DoOnMessageAvailable(
                                        msg, aIsBinary);
                                  }
                                }),
                            NS_DISPATCH_FALLIBLE);
}

}  // namespace mozilla

Messung V0.5 in Prozent
C=88 H=94 G=90

¤ Dauer der Verarbeitung: 0.41 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.