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?", RespondToV2Message); AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message); } private void AddMessageResponder(string message, Action responder) { var expectedMessageBytes = Encoding.UTF8.GetBytes(message); _responders.Add(new Tuple>(message, expectedMessageBytes, responder)); } /// /// Raises the event. /// /// The instance containing the event data. private void OnMessageReceived(UdpMessageReceivedEventArgs e) { var responder = _responders.FirstOrDefault(i => i.Item2.SequenceEqual(e.Bytes)); var encoding = Encoding.UTF8; if (responder == null) { var text = Encoding.Unicode.GetString(e.Bytes); responder = _responders.FirstOrDefault(i => string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase)); if (responder != null) { encoding = Encoding.Unicode; } } if (responder != null) { try { responder.Item3(e.RemoteEndPoint, encoding); } catch (Exception ex) { _logger.ErrorException("Error in OnMessageReceived", ex); } } } private async void RespondToV1Message(string endpoint, Encoding encoding) { var localAddress = _appHost.LocalApiUrl; if (!string.IsNullOrEmpty(localAddress)) { // This is how we did the old v1 search, so need to strip off the protocol var index = localAddress.IndexOf("://", StringComparison.OrdinalIgnoreCase); if (index != -1) { localAddress = localAddress.Substring(index + 3); } // Send a response back with our ip address and port var response = String.Format("MediaBrowserServer|{0}", localAddress); await SendAsync(Encoding.UTF8.GetBytes(response), endpoint); } else { _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); } } private async void RespondToV2Message(string endpoint, Encoding encoding) { var localUrl = _appHost.LocalApiUrl; if (!string.IsNullOrEmpty(localUrl)) { var response = new ServerDiscoveryInfo { Address = localUrl, Id = _appHost.SystemId, Name = _appHost.FriendlyName }; await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint); } 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); } } } }