Skip to content

Commit

Permalink
feat(e2ee) add support for WebRTC Encoded Transform
Browse files Browse the repository at this point in the history
An alternative to Insertable Streams, currently implemented in Safarii / WebKit.

https://w3c.github.io/webrtc-encoded-transform/

Fixes: jitsi/jitsi-meet#9585
  • Loading branch information
saghul committed Aug 24, 2021
1 parent 47b3572 commit 61c977f
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 46 deletions.
13 changes: 13 additions & 0 deletions modules/browser/BrowserCapabilities.js
Expand Up @@ -235,6 +235,19 @@ export default class BrowserCapabilities extends BrowserDetection {
!== 'undefined');
}

/**
* Checks if the browser supports WebRTC Encoded Transform, an alternative
* to insertable streams.
*
* NOTE: At the time of this writing the only browser supporting this is
* Safari / WebKit, behind a flag.
*
* @returns {boolean} {@code true} if the browser supports it.
*/
supportsEncodedTransform() {
return Boolean(window.RTCRtpScriptTransform);
}

/**
* Checks if the browser supports insertable streams, needed for E2EE.
* @returns {boolean} {@code true} if the browser supports insertable streams.
Expand Down
52 changes: 35 additions & 17 deletions modules/e2ee/E2EEContext.js
@@ -1,4 +1,4 @@
/* global __filename */
/* global __filename, RTCRtpScriptTransform */

import { getLogger } from 'jitsi-meet-logger';

Expand Down Expand Up @@ -74,14 +74,23 @@ export default class E2EEcontext {
}
receiver[kJitsiE2EE] = true;

const receiverStreams = receiver.createEncodedStreams();

this._worker.postMessage({
operation: 'decode',
readableStream: receiverStreams.readable,
writableStream: receiverStreams.writable,
participantId
}, [ receiverStreams.readable, receiverStreams.writable ]);
if (window.RTCRtpScriptTransform) {
const options = {
operation: 'decode',
participantId
};

receiver.transform = new RTCRtpScriptTransform(this._worker, options);
} else {
const receiverStreams = receiver.createEncodedStreams();

this._worker.postMessage({
operation: 'decode',
readableStream: receiverStreams.readable,
writableStream: receiverStreams.writable,
participantId
}, [ receiverStreams.readable, receiverStreams.writable ]);
}
}

/**
Expand All @@ -98,14 +107,23 @@ export default class E2EEcontext {
}
sender[kJitsiE2EE] = true;

const senderStreams = sender.createEncodedStreams();

this._worker.postMessage({
operation: 'encode',
readableStream: senderStreams.readable,
writableStream: senderStreams.writable,
participantId
}, [ senderStreams.readable, senderStreams.writable ]);
if (window.RTCRtpScriptTransform) {
const options = {
operation: 'encode',
participantId
};

sender.transform = new RTCRtpScriptTransform(this._worker, options);
} else {
const senderStreams = sender.createEncodedStreams();

this._worker.postMessage({
operation: 'encode',
readableStream: senderStreams.readable,
writableStream: senderStreams.writable,
participantId
}, [ senderStreams.readable, senderStreams.writable ]);
}
}

/**
Expand Down
7 changes: 4 additions & 3 deletions modules/e2ee/E2EEncryption.js
Expand Up @@ -96,9 +96,10 @@ export class E2EEncryption {
* @returns {boolean}
*/
static isSupported(config) {
return browser.supportsInsertableStreams()
&& OlmAdapter.isSupported()
&& !(config.testing && config.testing.disableE2EE);
return !(config.testing && config.testing.disableE2EE)
&& (browser.supportsInsertableStreams()
|| (config.enableEncodedTransformSupport && browser.supportsEncodedTransform()))
&& OlmAdapter.isSupported();
}

/**
Expand Down
73 changes: 47 additions & 26 deletions modules/e2ee/Worker.js
Expand Up @@ -7,44 +7,54 @@ import { Context } from './Context';

const contexts = new Map(); // Map participant id => context

onmessage = async event => {
const { operation } = event.data;
/**
* Retrieves the participant {@code Context}, creating it if necessary.
*
* @param {string} participantId - The participant whose context we need.
* @returns {Object} The context.
*/
function getParticipantContext(participantId) {
if (!contexts.has(participantId)) {
contexts.set(participantId, new Context(participantId));
}

if (operation === 'encode') {
const { readableStream, writableStream, participantId } = event.data;
return contexts.get(participantId);
}

if (!contexts.has(participantId)) {
contexts.set(participantId, new Context(participantId));
}
const context = contexts.get(participantId);
/**
* Sets an encode / decode transform.
*
* @param {Object} context - The participant context where the transform will be applied.
* @param {string} operation - Encode / decode.
* @param {Object} readableStream - Readable stream part.
* @param {Object} writableStream - Writable stream part.
*/
function handleTransform(context, operation, readableStream, writableStream) {
if (operation === 'encode' || operation === 'decode') {
const transformFn = operation === 'encode' ? context.encodeFunction : context.decodeFunction;
const transformStream = new TransformStream({
transform: context.encodeFunction.bind(context)
transform: transformFn.bind(context)
});

readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
} else if (operation === 'decode') {
const { readableStream, writableStream, participantId } = event.data;
} else {
console.error(`Invalid operation: ${operation}`);
}
}

if (!contexts.has(participantId)) {
contexts.set(participantId, new Context(participantId));
}
const context = contexts.get(participantId);
const transformStream = new TransformStream({
transform: context.decodeFunction.bind(context)
});
onmessage = async event => {
const { operation } = event.data;

readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
if (operation === 'encode' || operation === 'decode') {
const { readableStream, writableStream, participantId } = event.data;
const context = getParticipantContext(participantId);

handleTransform(context, operation, readableStream, writableStream);
} else if (operation === 'setKey') {
const { participantId, key, keyIndex } = event.data;

if (!contexts.has(participantId)) {
contexts.set(participantId, new Context(participantId));
}
const context = contexts.get(participantId);
const context = getParticipantContext(participantId);

if (key) {
context.setKey(key, keyIndex);
Expand All @@ -59,3 +69,14 @@ onmessage = async event => {
console.error('e2ee worker', operation);
}
};

// Operations using RTCRtpScriptTransform.
if (self.RTCTransformEvent) {
self.onrtctransform = event => {
const transformer = event.transformer;
const { operation, participantId } = transformer.options;
const context = getParticipantContext(participantId);

handleTransform(context, operation, transformer.readable, transformer.writable);
};
}

0 comments on commit 61c977f

Please sign in to comment.