PacketHandler.js (3595B)
1 'use strict'; 2 3 const EventEmitter = require('events'); 4 const secretbox = require('../util/Secretbox'); 5 6 // The delay between packets when a user is considered to have stopped speaking 7 // https://github.com/discordjs/discord.js/issues/3524#issuecomment-540373200 8 const DISCORD_SPEAKING_DELAY = 250; 9 10 class Readable extends require('stream').Readable { 11 _read() {} // eslint-disable-line no-empty-function 12 } 13 14 class PacketHandler extends EventEmitter { 15 constructor(receiver) { 16 super(); 17 this.nonce = Buffer.alloc(24); 18 this.receiver = receiver; 19 this.streams = new Map(); 20 this.speakingTimeouts = new Map(); 21 } 22 23 get connection() { 24 return this.receiver.connection; 25 } 26 27 _stoppedSpeaking(userID) { 28 const streamInfo = this.streams.get(userID); 29 if (streamInfo && streamInfo.end === 'silence') { 30 this.streams.delete(userID); 31 streamInfo.stream.push(null); 32 } 33 } 34 35 makeStream(user, end) { 36 if (this.streams.has(user)) return this.streams.get(user).stream; 37 const stream = new Readable(); 38 stream.on('end', () => this.streams.delete(user)); 39 this.streams.set(user, { stream, end }); 40 return stream; 41 } 42 43 parseBuffer(buffer) { 44 const { secret_key, mode } = this.receiver.connection.authentication; 45 46 // Choose correct nonce depending on encryption 47 let end; 48 if (mode === 'xsalsa20_poly1305_lite') { 49 buffer.copy(this.nonce, 0, buffer.length - 4); 50 end = buffer.length - 4; 51 } else if (mode === 'xsalsa20_poly1305_suffix') { 52 buffer.copy(this.nonce, 0, buffer.length - 24); 53 end = buffer.length - 24; 54 } else { 55 buffer.copy(this.nonce, 0, 0, 12); 56 } 57 58 // Open packet 59 let packet = secretbox.methods.open(buffer.slice(12, end), this.nonce, secret_key); 60 if (!packet) return new Error('Failed to decrypt voice packet'); 61 packet = Buffer.from(packet); 62 63 // Strip RTP Header Extensions (one-byte only) 64 if (packet[0] === 0xbe && packet[1] === 0xde && packet.length > 4) { 65 const headerExtensionLength = packet.readUInt16BE(2); 66 let offset = 4; 67 for (let i = 0; i < headerExtensionLength; i++) { 68 const byte = packet[offset]; 69 offset++; 70 if (byte === 0) continue; 71 offset += 1 + (0b1111 & (byte >> 4)); 72 } 73 // Skip over undocumented Discord byte 74 offset++; 75 76 packet = packet.slice(offset); 77 } 78 79 return packet; 80 } 81 82 push(buffer) { 83 const ssrc = buffer.readUInt32BE(8); 84 const userStat = this.connection.ssrcMap.get(ssrc); 85 if (!userStat) return; 86 87 let speakingTimeout = this.speakingTimeouts.get(ssrc); 88 if (typeof speakingTimeout === 'undefined') { 89 this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: userStat.speaking }); 90 speakingTimeout = this.receiver.connection.client.setTimeout(() => { 91 try { 92 this.connection.onSpeaking({ user_id: userStat.userID, ssrc: ssrc, speaking: 0 }); 93 this.receiver.connection.client.clearTimeout(speakingTimeout); 94 this.speakingTimeouts.delete(ssrc); 95 } catch { 96 // Connection already closed, ignore 97 } 98 }, DISCORD_SPEAKING_DELAY); 99 this.speakingTimeouts.set(ssrc, speakingTimeout); 100 } else { 101 speakingTimeout.refresh(); 102 } 103 104 let stream = this.streams.get(userStat.userID); 105 if (!stream) return; 106 stream = stream.stream; 107 const opusPacket = this.parseBuffer(buffer); 108 if (opusPacket instanceof Error) { 109 this.emit('error', opusPacket); 110 return; 111 } 112 stream.push(opusPacket); 113 } 114 } 115 116 module.exports = PacketHandler;