diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index b1e84dc9f5..462f33fe67 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -51,8 +51,7 @@ namespace Rssdp.Infrastructure /// /// Sends a message to the SSDP multicast address and port. /// - /// A byte array containing the data to send. - Task SendMulticastMessage(byte[] messageData); + Task SendMulticastMessage(string message); #endregion diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index fb4d67e1ab..d60f6ea441 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -65,6 +65,7 @@ + diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 01c96463fe..3ea17237df 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -21,7 +21,7 @@ namespace Rssdp /// Default constructor. Constructs a new instance using the default and implementations for this platform. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="Can't expose along exception paths here (exceptions should be very rare anyway, and probably fatal too) and we shouldn't dipose the items we pass to base in any other case.")] - public SsdpDeviceLocator(ISocketFactory socketFactory, ITimerFactory timerFacatory) : base(new SsdpCommunicationsServer(socketFactory), timerFacatory) + public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFacatory) : base(communicationsServer, timerFacatory) { // This is not the problem you are looking for; // Yes, this is poor man's dependency injection which some call an anti-pattern. diff --git a/RSSDP/SsdpDeviceLocatorBase.cs b/RSSDP/SsdpDeviceLocatorBase.cs index ed4c087cda..b6276e4997 100644 --- a/RSSDP/SsdpDeviceLocatorBase.cs +++ b/RSSDP/SsdpDeviceLocatorBase.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Threading; +using RSSDP; namespace Rssdp.Infrastructure { @@ -28,14 +29,6 @@ namespace Rssdp.Infrastructure private ITimer _ExpireCachedDevicesTimer; private ITimerFactory _timerFactory; - private const string HttpURequestMessageFormat = @"{0} * HTTP/1.1 -HOST: {1}:{2} -MAN: ""{3}"" -MX: {5} -ST: {4} - -"; - private static readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4); private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1); @@ -166,21 +159,16 @@ ST: {4} if (searchWaitTime > TimeSpan.Zero) await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false); - await Task.Run(() => + lock (_SearchResultsSynchroniser) { - lock (_SearchResultsSynchroniser) + foreach (var device in GetUnexpiredDevices().Where(NotificationTypeMatchesFilter)) { - foreach (var device in GetUnexpiredDevices().Where((d) => NotificationTypeMatchesFilter(d))) - { - if (this.IsDisposed) return; - - DeviceFound(device, false); - } + DeviceFound(device, false); } - }).ConfigureAwait(false); + } if (searchWaitTime != TimeSpan.Zero) - await Task.Delay(searchWaitTime); + await Task.Delay(searchWaitTime).ConfigureAwait(false); IEnumerable retVal = null; @@ -192,7 +180,7 @@ ST: {4} _SearchResults = null; } - var expireTask = RemoveExpiredDevicesFromCacheAsync(); + RemoveExpiredDevicesFromCache(); } finally { @@ -417,25 +405,27 @@ ST: {4} #region Network Message Processing - private static byte[] BuildDiscoverMessage(string serviceType, TimeSpan mxValue) - { - return System.Text.UTF8Encoding.UTF8.GetBytes( - String.Format(HttpURequestMessageFormat, - SsdpConstants.MSearchMethod, - SsdpConstants.MulticastLocalAdminAddress, - SsdpConstants.MulticastPort, - SsdpConstants.SsdpDiscoverMessage, - serviceType, - mxValue.TotalSeconds - ) - ); - } - private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue) { - var broadcastMessage = BuildDiscoverMessage(serviceType, mxValue); + var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - return _CommunicationsServer.SendMulticastMessage(broadcastMessage); + values["HOST"] = "239.255.255.250:1900"; + values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2"; + //values["X-EMBY-SERVERID"] = _appHost.SystemId; + + values["MAN"] = "\"ssdp:discover\""; + + // Search target + values["ST"] = "ssdp:all"; + + // Seconds to delay response + values["MX"] = "3"; + + var header = "M-SEARCH * HTTP/1.1"; + + var message = SsdpHelper.BuildMessage(header, values); + + return _CommunicationsServer.SendMulticastMessage(message); } private void ProcessSearchResponseMessage(HttpResponseMessage message) @@ -608,19 +598,11 @@ ST: {4} #region Expiry and Device Removal - private Task RemoveExpiredDevicesFromCacheAsync() - { - return Task.Run(() => - { - RemoveExpiredDevicesFromCache(); - }); - } - private void RemoveExpiredDevicesFromCache() { if (this.IsDisposed) return; - IEnumerable expiredDevices = null; + DiscoveredSsdpDevice[] expiredDevices = null; lock (_Devices) { expiredDevices = (from device in _Devices where device.IsExpired() select device).ToArray(); diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 56f27b3a29..1c17c78372 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -26,8 +26,8 @@ namespace Rssdp /// Uses the default implementation and network settings for Windows and the SSDP specification. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")] - public SsdpDevicePublisher(ISocketFactory socketFactory, ITimerFactory timerFactory, string osName, string osVersion) - : base(new SsdpCommunicationsServer(socketFactory), timerFactory, osName, osVersion) + public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory, string osName, string osVersion) + : base(communicationsServer, timerFactory, osName, osVersion) { } diff --git a/RSSDP/SsdpHelper.cs b/RSSDP/SsdpHelper.cs new file mode 100644 index 0000000000..2eacf3c11b --- /dev/null +++ b/RSSDP/SsdpHelper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Text; + +namespace RSSDP +{ + public class SsdpHelper + { + private readonly ITextEncoding _encoding; + + public SsdpHelper(ITextEncoding encoding) + { + _encoding = encoding; + } + + public SsdpMessageInfo ParseSsdpResponse(byte[] data) + { + using (var ms = new MemoryStream(data)) + { + using (var reader = new StreamReader(ms, _encoding.GetASCIIEncoding())) + { + var proto = (reader.ReadLine() ?? string.Empty).Trim(); + var method = proto.Split(new[] { ' ' }, 2)[0]; + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) + { + line = line.Trim(); + if (string.IsNullOrEmpty(line)) + { + break; + } + var parts = line.Split(new[] { ':' }, 2); + + if (parts.Length >= 2) + { + headers[parts[0]] = parts[1].Trim(); + } + } + + return new SsdpMessageInfo + { + Method = method, + Headers = headers, + Message = data + }; + } + } + } + + public static string BuildMessage(string header, Dictionary values) + { + var builder = new StringBuilder(); + + const string argFormat = "{0}: {1}\r\n"; + + builder.AppendFormat("{0}\r\n", header); + + foreach (var pair in values) + { + builder.AppendFormat(argFormat, pair.Key, pair.Value); + } + + builder.Append("\r\n"); + + return builder.ToString(); + } + } + + public class SsdpMessageInfo + { + public string Method { get; set; } + + public IpEndPointInfo EndPoint { get; set; } + + public Dictionary Headers { get; set; } + + public IpEndPointInfo LocalEndPoint { get; set; } + public byte[] Message { get; set; } + + public SsdpMessageInfo() + { + Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +}