Skip to content

Commit

Permalink
Switch to voice gateway v4
Browse files Browse the repository at this point in the history
  • Loading branch information
abalabahaha committed Apr 25, 2021
1 parent 95cffe4 commit 94e7e18
Showing 1 changed file with 83 additions and 75 deletions.
158 changes: 83 additions & 75 deletions lib/voice/VoiceConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Base = require("../structures/Base");
const ChildProcess = require("child_process");
const {VoiceOPCodes, GatewayOPCodes} = require("../Constants");
const Dgram = require("dgram");
const Net = require("net");
const Piper = require("./Piper");
const VoiceDataStream = require("./VoiceDataStream");
const {createOpus} = require("../util/Opus");
Expand Down Expand Up @@ -122,9 +123,9 @@ class VoiceConnection extends EventEmitter {

this.nonce = Buffer.alloc(24);

this.packetBuffer = Buffer.alloc(12 + 16 + MAX_FRAME_SIZE);
this.packetBuffer[0] = 0x80;
this.packetBuffer[1] = 0x78;
this.sendBuffer = Buffer.alloc(12 + 16 + MAX_FRAME_SIZE);
this.sendBuffer[0] = 0x80;
this.sendBuffer[1] = 0x78;

if(!options.shared) {
if(!converterCommand.cmd) {
Expand Down Expand Up @@ -174,8 +175,12 @@ class VoiceConnection extends EventEmitter {
return;
}
this.channelID = data.channel_id;
this.endpoint = data.endpoint.split(":")[0];
this.ws = new WebSocket("wss://" + this.endpoint);
this.endpoint = new URL(`wss://${data.endpoint}`);
if(this.endpoint.port === "80") {
this.endpoint.port = "";
}
this.endpoint.searchParams.set("v", 4);
this.ws = new WebSocket(this.endpoint.href);
/**
* Fired when stuff happens and gives more info
* @event VoiceConnection#debug
Expand Down Expand Up @@ -206,18 +211,8 @@ class VoiceConnection extends EventEmitter {
}
switch(packet.op) {
case VoiceOPCodes.READY: {
if(packet.d.heartbeat_interval > 0) {
if(this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
this.heartbeatInterval = setInterval(() => {
this.heartbeat();
}, packet.d.heartbeat_interval);
this.heartbeat();
}

this.ssrc = packet.d.ssrc;
this.packetBuffer.writeUIntBE(this.ssrc, 8, 4);
this.sendBuffer.writeUInt32BE(this.ssrc, 8);
if(!packet.d.modes.includes(ENCRYPTION_MODE)) {
throw new Error("No supported voice mode found");
}
Expand All @@ -229,15 +224,24 @@ class VoiceConnection extends EventEmitter {

this.emit("debug", "Connecting to UDP: " + this.udpIP + ":" + this.udpPort);

this.udpSocket = Dgram.createSocket("udp4");
this.udpSocket = Dgram.createSocket(Net.isIPv6(this.udpIP) ? "udp6" : "udp4");
this.udpSocket.on("error", (err, msg) => {
this.emit("error", err);
if(msg) {
this.emit("debug", "Voice UDP error: " + msg);
}
if(this.ready || this.connecting) {
this.disconnect(err);
}
});
this.udpSocket.once("message", (packet) => {
this.emit("debug", packet.toString());
let localIP = "";
let i = 3;
while(++i < packet.indexOf(0, i)) {
localIP += String.fromCharCode(packet[i]);
let i = 8;
while(packet[i] !== 0) {
i++;
}
const localPort = parseInt(packet.readUIntBE(packet.length - 2, 2).toString(10));
const localIP = packet.toString("ascii", 8, i);
const localPort = packet.readUInt16BE(packet.length - 2);
this.emit("debug", `Discovered IP: ${localIP}:${localPort} (${packet.toString("hex")})`);

this.sendWS(VoiceOPCodes.SELECT_PROTOCOL, {
protocol: "udp",
Expand All @@ -248,15 +252,6 @@ class VoiceConnection extends EventEmitter {
}
});
});
this.udpSocket.on("error", (err, msg) => {
this.emit("error", err);
if(msg) {
this.emit("debug", "Voice UDP error: " + msg);
}
if(this.ready || this.connecting) {
this.disconnect(err);
}
});
this.udpSocket.on("close", (err) => {
if(err) {
this.emit("warn", "Voice UDP close: " + err);
Expand All @@ -265,17 +260,16 @@ class VoiceConnection extends EventEmitter {
this.disconnect(err);
}
});
const udpMessage = Buffer.alloc(70);
udpMessage.writeUIntBE(this.ssrc, 0, 4);
const udpMessage = Buffer.allocUnsafe(70);
udpMessage.writeUInt16BE(0x1, 0);
udpMessage.writeUInt16BE(70, 2);
udpMessage.writeUInt32BE(this.ssrc, 4);
this._sendPacket(udpMessage);
break;
}
case VoiceOPCodes.SESSION_DESCRIPTION: {
this.mode = packet.d.mode;
this.secret = new Uint8Array(new ArrayBuffer(packet.d.secret_key.length));
for(let i = 0; i < packet.d.secret_key.length; ++i) {
this.secret[i] = packet.d.secret_key[i];
}
this.secret = Buffer.from(packet.d.secret_key);
this.connecting = false;
this.reconnecting = false;
this.ready = true;
Expand All @@ -287,7 +281,7 @@ class VoiceConnection extends EventEmitter {
this.resume();
break;
}
case VoiceOPCodes.HEARTBEAT: {
case VoiceOPCodes.HEARTBEAT_ACK: {
/**
* Fired when the voice connection receives a pong
* @event VoiceConnection#pong
Expand All @@ -311,6 +305,17 @@ class VoiceConnection extends EventEmitter {
this.emit(packet.d.speaking ? "speakingStart" : "speakingStop", packet.d.user_id);
break;
}
case VoiceOPCodes.HELLO: {
if(this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
this.heartbeatInterval = setInterval(() => {
this.heartbeat();
}, packet.d.heartbeat_interval);

this.heartbeat();
break;
}
case VoiceOPCodes.DISCONNECT: {
if(this.opus) {
// opusscript requires manual cleanup
Expand Down Expand Up @@ -339,7 +344,7 @@ class VoiceConnection extends EventEmitter {
this.emit("error", err);
});
this.ws.on("close", (code, reason) => {
const err = !code || code === 1000 ? null : new Error(code + ": " + reason);
let err = !code || code === 1000 ? null : new Error(code + ": " + reason);
this.emit("warn", `Voice WS close ${code}: ${reason}`);
if(this.connecting || this.ready) {
let reconnecting = true;
Expand Down Expand Up @@ -439,14 +444,19 @@ class VoiceConnection extends EventEmitter {

heartbeat() {
this.sendWS(VoiceOPCodes.HEARTBEAT, Date.now());
if(this.udpSocket) {
// NAT/connection table keep-alive
const udpMessage = Buffer.from([0x80, 0xC8, 0x0, 0x0]);
this._sendPacket(udpMessage);
}
}

/**
* Pause sending audio (if playing)
*/
pause() {
this.paused = true;
this.setSpeaking(false);
this.setSpeaking(0);
if(this.current) {
if(!this.current.pausedTimestamp) {
this.current.pausedTimestamp = Date.now();
Expand Down Expand Up @@ -549,7 +559,7 @@ class VoiceConnection extends EventEmitter {
const nonce = Buffer.alloc(24);
msg.copy(nonce, 0, 0, 12);
let data;
if(!NaCl) {
if(Sodium) {
data = Buffer.alloc(msg.length - 12 - Sodium.crypto_secretbox_MACBYTES);
Sodium.crypto_secretbox_open_easy(data, msg.subarray(12), nonce, this.secret);
} else {
Expand Down Expand Up @@ -608,13 +618,15 @@ class VoiceConnection extends EventEmitter {
*/
resume() {
this.paused = false;
this.setSpeaking(true);
if(this.current) {
this.setSpeaking(1);
if(this.current.pausedTimestamp) {
this.current.pausedTime += Date.now() - this.current.pausedTimestamp;
this.current.pausedTimestamp = 0;
}
this._send();
} else {
this.setSpeaking(0);
}
}

Expand All @@ -626,15 +638,13 @@ class VoiceConnection extends EventEmitter {
}
}

setSpeaking(value) {
if((value = !!value) != this.speaking) {
this.speaking = value;
this.sendWS(VoiceOPCodes.SPEAKING, {
speaking: value,
delay: 0,
ssrc: this.ssrc
});
}
setSpeaking(value, delay = 0) {
this.speaking = value === true ? 1 : value === false ? 0 : value;
this.sendWS(VoiceOPCodes.SPEAKING, {
speaking: value,
delay: delay,
ssrc: this.ssrc
});
}

/**
Expand Down Expand Up @@ -674,10 +684,11 @@ class VoiceConnection extends EventEmitter {
this.sequence -= 65536;
}

this._sendPacket(this._createPacket(Buffer.from([0xF8, 0xFF, 0xFE])));
this._sendAudioPacket(Buffer.from([0xF8, 0xFF, 0xFE]));
}
}
this.setSpeaking(this.playing = false);
this.playing = false;
this.setSpeaking(0);

/**
* Fired when the voice connection finishes playing a stream
Expand Down Expand Up @@ -721,29 +732,26 @@ class VoiceConnection extends EventEmitter {
}
}

_createPacket(_buffer) {
this.packetBuffer.writeUIntBE(this.sequence, 2, 2);
this.packetBuffer.writeUIntBE(this.timestamp, 4, 4);
_sendAudioPacket(audio) {
this.sendBuffer.writeUInt16BE(this.sequence, 2);
this.sendBuffer.writeUInt32BE(this.timestamp, 4);

this.packetBuffer.copy(this.nonce, 0, 0, 12);
this.sendBuffer.copy(this.nonce, 0, 0, 12);

let buffer;
let {length} = _buffer;
if(!NaCl) {
let length = audio.length;
if(Sodium) {
length += Sodium.crypto_secretbox_MACBYTES;

buffer = Buffer.allocUnsafe(length);
Sodium.crypto_secretbox_easy(buffer, _buffer, this.nonce, this.secret);
Sodium.crypto_secretbox_easy(buffer, audio, this.nonce, this.secret);
} else {
buffer = NaCl.secretbox(_buffer, this.nonce, this.secret);
length += NaCl.lowlevel.crypto_secretbox_BOXZEROBYTES;
let typedArray = NaCl.secretbox(audio, this.nonce, this.secret);
buffer = Buffer.from(typedArray.buffer, typedArray.byteOffset);
}
this.packetBuffer.fill(0, 12, 12 + length);
for(let i = 0; i < length; ++i) {
this.packetBuffer[12 + i] = buffer[i];
}
buffer.copy(this.sendBuffer, 12, 0, length)

return this.packetBuffer.slice(0, 12 + length);
return this._sendPacket(this.sendBuffer.slice(0, 12 + length));
}

_destroy() {
Expand Down Expand Up @@ -784,11 +792,11 @@ class VoiceConnection extends EventEmitter {
}
if(this.current.bufferingTicks > 0) {
this.current.bufferingTicks = 0;
this.setSpeaking(true);
this.setSpeaking(1);
}
} else if(this.current.options.voiceDataTimeout === -1 || this.current.bufferingTicks < this.current.options.voiceDataTimeout / (4 * this.current.options.frameDuration)) { // wait for data
if(++this.current.bufferingTicks === 1) {
this.setSpeaking(false);
this.setSpeaking(0);
}
this.current.pausedTime += 4 * this.current.options.frameDuration;
this.timestamp += 3 * this.current.options.frameSize;
Expand All @@ -801,16 +809,16 @@ class VoiceConnection extends EventEmitter {
return this.stopPlaying();
}

this._sendPacket(this._createPacket(this.current.buffer));
this._sendAudioPacket(this.current.buffer);
this.current.playTime += this.current.options.frameDuration;
this.current.timeout = setTimeout(this._send, this.current.startTime + this.current.pausedTime + this.current.playTime - Date.now());
}

_sendPacket(packet) {
try {
this.udpSocket.send(packet, 0, packet.length, this.udpPort, this.udpIP);
} catch(e) {
if(this.udpSocket) {
if(this.udpSocket) {
try {
this.udpSocket.send(packet, 0, packet.length, this.udpPort, this.udpIP);
} catch(e) {
this.emit("error", e);
}
}
Expand Down

0 comments on commit 94e7e18

Please sign in to comment.