@@ -62,6 +62,9 @@ | |||||
<Content Include="lib\libopus.so"> | <Content Include="lib\libopus.so"> | ||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
</Content> | </Content> | ||||
<Content Include="lib\libsodium.dll"> | |||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||||
</Content> | |||||
<Content Include="lib\opus.dll"> | <Content Include="lib\opus.dll"> | ||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
</Content> | </Content> | ||||
@@ -101,6 +104,9 @@ | |||||
<Compile Include="..\Discord.Net\Audio\OpusEncoder.cs"> | <Compile Include="..\Discord.Net\Audio\OpusEncoder.cs"> | ||||
<Link>Audio\OpusEncoder.cs</Link> | <Link>Audio\OpusEncoder.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Audio\Sodium.cs"> | |||||
<Link>Audio\Sodium.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\Collections\AsyncCollection.cs"> | <Compile Include="..\Discord.Net\Collections\AsyncCollection.cs"> | ||||
<Link>Collections\AsyncCollection.cs</Link> | <Link>Collections\AsyncCollection.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices; | |||||
namespace Discord.Audio | namespace Discord.Audio | ||||
{ | { | ||||
internal unsafe class Opus | |||||
internal static unsafe class Opus | |||||
{ | { | ||||
[DllImport("lib/opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] | [DllImport("lib/opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] | ||||
public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error); | public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out Error error); | ||||
@@ -0,0 +1,15 @@ | |||||
using System.Runtime.InteropServices; | |||||
namespace Discord.Audio | |||||
{ | |||||
internal static class Sodium | |||||
{ | |||||
[DllImport("lib/libsodium", EntryPoint = "crypto_stream_xor", CallingConvention = CallingConvention.Cdecl)] | |||||
private static extern int StreamXOR(byte[] output, byte[] msg, long msgLength, byte[] nonce, byte[] secret); | |||||
public static int Encrypt(byte[] buffer, int inputLength, byte[] output, byte[] nonce, byte[] secret) | |||||
{ | |||||
return StreamXOR(output, buffer, inputLength, nonce, secret); | |||||
} | |||||
} | |||||
} |
@@ -4,7 +4,7 @@ namespace Discord | |||||
{ | { | ||||
public class DiscordClientConfig | public class DiscordClientConfig | ||||
{ | { | ||||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to verbose will hinder performance but should help investigate any internal issues. </summary> | |||||
/// <summary> Specifies the minimum log level severity that will be sent to the LogMessage event. Warning: setting this to debug will really hurt performance but should help investigate any internal issues. </summary> | |||||
public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | public LogMessageSeverity LogLevel { get { return _logLevel; } set { SetValue(ref _logLevel, value); } } | ||||
private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | private LogMessageSeverity _logLevel = LogMessageSeverity.Info; | ||||
@@ -36,8 +36,12 @@ namespace Discord | |||||
/// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the opus .dll or .so be in the local lib/ folder. </summary> | /// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the opus .dll or .so be in the local lib/ folder. </summary> | ||||
public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } | public bool EnableVoice { get { return _enableVoice; } set { SetValue(ref _enableVoice, value); } } | ||||
private bool _enableVoice = false; | private bool _enableVoice = false; | ||||
/// <summary> (Experimental) Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local lib/ folder. </summary> | |||||
public bool EnableVoiceEncryption { get { return _enableVoiceEncryption; } set { SetValue(ref _enableVoiceEncryption, value); } } | |||||
private bool _enableVoiceEncryption = false; | |||||
#else | #else | ||||
internal bool EnableVoice => false; | internal bool EnableVoice => false; | ||||
internal bool EnableVoiceEncryption => false; | |||||
#endif | #endif | ||||
/// <summary> (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. </summary> | /// <summary> (Experimental) Enables or disables the internal message queue. This will allow SendMessage to return immediately and handle messages internally. Messages will set the IsQueued and HasFailed properties to show their progress. </summary> | ||||
public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } | public bool UseMessageQueue { get { return _useMessageQueue; } set { SetValue(ref _useMessageQueue, value); } } | ||||
@@ -33,7 +33,7 @@ namespace Discord.WebSockets.Voice | |||||
private byte[] _secretKey; | private byte[] _secretKey; | ||||
private ushort _sequence; | private ushort _sequence; | ||||
private byte[] _encodingBuffer; | private byte[] _encodingBuffer; | ||||
private string _serverId, _userId, _sessionId, _token; | |||||
private string _serverId, _userId, _sessionId, _token, _encryptionMode; | |||||
#if USE_THREAD | #if USE_THREAD | ||||
private Thread _sendThread; | private Thread _sendThread; | ||||
@@ -213,12 +213,18 @@ namespace Discord.WebSockets.Voice | |||||
Stopwatch sw = Stopwatch.StartNew(); | Stopwatch sw = Stopwatch.StartNew(); | ||||
byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; | byte[] rtpPacket = new byte[_encodingBuffer.Length + 12]; | ||||
byte[] nonce = null; | |||||
rtpPacket[0] = 0x80; //Flags; | rtpPacket[0] = 0x80; //Flags; | ||||
rtpPacket[1] = 0x78; //Payload Type | rtpPacket[1] = 0x78; //Payload Type | ||||
rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); | rtpPacket[8] = (byte)((_ssrc >> 24) & 0xFF); | ||||
rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); | rtpPacket[9] = (byte)((_ssrc >> 16) & 0xFF); | ||||
rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); | rtpPacket[10] = (byte)((_ssrc >> 8) & 0xFF); | ||||
rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); | rtpPacket[11] = (byte)((_ssrc >> 0) & 0xFF); | ||||
if (_isEncrypted) | |||||
{ | |||||
nonce = new byte[24]; | |||||
Buffer.BlockCopy(rtpPacket, 0, nonce, 0, 12); | |||||
} | |||||
while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
@@ -238,6 +244,13 @@ namespace Discord.WebSockets.Voice | |||||
rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); | rtpPacket[5] = (byte)((timestamp >> 16) & 0xFF); | ||||
rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); | rtpPacket[6] = (byte)((timestamp >> 8) & 0xFF); | ||||
rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); | rtpPacket[7] = (byte)((timestamp >> 0) & 0xFF); | ||||
if (_isEncrypted) | |||||
{ | |||||
Buffer.BlockCopy(rtpPacket, 2, nonce, 2, 6); //Update nonce | |||||
int ret = Sodium.Encrypt(packet, packet.Length, packet, nonce, _secretKey); | |||||
if (ret != 0) | |||||
continue; | |||||
} | |||||
Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); | Buffer.BlockCopy(packet, 0, rtpPacket, 12, packet.Length); | ||||
#if USE_THREAD | #if USE_THREAD | ||||
_udp.Send(rtpPacket, packet.Length + 12); | _udp.Send(rtpPacket, packet.Length + 12); | ||||
@@ -298,8 +311,22 @@ namespace Discord.WebSockets.Voice | |||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
_ssrc = payload.SSRC; | _ssrc = payload.SSRC; | ||||
_endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | _endpoint = new IPEndPoint((await Dns.GetHostAddressesAsync(Host.Replace("wss://", "")).ConfigureAwait(false)).FirstOrDefault(), payload.Port); | ||||
//_mode = payload.Modes.LastOrDefault(); | |||||
_isEncrypted = !payload.Modes.Contains("plain"); | |||||
if (_client.Config.EnableVoiceEncryption) | |||||
{ | |||||
if (payload.Modes.Contains(EncryptedMode)) | |||||
{ | |||||
_encryptionMode = EncryptedMode; | |||||
_isEncrypted = true; | |||||
} | |||||
else | |||||
throw new InvalidOperationException("Unexpected encryption format."); | |||||
} | |||||
else | |||||
{ | |||||
_encryptionMode = UnencryptedMode; | |||||
_isEncrypted = false; | |||||
} | |||||
_udp.Connect(_endpoint); | _udp.Connect(_endpoint); | ||||
_sequence = (ushort)_rand.Next(0, ushort.MaxValue); | _sequence = (ushort)_rand.Next(0, ushort.MaxValue); | ||||
@@ -361,7 +388,7 @@ namespace Discord.WebSockets.Voice | |||||
var login2 = new Login2Command(); | var login2 = new Login2Command(); | ||||
login2.Payload.Protocol = "udp"; | login2.Payload.Protocol = "udp"; | ||||
login2.Payload.SocketData.Address = ip; | login2.Payload.SocketData.Address = ip; | ||||
login2.Payload.SocketData.Mode = _isEncrypted ? EncryptedMode : UnencryptedMode; | |||||
login2.Payload.SocketData.Mode = _encryptionMode; | |||||
login2.Payload.SocketData.Port = port; | login2.Payload.SocketData.Port = port; | ||||
QueueMessage(login2); | QueueMessage(login2); | ||||
} | } | ||||