using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Udp { /// /// Provides a Udp Server /// public class UdpServer : IDisposable { /// /// The _logger /// private readonly ILogger _logger; private bool _isDisposed; private readonly List>> _responders = new List>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; /// /// Initializes a new instance of the class. /// public UdpServer(ILogger logger, IServerApplicationHost appHost, IJsonSerializer json, ISocketFactory socketFactory) { _logger = logger; _appHost = appHost; _json = json; _socketFactory = socketFactory; AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message); } private void AddMessageResponder(string message, bool isSubstring, Func responder) { _responders.Add(new Tuple>(message, isSubstring, responder)); } /// /// Raises the event. /// private async void OnMessageReceived(GenericEventArgs e) { var message = e.Argument; var encoding = Encoding.UTF8; var responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); if (responder == null) { encoding = Encoding.Unicode; responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding); } if (responder != null) { var cancellationToken = CancellationToken.None; try { await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception ex) { _logger.LogError(ex, "Error in OnMessageReceived"); } } } private Tuple>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding) { var text = encoding.GetString(buffer, 0, bytesReceived); var responder = _responders.FirstOrDefault(i => { if (i.Item2) { return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1; } return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase); }); if (responder == null) { return null; } return new Tuple>>(text, responder); } private async Task RespondToV2Message(string messageText, IPEndPoint endpoint, Encoding encoding, CancellationToken cancellationToken) { var parts = messageText.Split('|'); var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) { var response = new ServerDiscoveryInfo { Address = localUrl, Id = _appHost.SystemId, Name = _appHost.FriendlyName }; await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint, cancellationToken).ConfigureAwait(false); if (parts.Length > 1) { _appHost.EnableLoopback(parts[1]); } } else { _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); } } /// /// The _udp client /// private ISocket _udpClient; private readonly ISocketFactory _socketFactory; /// /// Starts the specified port. /// /// The port. public void Start(int port) { _udpClient = _socketFactory.CreateUdpSocket(port); Task.Run(() => BeginReceive()); } private readonly byte[] _receiveBuffer = new byte[8192]; private void BeginReceive() { if (_isDisposed) { return; } try { var result = _udpClient.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, OnReceiveResult); if (result.CompletedSynchronously) { OnReceiveResult(result); } } catch (ObjectDisposedException) { //TODO Investigate and properly fix. } catch (Exception ex) { _logger.LogError(ex, "Error receiving udp message"); } } private void OnReceiveResult(IAsyncResult result) { if (_isDisposed) { return; } try { var socketResult = _udpClient.EndReceive(result); OnMessageReceived(socketResult); } catch (ObjectDisposedException) { //TODO Investigate and properly fix. } catch (Exception ex) { _logger.LogError(ex, "Error receiving udp message"); } BeginReceive(); } /// /// Called when [message received]. /// /// The message. private void OnMessageReceived(SocketReceiveResult message) { if (_isDisposed) { return; } if (message.RemoteEndPoint.Port == 0) { return; } try { OnMessageReceived(new GenericEventArgs { Argument = message }); } catch (Exception ex) { _logger.LogError(ex, "Error handling UDP message"); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (dispose) { _isDisposed = true; if (_udpClient != null) { _udpClient.Dispose(); } } } public async Task SendAsync(byte[] bytes, IPEndPoint remoteEndPoint, CancellationToken cancellationToken) { if (_isDisposed) { throw new ObjectDisposedException(GetType().Name); } if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); } if (remoteEndPoint == null) { throw new ArgumentNullException(nameof(remoteEndPoint)); } try { await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, cancellationToken).ConfigureAwait(false); _logger.LogInformation("Udp message sent to {remoteEndPoint}", remoteEndPoint); } catch (OperationCanceledException) { } catch (Exception ex) { _logger.LogError(ex, "Error sending message to {remoteEndPoint}", remoteEndPoint); } } } }