feat: removing old voice encryption modes
- VP8 working (ok) - H264 working (bad) by #101 (dank074/Discord-video-stream)
This commit is contained in:
@@ -22,15 +22,7 @@ class SingleSilence extends Silence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_MODES = [
|
const SUPPORTED_MODES = ['aead_aes256_gcm_rtpsize', 'aead_xchacha20_poly1305_rtpsize'];
|
||||||
// Avalible (but only voice working)
|
|
||||||
// 'aead_aes256_gcm_rtpsize',
|
|
||||||
// 'aead_xchacha20_poly1305_rtpsize',
|
|
||||||
// Deprecated
|
|
||||||
'xsalsa20_poly1305_lite',
|
|
||||||
'xsalsa20_poly1305_suffix',
|
|
||||||
'xsalsa20_poly1305',
|
|
||||||
];
|
|
||||||
const SUPPORTED_CODECS = ['VP8', 'H264'];
|
const SUPPORTED_CODECS = ['VP8', 'H264'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -38,13 +38,12 @@ class AnnexBDispatcher extends VideoDispatcher {
|
|||||||
const nalu = accessUnit.subarray(offset, offset + naluSize);
|
const nalu = accessUnit.subarray(offset, offset + naluSize);
|
||||||
const isLastNal = offset + naluSize >= accessUnit.length;
|
const isLastNal = offset + naluSize >= accessUnit.length;
|
||||||
if (nalu.length <= this.mtu) {
|
if (nalu.length <= this.mtu) {
|
||||||
// If NALU size is within MTU, send it directly
|
// Send as Single NAL Unit Packet.
|
||||||
this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), isLastNal);
|
this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal);
|
||||||
} else {
|
} else {
|
||||||
// If NALU size exceeds MTU, fragment it
|
|
||||||
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
|
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
|
||||||
const dataFragments = this.partitionVideoData(naluData);
|
const dataFragments = this.partitionVideoData(naluData);
|
||||||
|
// Send as Fragmentation Unit A (FU-A):
|
||||||
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
|
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
|
||||||
const data = dataFragments[fragmentIndex];
|
const data = dataFragments[fragmentIndex];
|
||||||
const isFirstPacket = fragmentIndex === 0;
|
const isFirstPacket = fragmentIndex === 0;
|
||||||
@@ -52,7 +51,7 @@ class AnnexBDispatcher extends VideoDispatcher {
|
|||||||
|
|
||||||
this._playChunk(
|
this._playChunk(
|
||||||
Buffer.concat([
|
Buffer.concat([
|
||||||
this.createHeaderExtension(),
|
this.createPayloadExtension(),
|
||||||
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
|
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
|
||||||
data,
|
data,
|
||||||
]),
|
]),
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const secretbox = require('../util/Secretbox');
|
|||||||
const CHANNELS = 2;
|
const CHANNELS = 2;
|
||||||
const MAX_NONCE_SIZE = 2 ** 32 - 1;
|
const MAX_NONCE_SIZE = 2 ** 32 - 1;
|
||||||
|
|
||||||
|
const extensions = [{ id: 5, len: 2, val: 0 }];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @external WritableStream
|
* @external WritableStream
|
||||||
* @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable}
|
* @see {@link https://nodejs.org/api/stream.html#stream_class_stream_writable}
|
||||||
@@ -257,15 +259,11 @@ class BaseDispatcher extends Writable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a single extension of type playout-delay
|
* Creates a one-byte extension header
|
||||||
* Discord seems to send this extension on every video packet
|
* https://www.rfc-editor.org/rfc/rfc5285#section-4.2
|
||||||
* @see https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay
|
* @returns {Buffer} extension header
|
||||||
* @returns {Buffer} playout-delay extension
|
|
||||||
* Buffer <be de 00 01 51 00 00 00>
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
createHeaderExtension() {
|
createHeaderExtension() {
|
||||||
const extensions = [{ id: 5, len: 2, val: 0 }];
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* 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
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -276,6 +274,17 @@ class BaseDispatcher extends Writable {
|
|||||||
profile[0] = 0xbe;
|
profile[0] = 0xbe;
|
||||||
profile[1] = 0xde;
|
profile[1] = 0xde;
|
||||||
profile.writeInt16BE(extensions.length, 2); // Extension count
|
profile.writeInt16BE(extensions.length, 2); // Extension count
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a single extension of type playout-delay
|
||||||
|
* Discord seems to send this extension on every video packet
|
||||||
|
* @see https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/playout-delay
|
||||||
|
* @returns {Buffer} playout-delay extension
|
||||||
|
*/
|
||||||
|
createPayloadExtension() {
|
||||||
const extensionsData = [];
|
const extensionsData = [];
|
||||||
for (let ext of extensions) {
|
for (let ext of extensions) {
|
||||||
/**
|
/**
|
||||||
@@ -301,7 +310,7 @@ class BaseDispatcher extends Writable {
|
|||||||
data.writeUIntBE(ext.val, 1, 2); // Not quite but its 0 anyway
|
data.writeUIntBE(ext.val, 1, 2); // Not quite but its 0 anyway
|
||||||
extensionsData.push(data);
|
extensionsData.push(data);
|
||||||
}
|
}
|
||||||
return Buffer.concat([profile, ...extensionsData]);
|
return Buffer.concat(extensionsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
_encrypt(buffer, additionalData) {
|
_encrypt(buffer, additionalData) {
|
||||||
@@ -338,16 +347,6 @@ class BaseDispatcher extends Writable {
|
|||||||
|
|
||||||
return [encrypted, noncePadding];
|
return [encrypted, noncePadding];
|
||||||
}
|
}
|
||||||
case 'xsalsa20_poly1305_lite': {
|
|
||||||
return [secretbox.methods.close(buffer, this._nonceBuffer, secret_key), noncePadding];
|
|
||||||
}
|
|
||||||
case 'xsalsa20_poly1305_suffix': {
|
|
||||||
const random = secretbox.methods.random(24);
|
|
||||||
return [secretbox.methods.close(buffer, random, secret_key), random];
|
|
||||||
}
|
|
||||||
case 'xsalsa20_poly1305': {
|
|
||||||
return [secretbox.methods.close(buffer, Buffer.concat([additionalData, Buffer.alloc(12)]), secret_key)];
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
// This should never happen. Our encryption mode is chosen from a list given to us by the gateway and checked with the ones we support.
|
// This should never happen. Our encryption mode is chosen from a list given to us by the gateway and checked with the ones we support.
|
||||||
throw new RangeError(`Unsupported encryption method: ${mode}`);
|
throw new RangeError(`Unsupported encryption method: ${mode}`);
|
||||||
@@ -357,7 +356,9 @@ class BaseDispatcher extends Writable {
|
|||||||
|
|
||||||
_createPacket(buffer, isLastPacket = false) {
|
_createPacket(buffer, isLastPacket = false) {
|
||||||
// Header
|
// Header
|
||||||
const rtpHeader = Buffer.alloc(12); // RTP_HEADER_SIZE
|
const rtpHeader = this.extensionEnabled
|
||||||
|
? Buffer.concat([Buffer.alloc(12), this.createHeaderExtension()])
|
||||||
|
: Buffer.alloc(12); // RTP_HEADER_SIZE
|
||||||
rtpHeader[0] = this.extensionEnabled ? 0x90 : 0x80; // Version + Flags (1 byte)
|
rtpHeader[0] = this.extensionEnabled ? 0x90 : 0x80; // Version + Flags (1 byte)
|
||||||
rtpHeader[1] = this.payloadType; // Payload Type (1 byte)
|
rtpHeader[1] = this.payloadType; // Payload Type (1 byte)
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ class VP8Dispatcher extends VideoDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeChunk(buffer, isFirstFrame) {
|
makeChunk(buffer, isFirstFrame) {
|
||||||
// Make frame
|
|
||||||
const headerExtensionBuf = this.createHeaderExtension();
|
|
||||||
// Vp8 payload descriptor
|
// Vp8 payload descriptor
|
||||||
const payloadDescriptorBuf = Buffer.alloc(2);
|
const payloadDescriptorBuf = Buffer.alloc(2);
|
||||||
payloadDescriptorBuf[0] = isFirstFrame ? 0x90 : 0x80; // Mark S bit, indicates start of frame: payloadDescriptorBuf[0] |= 0b00010000;
|
payloadDescriptorBuf[0] = isFirstFrame ? 0x90 : 0x80; // Mark S bit, indicates start of frame: payloadDescriptorBuf[0] |= 0b00010000;
|
||||||
@@ -36,13 +34,16 @@ class VP8Dispatcher extends VideoDispatcher {
|
|||||||
const pictureIdBuf = Buffer.alloc(2);
|
const pictureIdBuf = Buffer.alloc(2);
|
||||||
pictureIdBuf.writeUintBE(this.count, 0, 2);
|
pictureIdBuf.writeUintBE(this.count, 0, 2);
|
||||||
pictureIdBuf[0] |= 0x80;
|
pictureIdBuf[0] |= 0x80;
|
||||||
return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]);
|
return Buffer.concat([payloadDescriptorBuf, pictureIdBuf, buffer]);
|
||||||
}
|
}
|
||||||
|
|
||||||
codecCallback(chunk) {
|
codecCallback(chunk) {
|
||||||
const chunkSplit = this.partitionVideoData(chunk);
|
const chunkSplit = this.partitionVideoData(chunk);
|
||||||
for (let i = 0; i < chunkSplit.length; i++) {
|
for (let i = 0; i < chunkSplit.length; i++) {
|
||||||
this._playChunk(this.makeChunk(chunkSplit[i], i == 0), i + 1 === chunkSplit.length);
|
this._playChunk(
|
||||||
|
Buffer.concat([this.createPayloadExtension(), this.makeChunk(chunkSplit[i], i == 0)]),
|
||||||
|
i + 1 === chunkSplit.length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,39 +2,36 @@
|
|||||||
|
|
||||||
const libs = {
|
const libs = {
|
||||||
sodium: sodium => ({
|
sodium: sodium => ({
|
||||||
/** @deprecated */
|
|
||||||
open: sodium.api.crypto_secretbox_open_easy,
|
|
||||||
/** @deprecated */
|
|
||||||
close: sodium.api.crypto_secretbox_easy,
|
|
||||||
random: n => sodium.randombytes_buf(n),
|
|
||||||
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
|
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
|
||||||
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
|
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
|
||||||
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
|
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
|
||||||
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key),
|
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key),
|
||||||
}),
|
}),
|
||||||
'libsodium-wrappers': sodium => ({
|
'libsodium-wrappers': sodium => ({
|
||||||
/** @deprecated */
|
|
||||||
open: sodium.crypto_secretbox_open_easy,
|
|
||||||
/** @deprecated */
|
|
||||||
close: sodium.crypto_secretbox_easy,
|
|
||||||
random: n => sodium.randombytes_buf(n),
|
|
||||||
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
|
crypto_aead_xchacha20poly1305_ietf_encrypt: (plaintext, additionalData, nonce, key) =>
|
||||||
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
|
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
|
||||||
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
|
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
|
||||||
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, plaintext, additionalData, nonce, key),
|
sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, plaintext, additionalData, nonce, key),
|
||||||
}),
|
}),
|
||||||
|
'@stablelib/xchacha20poly1305': stablelib => ({
|
||||||
|
crypto_aead_xchacha20poly1305_ietf_encrypt(cipherText, additionalData, nonce, key) {
|
||||||
|
const crypto = new stablelib.XChaCha20Poly1305(key);
|
||||||
|
return crypto.seal(nonce, cipherText, additionalData);
|
||||||
|
},
|
||||||
|
crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, nonce, key) {
|
||||||
|
const crypto = new stablelib.XChaCha20Poly1305(key);
|
||||||
|
return crypto.open(nonce, plaintext, additionalData);
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
function NoLib() {
|
function NoLib() {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot play audio as no valid encryption package is installed.\n- Install sodium or libsodium-wrappers.',
|
'Cannot play audio as no valid encryption package is installed.\n- Install sodium, libsodium-wrappers, or @stablelib/xchacha20poly1305.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.methods = {
|
exports.methods = {
|
||||||
open: NoLib,
|
|
||||||
close: NoLib,
|
|
||||||
random: NoLib,
|
|
||||||
crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib,
|
crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib,
|
||||||
crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib,
|
crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib,
|
||||||
};
|
};
|
||||||
@@ -42,7 +39,7 @@ exports.methods = {
|
|||||||
(async () => {
|
(async () => {
|
||||||
for (const libName of Object.keys(libs)) {
|
for (const libName of Object.keys(libs)) {
|
||||||
try {
|
try {
|
||||||
const lib = require(libName);
|
const lib = await import(libName);
|
||||||
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop
|
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready; // eslint-disable-line no-await-in-loop
|
||||||
exports.methods = libs[libName](lib);
|
exports.methods = libs[libName](lib);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user