diff --git a/src/client/voice/VoiceConnectionUDPClient.js b/src/client/voice/VoiceConnectionUDPClient.js index 118c55a9a..93c0a1c6a 100644 --- a/src/client/voice/VoiceConnectionUDPClient.js +++ b/src/client/voice/VoiceConnectionUDPClient.js @@ -3,9 +3,132 @@ const dns = require('dns'); const Constants = require('../../util/Constants'); const EventEmitter = require('events').EventEmitter; +function parseLocalPacket(message) { + try { + const packet = new Buffer(message); + let address = ''; + for (let i = 4; i < packet.indexOf(0, i); i++) { + address += String.fromCharCode(packet[i]); + } + const port = parseInt(packet.readUIntLE(packet.length - 2, 2).toString(10), 10); + return { address, port }; + } catch (error) { + return { error }; + } +} + +/** + * Represents a UDP Client for a Voice Connection + * @extends {EventEmitter} + */ class VoiceConnectionUDPClient extends EventEmitter { - constructor(voiceConnection, data) { + constructor(voiceConnection) { super(); + /** + * The voice connection that this UDP client serves + * @type {VoiceConnection} + */ + this.voiceConnection = voiceConnection; + /** + * The UDP socket + * @type {?Socket} + */ + this.socket = null; + /** + * The address of the discord voice server + * @type {?string} + */ + this.discordAddress = null; + /** + * The local IP address + * @type {?string} + */ + this.localAddress = null; + /** + * The local port + * @type {?string} + */ + this.localPort = null; + } + /** + * The port of the discord voice server + * @readonly + */ + get discordPort() { + return this.voiceConnection.authentication.port; + } + + /** + * Tries to resolve the voice server endpoint to an address + * @returns {Promise} + */ + findEndpointAddress() { + return new Promise((resolve, reject) => { + dns.lookup(this.voiceConnection.authentication.endpoint, (error, address) => { + if (error) { + reject(error); + return; + } + resolve(address); + }); + }); + } + + /** + * Send a packet to the UDP client + * @param {Object} packet the packet to send + * @returns {Promise} + */ + send(packet) { + return new Promise((resolve, reject) => { + if (this.socket) { + if (!this.address || !this.port) { + reject(new Error('malformed UDP address or port')); + return; + } + this.socket.send(packet, 0, packet.length, this.port, this.address, error => { + if (error) { + reject(error); + } else { + resolve(packet); + } + }); + } else { + reject(new Error('tried to send a UDP packet but there is no socket available')); + } + }); + } + + createUDPSocket(address) { + this.discordAddress = address; + const socket = this.socket = udp.createSocket('udp4'); + + socket.once('message', message => { + const packet = parseLocalPacket(message); + if (packet.error) { + this.emit('error', packet.error); + return; + } + + this.localAddress = packet.address; + this.localPort = packet.port; + + this.voiceConnection.sockets.ws.sendPacket({ + op: Constants.VoiceOPCodes.SELECT_PROTOCOL, + d: { + protocol: 'udp', + data: { + address: packet.address, + port: packet.port, + mode: 'xsalsa20_poly1305', + }, + }, + }); + }); + + const blankMessage = new Buffer(70); + blankMessage.writeUIntBE(this.voiceConnection.authentication.ssrc, 0, 4); + this.send(blankMessage); } }