| @@ -12,6 +12,7 @@ using System.Text; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | |||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| { | { | ||||
| @@ -45,14 +46,17 @@ namespace Discord.Audio | |||||
| private string _url, _sessionId, _token; | private string _url, _sessionId, _token; | ||||
| private ulong _userId; | private ulong _userId; | ||||
| private uint _ssrc; | private uint _ssrc; | ||||
| private byte[] _secretKey; | |||||
| private GCHandle _secretKeyHandle; | |||||
| private bool _isSpeaking; | private bool _isSpeaking; | ||||
| private bool _isDisposed; | |||||
| public SocketGuild Guild { get; } | public SocketGuild Guild { get; } | ||||
| public DiscordVoiceAPIClient ApiClient { get; private set; } | public DiscordVoiceAPIClient ApiClient { get; private set; } | ||||
| public int Latency { get; private set; } | public int Latency { get; private set; } | ||||
| public int UdpLatency { get; private set; } | public int UdpLatency { get; private set; } | ||||
| public ulong ChannelId { get; internal set; } | public ulong ChannelId { get; internal set; } | ||||
| internal byte[] SecretKey { get; private set; } | |||||
| internal IntPtr SecretKeyPtr { get; private set; } | |||||
| private DiscordSocketClient Discord => Guild.Discord; | private DiscordSocketClient Discord => Guild.Discord; | ||||
| public ConnectionState ConnectionState => _connection.State; | public ConnectionState ConnectionState => _connection.State; | ||||
| @@ -93,6 +97,20 @@ namespace Discord.Audio | |||||
| UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); | ||||
| } | } | ||||
| internal void Dispose(bool disposing) | |||||
| { | |||||
| if (disposing && !_isDisposed) | |||||
| { | |||||
| StopAsync().GetAwaiter().GetResult(); | |||||
| ApiClient.Dispose(); | |||||
| if (_secretKeyHandle.IsAllocated) | |||||
| _secretKeyHandle.Free(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void Dispose() => Dispose(true); | |||||
| internal async Task StartAsync(string url, ulong userId, string sessionId, string token) | internal async Task StartAsync(string url, ulong userId, string sessionId, string token) | ||||
| { | { | ||||
| _url = url; | _url = url; | ||||
| @@ -242,7 +260,12 @@ namespace Discord.Audio | |||||
| if (data.Mode != DiscordVoiceAPIClient.Mode) | if (data.Mode != DiscordVoiceAPIClient.Mode) | ||||
| throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); | ||||
| SecretKey = data.SecretKey; | |||||
| _secretKey = data.SecretKey; | |||||
| if (_secretKeyHandle != null) | |||||
| _secretKeyHandle.Free(); | |||||
| _secretKeyHandle = GCHandle.Alloc(data.SecretKey, GCHandleType.Pinned); | |||||
| SecretKeyPtr = _secretKeyHandle.AddrOfPinnedObject(); | |||||
| _isSpeaking = false; | _isSpeaking = false; | ||||
| await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); | await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); | ||||
| _keepaliveTask = RunKeepaliveAsync(5000, _connection.CancelToken); | _keepaliveTask = RunKeepaliveAsync(5000, _connection.CancelToken); | ||||
| @@ -386,7 +409,7 @@ namespace Discord.Audio | |||||
| await _audioLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | await _audioLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); | ||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| var now = Environment.TickCount; | |||||
| int now = Environment.TickCount; | |||||
| //Did server respond to our last heartbeat? | //Did server respond to our last heartbeat? | ||||
| if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis && | if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis && | ||||
| @@ -427,7 +450,7 @@ namespace Discord.Audio | |||||
| await _audioLogger.DebugAsync("Keepalive Started").ConfigureAwait(false); | await _audioLogger.DebugAsync("Keepalive Started").ConfigureAwait(false); | ||||
| while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
| { | { | ||||
| var now = Environment.TickCount; | |||||
| int now = Environment.TickCount; | |||||
| try | try | ||||
| { | { | ||||
| @@ -474,16 +497,5 @@ namespace Discord.Audio | |||||
| return buffer; | return buffer; | ||||
| return new byte[OpusConverter.FrameBytes]; | return new byte[OpusConverter.FrameBytes]; | ||||
| } | } | ||||
| internal void Dispose(bool disposing) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| StopAsync().GetAwaiter().GetResult(); | |||||
| ApiClient.Dispose(); | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void Dispose() => Dispose(true); | |||||
| } | } | ||||
| } | } | ||||
| @@ -20,12 +20,9 @@ namespace Discord.Audio | |||||
| CheckError(error); | CheckError(error); | ||||
| } | } | ||||
| public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset, bool decodeFEC) | |||||
| public unsafe int DecodeFrame(byte* inPtr, int inputOffset, int inputCount, byte* outPtr, int outputOffset, bool decodeFEC) | |||||
| { | { | ||||
| int result = 0; | |||||
| fixed (byte* inPtr = input) | |||||
| fixed (byte* outPtr = output) | |||||
| result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, decodeFEC ? 1 : 0); | |||||
| int result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, decodeFEC ? 1 : 0); | |||||
| CheckError(result); | CheckError(result); | ||||
| return result * SampleBytes; | return result * SampleBytes; | ||||
| } | } | ||||
| @@ -53,12 +53,9 @@ namespace Discord.Audio | |||||
| CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate)); | CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate)); | ||||
| } | } | ||||
| public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output, int outputOffset) | |||||
| public unsafe int EncodeFrame(byte* inPtr, int inputOffset, byte* outPtr, int outputOffset, int count) | |||||
| { | { | ||||
| int result = 0; | |||||
| fixed (byte* inPtr = input) | |||||
| fixed (byte* outPtr = output) | |||||
| result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, output.Length - outputOffset); | |||||
| int result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, count); | |||||
| CheckError(result); | CheckError(result); | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -6,31 +6,23 @@ namespace Discord.Audio | |||||
| public unsafe static class SecretBox | public unsafe static class SecretBox | ||||
| { | { | ||||
| [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
| private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | |||||
| private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte* nonce, byte* secret); | |||||
| [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] | [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] | ||||
| private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); | |||||
| private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte* nonce, byte* secret); | |||||
| public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | |||||
| public static int Encrypt(byte* inPtr, int inputOffset, int inputLength, byte* outPtr, int outputOffset, byte* nonce, byte* secret) | |||||
| { | { | ||||
| fixed (byte* inPtr = input) | |||||
| fixed (byte* outPtr = output) | |||||
| { | |||||
| int error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); | |||||
| if (error != 0) | |||||
| throw new Exception($"Sodium Error: {error}"); | |||||
| return inputLength + 16; | |||||
| } | |||||
| int error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); | |||||
| if (error != 0) | |||||
| throw new Exception($"Sodium Error: {error}"); | |||||
| return inputLength + 16; | |||||
| } | } | ||||
| public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) | |||||
| public static int Decrypt(byte* inPtr, int inputOffset, int inputLength, byte* outPtr, int outputOffset, byte* nonce, byte* secret) | |||||
| { | { | ||||
| fixed (byte* inPtr = input) | |||||
| fixed (byte* outPtr = output) | |||||
| { | |||||
| int error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); | |||||
| if (error != 0) | |||||
| throw new Exception($"Sodium Error: {error}"); | |||||
| return inputLength - 16; | |||||
| } | |||||
| int error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); | |||||
| if (error != 0) | |||||
| throw new Exception($"Sodium Error: {error}"); | |||||
| return inputLength - 16; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -12,15 +13,31 @@ namespace Discord.Audio.Streams | |||||
| private readonly AudioStream _next; | private readonly AudioStream _next; | ||||
| private readonly OpusDecoder _decoder; | private readonly OpusDecoder _decoder; | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly GCHandle _bufferHandle; | |||||
| private readonly IntPtr _bufferPtr; | |||||
| private bool _nextMissed; | private bool _nextMissed; | ||||
| private bool _hasHeader; | private bool _hasHeader; | ||||
| private bool _isDisposed; | |||||
| public OpusDecodeStream(AudioStream next) | public OpusDecodeStream(AudioStream next) | ||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _buffer = new byte[OpusConverter.FrameBytes]; | _buffer = new byte[OpusConverter.FrameBytes]; | ||||
| _bufferHandle = GCHandle.Alloc(_buffer, GCHandleType.Pinned); | |||||
| _bufferPtr = _bufferHandle.AddrOfPinnedObject(); | |||||
| _decoder = new OpusDecoder(); | _decoder = new OpusDecoder(); | ||||
| } | } | ||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing && !_isDisposed) | |||||
| { | |||||
| _decoder.Dispose(); | |||||
| _bufferHandle.Free(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | |||||
| public override void WriteHeader(ushort seq, uint timestamp, bool missed) | public override void WriteHeader(ushort seq, uint timestamp, bool missed) | ||||
| { | { | ||||
| @@ -39,17 +56,28 @@ namespace Discord.Audio.Streams | |||||
| if (!_nextMissed) | if (!_nextMissed) | ||||
| { | { | ||||
| count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, false); | |||||
| unsafe | |||||
| { | |||||
| fixed (byte* inPtr = buffer) | |||||
| count = _decoder.DecodeFrame(inPtr, offset, count, (byte*)_bufferPtr, 0, false); | |||||
| } | |||||
| await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| else if (count > 0) | else if (count > 0) | ||||
| { | { | ||||
| count = _decoder.DecodeFrame(buffer, offset, count, _buffer, 0, true); | |||||
| unsafe | |||||
| { | |||||
| fixed(byte* inPtr = buffer) | |||||
| count = _decoder.DecodeFrame(inPtr, offset, count, (byte*)_bufferPtr, 0, true); | |||||
| } | |||||
| await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| count = _decoder.DecodeFrame(null, 0, 0, _buffer, 0, true); | |||||
| unsafe | |||||
| { | |||||
| count = _decoder.DecodeFrame(null, 0, 0, (byte*)_bufferPtr, 0, true); | |||||
| } | |||||
| await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(_buffer, 0, count, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| } | } | ||||
| @@ -62,13 +90,5 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| await _next.ClearAsync(cancelToken).ConfigureAwait(false); | await _next.ClearAsync(cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing) | |||||
| _decoder.Dispose(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -12,26 +13,48 @@ namespace Discord.Audio.Streams | |||||
| private readonly AudioStream _next; | private readonly AudioStream _next; | ||||
| private readonly OpusEncoder _encoder; | private readonly OpusEncoder _encoder; | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly GCHandle _bufferHandle; | |||||
| private readonly IntPtr _bufferPtr; | |||||
| private int _partialFramePos; | private int _partialFramePos; | ||||
| private ushort _seq; | private ushort _seq; | ||||
| private uint _timestamp; | private uint _timestamp; | ||||
| private bool _isDisposed; | |||||
| public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss) | public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss) | ||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _encoder = new OpusEncoder(bitrate, application, packetLoss); | _encoder = new OpusEncoder(bitrate, application, packetLoss); | ||||
| _buffer = new byte[OpusConverter.FrameBytes]; | _buffer = new byte[OpusConverter.FrameBytes]; | ||||
| _bufferHandle = GCHandle.Alloc(_buffer, GCHandleType.Pinned); | |||||
| _bufferPtr = _bufferHandle.AddrOfPinnedObject(); | |||||
| } | |||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing && !_isDisposed) | |||||
| { | |||||
| _encoder.Dispose(); | |||||
| _bufferHandle.Free(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | } | ||||
| public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | ||||
| { | { | ||||
| //Assume threadsafe | //Assume threadsafe | ||||
| int encFrameSize = 0; | |||||
| while (count > 0) | while (count > 0) | ||||
| { | { | ||||
| if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes) | if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes) | ||||
| { | { | ||||
| //We have enough data and no partial frames. Pass the buffer directly to the encoder | //We have enough data and no partial frames. Pass the buffer directly to the encoder | ||||
| int encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0); | |||||
| unsafe | |||||
| { | |||||
| fixed (byte* inPtr = buffer) | |||||
| encFrameSize = _encoder.EncodeFrame(inPtr, offset, (byte*)_bufferPtr, 0, OpusConverter.FrameBytes); | |||||
| } | |||||
| _next.WriteHeader(_seq, _timestamp, false); | _next.WriteHeader(_seq, _timestamp, false); | ||||
| await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); | ||||
| @@ -45,7 +68,10 @@ namespace Discord.Audio.Streams | |||||
| //We have enough data to complete a previous partial frame. | //We have enough data to complete a previous partial frame. | ||||
| int partialSize = OpusConverter.FrameBytes - _partialFramePos; | int partialSize = OpusConverter.FrameBytes - _partialFramePos; | ||||
| Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize); | Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize); | ||||
| int encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0); | |||||
| unsafe | |||||
| { | |||||
| encFrameSize = _encoder.EncodeFrame((byte*)_bufferPtr, 0, (byte*)_bufferPtr, 0, OpusConverter.FrameBytes); | |||||
| } | |||||
| _next.WriteHeader(_seq, _timestamp, false); | _next.WriteHeader(_seq, _timestamp, false); | ||||
| await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); | ||||
| @@ -86,13 +112,5 @@ namespace Discord.Audio.Streams | |||||
| { | { | ||||
| await _next.ClearAsync(cancelToken).ConfigureAwait(false); | await _next.ClearAsync(cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing) | |||||
| _encoder.Dispose(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -10,6 +11,10 @@ namespace Discord.Audio.Streams | |||||
| private readonly AudioClient _client; | private readonly AudioClient _client; | ||||
| private readonly AudioStream _next; | private readonly AudioStream _next; | ||||
| private readonly byte[] _nonce; | private readonly byte[] _nonce; | ||||
| private readonly GCHandle _nonceHandle; | |||||
| private readonly IntPtr _noncePtr; | |||||
| private bool _isDisposed; | |||||
| public override bool CanRead => true; | public override bool CanRead => true; | ||||
| public override bool CanSeek => false; | public override bool CanSeek => false; | ||||
| @@ -20,17 +25,32 @@ namespace Discord.Audio.Streams | |||||
| _next = next; | _next = next; | ||||
| _client = (AudioClient)client; | _client = (AudioClient)client; | ||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| _nonceHandle = GCHandle.Alloc(_nonce, GCHandleType.Pinned); | |||||
| _noncePtr = _nonceHandle.AddrOfPinnedObject(); | |||||
| } | |||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing && !_isDisposed) | |||||
| { | |||||
| _nonceHandle.Free(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | } | ||||
| public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) | ||||
| { | { | ||||
| cancelToken.ThrowIfCancellationRequested(); | cancelToken.ThrowIfCancellationRequested(); | ||||
| if (_client.SecretKey == null) | |||||
| if (_client.SecretKeyPtr == null) | |||||
| return; | return; | ||||
| Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce | Buffer.BlockCopy(buffer, 0, _nonce, 0, 12); //Copy RTP header to nonce | ||||
| count = SecretBox.Decrypt(buffer, offset + 12, count - 12, buffer, offset + 12, _nonce, _client.SecretKey); | |||||
| unsafe | |||||
| { | |||||
| fixed (byte* ptr = buffer) | |||||
| count = SecretBox.Decrypt(ptr, offset + 12, count - 12, ptr, offset + 12, (byte*)_noncePtr, (byte*)_client.SecretKeyPtr); | |||||
| } | |||||
| await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.Runtime.InteropServices; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -10,17 +11,32 @@ namespace Discord.Audio.Streams | |||||
| private readonly AudioClient _client; | private readonly AudioClient _client; | ||||
| private readonly AudioStream _next; | private readonly AudioStream _next; | ||||
| private readonly byte[] _nonce; | private readonly byte[] _nonce; | ||||
| private readonly GCHandle _nonceHandle; | |||||
| private readonly IntPtr _noncePtr; | |||||
| private bool _hasHeader; | private bool _hasHeader; | ||||
| private ushort _nextSeq; | private ushort _nextSeq; | ||||
| private uint _nextTimestamp; | private uint _nextTimestamp; | ||||
| private bool _isDisposed; | |||||
| public SodiumEncryptStream(AudioStream next, IAudioClient client) | public SodiumEncryptStream(AudioStream next, IAudioClient client) | ||||
| { | { | ||||
| _next = next; | _next = next; | ||||
| _client = (AudioClient)client; | _client = (AudioClient)client; | ||||
| _nonce = new byte[24]; | _nonce = new byte[24]; | ||||
| _nonceHandle = GCHandle.Alloc(_nonce, GCHandleType.Pinned); | |||||
| _noncePtr = _nonceHandle.AddrOfPinnedObject(); | |||||
| } | |||||
| protected override void Dispose(bool disposing) | |||||
| { | |||||
| base.Dispose(disposing); | |||||
| if (disposing && !_isDisposed) | |||||
| { | |||||
| _nonceHandle.Free(); | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | } | ||||
| public override void WriteHeader(ushort seq, uint timestamp, bool missed) | public override void WriteHeader(ushort seq, uint timestamp, bool missed) | ||||
| { | { | ||||
| if (_hasHeader) | if (_hasHeader) | ||||
| @@ -37,11 +53,15 @@ namespace Discord.Audio.Streams | |||||
| throw new InvalidOperationException("Received payload without an RTP header"); | throw new InvalidOperationException("Received payload without an RTP header"); | ||||
| _hasHeader = false; | _hasHeader = false; | ||||
| if (_client.SecretKey == null) | |||||
| if (_client.SecretKeyPtr == null) | |||||
| return; | return; | ||||
| Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header | Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header | ||||
| count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey); | |||||
| unsafe | |||||
| { | |||||
| fixed (byte* ptr = buffer) | |||||
| count = SecretBox.Encrypt(ptr, offset + 12, count - 12, ptr, 12, (byte*)_noncePtr, (byte*)_client.SecretKeyPtr); | |||||
| } | |||||
| _next.WriteHeader(_nextSeq, _nextTimestamp, false); | _next.WriteHeader(_nextSeq, _nextTimestamp, false); | ||||
| await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); | ||||
| } | } | ||||