diff --git a/CHANGELOG.md b/CHANGELOG.md index 95d4a8de44..5a5251e34e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### WIP * More SRTP crypto suites (PR #837). +* Improve `EnhancedEventEmitter` (PR #836). +* `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port (PR #834). * `TransportCongestionControlClient`: Allow setting max outgoing bitrate before created (PR #833). diff --git a/node/lib/Router.d.ts b/node/lib/Router.d.ts index 5500c70bca..cda4a22f5c 100644 --- a/node/lib/Router.d.ts +++ b/node/lib/Router.d.ts @@ -125,6 +125,11 @@ export declare class Router extends EnhancedEventEmitter { * Observer. */ get observer(): EnhancedEventEmitter; + /** + * @private + * Just for testing purposes. + */ + get transportsForTesting(): Map; /** * Close the Router. */ @@ -142,15 +147,11 @@ export declare class Router extends EnhancedEventEmitter { /** * Create a WebRtcTransport. */ - createWebRtcTransport({ listenIps, port, enableUdp, enableTcp, preferUdp, preferTcp, initialAvailableOutgoingBitrate, enableSctp, numSctpStreams, maxSctpMessageSize, sctpSendBufferSize, appData }: WebRtcTransportOptions): Promise; + createWebRtcTransport({ webRtcServer, listenIps, port, enableUdp, enableTcp, preferUdp, preferTcp, initialAvailableOutgoingBitrate, enableSctp, numSctpStreams, maxSctpMessageSize, sctpSendBufferSize, appData }: WebRtcTransportOptions): Promise; /** * Create a PlainTransport. */ createPlainTransport({ listenIp, port, rtcpMux, comedia, enableSctp, numSctpStreams, maxSctpMessageSize, sctpSendBufferSize, enableSrtp, srtpCryptoSuite, appData }: PlainTransportOptions): Promise; - /** - * DEPRECATED: Use createPlainTransport(). - */ - createPlainRtpTransport(options: PlainTransportOptions): Promise; /** * Create a PipeTransport. */ diff --git a/node/lib/Router.d.ts.map b/node/lib/Router.d.ts.map index 9078943ebe..a2a6b961d7 100644 --- a/node/lib/Router.d.ts.map +++ b/node/lib/Router.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Router.d.ts","sourceRoot":"","sources":["../src/Router.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"Router.d.ts","sourceRoot":"","sources":["../src/Router.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node/lib/Router.js b/node/lib/Router.js index 9736c35dce..95fbfe0c60 100644 --- a/node/lib/Router.js +++ b/node/lib/Router.js @@ -87,6 +87,13 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { get observer() { return this.#observer; } + /** + * @private + * Just for testing purposes. + */ + get transportsForTesting() { + return this.#transports; + } /** * Close the Router. */ @@ -153,27 +160,37 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { /** * Create a WebRtcTransport. */ - async createWebRtcTransport({ listenIps, port, enableUdp = true, enableTcp = false, preferUdp = false, preferTcp = false, initialAvailableOutgoingBitrate = 600000, enableSctp = false, numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 262144, sctpSendBufferSize = 262144, appData }) { + async createWebRtcTransport({ webRtcServer, listenIps, port, enableUdp = true, enableTcp = false, preferUdp = false, preferTcp = false, initialAvailableOutgoingBitrate = 600000, enableSctp = false, numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 262144, sctpSendBufferSize = 262144, appData }) { logger.debug('createWebRtcTransport()'); - if (!Array.isArray(listenIps)) - throw new TypeError('missing listenIps'); + if (!webRtcServer && !Array.isArray(listenIps)) + throw new TypeError('missing webRtcServer and listenIps (one of them is mandatory)'); else if (appData && typeof appData !== 'object') throw new TypeError('if given, appData must be an object'); - listenIps = listenIps.map((listenIp) => { - if (typeof listenIp === 'string' && listenIp) { - return { ip: listenIp }; - } - else if (typeof listenIp === 'object') { - return { - ip: listenIp.ip, - announcedIp: listenIp.announcedIp || undefined - }; - } - else { - throw new TypeError('wrong listenIp'); - } - }); - const internal = { ...this.#internal, transportId: (0, uuid_1.v4)() }; + if (webRtcServer) { + listenIps = undefined; + port = undefined; + } + if (listenIps) { + listenIps = listenIps.map((listenIp) => { + if (typeof listenIp === 'string' && listenIp) { + return { ip: listenIp }; + } + else if (typeof listenIp === 'object') { + return { + ip: listenIp.ip, + announcedIp: listenIp.announcedIp || undefined + }; + } + else { + throw new TypeError('wrong listenIp'); + } + }); + } + const internal = { + ...this.#internal, + transportId: (0, uuid_1.v4)(), + webRtcServerId: webRtcServer ? webRtcServer.id : undefined + }; const reqData = { listenIps, port, @@ -188,7 +205,9 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { sctpSendBufferSize, isDataChannel: true }; - const data = await this.#channel.request('router.createWebRtcTransport', internal, reqData); + const data = webRtcServer + ? await this.#channel.request('router.createWebRtcTransportWithServer', internal, reqData) + : await this.#channel.request('router.createWebRtcTransport', internal, reqData); const transport = new WebRtcTransport_1.WebRtcTransport({ internal, data, @@ -201,10 +220,13 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer) => (this.#dataProducers.set(dataProducer.id, dataProducer))); transport.on('@dataproducerclose', (dataProducer) => (this.#dataProducers.delete(dataProducer.id))); + if (webRtcServer) + webRtcServer.handleWebRtcTransport(transport); // Emit observer event. this.#observer.safeEmit('newtransport', transport); return transport; @@ -258,6 +280,7 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer) => (this.#dataProducers.set(dataProducer.id, dataProducer))); @@ -266,13 +289,6 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { this.#observer.safeEmit('newtransport', transport); return transport; } - /** - * DEPRECATED: Use createPlainTransport(). - */ - async createPlainRtpTransport(options) { - logger.warn('createPlainRtpTransport() is DEPRECATED, use createPlainTransport()'); - return this.createPlainTransport(options); - } /** * Create a PipeTransport. */ @@ -320,6 +336,7 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer) => (this.#dataProducers.set(dataProducer.id, dataProducer))); @@ -350,6 +367,7 @@ class Router extends EnhancedEventEmitter_1.EnhancedEventEmitter { }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer) => (this.#dataProducers.set(dataProducer.id, dataProducer))); diff --git a/node/lib/Transport.d.ts b/node/lib/Transport.d.ts index ee7d76e028..e7df227e19 100644 --- a/node/lib/Transport.d.ts +++ b/node/lib/Transport.d.ts @@ -56,12 +56,14 @@ export interface TransportTraceEventData { export declare type SctpState = 'new' | 'connecting' | 'connected' | 'failed' | 'closed'; export declare type TransportEvents = { routerclose: []; + listenserverclose: []; trace: [TransportTraceEventData]; '@close': []; '@newproducer': [Producer]; '@producerclose': [Producer]; '@newdataproducer': [DataProducer]; '@dataproducerclose': [DataProducer]; + '@listenserverclose': []; }; export declare type TransportObserverEvents = { close: []; @@ -134,6 +136,13 @@ export declare class Transport; +}; +export declare type WebRtcServerEvents = { + workerclose: []; + '@close': []; +}; +export declare type WebRtcServerObserverEvents = { + close: []; + webrtctransporthandled: [WebRtcTransport]; + webrtctransportunhandled: [WebRtcTransport]; +}; +export declare class WebRtcServer extends EnhancedEventEmitter { + #private; + /** + * @private + */ + constructor({ internal, channel, appData }: { + internal: any; + channel: Channel; + appData?: Record; + }); + /** + * WebRtcServer id. + */ + get id(): string; + /** + * Whether the WebRtcServer is closed. + */ + get closed(): boolean; + /** + * App custom data. + */ + get appData(): Record; + /** + * Invalid setter. + */ + set appData(appData: Record); + /** + * Observer. + */ + get observer(): EnhancedEventEmitter; + /** + * @private + * Just for testing purposes. + */ + get webRtcTransportsForTesting(): Map; + /** + * Close the WebRtcServer. + */ + close(): void; + /** + * Worker was closed. + * + * @private + */ + workerClosed(): void; + /** + * Dump WebRtcServer. + */ + dump(): Promise; + /** + * @private + */ + handleWebRtcTransport(webRtcTransport: WebRtcTransport): void; +} +//# sourceMappingURL=WebRtcServer.d.ts.map \ No newline at end of file diff --git a/node/lib/WebRtcServer.d.ts.map b/node/lib/WebRtcServer.d.ts.map new file mode 100644 index 0000000000..c57f843e32 --- /dev/null +++ b/node/lib/WebRtcServer.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"WebRtcServer.d.ts","sourceRoot":"","sources":["../src/WebRtcServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,sBAAsB;IAEtC;;OAEG;IACH,QAAQ,EAAE,iBAAiB,CAAC;IAE5B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACb;AAED,oBAAY,mBAAmB,GAC/B;IACC;;OAEG;IACH,WAAW,EAAE,sBAAsB,EAAE,CAAC;IAEtC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAA;AAED,oBAAY,kBAAkB,GAC9B;IACC,WAAW,EAAE,EAAE,CAAC;IAEhB,QAAQ,EAAE,EAAE,CAAC;CACb,CAAA;AAED,oBAAY,0BAA0B,GACtC;IACC,KAAK,EAAE,EAAE,CAAC;IACV,sBAAsB,EAAE,CAAC,eAAe,CAAC,CAAC;IAC1C,wBAAwB,EAAE,CAAC,eAAe,CAAC,CAAC;CAC5C,CAAA;AAID,qBAAa,YAAa,SAAQ,oBAAoB,CAAC,kBAAkB,CAAC;;IAuBzE;;OAEG;gBAEF,EACC,QAAQ,EACR,OAAO,EACP,OAAO,EACP,EACD;QACC,QAAQ,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC;IAYF;;OAEG;IACH,IAAI,EAAE,IAAI,MAAM,CAGf;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAGpB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGrC;IAED;;OAEG;IACH,IAAI,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAG3C;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,oBAAoB,CAAC,0BAA0B,CAAC,CAG/D;IAED;;;OAGG;IACH,IAAI,0BAA0B,IAAI,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAG7D;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;IA4Bb;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAmBpB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAO1B;;OAEG;IACH,qBAAqB,CAAC,eAAe,EAAE,eAAe,GAAG,IAAI;CAe7D"} \ No newline at end of file diff --git a/node/lib/WebRtcServer.js b/node/lib/WebRtcServer.js new file mode 100644 index 0000000000..fc860ed164 --- /dev/null +++ b/node/lib/WebRtcServer.js @@ -0,0 +1,126 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebRtcServer = void 0; +const Logger_1 = require("./Logger"); +const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); +const logger = new Logger_1.Logger('WebRtcServer'); +class WebRtcServer extends EnhancedEventEmitter_1.EnhancedEventEmitter { + // Internal data. + #internal; + // Channel instance. + #channel; + // Closed flag. + #closed = false; + // Custom app data. + #appData; + // Transports map. + #webRtcTransports = new Map(); + // Observer instance. + #observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); + /** + * @private + */ + constructor({ internal, channel, appData }) { + super(); + logger.debug('constructor()'); + this.#internal = internal; + this.#channel = channel; + this.#appData = appData || {}; + } + /** + * WebRtcServer id. + */ + get id() { + return this.#internal.webRtcServerId; + } + /** + * Whether the WebRtcServer is closed. + */ + get closed() { + return this.#closed; + } + /** + * App custom data. + */ + get appData() { + return this.#appData; + } + /** + * Invalid setter. + */ + set appData(appData) { + throw new Error('cannot override appData object'); + } + /** + * Observer. + */ + get observer() { + return this.#observer; + } + /** + * @private + * Just for testing purposes. + */ + get webRtcTransportsForTesting() { + return this.#webRtcTransports; + } + /** + * Close the WebRtcServer. + */ + close() { + if (this.#closed) + return; + logger.debug('close()'); + this.#closed = true; + this.#channel.request('webRtcServer.close', this.#internal) + .catch(() => { }); + // Close every WebRtcTransport. + for (const webRtcTransport of this.#webRtcTransports.values()) { + webRtcTransport.listenServerClosed(); + // Emit observer event. + this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); + } + this.#webRtcTransports.clear(); + this.emit('@close'); + // Emit observer event. + this.#observer.safeEmit('close'); + } + /** + * Worker was closed. + * + * @private + */ + workerClosed() { + if (this.#closed) + return; + logger.debug('workerClosed()'); + this.#closed = true; + // NOTE: No need to close WebRtcTransports since they are closed by their + // respective Router parents. + this.#webRtcTransports.clear(); + this.safeEmit('workerclose'); + // Emit observer event. + this.#observer.safeEmit('close'); + } + /** + * Dump WebRtcServer. + */ + async dump() { + logger.debug('dump()'); + return this.#channel.request('webRtcServer.dump', this.#internal); + } + /** + * @private + */ + handleWebRtcTransport(webRtcTransport) { + this.#webRtcTransports.set(webRtcTransport.id, webRtcTransport); + // Emit observer event. + this.#observer.safeEmit('webrtctransporthandled', webRtcTransport); + webRtcTransport.on('@close', () => { + this.#webRtcTransports.delete(webRtcTransport.id); + // Emit observer event. + this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); + }); + } +} +exports.WebRtcServer = WebRtcServer; diff --git a/node/lib/WebRtcTransport.d.ts b/node/lib/WebRtcTransport.d.ts index b2c1f6896a..6bc9bb5587 100644 --- a/node/lib/WebRtcTransport.d.ts +++ b/node/lib/WebRtcTransport.d.ts @@ -1,11 +1,16 @@ import { Transport, TransportListenIp, TransportProtocol, TransportTuple, TransportEvents, TransportObserverEvents, SctpState } from './Transport'; +import { WebRtcServer } from './WebRtcServer'; import { SctpParameters, NumSctpStreams } from './SctpParameters'; export declare type WebRtcTransportOptions = { + /** + * Instance of WebRtcServer. Mandatory unless listenIps is given. + */ + webRtcServer?: WebRtcServer; /** * Listening IP address or addresses in order of preference (first one is the - * preferred one). + * preferred one). Mandatory unless webRtcServer is given. */ - listenIps: (TransportListenIp | string)[]; + listenIps?: (TransportListenIp | string)[]; /** * Fixed port to listen on instead of selecting automatically from Worker's port * range. @@ -183,6 +188,12 @@ export declare class WebRtcTransport extends Transport { @@ -149,6 +151,11 @@ export declare class Worker extends EnhancedEventEmitter { * Observer. */ get observer(): EnhancedEventEmitter; + /** + * @private + * Just for testing purposes. + */ + get webRtcServersForTesting(): Set; /** * @private * Just for testing purposes. @@ -170,6 +177,10 @@ export declare class Worker extends EnhancedEventEmitter { * Update settings. */ updateSettings({ logLevel, logTags }?: WorkerUpdateableSettings): Promise; + /** + * Create a WebRtcServer. + */ + createWebRtcServer({ listenInfos, appData }: WebRtcServerOptions): Promise; /** * Create a Router. */ diff --git a/node/lib/Worker.d.ts.map b/node/lib/Worker.d.ts.map index c82bf4f91b..7af6162173 100644 --- a/node/lib/Worker.d.ts.map +++ b/node/lib/Worker.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../src/Worker.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"Worker.d.ts","sourceRoot":"","sources":["../src/Worker.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node/lib/Worker.js b/node/lib/Worker.js index 2021acfd52..0888d8e3e4 100644 --- a/node/lib/Worker.js +++ b/node/lib/Worker.js @@ -11,6 +11,7 @@ const ortc = require("./ortc"); const Channel_1 = require("./Channel"); const PayloadChannel_1 = require("./PayloadChannel"); const Router_1 = require("./Router"); +const WebRtcServer_1 = require("./WebRtcServer"); // If env MEDIASOUP_WORKER_BIN is given, use it as worker binary. // Otherwise if env MEDIASOUP_BUILDTYPE is 'Debug' use the Debug binary. // Otherwise use the Release binary. @@ -36,6 +37,8 @@ class Worker extends EnhancedEventEmitter_1.EnhancedEventEmitter { #died = false; // Custom app data. #appData; + // WebRtcServers set. + #webRtcServers = new Set(); // Routers set. #routers = new Set(); // Observer instance. @@ -202,6 +205,13 @@ class Worker extends EnhancedEventEmitter_1.EnhancedEventEmitter { get observer() { return this.#observer; } + /** + * @private + * Just for testing purposes. + */ + get webRtcServersForTesting() { + return this.#webRtcServers; + } /** * @private * Just for testing purposes. @@ -236,6 +246,11 @@ class Worker extends EnhancedEventEmitter_1.EnhancedEventEmitter { router.workerClosed(); } this.#routers.clear(); + // Close every WebRtcServer. + for (const webRtcServer of this.#webRtcServers) { + webRtcServer.workerClosed(); + } + this.#webRtcServers.clear(); // Emit observer event. this.#observer.safeEmit('close'); } @@ -261,6 +276,27 @@ class Worker extends EnhancedEventEmitter_1.EnhancedEventEmitter { const reqData = { logLevel, logTags }; await this.#channel.request('worker.updateSettings', undefined, reqData); } + /** + * Create a WebRtcServer. + */ + async createWebRtcServer({ listenInfos, appData }) { + logger.debug('createWebRtcServer()'); + if (appData && typeof appData !== 'object') + throw new TypeError('if given, appData must be an object'); + const internal = { webRtcServerId: (0, uuid_1.v4)() }; + const reqData = { listenInfos }; + await this.#channel.request('worker.createWebRtcServer', internal, reqData); + const webRtcServer = new WebRtcServer_1.WebRtcServer({ + internal, + channel: this.#channel, + appData + }); + this.#webRtcServers.add(webRtcServer); + webRtcServer.on('@close', () => this.#webRtcServers.delete(webRtcServer)); + // Emit observer event. + this.#observer.safeEmit('newwebrtcserver', webRtcServer); + return webRtcServer; + } /** * Create a Router. */ @@ -301,6 +337,11 @@ class Worker extends EnhancedEventEmitter_1.EnhancedEventEmitter { router.workerClosed(); } this.#routers.clear(); + // Close every WebRtcServer. + for (const webRtcServer of this.#webRtcServers) { + webRtcServer.workerClosed(); + } + this.#webRtcServers.clear(); this.safeEmit('died', error); // Emit observer event. this.#observer.safeEmit('close'); diff --git a/node/lib/types.d.ts b/node/lib/types.d.ts index e6357b6c3d..01ad1c323e 100644 --- a/node/lib/types.d.ts +++ b/node/lib/types.d.ts @@ -1,4 +1,5 @@ export * from './Worker'; +export * from './WebRtcServer'; export * from './Router'; export * from './Transport'; export * from './WebRtcTransport'; diff --git a/node/lib/types.d.ts.map b/node/lib/types.d.ts.map index 88f029fe0f..b34a5c6f17 100644 --- a/node/lib/types.d.ts.map +++ b/node/lib/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"} \ No newline at end of file diff --git a/node/lib/types.js b/node/lib/types.js index 00c6b24b1d..65d74efd03 100644 --- a/node/lib/types.js +++ b/node/lib/types.js @@ -15,6 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { }; Object.defineProperty(exports, "__esModule", { value: true }); __exportStar(require("./Worker"), exports); +__exportStar(require("./WebRtcServer"), exports); __exportStar(require("./Router"), exports); __exportStar(require("./Transport"), exports); __exportStar(require("./WebRtcTransport"), exports); diff --git a/node/src/Router.ts b/node/src/Router.ts index af8349ca4d..674464394e 100644 --- a/node/src/Router.ts +++ b/node/src/Router.ts @@ -245,6 +245,15 @@ export class Router extends EnhancedEventEmitter return this.#observer; } + /** + * @private + * Just for testing purposes. + */ + get transportsForTesting(): Map + { + return this.#transports; + } + /** * Close the Router. */ @@ -341,6 +350,7 @@ export class Router extends EnhancedEventEmitter */ async createWebRtcTransport( { + webRtcServer, listenIps, port, enableUdp = true, @@ -358,31 +368,46 @@ export class Router extends EnhancedEventEmitter { logger.debug('createWebRtcTransport()'); - if (!Array.isArray(listenIps)) - throw new TypeError('missing listenIps'); + if (!webRtcServer && !Array.isArray(listenIps)) + throw new TypeError('missing webRtcServer and listenIps (one of them is mandatory)'); else if (appData && typeof appData !== 'object') throw new TypeError('if given, appData must be an object'); - listenIps = listenIps.map((listenIp) => + if (webRtcServer) { - if (typeof listenIp === 'string' && listenIp) - { - return { ip: listenIp }; - } - else if (typeof listenIp === 'object') - { - return { - ip : listenIp.ip, - announcedIp : listenIp.announcedIp || undefined - }; - } - else + listenIps = undefined; + port = undefined; + } + + if (listenIps) + { + listenIps = listenIps.map((listenIp) => { - throw new TypeError('wrong listenIp'); - } - }); + if (typeof listenIp === 'string' && listenIp) + { + return { ip: listenIp }; + } + else if (typeof listenIp === 'object') + { + return { + ip : listenIp.ip, + announcedIp : listenIp.announcedIp || undefined + }; + } + else + { + throw new TypeError('wrong listenIp'); + } + }); + } + + const internal = + { + ...this.#internal, + transportId : uuidv4(), + webRtcServerId : webRtcServer ? webRtcServer.id : undefined + }; - const internal = { ...this.#internal, transportId: uuidv4() }; const reqData = { listenIps, port, @@ -398,8 +423,9 @@ export class Router extends EnhancedEventEmitter isDataChannel : true }; - const data = - await this.#channel.request('router.createWebRtcTransport', internal, reqData); + const data = webRtcServer + ? await this.#channel.request('router.createWebRtcTransportWithServer', internal, reqData) + : await this.#channel.request('router.createWebRtcTransport', internal, reqData); const transport = new WebRtcTransport( { @@ -419,6 +445,7 @@ export class Router extends EnhancedEventEmitter this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer: DataProducer) => ( @@ -428,6 +455,9 @@ export class Router extends EnhancedEventEmitter this.#dataProducers.delete(dataProducer.id) )); + if (webRtcServer) + webRtcServer.handleWebRtcTransport(transport); + // Emit observer event. this.#observer.safeEmit('newtransport', transport); @@ -513,6 +543,7 @@ export class Router extends EnhancedEventEmitter this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer: DataProducer) => ( @@ -528,19 +559,6 @@ export class Router extends EnhancedEventEmitter return transport; } - /** - * DEPRECATED: Use createPlainTransport(). - */ - async createPlainRtpTransport( - options: PlainTransportOptions - ): Promise - { - logger.warn( - 'createPlainRtpTransport() is DEPRECATED, use createPlainTransport()'); - - return this.createPlainTransport(options); - } - /** * Create a PipeTransport. */ @@ -616,6 +634,7 @@ export class Router extends EnhancedEventEmitter this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer: DataProducer) => ( @@ -670,6 +689,7 @@ export class Router extends EnhancedEventEmitter this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); + transport.on('@listenserverclose', () => this.#transports.delete(transport.id)); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer)); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id)); transport.on('@newdataproducer', (dataProducer: DataProducer) => ( diff --git a/node/src/Transport.ts b/node/src/Transport.ts index 93ca4e9e1a..f07a5aef4b 100644 --- a/node/src/Transport.ts +++ b/node/src/Transport.ts @@ -83,7 +83,8 @@ export type SctpState = 'new' | 'connecting' | 'connected' | 'failed' | 'closed' export type TransportEvents = { - routerclose: []; + routerclose: []; + listenserverclose: []; trace: [TransportTraceEventData]; // Private events. '@close': []; @@ -91,6 +92,7 @@ export type TransportEvents = '@producerclose': [Producer]; '@newdataproducer': [DataProducer]; '@dataproducerclose': [DataProducer]; + '@listenserverclose': []; } export type TransportObserverEvents = @@ -379,6 +381,70 @@ export class Transport; +} + +export type WebRtcServerEvents = +{ + workerclose: []; + // Private events. + '@close': []; +} + +export type WebRtcServerObserverEvents = +{ + close: []; + webrtctransporthandled: [WebRtcTransport]; + webrtctransportunhandled: [WebRtcTransport]; +} + +const logger = new Logger('WebRtcServer'); + +export class WebRtcServer extends EnhancedEventEmitter +{ + // Internal data. + readonly #internal: + { + webRtcServerId: string; + }; + + // Channel instance. + readonly #channel: Channel; + + // Closed flag. + #closed = false; + + // Custom app data. + readonly #appData: Record; + + // Transports map. + readonly #webRtcTransports: Map = new Map(); + + // Observer instance. + readonly #observer = new EnhancedEventEmitter(); + + /** + * @private + */ + constructor( + { + internal, + channel, + appData + }: + { + internal: any; + channel: Channel; + appData?: Record; + } + ) + { + super(); + + logger.debug('constructor()'); + + this.#internal = internal; + this.#channel = channel; + this.#appData = appData || {}; + } + + /** + * WebRtcServer id. + */ + get id(): string + { + return this.#internal.webRtcServerId; + } + + /** + * Whether the WebRtcServer is closed. + */ + get closed(): boolean + { + return this.#closed; + } + + /** + * App custom data. + */ + get appData(): Record + { + return this.#appData; + } + + /** + * Invalid setter. + */ + set appData(appData: Record) // eslint-disable-line no-unused-vars + { + throw new Error('cannot override appData object'); + } + + /** + * Observer. + */ + get observer(): EnhancedEventEmitter + { + return this.#observer; + } + + /** + * @private + * Just for testing purposes. + */ + get webRtcTransportsForTesting(): Map + { + return this.#webRtcTransports; + } + + /** + * Close the WebRtcServer. + */ + close(): void + { + if (this.#closed) + return; + + logger.debug('close()'); + + this.#closed = true; + + this.#channel.request('webRtcServer.close', this.#internal) + .catch(() => {}); + + // Close every WebRtcTransport. + for (const webRtcTransport of this.#webRtcTransports.values()) + { + webRtcTransport.listenServerClosed(); + + // Emit observer event. + this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); + } + this.#webRtcTransports.clear(); + + this.emit('@close'); + + // Emit observer event. + this.#observer.safeEmit('close'); + } + + /** + * Worker was closed. + * + * @private + */ + workerClosed(): void + { + if (this.#closed) + return; + + logger.debug('workerClosed()'); + + this.#closed = true; + + // NOTE: No need to close WebRtcTransports since they are closed by their + // respective Router parents. + this.#webRtcTransports.clear(); + + this.safeEmit('workerclose'); + + // Emit observer event. + this.#observer.safeEmit('close'); + } + + /** + * Dump WebRtcServer. + */ + async dump(): Promise + { + logger.debug('dump()'); + + return this.#channel.request('webRtcServer.dump', this.#internal); + } + + /** + * @private + */ + handleWebRtcTransport(webRtcTransport: WebRtcTransport): void + { + this.#webRtcTransports.set(webRtcTransport.id, webRtcTransport); + + // Emit observer event. + this.#observer.safeEmit('webrtctransporthandled', webRtcTransport); + + webRtcTransport.on('@close', () => + { + this.#webRtcTransports.delete(webRtcTransport.id); + + // Emit observer event. + this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); + }); + } +} diff --git a/node/src/WebRtcTransport.ts b/node/src/WebRtcTransport.ts index bb70be11ee..4f8f4edb99 100644 --- a/node/src/WebRtcTransport.ts +++ b/node/src/WebRtcTransport.ts @@ -9,15 +9,20 @@ import { TransportObserverEvents, SctpState } from './Transport'; +import { WebRtcServer } from './WebRtcServer'; import { SctpParameters, NumSctpStreams } from './SctpParameters'; export type WebRtcTransportOptions = { + /** + * Instance of WebRtcServer. Mandatory unless listenIps is given. + */ + webRtcServer?: WebRtcServer; /** * Listening IP address or addresses in order of preference (first one is the - * preferred one). + * preferred one). Mandatory unless webRtcServer is given. */ - listenIps: (TransportListenIp | string)[]; + listenIps?: (TransportListenIp | string)[]; /** * Fixed port to listen on instead of selecting automatically from Worker's port @@ -336,6 +341,26 @@ export class WebRtcTransport extends super.routerClosed(); } + /** + * Called when closing the associated WebRtcServer. + * + * @private + */ + webRtcServerClosed(): void + { + if (this.closed) + return; + + this.#data.iceState = 'closed'; + this.#data.iceSelectedTuple = undefined; + this.#data.dtlsState = 'closed'; + + if (this.#data.sctpState) + this.#data.sctpState = 'closed'; + + super.listenServerClosed(); + } + /** * Get WebRtcTransport stats. * diff --git a/node/src/Worker.ts b/node/src/Worker.ts index 8243e9c27d..2d92c91e22 100644 --- a/node/src/Worker.ts +++ b/node/src/Worker.ts @@ -8,6 +8,7 @@ import * as ortc from './ortc'; import { Channel } from './Channel'; import { PayloadChannel } from './PayloadChannel'; import { Router, RouterOptions } from './Router'; +import { WebRtcServer, WebRtcServerOptions } from './WebRtcServer'; export type WorkerLogLevel = 'debug' | 'warn' | 'error' | 'none'; @@ -175,6 +176,7 @@ export type WorkerEvents = export type WorkerObserverEvents = { close: []; + newwebrtcserver: [WebRtcServer]; newrouter: [Router]; } @@ -213,6 +215,9 @@ export class Worker extends EnhancedEventEmitter // Custom app data. readonly #appData: Record; + // WebRtcServers set. + readonly #webRtcServers: Set = new Set(); + // Routers set. readonly #routers: Set = new Set(); @@ -471,6 +476,15 @@ export class Worker extends EnhancedEventEmitter return this.#observer; } + /** + * @private + * Just for testing purposes. + */ + get webRtcServersForTesting(): Set + { + return this.#webRtcServers; + } + /** * @private * Just for testing purposes. @@ -517,6 +531,13 @@ export class Worker extends EnhancedEventEmitter } this.#routers.clear(); + // Close every WebRtcServer. + for (const webRtcServer of this.#webRtcServers) + { + webRtcServer.workerClosed(); + } + this.#webRtcServers.clear(); + // Emit observer event. this.#observer.safeEmit('close'); } @@ -558,6 +579,41 @@ export class Worker extends EnhancedEventEmitter await this.#channel.request('worker.updateSettings', undefined, reqData); } + /** + * Create a WebRtcServer. + */ + async createWebRtcServer( + { + listenInfos, + appData + }: WebRtcServerOptions): Promise + { + logger.debug('createWebRtcServer()'); + + if (appData && typeof appData !== 'object') + throw new TypeError('if given, appData must be an object'); + + const internal = { webRtcServerId: uuidv4() }; + const reqData = { listenInfos }; + + await this.#channel.request('worker.createWebRtcServer', internal, reqData); + + const webRtcServer = new WebRtcServer( + { + internal, + channel : this.#channel, + appData + }); + + this.#webRtcServers.add(webRtcServer); + webRtcServer.on('@close', () => this.#webRtcServers.delete(webRtcServer)); + + // Emit observer event. + this.#observer.safeEmit('newwebrtcserver', webRtcServer); + + return webRtcServer; + } + /** * Create a Router. */ @@ -621,6 +677,13 @@ export class Worker extends EnhancedEventEmitter } this.#routers.clear(); + // Close every WebRtcServer. + for (const webRtcServer of this.#webRtcServers) + { + webRtcServer.workerClosed(); + } + this.#webRtcServers.clear(); + this.safeEmit('died', error); // Emit observer event. diff --git a/node/src/types.ts b/node/src/types.ts index f6187b7675..b3fe195f77 100644 --- a/node/src/types.ts +++ b/node/src/types.ts @@ -1,4 +1,5 @@ export * from './Worker'; +export * from './WebRtcServer'; export * from './Router'; export * from './Transport'; export * from './WebRtcTransport'; diff --git a/node/tests/test-Router.js b/node/tests/test-Router.js index 0e6899416a..264580f773 100644 --- a/node/tests/test-Router.js +++ b/node/tests/test-Router.js @@ -63,7 +63,7 @@ test('worker.createRouter() succeeds', async () => await expect(worker.dump()) .resolves - .toEqual({ pid: worker.pid, routerIds: [ router.id ] }); + .toEqual({ pid: worker.pid, webRtcServerIds: [], routerIds: [ router.id ] }); await expect(router.dump()) .resolves diff --git a/node/tests/test-WebRtcServer.js b/node/tests/test-WebRtcServer.js new file mode 100644 index 0000000000..26c0f73b55 --- /dev/null +++ b/node/tests/test-WebRtcServer.js @@ -0,0 +1,485 @@ +const { toBeType } = require('jest-tobetype'); +const pickPort = require('pick-port'); +const mediasoup = require('../lib/'); +const { createWorker } = mediasoup; +const { InvalidStateError } = require('../lib/errors'); + +expect.extend({ toBeType }); + +let worker; + +beforeEach(() => worker && !worker.closed && worker.close()); +afterEach(() => worker && !worker.closed && worker.close()); + +test('worker.createWebRtcServer() succeeds', async () => +{ + worker = await createWorker(); + + const onObserverNewWebRtcServer = jest.fn(); + + worker.observer.once('newwebrtcserver', onObserverNewWebRtcServer); + + const port1 = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0 }); + + const webRtcServer = await worker.createWebRtcServer( + { + listenInfos : + [ + { + protocol : 'udp', + ip : '127.0.0.1', + port : port1 + }, + { + protocol : 'tcp', + ip : '127.0.0.1', + announcedIp : '1.2.3.4', + port : port2 + } + ], + appData : { foo: 123 } + }); + + expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1); + expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer); + expect(webRtcServer.id).toBeType('string'); + expect(webRtcServer.closed).toBe(false); + expect(webRtcServer.appData).toEqual({ foo: 123 }); + + await expect(worker.dump()) + .resolves + .toEqual({ pid: worker.pid, webRtcServerIds: [ webRtcServer.id ], routerIds: [] }); + + await expect(webRtcServer.dump()) + .resolves + .toMatchObject( + { + id : webRtcServer.id, + udpSockets : + [ + { ip: '127.0.0.1', port: port1 } + ], + tcpServers : + [ + { ip: '127.0.0.1', port: port2 } + ], + webRtcTransportIds : [], + localIceUsernameFragments : [], + tupleHashes : [] + }); + + // Private API. + expect(worker.webRtcServersForTesting.size).toBe(1); + + worker.close(); + + expect(webRtcServer.closed).toBe(true); + + // Private API. + expect(worker.webRtcServersForTesting.size).toBe(0); +}, 2000); + +test('worker.createWebRtcServer() with wrong arguments rejects with TypeError', async () => +{ + worker = await createWorker(); + + await expect(worker.createWebRtcServer({ })) + .rejects + .toThrow(TypeError); + + await expect(worker.createWebRtcServer({ listenInfos: 'NOT-AN-ARRAY' })) + .rejects + .toThrow(TypeError); + + await expect(worker.createWebRtcServer({ listenInfos: [ 'NOT-AN-OBJECT' ] })) + .rejects + .toThrow(TypeError); + + // Empty listenInfos so should fail. + await expect(worker.createWebRtcServer({ listenInfos: [] })) + .rejects + .toThrow(TypeError); + + worker.close(); +}, 2000); + +test('worker.createWebRtcServer() with unavailable listenInfos rejects with Error', async () => +{ + const worker1 = await createWorker(); + const worker2 = await createWorker(); + const port1 = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const port2 = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + + // Using an unavailable listen IP. + await expect(worker1.createWebRtcServer( + { + listenInfos : + [ + { + protocol : 'udp', + ip : '127.0.0.1', + port : port1 + }, + { + protocol : 'udp', + ip : '1.2.3.4', + port : port2 + } + ] + })) + .rejects + .toThrow(Error); + + // Using the same UDP port in two listenInfos. + await expect(worker1.createWebRtcServer( + { + listenInfos : + [ + { + protocol : 'udp', + ip : '127.0.0.1', + port : port1 + }, + { + protocol : 'udp', + ip : '0.0.0.0', + announcedIp : '1.2.3.4', + port : port1 + } + ] + })) + .rejects + .toThrow(Error); + + await worker1.createWebRtcServer( + { + listenInfos : + [ + { + protocol : 'udp', + ip : '127.0.0.1', + port : port1 + } + ] + }); + + // Using the same UDP port in a second Worker. + await expect(worker1.createWebRtcServer( + { + listenInfos : + [ + { + protocol : 'udp', + ip : '127.0.0.1', + port : port1 + } + ] + })) + .rejects + .toThrow(Error); + + worker1.close(); + worker2.close(); +}, 2000); + +test('worker.createWebRtcServer() rejects with InvalidStateError if Worker is closed', async () => +{ + worker = await createWorker(); + + worker.close(); + + const port = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + + await expect(worker.createWebRtcServer( + { + listenInfos : [ { protocol: 'udp', ip: '127.0.0.1', port } ] + })) + .rejects + .toThrow(InvalidStateError); +}, 2000); + +test('webRtcServer.close() succeeds', async () => +{ + worker = await createWorker(); + + const port = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const webRtcServer = await worker.createWebRtcServer( + { + listenInfos : [ { protocol: 'udp', ip: '127.0.0.1', port } ] + }); + const onObserverClose = jest.fn(); + + webRtcServer.observer.once('close', onObserverClose); + webRtcServer.close(); + + expect(onObserverClose).toHaveBeenCalledTimes(1); + expect(webRtcServer.closed).toBe(true); +}, 2000); + +test('WebRtcServer emits "workerclose" if Worker is closed', async () => +{ + worker = await createWorker(); + + const port = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const webRtcServer = await worker.createWebRtcServer( + { + listenInfos : [ { protocol: 'tcp', ip: '127.0.0.1', port } ] + }); + const onObserverClose = jest.fn(); + + webRtcServer.observer.once('close', onObserverClose); + + await new Promise((resolve) => + { + webRtcServer.on('workerclose', resolve); + worker.close(); + }); + + expect(onObserverClose).toHaveBeenCalledTimes(1); + expect(webRtcServer.closed).toBe(true); +}, 2000); + +test('router.createWebRtcTransport() with webRtcServer succeeds and transport is closed', async () => +{ + worker = await createWorker(); + + const port1 = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0 }); + const webRtcServer = await worker.createWebRtcServer( + { + listenInfos : + [ + { protocol: 'udp', ip: '127.0.0.1', port: port1 }, + { protocol: 'tcp', ip: '127.0.0.1', port: port2 } + ] + }); + + const onObserverWebRtcTransportHandled = jest.fn(); + const onObserverWebRtcTransportUnhandled = jest.fn(); + + webRtcServer.observer.once('webrtctransporthandled', onObserverWebRtcTransportHandled); + webRtcServer.observer.once('webrtctransportunhandled', onObserverWebRtcTransportUnhandled); + + const router = await worker.createRouter(); + + const onObserverNewTransport = jest.fn(); + + router.observer.once('newtransport', onObserverNewTransport); + + const transport = await router.createWebRtcTransport( + { + webRtcServer, + enableTcp : false, + appData : { foo: 'bar' } + }); + + await expect(router.dump()) + .resolves + .toMatchObject({ transportIds: [ transport.id ] }); + + expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1); + expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport); + expect(onObserverNewTransport).toHaveBeenCalledTimes(1); + expect(onObserverNewTransport).toHaveBeenCalledWith(transport); + expect(transport.id).toBeType('string'); + expect(transport.closed).toBe(false); + expect(transport.appData).toEqual({ foo: 'bar' }); + + const iceCandidates = transport.iceCandidates; + + expect(iceCandidates.length).toBe(1); + expect(iceCandidates[0].ip).toBe('127.0.0.1'); + expect(iceCandidates[0].port).toBe(port1); + expect(iceCandidates[0].protocol).toBe('udp'); + expect(iceCandidates[0].type).toBe('host'); + expect(iceCandidates[0].tcpType).toBeUndefined(); + + expect(transport.iceState).toBe('new'); + expect(transport.iceSelectedTuple).toBeUndefined(); + + expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1); + expect(router.transportsForTesting.size).toBe(1); + + await expect(webRtcServer.dump()) + .resolves + .toMatchObject( + { + id : webRtcServer.id, + udpSockets : + [ + { ip: '127.0.0.1', port: port1 } + ], + tcpServers : + [ + { ip: '127.0.0.1', port: port2 } + ], + webRtcTransportIds : [ transport.id ], + localIceUsernameFragments : + [ + { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id } + ], + tupleHashes : [] + }); + + transport.close(); + + expect(transport.closed).toBe(true); + expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1); + expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport); + expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0); + expect(router.transportsForTesting.size).toBe(0); + + await expect(webRtcServer.dump()) + .resolves + .toMatchObject( + { + id : webRtcServer.id, + udpSockets : + [ + { ip: '127.0.0.1', port: port1 } + ], + tcpServers : + [ + { ip: '127.0.0.1', port: port2 } + ], + webRtcTransportIds : [], + localIceUsernameFragments : [], + tupleHashes : [] + }); +}, 2000); + +test('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer is closed', async () => +{ + worker = await createWorker(); + + const port1 = await pickPort({ ip: '127.0.0.1', reserveTimeout: 0 }); + const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0 }); + const webRtcServer = await worker.createWebRtcServer( + { + listenInfos : + [ + { protocol: 'udp', ip: '127.0.0.1', port: port1 }, + { protocol: 'tcp', ip: '127.0.0.1', port: port2 } + ] + }); + + const onObserverWebRtcTransportHandled = jest.fn(); + const onObserverWebRtcTransportUnhandled = jest.fn(); + + webRtcServer.observer.once('webrtctransporthandled', onObserverWebRtcTransportHandled); + webRtcServer.observer.once('webrtctransportunhandled', onObserverWebRtcTransportUnhandled); + + const router = await worker.createRouter(); + const transport = await router.createWebRtcTransport( + { + webRtcServer, + enableUdp : false, + enableTcp : true, + appData : { foo: 'bar' } + }); + + expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1); + expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport); + + await expect(router.dump()) + .resolves + .toMatchObject({ transportIds: [ transport.id ] }); + + expect(transport.id).toBeType('string'); + expect(transport.closed).toBe(false); + expect(transport.appData).toEqual({ foo: 'bar' }); + + const iceCandidates = transport.iceCandidates; + + expect(iceCandidates.length).toBe(1); + expect(iceCandidates[0].ip).toBe('127.0.0.1'); + expect(iceCandidates[0].port).toBe(port2); + expect(iceCandidates[0].protocol).toBe('tcp'); + expect(iceCandidates[0].type).toBe('host'); + expect(iceCandidates[0].tcpType).toBe('passive'); + + expect(transport.iceState).toBe('new'); + expect(transport.iceSelectedTuple).toBeUndefined(); + + expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1); + expect(router.transportsForTesting.size).toBe(1); + + await expect(webRtcServer.dump()) + .resolves + .toMatchObject( + { + id : webRtcServer.id, + udpSockets : + [ + { ip: '127.0.0.1', port: port1 } + ], + tcpServers : + [ + { ip: '127.0.0.1', port: port2 } + ], + webRtcTransportIds : [ transport.id ], + localIceUsernameFragments : + [ + { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id } + ], + tupleHashes : [] + }); + + // Let's restart ICE in the transport so it should add a new entry in + // localIceUsernameFragments in the WebRtcServer. + await transport.restartIce(); + + await expect(webRtcServer.dump()) + .resolves + .toMatchObject( + { + id : webRtcServer.id, + udpSockets : + [ + { ip: '127.0.0.1', port: port1 } + ], + tcpServers : + [ + { ip: '127.0.0.1', port: port2 } + ], + webRtcTransportIds : [ transport.id ], + localIceUsernameFragments : + [ + { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id }, + { /* localIceUsernameFragment: yyy, */ webRtcTransportId: transport.id } + ], + tupleHashes : [] + }); + + const onObserverClose = jest.fn(); + + webRtcServer.observer.once('close', onObserverClose); + + const onListenServerClose = jest.fn(); + + transport.once('listenserverclose', onListenServerClose); + + webRtcServer.close(); + + expect(webRtcServer.closed).toBe(true); + expect(onObserverClose).toHaveBeenCalledTimes(1); + expect(onListenServerClose).toHaveBeenCalledTimes(1); + expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1); + expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport); + expect(transport.closed).toBe(true); + expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0); + expect(router.transportsForTesting.size).toBe(0); + + await expect(worker.dump()) + .resolves + .toEqual({ pid: worker.pid, webRtcServerIds: [], routerIds: [ router.id ] }); + + await expect(router.dump()) + .resolves + .toMatchObject( + { + id : router.id, + transportIds : [] + }); +}, 2000); diff --git a/node/tests/test-Worker.js b/node/tests/test-Worker.js index fa5163ce87..525054741c 100644 --- a/node/tests/test-Worker.js +++ b/node/tests/test-Worker.js @@ -124,7 +124,7 @@ test('worker.dump() succeeds', async () => await expect(worker.dump()) .resolves - .toEqual({ pid: worker.pid, routerIds: [] }); + .toEqual({ pid: worker.pid, webRtcServerIds: [], routerIds: [] }); worker.close(); }, 2000); diff --git a/worker/include/Channel/ChannelRequest.hpp b/worker/include/Channel/ChannelRequest.hpp index cca5dda3fc..dfb8231e2f 100644 --- a/worker/include/Channel/ChannelRequest.hpp +++ b/worker/include/Channel/ChannelRequest.hpp @@ -23,10 +23,14 @@ namespace Channel WORKER_DUMP, WORKER_GET_RESOURCE_USAGE, WORKER_UPDATE_SETTINGS, + WORKER_CREATE_WEBRTC_SERVER, WORKER_CREATE_ROUTER, + WEBRTC_SERVER_CLOSE, + WEBRTC_SERVER_DUMP, ROUTER_CLOSE, ROUTER_DUMP, ROUTER_CREATE_WEBRTC_TRANSPORT, + ROUTER_CREATE_WEBRTC_TRANSPORT_WITH_SERVER, ROUTER_CREATE_PLAIN_TRANSPORT, ROUTER_CREATE_PIPE_TRANSPORT, ROUTER_CREATE_DIRECT_TRANSPORT, diff --git a/worker/include/RTC/IceServer.hpp b/worker/include/RTC/IceServer.hpp index 1df80065ec..51fa85919d 100644 --- a/worker/include/RTC/IceServer.hpp +++ b/worker/include/RTC/IceServer.hpp @@ -33,6 +33,13 @@ namespace RTC */ virtual void OnIceServerSendStunPacket( const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) = 0; + virtual void OnIceServerLocalUsernameFragmentAdded( + const RTC::IceServer* iceServer, const std::string& usernameFragment) = 0; + virtual void OnIceServerLocalUsernameFragmentRemoved( + const RTC::IceServer* iceServer, const std::string& usernameFragment) = 0; + virtual void OnIceServerTupleAdded(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; + virtual void OnIceServerTupleRemoved( + const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerSelectedTuple( const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerConnected(const RTC::IceServer* iceServer) = 0; @@ -42,6 +49,7 @@ namespace RTC public: IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password); + ~IceServer(); public: void ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple); @@ -63,6 +71,11 @@ namespace RTC } void RestartIce(const std::string& usernameFragment, const std::string& password) { + if (!this->oldUsernameFragment.empty()) + { + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); + } + this->oldUsernameFragment = this->usernameFragment; this->usernameFragment = usernameFragment; @@ -70,6 +83,13 @@ namespace RTC this->password = password; this->remoteNomination = 0u; + + // Notify the listener. + this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment); + + // NOTE: Do not call listener->OnIceServerLocalUsernameFragmentRemoved() + // yet with old usernameFragment. Wait until we receive a STUN packet + // with the new one. } bool IsValidTuple(const RTC::TransportTuple* tuple) const; void RemoveTuple(RTC::TransportTuple* tuple); diff --git a/worker/include/RTC/Router.hpp b/worker/include/RTC/Router.hpp index 02b331b42d..2e333522d6 100644 --- a/worker/include/RTC/Router.hpp +++ b/worker/include/RTC/Router.hpp @@ -13,6 +13,7 @@ #include "RTC/RtpPacket.hpp" #include "RTC/RtpStream.hpp" #include "RTC/Transport.hpp" +#include "RTC/WebRtcServer.hpp" #include #include #include @@ -25,7 +26,18 @@ namespace RTC class Router : public RTC::Transport::Listener { public: - explicit Router(const std::string& id); + class Listener + { + public: + virtual ~Listener() = default; + + public: + virtual RTC::WebRtcServer* OnRouterNeedWebRtcServer( + RTC::Router* router, std::string& webRtcServerId) = 0; + }; + + public: + explicit Router(const std::string& id, Listener* listener); virtual ~Router(); public: @@ -86,10 +98,12 @@ namespace RTC void OnTransportDataConsumerClosed(RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override; void OnTransportDataConsumerDataProducerClosed( RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override; + void OnTransportListenServerClosed(RTC::Transport* transport) override; public: // Passed by argument. const std::string id; + Listener* listener{ nullptr }; private: // Allocated by this. diff --git a/worker/include/RTC/Transport.hpp b/worker/include/RTC/Transport.hpp index d1195504a1..32d4876c8d 100644 --- a/worker/include/RTC/Transport.hpp +++ b/worker/include/RTC/Transport.hpp @@ -103,7 +103,8 @@ namespace RTC virtual void OnTransportDataConsumerClosed( RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0; virtual void OnTransportDataConsumerDataProducerClosed( - RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0; + RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0; + virtual void OnTransportListenServerClosed(RTC::Transport* transport) = 0; }; private: @@ -119,6 +120,7 @@ namespace RTC public: void CloseProducersAndConsumers(); + void ListenServerClosed(); // Subclasses must also invoke the parent Close(). virtual void FillJson(json& jsonObject) const; virtual void FillJsonStats(json& jsonArray); diff --git a/worker/include/RTC/TransportTuple.hpp b/worker/include/RTC/TransportTuple.hpp index d1c5d28b5d..535d221a68 100644 --- a/worker/include/RTC/TransportTuple.hpp +++ b/worker/include/RTC/TransportTuple.hpp @@ -28,15 +28,17 @@ namespace RTC TransportTuple(RTC::UdpSocket* udpSocket, const struct sockaddr* udpRemoteAddr) : udpSocket(udpSocket), udpRemoteAddr((struct sockaddr*)udpRemoteAddr), protocol(Protocol::UDP) { + SetHash(); } explicit TransportTuple(RTC::TcpConnection* tcpConnection) : tcpConnection(tcpConnection), protocol(Protocol::TCP) { + SetHash(); } explicit TransportTuple(const TransportTuple* tuple) - : udpSocket(tuple->udpSocket), udpRemoteAddr(tuple->udpRemoteAddr), + : hash(tuple->hash), udpSocket(tuple->udpSocket), udpRemoteAddr(tuple->udpRemoteAddr), tcpConnection(tuple->tcpConnection), localAnnouncedIp(tuple->localAnnouncedIp), protocol(tuple->protocol) { @@ -45,6 +47,22 @@ namespace RTC } public: + void Close() + { + if (this->protocol == Protocol::UDP) + this->udpSocket->Close(); + else + this->tcpConnection->Close(); + } + + bool IsClosed() + { + if (this->protocol == Protocol::UDP) + return this->udpSocket->IsClosed(); + else + return this->tcpConnection->IsClosed(); + } + void FillJson(json& jsonObject) const; void Dump() const; @@ -59,20 +77,7 @@ namespace RTC bool Compare(const TransportTuple* tuple) const { - if (this->protocol == Protocol::UDP && tuple->GetProtocol() == Protocol::UDP) - { - return ( - this->udpSocket == tuple->udpSocket && - Utils::IP::CompareAddresses(this->udpRemoteAddr, tuple->GetRemoteAddress())); - } - else if (this->protocol == Protocol::TCP && tuple->GetProtocol() == Protocol::TCP) - { - return (this->tcpConnection == tuple->tcpConnection); - } - else - { - return false; - } + return this->hash == tuple->hash; } void SetLocalAnnouncedIp(std::string& localAnnouncedIp) @@ -125,6 +130,80 @@ namespace RTC return this->tcpConnection->GetSentBytes(); } + private: + /* + * Hash for IPv4 + * + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PORT | IP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | IP | |F|P| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Hash for IPv6 + * + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PORT | IP[0] ^ IP[1] ^ IP[2] ^ IP[3]| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |IP[0] ^ IP[1] ^ IP[2] ^ IP[3] | IP[0] >> 16 |F|P| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + void SetHash() + { + const struct sockaddr* remoteSockAddr = GetRemoteAddress(); + + switch (remoteSockAddr->sa_family) + { + case AF_INET: + { + auto* remoteSockAddrIn = reinterpret_cast(remoteSockAddr); + + const uint64_t address = ntohl(remoteSockAddrIn->sin_addr.s_addr); + const uint64_t port = (ntohs(remoteSockAddrIn->sin_port)); + + this->hash = port << 48; + this->hash |= address << 16; + this->hash |= 0x0000; // AF_INET. + + break; + } + + case AF_INET6: + { + auto* remoteSockAddrIn6 = reinterpret_cast(remoteSockAddr); + auto* a = reinterpret_cast(std::addressof(remoteSockAddrIn6->sin6_addr)); + + const auto address1 = a[0] ^ a[1] ^ a[2] ^ a[3]; + const auto address2 = a[0]; + const uint64_t port = ntohs(remoteSockAddrIn6->sin6_port); + + this->hash = port << 48; + this->hash |= static_cast(address1) << 16; + this->hash |= address2 >> 16 & 0xFFFC; + this->hash |= 0x0002; // AF_INET6. + + break; + } + } + + // Override least significant bit with protocol information: + // - If UDP, start with 0. + // - If TCP, start with 1. + if (this->protocol == Protocol::UDP) + { + this->hash |= 0x0000; + } + else + { + this->hash |= 0x0001; + } + } + + public: + uint64_t hash{ 0u }; + private: // Passed by argument. RTC::UdpSocket* udpSocket{ nullptr }; diff --git a/worker/include/RTC/WebRtcServer.hpp b/worker/include/RTC/WebRtcServer.hpp new file mode 100644 index 0000000000..cb53233c4f --- /dev/null +++ b/worker/include/RTC/WebRtcServer.hpp @@ -0,0 +1,107 @@ +#ifndef MS_RTC_WEBRTC_SERVER_HPP +#define MS_RTC_WEBRTC_SERVER_HPP + +#include "Channel/ChannelRequest.hpp" +#include "RTC/IceCandidate.hpp" +#include "RTC/StunPacket.hpp" +#include "RTC/TcpConnection.hpp" +#include "RTC/TcpServer.hpp" +#include "RTC/TransportTuple.hpp" +#include "RTC/UdpSocket.hpp" +#include "RTC/WebRtcTransport.hpp" +#include +#include +#include +#include +#include + +namespace RTC +{ + class WebRtcServer : public RTC::UdpSocket::Listener, + public RTC::TcpServer::Listener, + public RTC::TcpConnection::Listener, + public RTC::WebRtcTransport::WebRtcTransportListener + { + private: + struct ListenInfo + { + RTC::TransportTuple::Protocol protocol; + std::string ip; + std::string announcedIp; + uint16_t port; + }; + + private: + struct UdpSocketOrTcpServer + { + // Expose a constructor to use vector.emplace_back(). + UdpSocketOrTcpServer(RTC::UdpSocket* udpSocket, RTC::TcpServer* tcpServer, std::string& announcedIp) + : udpSocket(udpSocket), tcpServer(tcpServer), announcedIp(announcedIp) + { + } + + RTC::UdpSocket* udpSocket; + RTC::TcpServer* tcpServer; + std::string announcedIp; + }; + + public: + WebRtcServer(const std::string& id, json& data); + ~WebRtcServer(); + + public: + void FillJson(json& jsonObject) const; + void HandleRequest(Channel::ChannelRequest* request); + std::vector GetIceCandidates( + bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp); + + private: + std::string GetLocalIceUsernameFragmentFromReceivedStunPacket(RTC::StunPacket* packet) const; + void OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); + void OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); + void OnNonStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); + + /* Pure virtual methods inherited from RTC::WebRtcTransport::WebRtcTransportListener. */ + public: + void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) override; + void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) override; + void OnWebRtcTransportLocalIceUsernameFragmentAdded( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override; + void OnWebRtcTransportLocalIceUsernameFragmentRemoved( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override; + void OnWebRtcTransportTransportTupleAdded( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override; + void OnWebRtcTransportTransportTupleRemoved( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override; + + /* Pure virtual methods inherited from RTC::UdpSocket::Listener. */ + public: + void OnUdpSocketPacketReceived( + RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr) override; + + /* Pure virtual methods inherited from RTC::TcpServer::Listener. */ + public: + void OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) override; + + /* Pure virtual methods inherited from RTC::TcpConnection::Listener. */ + public: + void OnTcpConnectionPacketReceived( + RTC::TcpConnection* connection, const uint8_t* data, size_t len) override; + + public: + // Passed by argument. + const std::string id; + + private: + // Vector of UdpSockets and TcpServers in the user given order. + std::vector udpSocketOrTcpServers; + // Set of WebRtcTransports. + absl::flat_hash_set webRtcTransports; + // Map of WebRtcTransports indexed by local ICE usernameFragment. + absl::flat_hash_map mapLocalIceUsernameFragmentWebRtcTransport; + // Map of WebRtcTransports indexed by TransportTuple.hash. + absl::flat_hash_map mapTupleWebRtcTransport; + }; +} // namespace RTC + +#endif diff --git a/worker/include/RTC/WebRtcTransport.hpp b/worker/include/RTC/WebRtcTransport.hpp index bcaed162af..4ed48a4d95 100644 --- a/worker/include/RTC/WebRtcTransport.hpp +++ b/worker/include/RTC/WebRtcTransport.hpp @@ -29,8 +29,33 @@ namespace RTC std::string announcedIp; }; + public: + class WebRtcTransportListener + { + public: + virtual ~WebRtcTransportListener() = default; + + public: + virtual void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) = 0; + virtual void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) = 0; + virtual void OnWebRtcTransportLocalIceUsernameFragmentAdded( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0; + virtual void OnWebRtcTransportLocalIceUsernameFragmentRemoved( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0; + virtual void OnWebRtcTransportTransportTupleAdded( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0; + virtual void OnWebRtcTransportTransportTupleRemoved( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0; + }; + public: WebRtcTransport(const std::string& id, RTC::Transport::Listener* listener, json& data); + WebRtcTransport( + const std::string& id, + RTC::Transport::Listener* listener, + WebRtcTransportListener* webRtcTransportListener, + std::vector& iceCandidates, + json& data); ~WebRtcTransport() override; public: @@ -38,6 +63,10 @@ namespace RTC void FillJsonStats(json& jsonArray) override; void HandleRequest(Channel::ChannelRequest* request) override; void HandleNotification(PayloadChannel::Notification* notification) override; + void ProcessStunPacketFromWebRtcServer(RTC::TransportTuple* tuple, RTC::StunPacket* packet); + void ProcessNonStunPacketFromWebRtcServer( + RTC::TransportTuple* tuple, const uint8_t* data, size_t len); + void RemoveTuple(RTC::TransportTuple* tuple); private: bool IsConnected() const override; @@ -83,6 +112,12 @@ namespace RTC const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) override; + void OnIceServerLocalUsernameFragmentAdded( + const RTC::IceServer* iceServer, const std::string& usernameFragment) override; + void OnIceServerLocalUsernameFragmentRemoved( + const RTC::IceServer* iceServer, const std::string& usernameFragment) override; + void OnIceServerTupleAdded(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) override; + void OnIceServerTupleRemoved(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) override; void OnIceServerSelectedTuple(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) override; void OnIceServerConnected(const RTC::IceServer* iceServer) override; void OnIceServerCompleted(const RTC::IceServer* iceServer) override; @@ -107,6 +142,8 @@ namespace RTC const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override; private: + // Passed by argument. + WebRtcTransportListener* webRtcTransportListener{ nullptr }; // Allocated by this. RTC::IceServer* iceServer{ nullptr }; // Map of UdpSocket/TcpServer and local announced IP (if any). diff --git a/worker/include/Worker.hpp b/worker/include/Worker.hpp index bbed438e4e..033f42091e 100644 --- a/worker/include/Worker.hpp +++ b/worker/include/Worker.hpp @@ -8,6 +8,7 @@ #include "PayloadChannel/PayloadChannelRequest.hpp" #include "PayloadChannel/PayloadChannelSocket.hpp" #include "RTC/Router.hpp" +#include "RTC/WebRtcServer.hpp" #include "handles/SignalsHandler.hpp" #include #include @@ -17,7 +18,8 @@ using json = nlohmann::json; class Worker : public Channel::ChannelSocket::Listener, public PayloadChannel::PayloadChannelSocket::Listener, - public SignalsHandler::Listener + public SignalsHandler::Listener, + public RTC::Router::Listener { public: explicit Worker(Channel::ChannelSocket* channel, PayloadChannel::PayloadChannelSocket* payloadChannel); @@ -27,6 +29,8 @@ class Worker : public Channel::ChannelSocket::Listener, void Close(); void FillJson(json& jsonObject) const; void FillJsonResourceUsage(json& jsonObject) const; + void SetNewWebRtcServerIdFromInternal(json& internal, std::string& webRtcServerId) const; + RTC::WebRtcServer* GetWebRtcServerFromInternal(json& internal) const; void SetNewRouterIdFromInternal(json& internal, std::string& routerId) const; RTC::Router* GetRouterFromInternal(json& internal) const; @@ -49,12 +53,17 @@ class Worker : public Channel::ChannelSocket::Listener, public: void OnSignal(SignalsHandler* signalsHandler, int signum) override; + /* Pure virtual methods inherited from RTC::Router::Listener. */ +public: + RTC::WebRtcServer* OnRouterNeedWebRtcServer(RTC::Router* router, std::string& webRtcServerId) override; + private: // Passed by argument. Channel::ChannelSocket* channel{ nullptr }; PayloadChannel::PayloadChannelSocket* payloadChannel{ nullptr }; // Allocated by this. SignalsHandler* signalsHandler{ nullptr }; + absl::flat_hash_map mapWebRtcServers; absl::flat_hash_map mapRouters; // Others. bool closed{ false }; diff --git a/worker/include/handles/TcpConnectionHandler.hpp b/worker/include/handles/TcpConnectionHandler.hpp index db5874d91d..0b536225ba 100644 --- a/worker/include/handles/TcpConnectionHandler.hpp +++ b/worker/include/handles/TcpConnectionHandler.hpp @@ -51,16 +51,16 @@ class TcpConnectionHandler public: void Close(); + bool IsClosed() const + { + return this->closed; + } virtual void Dump() const; void Setup( Listener* listener, struct sockaddr_storage* localAddr, const std::string& localIp, uint16_t localPort); - bool IsClosed() const - { - return this->closed; - } uv_tcp_t* GetUvHandle() const { return this->uvHandle; diff --git a/worker/include/handles/TcpServerHandler.hpp b/worker/include/handles/TcpServerHandler.hpp index 7c5ddc400c..ad7b52e8aa 100644 --- a/worker/include/handles/TcpServerHandler.hpp +++ b/worker/include/handles/TcpServerHandler.hpp @@ -13,7 +13,7 @@ class TcpServerHandler : public TcpConnectionHandler::Listener /** * uvHandle must be an already initialized and binded uv_tcp_t pointer. */ - TcpServerHandler(uv_tcp_t* uvHandle, int backlog); + TcpServerHandler(uv_tcp_t* uvHandle); virtual ~TcpServerHandler() override; public: diff --git a/worker/include/handles/UdpSocketHandler.hpp b/worker/include/handles/UdpSocketHandler.hpp index beadfd76e0..188f23b4fa 100644 --- a/worker/include/handles/UdpSocketHandler.hpp +++ b/worker/include/handles/UdpSocketHandler.hpp @@ -44,6 +44,10 @@ class UdpSocketHandler public: void Close(); + bool IsClosed() const + { + return this->closed; + } virtual void Dump() const; void Send( const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandler::onSendCallback* cb); diff --git a/worker/meson.build b/worker/meson.build index 3961e7e297..497667f31e 100644 --- a/worker/meson.build +++ b/worker/meson.build @@ -97,6 +97,7 @@ common_sources = [ 'src/RTC/TransportTuple.cpp', 'src/RTC/TrendCalculator.cpp', 'src/RTC/UdpSocket.cpp', + 'src/RTC/WebRtcServer.cpp', 'src/RTC/WebRtcTransport.cpp', 'src/RTC/Codecs/H264.cpp', 'src/RTC/Codecs/H264_SVC.cpp', diff --git a/worker/src/Channel/ChannelRequest.cpp b/worker/src/Channel/ChannelRequest.cpp index 1171900f91..520537e69d 100644 --- a/worker/src/Channel/ChannelRequest.cpp +++ b/worker/src/Channel/ChannelRequest.cpp @@ -17,10 +17,14 @@ namespace Channel { "worker.dump", ChannelRequest::MethodId::WORKER_DUMP }, { "worker.getResourceUsage", ChannelRequest::MethodId::WORKER_GET_RESOURCE_USAGE }, { "worker.updateSettings", ChannelRequest::MethodId::WORKER_UPDATE_SETTINGS }, + { "worker.createWebRtcServer", ChannelRequest::MethodId::WORKER_CREATE_WEBRTC_SERVER }, { "worker.createRouter", ChannelRequest::MethodId::WORKER_CREATE_ROUTER }, + { "webRtcServer.close", ChannelRequest::MethodId::WEBRTC_SERVER_CLOSE }, + { "webRtcServer.dump", ChannelRequest::MethodId::WEBRTC_SERVER_DUMP }, { "router.close", ChannelRequest::MethodId::ROUTER_CLOSE }, { "router.dump", ChannelRequest::MethodId::ROUTER_DUMP }, { "router.createWebRtcTransport", ChannelRequest::MethodId::ROUTER_CREATE_WEBRTC_TRANSPORT }, + { "router.createWebRtcTransportWithServer", ChannelRequest::MethodId::ROUTER_CREATE_WEBRTC_TRANSPORT_WITH_SERVER }, { "router.createPlainTransport", ChannelRequest::MethodId::ROUTER_CREATE_PLAIN_TRANSPORT }, { "router.createPipeTransport", ChannelRequest::MethodId::ROUTER_CREATE_PIPE_TRANSPORT }, { "router.createDirectTransport", ChannelRequest::MethodId::ROUTER_CREATE_DIRECT_TRANSPORT }, diff --git a/worker/src/RTC/IceServer.cpp b/worker/src/RTC/IceServer.cpp index b6df5f2bba..27db36344b 100644 --- a/worker/src/RTC/IceServer.cpp +++ b/worker/src/RTC/IceServer.cpp @@ -12,6 +12,7 @@ namespace RTC static constexpr size_t StunSerializeBufferSize{ 65536 }; thread_local static uint8_t StunSerializeBuffer[StunSerializeBufferSize]; + static constexpr size_t MaxTuples{ 8 }; /* Instance methods. */ @@ -19,6 +20,32 @@ namespace RTC : listener(listener), usernameFragment(usernameFragment), password(password) { MS_TRACE(); + + // Notify the listener. + this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment); + } + + IceServer::~IceServer() + { + MS_TRACE(); + + // Here we must notify the listener about the removal of current + // usernameFragments (and also the old one if any) and all tuples. + + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, usernameFragment); + + if (!this->oldUsernameFragment.empty()) + { + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); + } + + for (const auto& it : this->tuples) + { + auto* storedTuple = const_cast(std::addressof(it)); + + // Notify the listener. + this->listener->OnIceServerTupleRemoved(this, storedTuple); + } } void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple) @@ -102,10 +129,13 @@ namespace RTC { case RTC::StunPacket::Authentication::OK: { - if (!this->oldPassword.empty()) + if (!this->oldUsernameFragment.empty() && !this->oldPassword.empty()) { MS_DEBUG_TAG(ice, "new ICE credentials applied"); + // Notify the listener. + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); + this->oldUsernameFragment.clear(); this->oldPassword.clear(); } @@ -264,29 +294,31 @@ namespace RTC if (!removedTuple) return; - // Remove from the list of tuples. + // Remove it from the list of tuples. this->tuples.erase(it); - // If this is not the selected tuple, stop here. - if (removedTuple != this->selectedTuple) - return; - - // Otherwise this was the selected tuple. - this->selectedTuple = nullptr; - - // Mark the first tuple as selected tuple (if any). - if (this->tuples.begin() != this->tuples.end()) + // If this is the selected tuple, do things. + if (removedTuple == this->selectedTuple) { - SetSelectedTuple(std::addressof(*this->tuples.begin())); - } - // Or just emit 'disconnected'. - else - { - // Update state. - this->state = IceState::DISCONNECTED; - // Notify the listener. - this->listener->OnIceServerDisconnected(this); + this->selectedTuple = nullptr; + + // Mark the first tuple as selected tuple (if any). + if (this->tuples.begin() != this->tuples.end()) + { + SetSelectedTuple(std::addressof(*this->tuples.begin())); + } + // Or just emit 'disconnected'. + else + { + // Update state. + this->state = IceState::DISCONNECTED; + // Notify the listener. + this->listener->OnIceServerDisconnected(this); + } } + + // Notify the listener. + this->listener->OnIceServerTupleRemoved(this, removedTuple); } void IceServer::ForceSelectedTuple(const RTC::TransportTuple* tuple) @@ -531,6 +563,43 @@ namespace RTC if (storedTuple->GetProtocol() == TransportTuple::Protocol::UDP) storedTuple->StoreUdpRemoteAddress(); + // Notify the listener. + this->listener->OnIceServerTupleAdded(this, storedTuple); + + // Don't allow more than MaxTuples. + if (this->tuples.size() > MaxTuples) + { + MS_WARN_TAG(ice, "too too many tuples, removing the oldest non selected one"); + + // Find the oldest tuple which is neither the added one nor the selected + // one (if any), and remove it. + RTC::TransportTuple* removedTuple{ nullptr }; + auto it = this->tuples.rbegin(); + + for (; it != this->tuples.rend(); ++it) + { + RTC::TransportTuple* otherStoredTuple = std::addressof(*it); + + if (otherStoredTuple != storedTuple && otherStoredTuple != this->selectedTuple) + { + removedTuple = otherStoredTuple; + + break; + } + } + + // This should not happen by design. + MS_ASSERT(removedTuple, "couldn't find any tuple to be removed"); + + // Remove it from the list of tuples. + // NOTE: This trick is needed since it is a reverse_iterator and + // erase() requires a iterator, const_iterator or bidirectional_iterator. + this->tuples.erase(std::next(it).base()); + + // Notify the listener. + this->listener->OnIceServerTupleRemoved(this, removedTuple); + } + // Return the address of the inserted tuple. return storedTuple; } diff --git a/worker/src/RTC/Router.cpp b/worker/src/RTC/Router.cpp index 17eac9e9e0..fe06136ee4 100644 --- a/worker/src/RTC/Router.cpp +++ b/worker/src/RTC/Router.cpp @@ -16,7 +16,7 @@ namespace RTC { /* Instance methods. */ - Router::Router(const std::string& id) : id(id) + Router::Router(const std::string& id, Listener* listener) : id(id), listener(listener) { MS_TRACE(); } @@ -202,6 +202,93 @@ namespace RTC break; } + case Channel::ChannelRequest::MethodId::ROUTER_CREATE_WEBRTC_TRANSPORT_WITH_SERVER: + { + std::string transportId; + + // This may throw. + SetNewTransportIdFromInternal(request->internal, transportId); + + auto jsonWebRtcServerIdIt = request->internal.find("webRtcServerId"); + + if (jsonWebRtcServerIdIt == request->internal.end() || !jsonWebRtcServerIdIt->is_string()) + { + MS_THROW_ERROR("missing internal.webRtcServerId"); + } + + std::string webRtcServerId = jsonWebRtcServerIdIt->get(); + + auto* webRtcServer = this->listener->OnRouterNeedWebRtcServer(this, webRtcServerId); + + if (!webRtcServer) + MS_THROW_ERROR("wrong internal.webRtcServerId (no associated WebRtcServer found)"); + + bool enableUdp{ true }; + auto jsonEnableUdpIt = request->data.find("enableUdp"); + + if (jsonEnableUdpIt != request->data.end()) + { + if (!jsonEnableUdpIt->is_boolean()) + MS_THROW_TYPE_ERROR("wrong enableUdp (not a boolean)"); + + enableUdp = jsonEnableUdpIt->get(); + } + + bool enableTcp{ false }; + auto jsonEnableTcpIt = request->data.find("enableTcp"); + + if (jsonEnableTcpIt != request->data.end()) + { + if (!jsonEnableTcpIt->is_boolean()) + MS_THROW_TYPE_ERROR("wrong enableTcp (not a boolean)"); + + enableTcp = jsonEnableTcpIt->get(); + } + + bool preferUdp{ false }; + auto jsonPreferUdpIt = request->data.find("preferUdp"); + + if (jsonPreferUdpIt != request->data.end()) + { + if (!jsonPreferUdpIt->is_boolean()) + MS_THROW_TYPE_ERROR("wrong preferUdp (not a boolean)"); + + preferUdp = jsonPreferUdpIt->get(); + } + + bool preferTcp{ false }; + auto jsonPreferTcpIt = request->data.find("preferTcp"); + + if (jsonPreferTcpIt != request->data.end()) + { + if (!jsonPreferTcpIt->is_boolean()) + MS_THROW_TYPE_ERROR("wrong preferTcp (not a boolean)"); + + preferTcp = jsonPreferTcpIt->get(); + } + + auto iceCandidates = + webRtcServer->GetIceCandidates(enableUdp, enableTcp, preferUdp, preferTcp); + + // This may throw. + auto* webRtcTransport = + new RTC::WebRtcTransport(transportId, this, webRtcServer, iceCandidates, request->data); + + // Insert into the map. + this->mapTransports[transportId] = webRtcTransport; + + MS_DEBUG_DEV( + "WebRtcTransport with WebRtcServer created [transportId:%s]", transportId.c_str()); + + json data = json::object(); + + webRtcTransport->FillJson(data); + + request->Accept(data); + + break; + } + case Channel::ChannelRequest::MethodId::ROUTER_CREATE_PLAIN_TRANSPORT: { std::string transportId; @@ -318,7 +405,7 @@ namespace RTC // notify us about their closures. transport->CloseProducersAndConsumers(); - // Remove it from the map and delete it. + // Remove it from the map. this->mapTransports.erase(transport->id); MS_DEBUG_DEV("Transport closed [transportId:%s]", transport->id.c_str()); @@ -993,4 +1080,23 @@ namespace RTC // Remove the DataConsumer from the map. this->mapDataConsumerDataProducer.erase(mapDataConsumerDataProducerIt); } + + inline void Router::OnTransportListenServerClosed(RTC::Transport* transport) + { + MS_TRACE(); + + MS_ASSERT( + this->mapTransports.find(transport->id) != this->mapTransports.end(), + "Transport not present in mapTransports"); + + // Tell the Transport to close all its Producers and Consumers so it will + // notify us about their closures. + transport->CloseProducersAndConsumers(); + + // Remove it from the map. + this->mapTransports.erase(transport->id); + + // Delete it. + delete transport; + } } // namespace RTC diff --git a/worker/src/RTC/TcpServer.cpp b/worker/src/RTC/TcpServer.cpp index 098a559c1d..66a18c179c 100644 --- a/worker/src/RTC/TcpServer.cpp +++ b/worker/src/RTC/TcpServer.cpp @@ -8,15 +8,11 @@ namespace RTC { - /* Static. */ - - static constexpr size_t MaxTcpConnectionsPerServer{ 10 }; - /* Instance methods. */ TcpServer::TcpServer(Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip) : // This may throw. - ::TcpServerHandler::TcpServerHandler(RTC::PortManager::BindTcp(ip), 256), listener(listener), + ::TcpServerHandler::TcpServerHandler(RTC::PortManager::BindTcp(ip)), listener(listener), connListener(connListener), fixedPort(false) { MS_TRACE(); @@ -25,8 +21,8 @@ namespace RTC TcpServer::TcpServer( Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip, uint16_t port) : // This may throw. - ::TcpServerHandler::TcpServerHandler(RTC::PortManager::BindTcp(ip, port), 256), - listener(listener), connListener(connListener), fixedPort(true) + ::TcpServerHandler::TcpServerHandler(RTC::PortManager::BindTcp(ip, port)), listener(listener), + connListener(connListener), fixedPort(true) { MS_TRACE(); } @@ -45,14 +41,6 @@ namespace RTC { MS_TRACE(); - // Allow just MaxTcpConnectionsPerServer. - if (GetNumConnections() >= MaxTcpConnectionsPerServer) - { - MS_ERROR("cannot handle more than %zu connections", MaxTcpConnectionsPerServer); - - return; - } - // Allocate a new RTC::TcpConnection for the TcpServer to handle it. auto* connection = new RTC::TcpConnection(this->connListener, 65536); diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 1c3ac20ccc..78c9dbe355 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -307,6 +307,14 @@ namespace RTC this->mapDataConsumers.clear(); } + void Transport::ListenServerClosed() + { + MS_TRACE(); + + // Ask our parent Router to close/delete us. + this->listener->OnTransportListenServerClosed(this); + } + void Transport::FillJson(json& jsonObject) const { MS_TRACE(); diff --git a/worker/src/RTC/TransportTuple.cpp b/worker/src/RTC/TransportTuple.cpp index d54c4fd6be..ab8dce24b2 100644 --- a/worker/src/RTC/TransportTuple.cpp +++ b/worker/src/RTC/TransportTuple.cpp @@ -3,7 +3,6 @@ #include "RTC/TransportTuple.hpp" #include "Logger.hpp" -#include "Utils.hpp" #include namespace RTC diff --git a/worker/src/RTC/WebRtcServer.cpp b/worker/src/RTC/WebRtcServer.cpp new file mode 100644 index 0000000000..95c8b067d4 --- /dev/null +++ b/worker/src/RTC/WebRtcServer.cpp @@ -0,0 +1,547 @@ +#define MS_CLASS "RTC::WebRtcServer" +// #define MS_LOG_DEV_LEVEL 3 + +#include "RTC/WebRtcServer.hpp" +#include "Logger.hpp" +#include "MediaSoupErrors.hpp" +#include "Utils.hpp" +#include "Channel/ChannelNotifier.hpp" +#include // std::pow() + +namespace RTC +{ + /* Static. */ + + static constexpr uint16_t IceCandidateDefaultLocalPriority{ 10000 }; + // We just provide "host" candidates so type preference is fixed. + static constexpr uint16_t IceTypePreference{ 64 }; + // We do not support non rtcp-mux so component is always 1. + static constexpr uint16_t IceComponent{ 1 }; + + static inline uint32_t generateIceCandidatePriority(uint16_t localPreference) + { + MS_TRACE(); + + return std::pow(2, 24) * IceTypePreference + std::pow(2, 8) * localPreference + + std::pow(2, 0) * (256 - IceComponent); + } + + /* Instance methods. */ + + WebRtcServer::WebRtcServer(const std::string& id, json& data) : id(id) + { + MS_TRACE(); + + auto jsonListenInfosIt = data.find("listenInfos"); + + if (jsonListenInfosIt == data.end()) + MS_THROW_TYPE_ERROR("missing listenInfos"); + else if (!jsonListenInfosIt->is_array()) + MS_THROW_TYPE_ERROR("wrong listenInfos (not an array)"); + else if (jsonListenInfosIt->empty()) + MS_THROW_TYPE_ERROR("wrong listenInfos (empty array)"); + else if (jsonListenInfosIt->size() > 8) + MS_THROW_TYPE_ERROR("wrong listenInfos (too many entries)"); + + std::vector listenInfos(jsonListenInfosIt->size()); + + for (size_t i{ 0 }; i < jsonListenInfosIt->size(); ++i) + { + auto& jsonListenInfo = (*jsonListenInfosIt)[i]; + auto& listenInfo = listenInfos[i]; + + if (!jsonListenInfo.is_object()) + MS_THROW_TYPE_ERROR("wrong listenInfo (not an object)"); + + auto jsonProtocolIt = jsonListenInfo.find("protocol"); + + if (jsonProtocolIt == jsonListenInfo.end()) + MS_THROW_TYPE_ERROR("missing listenInfo.protocol"); + else if (!jsonProtocolIt->is_string()) + MS_THROW_TYPE_ERROR("wrong listenInfo.protocol (not an string"); + + std::string protocolStr = jsonProtocolIt->get(); + + Utils::String::ToLowerCase(protocolStr); + + if (protocolStr == "udp") + listenInfo.protocol = RTC::TransportTuple::Protocol::UDP; + else if (protocolStr == "tcp") + listenInfo.protocol = RTC::TransportTuple::Protocol::TCP; + else + MS_THROW_TYPE_ERROR("invalid listenInfo.protocol (must be 'udp' or 'tcp'"); + + auto jsonIpIt = jsonListenInfo.find("ip"); + + if (jsonIpIt == jsonListenInfo.end()) + MS_THROW_TYPE_ERROR("missing listenInfo.ip"); + else if (!jsonIpIt->is_string()) + MS_THROW_TYPE_ERROR("wrong listenInfo.ip (not an string"); + + listenInfo.ip.assign(jsonIpIt->get()); + + // This may throw. + Utils::IP::NormalizeIp(listenInfo.ip); + + auto jsonAnnouncedIpIt = jsonListenInfo.find("announcedIp"); + + if (jsonAnnouncedIpIt != jsonListenInfo.end()) + { + if (!jsonAnnouncedIpIt->is_string()) + MS_THROW_TYPE_ERROR("wrong listenInfo.announcedIp (not an string)"); + + listenInfo.announcedIp.assign(jsonAnnouncedIpIt->get()); + } + + auto jsonPortIt = jsonListenInfo.find("port"); + + if (jsonPortIt == jsonListenInfo.end()) + MS_THROW_TYPE_ERROR("missing listenInfo.port"); + else if (!(jsonPortIt->is_number() && Utils::Json::IsPositiveInteger(*jsonPortIt))) + MS_THROW_TYPE_ERROR("wrong listenInfo.port (not a positive number)"); + + listenInfo.port = jsonPortIt->get(); + } + + try + { + for (auto& listenInfo : listenInfos) + { + if (listenInfo.protocol == RTC::TransportTuple::Protocol::UDP) + { + // This may throw. + auto* udpSocket = new RTC::UdpSocket(this, listenInfo.ip, listenInfo.port); + + this->udpSocketOrTcpServers.emplace_back(udpSocket, nullptr, listenInfo.announcedIp); + } + else if (listenInfo.protocol == RTC::TransportTuple::Protocol::TCP) + { + // This may throw. + auto* tcpServer = new RTC::TcpServer(this, this, listenInfo.ip, listenInfo.port); + + this->udpSocketOrTcpServers.emplace_back(nullptr, tcpServer, listenInfo.announcedIp); + } + } + } + catch (const MediaSoupError& error) + { + // Must delete everything since the destructor won't be called. + + for (auto& item : this->udpSocketOrTcpServers) + { + delete item.udpSocket; + item.udpSocket = nullptr; + + delete item.tcpServer; + item.tcpServer = nullptr; + } + this->udpSocketOrTcpServers.clear(); + + throw; + } + } + + WebRtcServer::~WebRtcServer() + { + MS_TRACE(); + + for (auto& item : this->udpSocketOrTcpServers) + { + delete item.udpSocket; + item.udpSocket = nullptr; + + delete item.tcpServer; + item.tcpServer = nullptr; + } + this->udpSocketOrTcpServers.clear(); + + for (auto* webRtcTransport : this->webRtcTransports) + { + webRtcTransport->ListenServerClosed(); + } + this->webRtcTransports.clear(); + } + + void WebRtcServer::FillJson(json& jsonObject) const + { + MS_TRACE(); + + // Add id. + jsonObject["id"] = this->id; + + // Add udpSockets and tcpServers. + jsonObject["udpSockets"] = json::array(); + auto jsonUdpSocketsIt = jsonObject.find("udpSockets"); + jsonObject["tcpServers"] = json::array(); + auto jsonTcpServersIt = jsonObject.find("tcpServers"); + + size_t udpSocketIdx{ 0 }; + size_t tcpServerIdx{ 0 }; + + for (auto& item : this->udpSocketOrTcpServers) + { + if (item.udpSocket) + { + jsonUdpSocketsIt->emplace_back(json::value_t::object); + + auto& jsonEntry = (*jsonUdpSocketsIt)[udpSocketIdx]; + + jsonEntry["ip"] = item.udpSocket->GetLocalIp(); + jsonEntry["port"] = item.udpSocket->GetLocalPort(); + + ++udpSocketIdx; + } + else if (item.tcpServer) + { + jsonTcpServersIt->emplace_back(json::value_t::object); + + auto& jsonEntry = (*jsonTcpServersIt)[tcpServerIdx]; + + jsonEntry["ip"] = item.tcpServer->GetLocalIp(); + jsonEntry["port"] = item.tcpServer->GetLocalPort(); + + ++tcpServerIdx; + } + } + + // Add webRtcTransportIds. + jsonObject["webRtcTransportIds"] = json::array(); + auto jsonWebRtcTransportIdsIt = jsonObject.find("webRtcTransportIds"); + + for (auto* webRtcTransport : this->webRtcTransports) + { + jsonWebRtcTransportIdsIt->emplace_back(webRtcTransport->id); + } + + size_t idx; + + // Add localIceUsernameFragments. + jsonObject["localIceUsernameFragments"] = json::array(); + auto jsonLocalIceUsernamesIt = jsonObject.find("localIceUsernameFragments"); + + idx = 0; + for (auto& kv : this->mapLocalIceUsernameFragmentWebRtcTransport) + { + const auto& localIceUsernameFragment = kv.first; + const auto* webRtcTransport = kv.second; + + jsonLocalIceUsernamesIt->emplace_back(json::value_t::object); + + auto& jsonEntry = (*jsonLocalIceUsernamesIt)[idx]; + + jsonEntry["localIceUsernameFragment"] = localIceUsernameFragment; + jsonEntry["webRtcTransportId"] = webRtcTransport->id; + + ++idx; + } + + // Add tupleHashes. + jsonObject["tupleHashes"] = json::array(); + auto jsonTupleHashesIt = jsonObject.find("tupleHashes"); + + idx = 0; + for (auto& kv : this->mapTupleWebRtcTransport) + { + const auto& tupleHash = kv.first; + const auto* webRtcTransport = kv.second; + + jsonTupleHashesIt->emplace_back(json::value_t::object); + + auto& jsonEntry = (*jsonTupleHashesIt)[idx]; + + jsonEntry["tupleHash"] = tupleHash; + jsonEntry["webRtcTransportId"] = webRtcTransport->id; + + ++idx; + } + } + + void WebRtcServer::HandleRequest(Channel::ChannelRequest* request) + { + MS_TRACE(); + + switch (request->methodId) + { + case Channel::ChannelRequest::MethodId::WEBRTC_SERVER_DUMP: + { + json data = json::object(); + + FillJson(data); + + request->Accept(data); + + break; + } + + default: + { + MS_THROW_ERROR("unknown method '%s'", request->method.c_str()); + } + } + } + + std::vector WebRtcServer::GetIceCandidates( + bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp) + { + MS_TRACE(); + + std::vector iceCandidates; + uint16_t iceLocalPreferenceDecrement{ 0 }; + + for (auto& item : this->udpSocketOrTcpServers) + { + if (item.udpSocket && enableUdp) + { + uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement; + + if (preferUdp) + iceLocalPreference += 1000; + + uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference); + + if (item.announcedIp.empty()) + iceCandidates.emplace_back(item.udpSocket, icePriority); + else + iceCandidates.emplace_back(item.udpSocket, icePriority, item.announcedIp); + } + else if (item.tcpServer && enableTcp) + { + uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement; + + if (preferTcp) + iceLocalPreference += 1000; + + uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference); + + if (item.announcedIp.empty()) + iceCandidates.emplace_back(item.tcpServer, icePriority); + else + iceCandidates.emplace_back(item.tcpServer, icePriority, item.announcedIp); + } + + // Decrement initial ICE local preference for next IP. + iceLocalPreferenceDecrement += 100; + } + + return iceCandidates; + } + + inline std::string WebRtcServer::GetLocalIceUsernameFragmentFromReceivedStunPacket( + RTC::StunPacket* packet) const + { + MS_TRACE(); + + // Here we inspect the USERNAME attribute of a received STUN request and + // extract its remote usernameFragment (the one given to our IceServer as + // local usernameFragment) which is the first value in the attribute value + // before the ":" symbol. + + auto& username = packet->GetUsername(); + size_t colonPos = username.find(":"); + + // If no colon is found just return the whole USERNAME attribute anyway. + if (colonPos == std::string::npos) + return username; + + return username.substr(0, colonPos); + } + + inline void WebRtcServer::OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len) + { + MS_TRACE(); + + if (RTC::StunPacket::IsStun(data, len)) + { + OnStunDataReceived(tuple, data, len); + } + else + { + OnNonStunDataReceived(tuple, data, len); + } + } + + inline void WebRtcServer::OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len) + { + MS_TRACE(); + + RTC::StunPacket* packet = RTC::StunPacket::Parse(data, len); + + if (!packet) + { + MS_WARN_TAG(ice, "ignoring wrong STUN packet received"); + + return; + } + + // First try doing lookup in the tuples table. + auto it1 = this->mapTupleWebRtcTransport.find(tuple->hash); + + if (it1 != this->mapTupleWebRtcTransport.end()) + { + auto* webRtcTransport = it1->second; + + webRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet); + + delete packet; + + return; + } + + // Otherwise try to match the local ICE username fragment. + auto key = GetLocalIceUsernameFragmentFromReceivedStunPacket(packet); + auto it2 = this->mapLocalIceUsernameFragmentWebRtcTransport.find(key); + + if (it2 == this->mapLocalIceUsernameFragmentWebRtcTransport.end()) + { + MS_WARN_TAG(ice, "ignoring received STUN packet with unknown remote ICE usernameFragment"); + + delete packet; + + return; + } + + auto* webRtcTransport = it2->second; + + webRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet); + + delete packet; + } + + inline void WebRtcServer::OnNonStunDataReceived( + RTC::TransportTuple* tuple, const uint8_t* data, size_t len) + { + MS_TRACE(); + + auto it = this->mapTupleWebRtcTransport.find(tuple->hash); + + if (it == this->mapTupleWebRtcTransport.end()) + { + MS_WARN_TAG(ice, "ignoring received non STUN data from unknown tuple"); + + return; + } + + auto* webRtcTransport = it->second; + + webRtcTransport->ProcessNonStunPacketFromWebRtcServer(tuple, data, len); + } + + inline void WebRtcServer::OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) + { + MS_TRACE(); + + MS_ASSERT( + this->webRtcTransports.find(webRtcTransport) == this->webRtcTransports.end(), + "WebRtcTransport already handled"); + + this->webRtcTransports.insert(webRtcTransport); + } + + inline void WebRtcServer::OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) + { + MS_TRACE(); + + MS_ASSERT( + this->webRtcTransports.find(webRtcTransport) != this->webRtcTransports.end(), + "WebRtcTransport not handled"); + + this->webRtcTransports.erase(webRtcTransport); + } + + inline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentAdded( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) + { + MS_TRACE(); + + MS_ASSERT( + this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) == + this->mapLocalIceUsernameFragmentWebRtcTransport.end(), + "local ICE username fragment already exists in the table"); + + this->mapLocalIceUsernameFragmentWebRtcTransport[usernameFragment] = webRtcTransport; + } + + inline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentRemoved( + RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) + { + MS_TRACE(); + + MS_ASSERT( + this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) != + this->mapLocalIceUsernameFragmentWebRtcTransport.end(), + "local ICE username fragment not found in the table"); + + this->mapLocalIceUsernameFragmentWebRtcTransport.erase(usernameFragment); + } + + inline void WebRtcServer::OnWebRtcTransportTransportTupleAdded( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) + { + MS_TRACE(); + + if (this->mapTupleWebRtcTransport.find(tuple->hash) != this->mapTupleWebRtcTransport.end()) + { + MS_WARN_TAG(ice, "tuple hash already exists in the table"); + + return; + } + + this->mapTupleWebRtcTransport[tuple->hash] = webRtcTransport; + } + + inline void WebRtcServer::OnWebRtcTransportTransportTupleRemoved( + RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) + { + MS_TRACE(); + + if (this->mapTupleWebRtcTransport.find(tuple->hash) == this->mapTupleWebRtcTransport.end()) + { + MS_WARN_TAG(ice, "tuple hash not found in the table"); + + return; + } + + this->mapTupleWebRtcTransport.erase(tuple->hash); + } + + inline void WebRtcServer::OnUdpSocketPacketReceived( + RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr) + { + MS_TRACE(); + + RTC::TransportTuple tuple(socket, remoteAddr); + + OnPacketReceived(&tuple, data, len); + } + + inline void WebRtcServer::OnRtcTcpConnectionClosed( + RTC::TcpServer* /*tcpServer*/, RTC::TcpConnection* connection) + { + MS_TRACE(); + + RTC::TransportTuple tuple(connection); + + // NOTE: We cannot assert whether this tuple is still in our + // mapTupleWebRtcTransport because this event may be called after the tuple + // was removed from it. + + auto it = this->mapTupleWebRtcTransport.find(tuple.hash); + + if (it == this->mapTupleWebRtcTransport.end()) + return; + + auto* webRtcTransport = it->second; + + webRtcTransport->RemoveTuple(&tuple); + } + + inline void WebRtcServer::OnTcpConnectionPacketReceived( + RTC::TcpConnection* connection, const uint8_t* data, size_t len) + { + MS_TRACE(); + + RTC::TransportTuple tuple(connection); + + OnPacketReceived(&tuple, data, len); + } +} // namespace RTC diff --git a/worker/src/RTC/WebRtcTransport.cpp b/worker/src/RTC/WebRtcTransport.cpp index 560786a27c..abf390e12f 100644 --- a/worker/src/RTC/WebRtcTransport.cpp +++ b/worker/src/RTC/WebRtcTransport.cpp @@ -199,7 +199,7 @@ namespace RTC // Create a ICE server. this->iceServer = new RTC::IceServer( - this, Utils::Crypto::GetRandomString(16), Utils::Crypto::GetRandomString(32)); + this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32)); // Create a DTLS transport. this->dtlsTransport = new RTC::DtlsTransport(this); @@ -236,6 +236,51 @@ namespace RTC } } + /** + * This constructor is used when the WebRtcTransport uses a WebRtcServer. + */ + WebRtcTransport::WebRtcTransport( + const std::string& id, + RTC::Transport::Listener* listener, + WebRtcTransportListener* webRtcTransportListener, + std::vector& iceCandidates, + json& data) + : RTC::Transport::Transport(id, listener, data), + webRtcTransportListener(webRtcTransportListener), iceCandidates(iceCandidates) + { + MS_TRACE(); + + try + { + if (iceCandidates.empty()) + MS_THROW_TYPE_ERROR("empty iceCandidates"); + + // Create a ICE server. + this->iceServer = new RTC::IceServer( + this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32)); + + // Create a DTLS transport. + this->dtlsTransport = new RTC::DtlsTransport(this); + + // Notify the webRtcTransportListener. + this->webRtcTransportListener->OnWebRtcTransportCreated(this); + } + catch (const MediaSoupError& error) + { + // Must delete everything since the destructor won't be called. + + delete this->dtlsTransport; + this->dtlsTransport = nullptr; + + delete this->iceServer; + this->iceServer = nullptr; + + this->iceCandidates.clear(); + + throw; + } + } + WebRtcTransport::~WebRtcTransport() { MS_TRACE(); @@ -271,6 +316,10 @@ namespace RTC delete this->srtpRecvSession; this->srtpRecvSession = nullptr; + + // Notify the webRtcTransportListener. + if (this->webRtcTransportListener) + this->webRtcTransportListener->OnWebRtcTransportClosed(this); } void WebRtcTransport::FillJson(json& jsonObject) const @@ -582,7 +631,7 @@ namespace RTC case Channel::ChannelRequest::MethodId::TRANSPORT_RESTART_ICE: { - std::string usernameFragment = Utils::Crypto::GetRandomString(16); + std::string usernameFragment = Utils::Crypto::GetRandomString(32); std::string password = Utils::Crypto::GetRandomString(32); this->iceServer->RestartIce(usernameFragment, password); @@ -621,6 +670,51 @@ namespace RTC RTC::Transport::HandleNotification(notification); } + void WebRtcTransport::ProcessStunPacketFromWebRtcServer( + RTC::TransportTuple* tuple, RTC::StunPacket* packet) + { + MS_TRACE(); + + // Pass it to the IceServer. + this->iceServer->ProcessStunPacket(packet, tuple); + } + + void WebRtcTransport::ProcessNonStunPacketFromWebRtcServer( + RTC::TransportTuple* tuple, const uint8_t* data, size_t len) + { + MS_TRACE(); + + // Increase receive transmission. + RTC::Transport::DataReceived(len); + + // Check if it's RTCP. + if (RTC::RTCP::Packet::IsRtcp(data, len)) + { + OnRtcpDataReceived(tuple, data, len); + } + // Check if it's RTP. + else if (RTC::RtpPacket::IsRtp(data, len)) + { + OnRtpDataReceived(tuple, data, len); + } + // Check if it's DTLS. + else if (RTC::DtlsTransport::IsDtls(data, len)) + { + OnDtlsDataReceived(tuple, data, len); + } + else + { + MS_WARN_DEV("ignoring received packet of unknown type"); + } + } + + void WebRtcTransport::RemoveTuple(RTC::TransportTuple* tuple) + { + MS_TRACE(); + + this->iceServer->RemoveTuple(tuple); + } + inline bool WebRtcTransport::IsConnected() const { MS_TRACE(); @@ -1120,6 +1214,56 @@ namespace RTC RTC::Transport::DataSent(packet->GetSize()); } + inline void WebRtcTransport::OnIceServerLocalUsernameFragmentAdded( + const RTC::IceServer* /*iceServer*/, const std::string& usernameFragment) + { + MS_TRACE(); + + if (this->webRtcTransportListener) + { + this->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentAdded( + this, usernameFragment); + } + } + + inline void WebRtcTransport::OnIceServerLocalUsernameFragmentRemoved( + const RTC::IceServer* /*iceServer*/, const std::string& usernameFragment) + { + MS_TRACE(); + + if (this->webRtcTransportListener) + { + this->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentRemoved( + this, usernameFragment); + } + } + + inline void WebRtcTransport::OnIceServerTupleAdded( + const RTC::IceServer* /*iceServer*/, RTC::TransportTuple* tuple) + { + MS_TRACE(); + + if (this->webRtcTransportListener) + { + this->webRtcTransportListener->OnWebRtcTransportTransportTupleAdded(this, tuple); + } + } + + inline void WebRtcTransport::OnIceServerTupleRemoved( + const RTC::IceServer* /*iceServer*/, RTC::TransportTuple* tuple) + { + MS_TRACE(); + + if (this->webRtcTransportListener) + { + this->webRtcTransportListener->OnWebRtcTransportTransportTupleRemoved(this, tuple); + } + + // If this is a TCP tuple, close its underlaying TCP connection. + if (tuple->GetProtocol() == RTC::TransportTuple::Protocol::TCP && !tuple->IsClosed()) + tuple->Close(); + } + inline void WebRtcTransport::OnIceServerSelectedTuple( const RTC::IceServer* /*iceServer*/, RTC::TransportTuple* /*tuple*/) { diff --git a/worker/src/Worker.cpp b/worker/src/Worker.cpp index 1754e81ab6..30dbbca76d 100644 --- a/worker/src/Worker.cpp +++ b/worker/src/Worker.cpp @@ -73,6 +73,15 @@ void Worker::Close() } this->mapRouters.clear(); + // Delete all WebRtcServers. + for (auto& kv : this->mapWebRtcServers) + { + auto* webRtcServer = kv.second; + + delete webRtcServer; + } + this->mapWebRtcServers.clear(); + // Close the Checker instance in DepUsrSCTP. DepUsrSCTP::CloseChecker(); @@ -90,6 +99,17 @@ void Worker::FillJson(json& jsonObject) const // Add pid. jsonObject["pid"] = Logger::pid; + // Add webRtcServerIds. + jsonObject["webRtcServerIds"] = json::array(); + auto jsonWebRtcServerIdsIt = jsonObject.find("webRtcServerIds"); + + for (auto& kv : this->mapWebRtcServers) + { + auto& WebRtcServerId = kv.first; + + jsonWebRtcServerIdsIt->emplace_back(WebRtcServerId); + } + // Add routerIds. jsonObject["routerIds"] = json::array(); auto jsonRouterIdsIt = jsonObject.find("routerIds"); @@ -165,6 +185,40 @@ void Worker::FillJsonResourceUsage(json& jsonObject) const jsonObject["ru_nivcsw"] = uvRusage.ru_nivcsw; } +void Worker::SetNewWebRtcServerIdFromInternal(json& internal, std::string& webRtcServerId) const +{ + MS_TRACE(); + + auto jsonWebRtcServerIdIt = internal.find("webRtcServerId"); + + if (jsonWebRtcServerIdIt == internal.end() || !jsonWebRtcServerIdIt->is_string()) + MS_THROW_ERROR("missing internal.webRtcServerId"); + + webRtcServerId.assign(jsonWebRtcServerIdIt->get()); + + if (this->mapWebRtcServers.find(webRtcServerId) != this->mapWebRtcServers.end()) + MS_THROW_ERROR("a WebRtcServer with same webRtcServerId already exists"); +} + +RTC::WebRtcServer* Worker::GetWebRtcServerFromInternal(json& internal) const +{ + MS_TRACE(); + + auto jsonWebRtcServerIdIt = internal.find("webRtcServerId"); + + if (jsonWebRtcServerIdIt == internal.end() || !jsonWebRtcServerIdIt->is_string()) + MS_THROW_ERROR("missing internal.webRtcServerId"); + + auto it = this->mapWebRtcServers.find(jsonWebRtcServerIdIt->get()); + + if (it == this->mapWebRtcServers.end()) + MS_THROW_ERROR("WebRtcServer not found"); + + RTC::WebRtcServer* webRtcServer = it->second; + + return webRtcServer; +} + void Worker::SetNewRouterIdFromInternal(json& internal, std::string& routerId) const { MS_TRACE(); @@ -249,6 +303,76 @@ inline void Worker::OnChannelRequest(Channel::ChannelSocket* /*channel*/, Channe break; } + case Channel::ChannelRequest::MethodId::WORKER_CREATE_WEBRTC_SERVER: + { + try + { + std::string webRtcServerId; + + SetNewWebRtcServerIdFromInternal(request->internal, webRtcServerId); + + auto* webRtcServer = new RTC::WebRtcServer(webRtcServerId, request->data); + + this->mapWebRtcServers[webRtcServerId] = webRtcServer; + + MS_DEBUG_DEV("WebRtcServer created [webRtcServerId:%s]", webRtcServerId.c_str()); + + request->Accept(); + } + catch (const MediaSoupTypeError& error) + { + MS_THROW_TYPE_ERROR("%s [method:%s]", error.what(), request->method.c_str()); + } + catch (const MediaSoupError& error) + { + MS_THROW_ERROR("%s [method:%s]", error.what(), request->method.c_str()); + } + + break; + } + + case Channel::ChannelRequest::MethodId::WEBRTC_SERVER_CLOSE: + { + RTC::WebRtcServer* webRtcServer{ nullptr }; + + try + { + webRtcServer = GetWebRtcServerFromInternal(request->internal); + } + catch (const MediaSoupError& error) + { + MS_THROW_ERROR("%s [method:%s]", error.what(), request->method.c_str()); + } + + // Remove it from the map and delete it. + this->mapWebRtcServers.erase(webRtcServer->id); + delete webRtcServer; + + MS_DEBUG_DEV("WebRtcServer closed [id:%s]", webRtcServer->id.c_str()); + + request->Accept(); + + break; + } + + case Channel::ChannelRequest::MethodId::WEBRTC_SERVER_DUMP: + { + RTC::WebRtcServer* webRtcServer{ nullptr }; + + try + { + webRtcServer = GetWebRtcServerFromInternal(request->internal); + + webRtcServer->HandleRequest(request); + } + catch (const MediaSoupError& error) + { + MS_THROW_ERROR("%s [method:%s]", error.what(), request->method.c_str()); + } + + break; + } + case Channel::ChannelRequest::MethodId::WORKER_CREATE_ROUTER: { std::string routerId; @@ -262,7 +386,7 @@ inline void Worker::OnChannelRequest(Channel::ChannelSocket* /*channel*/, Channe MS_THROW_ERROR("%s [method:%s]", error.what(), request->method.c_str()); } - auto* router = new RTC::Router(routerId); + auto* router = new RTC::Router(routerId, this); this->mapRouters[routerId] = router; @@ -437,3 +561,20 @@ inline void Worker::OnSignal(SignalsHandler* /*signalsHandler*/, int signum) } } } + +inline RTC::WebRtcServer* Worker::OnRouterNeedWebRtcServer( + RTC::Router* /*router*/, std::string& webRtcServerId) +{ + MS_TRACE(); + + RTC::WebRtcServer* webRtcServer{ nullptr }; + + auto it = this->mapWebRtcServers.find(webRtcServerId); + + if (it != this->mapWebRtcServers.end()) + { + webRtcServer = it->second; + } + + return webRtcServer; +} diff --git a/worker/src/handles/TcpServerHandler.cpp b/worker/src/handles/TcpServerHandler.cpp index 82fc9adc26..a3213d709e 100644 --- a/worker/src/handles/TcpServerHandler.cpp +++ b/worker/src/handles/TcpServerHandler.cpp @@ -6,6 +6,10 @@ #include "MediaSoupErrors.hpp" #include "Utils.hpp" +/* Static. */ + +static constexpr int ListenBacklog{ 512 }; + /* Static methods for UV callbacks. */ inline static void onConnection(uv_stream_t* handle, int status) @@ -24,7 +28,7 @@ inline static void onClose(uv_handle_t* handle) /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) -TcpServerHandler::TcpServerHandler(uv_tcp_t* uvHandle, int backlog) : uvHandle(uvHandle) +TcpServerHandler::TcpServerHandler(uv_tcp_t* uvHandle) : uvHandle(uvHandle) { MS_TRACE(); @@ -34,7 +38,7 @@ TcpServerHandler::TcpServerHandler(uv_tcp_t* uvHandle, int backlog) : uvHandle(u err = uv_listen( reinterpret_cast(this->uvHandle), - backlog, + ListenBacklog, static_cast(onConnection)); if (err != 0)