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);
+ }
+ }
+}