UNPKG

mediasoup

Version:

Cutting Edge WebRTC Video Conferencing

169 lines (168 loc) 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const dgram = require("node:dgram"); // @ts-ignore const sctp = require("sctp"); const mediasoup = require("../"); const enhancedEvents_1 = require("../enhancedEvents"); const ctx = {}; beforeEach(async () => { // Set node-sctp default PMTU to 1200. sctp.defaults({ PMTU: 1200 }); ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter(); ctx.plainTransport = await ctx.router.createPlainTransport({ // https://github.com/nodejs/node/issues/14900. listenIp: '127.0.0.1', // So we don't need to call plainTransport.connect(). comedia: true, enableSctp: true, numSctpStreams: { OS: 256, MIS: 256 }, }); // Node UDP socket for SCTP. ctx.udpSocket = dgram.createSocket({ type: 'udp4' }); await new Promise(resolve => { ctx.udpSocket.bind(0, '127.0.0.1', resolve); }); const remoteUdpIp = ctx.plainTransport.tuple.localAddress; const remoteUdpPort = ctx.plainTransport.tuple.localPort; const { OS, MIS } = ctx.plainTransport.sctpParameters; await new Promise((resolve, reject) => { // @ts-ignore ctx.udpSocket.connect(remoteUdpPort, remoteUdpIp, (error) => { if (error) { reject(error); return; } ctx.sctpSocket = sctp.connect({ localPort: 5000, // Required for SCTP over UDP in mediasoup. port: 5000, // Required for SCTP over UDP in mediasoup. OS: OS, MIS: MIS, udpTransport: ctx.udpSocket, }); resolve(); }); }); // Wait for the SCTP association to be open. await Promise.race([ (0, enhancedEvents_1.enhancedOnce)(ctx.sctpSocket, 'connect'), new Promise((resolve, reject) => setTimeout(() => reject(new Error('SCTP connection timeout')), 3000)), ]); // Create an explicit SCTP outgoing stream with id 123 (id 0 is already used // by the implicit SCTP outgoing stream built-in the SCTP socket). ctx.sctpSendStreamId = 123; ctx.sctpSendStream = ctx.sctpSocket.createStream(ctx.sctpSendStreamId); // Create a DataProducer with the corresponding SCTP stream id. ctx.dataProducer = await ctx.plainTransport.produceData({ sctpStreamParameters: { streamId: ctx.sctpSendStreamId, ordered: true, }, label: 'node-sctp', protocol: 'foo & bar 😀😀😀', }); // Create a DataConsumer to receive messages from the DataProducer over the // same plainTransport. ctx.dataConsumer = await ctx.plainTransport.consumeData({ dataProducerId: ctx.dataProducer.id, }); }); afterEach(async () => { ctx.udpSocket?.close(); ctx.sctpSocket?.end(); ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await (0, enhancedEvents_1.enhancedOnce)(ctx.worker, 'subprocessclose'); } // NOTE: For some reason we have to wait a bit for the SCTP stuff to release // internal things, otherwise Jest reports open handles. We don't care much // honestly. await new Promise(resolve => setTimeout(resolve, 1000)); }); test('ordered DataProducer delivers all SCTP messages to the DataConsumer', async () => { const onStream = jest.fn(); const numMessages = 200; let sentMessageBytes = 0; let recvMessageBytes = 0; let numSentMessages = 0; let numReceivedMessages = 0; // It must be zero because it's the first DataConsumer on the plainTransport. expect(ctx.dataConsumer.sctpStreamParameters?.streamId).toBe(0); await new Promise((resolve, reject) => { sendNextMessage(); async function sendNextMessage() { const id = ++numSentMessages; const data = Buffer.from(String(id)); // Set ppid of type WebRTC DataChannel string. if (id < numMessages / 2) { // @ts-ignore data.ppid = sctp.PPID.WEBRTC_STRING; } // Set ppid of type WebRTC DataChannel binary. else { // @ts-ignore data.ppid = sctp.PPID.WEBRTC_BINARY; } ctx.sctpSendStream.write(data); sentMessageBytes += data.byteLength; if (id < numMessages) { sendNextMessage(); } } ctx.sctpSocket.on('stream', onStream); // Handle the generated SCTP incoming stream and SCTP messages receives on it. // @ts-ignore ctx.sctpSocket.on('stream', (stream, streamId) => { // It must be zero because it's the first SCTP incoming stream (so first // DataConsumer). if (streamId !== 0) { reject(new Error(`streamId should be 0 but it is ${streamId}`)); return; } // @ts-ignore stream.on('data', (data) => { ++numReceivedMessages; recvMessageBytes += data.byteLength; const id = Number(data.toString('utf8')); // @ts-ignore const ppid = data.ppid; if (id !== numReceivedMessages) { reject(new Error(`id ${id} in message should match numReceivedMessages ${numReceivedMessages}`)); } else if (id === numMessages) { resolve(); } else if (id < numMessages / 2 && ppid !== sctp.PPID.WEBRTC_STRING) { reject(new Error(`ppid in message with id ${id} should be ${sctp.PPID.WEBRTC_STRING} but it is ${ppid}`)); } else if (id > numMessages / 2 && ppid !== sctp.PPID.WEBRTC_BINARY) { reject(new Error(`ppid in message with id ${id} should be ${sctp.PPID.WEBRTC_BINARY} but it is ${ppid}`)); return; } }); }); }); expect(onStream).toHaveBeenCalledTimes(1); expect(numSentMessages).toBe(numMessages); expect(numReceivedMessages).toBe(numMessages); expect(recvMessageBytes).toBe(sentMessageBytes); await expect(ctx.dataProducer.getStats()).resolves.toMatchObject([ { type: 'data-producer', label: ctx.dataProducer.label, protocol: ctx.dataProducer.protocol, messagesReceived: numMessages, bytesReceived: sentMessageBytes, }, ]); await expect(ctx.dataConsumer.getStats()).resolves.toMatchObject([ { type: 'data-consumer', label: ctx.dataConsumer.label, protocol: ctx.dataConsumer.protocol, messagesSent: numMessages, bytesSent: recvMessageBytes, }, ]); }, 10000);