using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Udp { /// /// Provides a Udp Server /// public class UdpServer : IDisposable { /// /// The _logger /// private readonly ILogger _logger; /// /// The _network manager /// private readonly INetworkManager _networkManager; private bool _isDisposed; private readonly List>> _responders = new List>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; /// /// Initializes a new instance of the class. /// /// The logger. /// The network manager. /// The application host. /// The json. public UdpServer(ILogger logger, INetworkManager networkManager, IServerApplicationHost appHost, IJsonSerializer json) { _logger = logger; _networkManager = networkManager; _appHost = appHost; _json = json; AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); } private void AddMessageResponder(string message, bool isSubstring, Func responder) { _responders.Add(new Tuple>(message, isSubstring, responder)); } /// /// Raises the event. /// /// The instance containing the event data. private async void OnMessageReceived(UdpMessageReceivedEventArgs e) { var encoding = Encoding.UTF8; var responder = GetResponder(e.Bytes, encoding); if (responder == null) { encoding = Encoding.Unicode; responder = GetResponder(e.Bytes, encoding); } if (responder != null) { try { await responder.Item2.Item3(responder.Item1, e.RemoteEndPoint, encoding).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error in OnMessageReceived", ex); } } } private Tuple>> GetResponder(byte[] bytes, Encoding encoding) { var text = encoding.GetString(bytes); 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, string endpoint, Encoding encoding) { var parts = messageText.Split('|'); var localUrl = await _appHost.GetLocalApiUrl().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).ConfigureAwait(false); if (parts.Length > 1) { _appHost.EnableLoopback(parts[1]); } } else { _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); } } /// /// The _udp client /// private UdpClient _udpClient; /// /// Starts the specified port. /// /// The port. public void Start(int port) { _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); Task.Run(() => StartListening()); } private async void StartListening() { while (!_isDisposed) { try { var result = await GetResult().ConfigureAwait(false); OnMessageReceived(result); } catch (ObjectDisposedException) { break; } catch (Exception ex) { _logger.ErrorException("Error in StartListening", ex); } } } private Task GetResult() { try { return _udpClient.ReceiveAsync(); } catch (ObjectDisposedException) { return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); } catch (Exception ex) { _logger.ErrorException("Error receiving udp message", ex); return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0))); } } /// /// Called when [message received]. /// /// The message. private void OnMessageReceived(UdpReceiveResult message) { if (message.RemoteEndPoint.Port == 0) { return; } var bytes = message.Buffer; try { OnMessageReceived(new UdpMessageReceivedEventArgs { Bytes = bytes, RemoteEndPoint = message.RemoteEndPoint.ToString() }); } catch (Exception ex) { _logger.ErrorException("Error handling UDP message", ex); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Stops this instance. /// public void Stop() { _isDisposed = true; if (_udpClient != null) { _udpClient.Close(); } } /// /// 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) { Stop(); } } /// /// Sends the async. /// /// The data. /// The ip address. /// The port. /// Task{System.Int32}. /// data public Task SendAsync(string data, string ipAddress, int port) { return SendAsync(Encoding.UTF8.GetBytes(data), ipAddress, port); } /// /// Sends the async. /// /// The bytes. /// The ip address. /// The port. /// Task{System.Int32}. /// bytes public Task SendAsync(byte[] bytes, string ipAddress, int port) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (string.IsNullOrEmpty(ipAddress)) { throw new ArgumentNullException("ipAddress"); } return _udpClient.SendAsync(bytes, bytes.Length, ipAddress, port); } /// /// Sends the async. /// /// The bytes. /// The remote end point. /// Task. /// /// bytes /// or /// remoteEndPoint /// public async Task SendAsync(byte[] bytes, string remoteEndPoint) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (string.IsNullOrEmpty(remoteEndPoint)) { throw new ArgumentNullException("remoteEndPoint"); } try { await _udpClient.SendAsync(bytes, bytes.Length, _networkManager.Parse(remoteEndPoint)).ConfigureAwait(false); _logger.Info("Udp message sent to {0}", remoteEndPoint); } catch (Exception ex) { _logger.ErrorException("Error sending message to {0}", ex, remoteEndPoint); } } } }