feat: removing old voice encryption modes

- VP8 working (ok)
- H264 working (bad)
by #101 (dank074/Discord-video-stream)
This commit is contained in:
Elysia
2024-09-16 22:01:15 +07:00
parent e9d81ce25c
commit 99e348fe2b
5 changed files with 42 additions and 52 deletions

View File

@@ -22,15 +22,7 @@ class SingleSilence extends Silence {
}
}
const SUPPORTED_MODES = [
// Avalible (but only voice working)
// 'aead_aes256_gcm_rtpsize',
// 'aead_xchacha20_poly1305_rtpsize',
// Deprecated
'xsalsa20_poly1305_lite',
'xsalsa20_poly1305_suffix',
'xsalsa20_poly1305',
];
const SUPPORTED_MODES = ['aead_aes256_gcm_rtpsize', 'aead_xchacha20_poly1305_rtpsize'];
const SUPPORTED_CODECS = ['VP8', 'H264'];
/**

View File

@@ -38,13 +38,12 @@ class AnnexBDispatcher extends VideoDispatcher {
const nalu = accessUnit.subarray(offset, offset + naluSize);
const isLastNal = offset + naluSize >= accessUnit.length;
if (nalu.length <= this.mtu) {
// If NALU size is within MTU, send it directly
this._playChunk(Buffer.concat([this.createHeaderExtension(), nalu]), isLastNal);
// Send as Single NAL Unit Packet.
this._playChunk(Buffer.concat([this.createPayloadExtension(), nalu]), isLastNal);
} else {
// If NALU size exceeds MTU, fragment it
const [naluHeader, naluData] = this._nalFunctions.splitHeader(nalu);
const dataFragments = this.partitionVideoData(naluData);
// Send as Fragmentation Unit A (FU-A):
for (let fragmentIndex = 0; fragmentIndex < dataFragments.length; fragmentIndex++) {
const data = dataFragments[fragmentIndex];
const isFirstPacket = fragmentIndex === 0;
@@ -52,7 +51,7 @@ class AnnexBDispatcher extends VideoDispatcher {
this._playChunk(
Buffer.concat([
this.createHeaderExtension(),
this.createPayloadExtension(),
this.makeFragmentationUnitHeader(isFirstPacket, isFinalPacket, naluHeader),
data,
]),

View File

@@ -11,6 +11,8 @@ const secretbox = require('../util/Secretbox');
const CHANNELS = 2;
const MAX_NONCE_SIZE = 2 ** 32 - 1;
const extensions = [{ id: 5, len: 2, val: 0 }];
/**
* @external WritableStream
* @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
* 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
* Buffer <be de 00 01 51 00 00 00>
* @private
* Creates a one-byte extension header
* https://www.rfc-editor.org/rfc/rfc5285#section-4.2
* @returns {Buffer} extension header
*/
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -276,6 +274,17 @@ class BaseDispatcher extends Writable {
profile[0] = 0xbe;
profile[1] = 0xde;
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 = [];
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
extensionsData.push(data);
}
return Buffer.concat([profile, ...extensionsData]);
return Buffer.concat(extensionsData);
}
_encrypt(buffer, additionalData) {
@@ -338,16 +347,6 @@ class BaseDispatcher extends Writable {
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: {
// 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}`);
@@ -357,7 +356,9 @@ class BaseDispatcher extends Writable {
_createPacket(buffer, isLastPacket = false) {
// 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[1] = this.payloadType; // Payload Type (1 byte)

View File

@@ -26,8 +26,6 @@ class VP8Dispatcher extends VideoDispatcher {
}
makeChunk(buffer, isFirstFrame) {
// Make frame
const headerExtensionBuf = this.createHeaderExtension();
// Vp8 payload descriptor
const payloadDescriptorBuf = Buffer.alloc(2);
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);
pictureIdBuf.writeUintBE(this.count, 0, 2);
pictureIdBuf[0] |= 0x80;
return Buffer.concat([headerExtensionBuf, payloadDescriptorBuf, pictureIdBuf, buffer]);
return Buffer.concat([payloadDescriptorBuf, pictureIdBuf, buffer]);
}
codecCallback(chunk) {
const chunkSplit = this.partitionVideoData(chunk);
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,
);
}
}
}

View File

@@ -2,39 +2,36 @@
const libs = {
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) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
crypto_aead_xchacha20poly1305_ietf_decrypt: (plaintext, additionalData, nonce, key) =>
sodium.api.crypto_aead_xchacha20poly1305_ietf_decrypt(plaintext, additionalData, null, nonce, key),
}),
'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) =>
sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, additionalData, null, nonce, key),
crypto_aead_xchacha20poly1305_ietf_decrypt: (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() {
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 = {
open: NoLib,
close: NoLib,
random: NoLib,
crypto_aead_xchacha20poly1305_ietf_encrypt: NoLib,
crypto_aead_xchacha20poly1305_ietf_decrypt: NoLib,
};
@@ -42,7 +39,7 @@ exports.methods = {
(async () => {
for (const libName of Object.keys(libs)) {
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
exports.methods = libs[libName](lib);
break;