using Discord.Net.Udp; using System; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace Discord.Net.Providers.UnstableUdpSocket { internal class UnstableUdpSocket : IUdpSocket, IDisposable { private const double FailureRate = 0.10; //10% public event Func ReceivedDatagram; private readonly SemaphoreSlim _lock; private readonly Random _rand; private UdpClient _udp; private IPEndPoint _destination; private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; private Task _task; private bool _isDisposed; public UnstableUdpSocket() { _lock = new SemaphoreSlim(1, 1); _rand = new Random(); _cancelTokenSource = new CancellationTokenSource(); } private void Dispose(bool disposing) { if (!_isDisposed) { if (disposing) StopInternalAsync(true).GetAwaiter().GetResult(); _isDisposed = true; } } public void Dispose() { Dispose(true); } public async Task StartAsync() { await _lock.WaitAsync().ConfigureAwait(false); try { await StartInternalAsync(_cancelToken).ConfigureAwait(false); } finally { _lock.Release(); } } public async Task StartInternalAsync(CancellationToken cancelToken) { await StopInternalAsync().ConfigureAwait(false); _cancelTokenSource = new CancellationTokenSource(); _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; _udp = new UdpClient(0); _task = RunAsync(_cancelToken); } public async Task StopAsync() { await _lock.WaitAsync().ConfigureAwait(false); try { await StopInternalAsync().ConfigureAwait(false); } finally { _lock.Release(); } } public async Task StopInternalAsync(bool isDisposing = false) { try { _cancelTokenSource.Cancel(false); } catch { } if (!isDisposing) await (_task ?? Task.Delay(0)).ConfigureAwait(false); if (_udp != null) { try { _udp.Dispose(); } catch { } _udp = null; } } public void SetDestination(string host, int port) { var entry = Dns.GetHostEntryAsync(host).GetAwaiter().GetResult(); _destination = new IPEndPoint(entry.AddressList[0], port); } public void SetCancelToken(CancellationToken cancelToken) { _parentToken = cancelToken; _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } public async Task SendAsync(byte[] data, int index, int count) { if (!UnstableCheck()) return; if (index != 0) //Should never happen? { var newData = new byte[count]; Buffer.BlockCopy(data, index, newData, 0, count); data = newData; } await _udp.SendAsync(data, count, _destination).ConfigureAwait(false); } private async Task RunAsync(CancellationToken cancelToken) { var closeTask = Task.Delay(-1, cancelToken); while (!cancelToken.IsCancellationRequested) { var receiveTask = _udp.ReceiveAsync(); var task = await Task.WhenAny(closeTask, receiveTask).ConfigureAwait(false); if (task == closeTask) break; var result = receiveTask.Result; await ReceivedDatagram(result.Buffer, 0, result.Buffer.Length).ConfigureAwait(false); } } private bool UnstableCheck() { return _rand.NextDouble() > FailureRate; } } }