using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Security; using System.Threading; using System.Threading.Tasks; using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Net; namespace Emby.Common.Implementations.Net { // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS // Be careful to check any changes compile and work for all platform projects it is shared in. internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket { private Socket _Socket; private int _LocalPort; private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { SocketFlags = SocketFlags.None }; private readonly SocketAsyncEventArgs _sendSocketAsyncEventArgs = new SocketAsyncEventArgs() { SocketFlags = SocketFlags.None }; private TaskCompletionSource _currentReceiveTaskCompletionSource; private TaskCompletionSource _currentSendTaskCompletionSource; private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); public UdpSocket(Socket socket, int localPort, IPAddress ip) { if (socket == null) throw new ArgumentNullException("socket"); _Socket = socket; _LocalPort = localPort; LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); InitReceiveSocketAsyncEventArgs(); } private void InitReceiveSocketAsyncEventArgs() { var receiveBuffer = new byte[8192]; _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; var sendBuffer = new byte[8192]; _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed; } private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) { var tcs = _currentReceiveTaskCompletionSource; if (tcs != null) { _currentReceiveTaskCompletionSource = null; if (e.SocketError == SocketError.Success) { tcs.TrySetResult(new SocketReceiveResult { Buffer = e.Buffer, ReceivedBytes = e.BytesTransferred, RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), LocalIPAddress = LocalIPAddress }); } else { tcs.TrySetException(new Exception("SocketError: " + e.SocketError)); } } } private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) { var tcs = _currentSendTaskCompletionSource; if (tcs != null) { _currentSendTaskCompletionSource = null; if (e.SocketError == SocketError.Success) { tcs.TrySetResult(e.BytesTransferred); } else { tcs.TrySetException(new Exception("SocketError: " + e.SocketError)); } } } public UdpSocket(Socket socket, IpEndPointInfo endPoint) { if (socket == null) throw new ArgumentNullException("socket"); _Socket = socket; _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); InitReceiveSocketAsyncEventArgs(); } public IpAddressInfo LocalIPAddress { get; private set; } public Task ReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); var tcs = new TaskCompletionSource(); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); cancellationToken.Register(() => tcs.TrySetCanceled()); _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; _currentReceiveTaskCompletionSource = tcs; var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); if (!willRaiseEvent) { _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs); } return tcs.Task; } public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) { ThrowIfDisposed(); if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); #if NETSTANDARD1_6 if (size != buffer.Length) { byte[] copy = new byte[size]; Buffer.BlockCopy(buffer, 0, copy, 0, size); buffer = copy; } cancellationToken.ThrowIfCancellationRequested(); _Socket.SendTo(buffer, ipEndPoint); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); try { _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => { if (cancellationToken.IsCancellationRequested) { taskSource.TrySetCanceled(); return; } try { _Socket.EndSend(result); taskSource.TrySetResult(true); } catch (Exception ex) { taskSource.TrySetException(ex); } }, null); } catch (Exception ex) { taskSource.TrySetException(ex); } //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); return taskSource.Task; #endif //ThrowIfDisposed(); //if (buffer == null) throw new ArgumentNullException("messageData"); //if (endPoint == null) throw new ArgumentNullException("endPoint"); //cancellationToken.ThrowIfCancellationRequested(); //var tcs = new TaskCompletionSource(); //cancellationToken.Register(() => tcs.TrySetCanceled()); //_sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size); //_sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint); //_currentSendTaskCompletionSource = tcs; //var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs); //if (!willRaiseEvent) //{ // _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs); //} //return tcs.Task; } public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) { ThrowIfDisposed(); //await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { await SendAsync(buffer, size, endPoint, cancellationToken).ConfigureAwait(false); } finally { //_sendLock.Release(); } } protected override void Dispose(bool disposing) { if (disposing) { var socket = _Socket; if (socket != null) socket.Dispose(); _sendLock.Dispose(); var tcs = _currentReceiveTaskCompletionSource; if (tcs != null) { tcs.TrySetCanceled(); } var sendTcs = _currentSendTaskCompletionSource; if (sendTcs != null) { sendTcs.TrySetCanceled(); } } } private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) { if (endpoint == null) { return null; } return NetworkManager.ToIpEndPointInfo(endpoint); } } }