| @@ -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); | ||||
| } | } | ||||