Need to specify DataConsumer SCTP StreamId

I’m trying to terminate a DataChannel SCTP stream in my server application, but finding that the DataConsumer chooses the wrong SCTP Stream ID regardless of what the application needs.

This happens not in the mediasoup worker but in the language bindings. For example in Node is here: node/src/Transport.ts. Turns out that getNextSctpStreamId() unconditionally chooses the first available Stream ID, and then keeps counting up from there.

Problem is that we’d ideally want that a SCTP DataProducer/DataConsumer pair have the same SCTP Stream ID. And these should be application-provided, to comply with rules about using odd or even numbers depending on the DTLS Role.

What I’m doing here is:

  1. webRtcTransport.produceData(), with application-provided streamId == 1 (consumed on a Direct DataConsumer).
  2. webRtcTransport.consumeData(), (consuming from a Direct DataProducer) gets an auto-assigned streamId == 0.

So inbound and outbound Stream IDs don’t coincide.

It is the DataConsumer’s streamId that gets signaled to the remote peer (a 3rd-party binary module), so this peer starts sending SCTP on streamId == 0. Because our DataProducer is listening on streamId == 1, this log happens in mediasoup worker:

no suitable DataProducer for received SCTP message [streamId:0]

However everything works fine if the correct streamId is forced into the DataConsumer.

I tested this by editing the language bindings and allowing a new optional streamId parameter to DataConsumerOptions:

type DataConsumerOptions = {
    dataProducerId: string;

    streamId?: number; // <-- Allow specifying optional Stream ID.

    ordered?: boolean;
    maxPacketLifeTime?: number;
    maxRetransmits?: number;
    appData?: DataConsumerAppData;
};
const dataConsumer = await webRtcTransport.consumeData({
    dataProducerId: "...",
    streamId: 1,
});

Then data is sent and received on the correct SCTP Stream ID.

What do you think? Would this addition to DataConsumerOptions be a reasonable change to mediasoup?

For now mediasoup-client and libmediasoupclient create separate transport for sending and receiving (including DataChannel traffic) so this is not a real problem in those environments.

I understand it’s a limitation for other clients. In this case AFAIS this is just about allowing passing streamId in transport.consumeData() (but if given then that method must verify that such a id is not already in the this.#sctpStreamIds container (if so, throw).

PR welcome, otherwise please write a ticket in GitHub with all this info. Note that the PR:

  • Must also include Rust changes.
  • Must include tests in Node and Rust.

OK, so you think it would make sense and be open for such change. That’s the first step, of course, before any development.

I’m in the middle of a lot of work so I have to continue and that will help me evaluating the complete picture, in case there’s something I hadn’t considered.

Yes, but also take into account that user given streamId should also be validated using logic similar to the one in Transport.ts here:

private getNextSctpStreamId(): number {
		if (
			!this.#data.sctpParameters ||
			typeof this.#data.sctpParameters.MIS !== 'number'
		) {
			throw new TypeError('missing sctpParameters.MIS');
		}

		const numStreams = this.#data.sctpParameters.MIS;

		if (!this.#sctpStreamIds) {
			this.#sctpStreamIds = Buffer.alloc(numStreams, 0);
		}

		let sctpStreamId;

		for (let idx = 0; idx < this.#sctpStreamIds.length; ++idx) {
			sctpStreamId =
				(this.#nextSctpStreamId + idx) % this.#sctpStreamIds.length;

			if (!this.#sctpStreamIds[sctpStreamId]) {
				this.#nextSctpStreamId = sctpStreamId + 1;

				return sctpStreamId;
			}
		}

		throw new Error('no sctpStreamId available');
	}

See how it takes into account const numStreams = this.#data.sctpParameters.MIS.

NOTE: There is an ongoing PR for this: Add optional sctpStreamId to SCTP-based data consumers by threema-lenny · Pull Request #1378 · versatica/mediasoup · GitHub

The author seems to not be working any more on that PR. Wanna take it?

Oh I see. I’d love to put my hands on it, actually, but first I need to finish making it work for my project. I’m using a Go binding for mediasoup, so that’s already 3 languages to update :slight_smile:

I’ll file this PR to keep it on sight, but won’t be able to really work on it for at least some weeks probably.

1 Like