mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-16 14:03:03 +03:00
Merge branch 'master' into SSDP
This commit is contained in:
@@ -10,15 +10,10 @@ namespace Rssdp
|
||||
{
|
||||
public IPAddress LocalIpAddress { get; set; }
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly DiscoveredSsdpDevice _DiscoveredDevice;
|
||||
|
||||
private readonly bool _IsNewlyDiscovered;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor.
|
||||
/// </summary>
|
||||
@@ -27,16 +22,15 @@ namespace Rssdp
|
||||
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception>
|
||||
public DeviceAvailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool isNewlyDiscovered)
|
||||
{
|
||||
if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice));
|
||||
if (discoveredDevice == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(discoveredDevice));
|
||||
}
|
||||
|
||||
_DiscoveredDevice = discoveredDevice;
|
||||
_IsNewlyDiscovered = isNewlyDiscovered;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the device was discovered due to an alive notification, or a search and was not already in the cache. Returns false if the item came from the cache but matched the current search request.
|
||||
/// </summary>
|
||||
@@ -52,8 +46,5 @@ namespace Rssdp
|
||||
{
|
||||
get { return _DiscoveredDevice; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,15 +7,8 @@ namespace Rssdp
|
||||
/// </summary>
|
||||
public sealed class DeviceEventArgs : EventArgs
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly SsdpDevice _Device;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance for the specified <see cref="SsdpDevice"/>.
|
||||
/// </summary>
|
||||
@@ -23,15 +16,14 @@ namespace Rssdp
|
||||
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
|
||||
public DeviceEventArgs(SsdpDevice device)
|
||||
{
|
||||
if (device == null) throw new ArgumentNullException(nameof(device));
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
_Device = device;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="SsdpDevice"/> instance the event being raised for.
|
||||
/// </summary>
|
||||
@@ -39,8 +31,5 @@ namespace Rssdp
|
||||
{
|
||||
get { return _Device; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,10 @@ namespace Rssdp
|
||||
/// </summary>
|
||||
public sealed class DeviceUnavailableEventArgs : EventArgs
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly DiscoveredSsdpDevice _DiscoveredDevice;
|
||||
|
||||
private readonly bool _Expired;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor.
|
||||
/// </summary>
|
||||
@@ -25,16 +19,15 @@ namespace Rssdp
|
||||
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception>
|
||||
public DeviceUnavailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool expired)
|
||||
{
|
||||
if (discoveredDevice == null) throw new ArgumentNullException(nameof(discoveredDevice));
|
||||
if (discoveredDevice == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(discoveredDevice));
|
||||
}
|
||||
|
||||
_DiscoveredDevice = discoveredDevice;
|
||||
_Expired = expired;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the device is considered unavailable because it's cached information expired before a new alive notification or search result was received. Returns false if the device is unavailable because it sent an explicit notification of it's unavailability.
|
||||
/// </summary>
|
||||
@@ -50,7 +43,5 @@ namespace Rssdp
|
||||
{
|
||||
get { return _DiscoveredDevice; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,8 @@ namespace Rssdp
|
||||
/// <seealso cref="Infrastructure.ISsdpDeviceLocator"/>
|
||||
public sealed class DiscoveredSsdpDevice
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
private DateTimeOffset _AsAt;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice.
|
||||
/// </summary>
|
||||
@@ -45,6 +38,7 @@ namespace Rssdp
|
||||
public DateTimeOffset AsAt
|
||||
{
|
||||
get { return _AsAt; }
|
||||
|
||||
set
|
||||
{
|
||||
if (_AsAt != value)
|
||||
@@ -55,14 +49,10 @@ namespace Rssdp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the headers from the SSDP device response message
|
||||
/// Returns the headers from the SSDP device response message.
|
||||
/// </summary>
|
||||
public HttpHeaders ResponseHeaders { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this device information has expired, based on the current date/time, and the <see cref="CacheLifetime"/> & <see cref="AsAt"/> properties.
|
||||
/// </summary>
|
||||
@@ -72,10 +62,6 @@ namespace Rssdp
|
||||
return this.CacheLifetime == TimeSpan.Zero || this.AsAt.Add(this.CacheLifetime) <= DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// Returns the device's <see cref="Usn"/> value.
|
||||
/// </summary>
|
||||
@@ -84,7 +70,5 @@ namespace Rssdp
|
||||
{
|
||||
return this.Usn;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public abstract class DisposableManagedObjectBase : IDisposable
|
||||
{
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Override this method and dispose any objects you own the lifetime of if disposing is true;
|
||||
/// </summary>
|
||||
@@ -26,13 +23,12 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="Dispose()"/>
|
||||
protected virtual void ThrowIfDisposed()
|
||||
{
|
||||
if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException(this.GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns a boolean indicating whether or not this instance has been disposed.
|
||||
/// </summary>
|
||||
@@ -43,8 +39,6 @@ namespace Rssdp.Infrastructure
|
||||
private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public string BuildMessage(string header, Dictionary<string, string> values)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
@@ -63,8 +57,6 @@ namespace Rssdp.Infrastructure
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this object instance and all internally managed resources.
|
||||
/// </summary>
|
||||
@@ -79,7 +71,5 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,9 @@ namespace Rssdp.Infrastructure
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public abstract class HttpParserBase<T> where T : new()
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly string[] LineTerminators = new string[] { "\r\n", "\n" };
|
||||
private readonly char[] SeparatorCharacters = new char[] { ',', ';' };
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses the <paramref name="data"/> provided into either a <see cref="HttpRequestMessage"/> or <see cref="HttpResponseMessage"/> object.
|
||||
/// </summary>
|
||||
@@ -38,15 +31,26 @@ namespace Rssdp.Infrastructure
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Honestly, it's fine. MemoryStream doesn't mind.")]
|
||||
protected virtual void Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", nameof(data));
|
||||
if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data));
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
if (data.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("data cannot be an empty string.", nameof(data));
|
||||
}
|
||||
|
||||
if (!LineTerminators.Any(data.Contains))
|
||||
{
|
||||
throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data));
|
||||
}
|
||||
|
||||
using (var retVal = new ByteArrayContent(Array.Empty<byte>()))
|
||||
{
|
||||
var lines = data.Split(LineTerminators, StringSplitOptions.None);
|
||||
|
||||
//First line is the 'request' line containing http protocol details like method, uri, http version etc.
|
||||
// First line is the 'request' line containing http protocol details like method, uri, http version etc.
|
||||
ParseStatusLine(lines[0], message);
|
||||
|
||||
ParseHeaders(headers, retVal.Headers, lines);
|
||||
@@ -73,18 +77,20 @@ namespace Rssdp.Infrastructure
|
||||
/// <returns>A <see cref="Version"/> object containing the parsed version data.</returns>
|
||||
protected Version ParseHttpVersion(string versionData)
|
||||
{
|
||||
if (versionData == null) throw new ArgumentNullException(nameof(versionData));
|
||||
if (versionData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(versionData));
|
||||
}
|
||||
|
||||
var versionSeparatorIndex = versionData.IndexOf('/');
|
||||
if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length) throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", nameof(versionData));
|
||||
if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length)
|
||||
{
|
||||
throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", nameof(versionData));
|
||||
}
|
||||
|
||||
return Version.Parse(versionData.Substring(versionSeparatorIndex + 1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses a line from an HTTP request or response message containing a header name and value pair.
|
||||
/// </summary>
|
||||
@@ -93,35 +99,39 @@ namespace Rssdp.Infrastructure
|
||||
/// <param name="contentHeaders">A reference to a <see cref="System.Net.Http.Headers.HttpHeaders"/> collection for the message content, to which the parsed header will be added.</param>
|
||||
private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders)
|
||||
{
|
||||
//Header format is
|
||||
//name: value
|
||||
// Header format is
|
||||
// name: value
|
||||
var headerKeySeparatorIndex = line.IndexOf(":", StringComparison.OrdinalIgnoreCase);
|
||||
var headerName = line.Substring(0, headerKeySeparatorIndex).Trim();
|
||||
var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim();
|
||||
|
||||
//Not sure how to determine where request headers and and content headers begin,
|
||||
//at least not without a known set of headers (general headers first the content headers)
|
||||
//which seems like a bad way of doing it. So we'll assume if it's a known content header put it there
|
||||
//else use request headers.
|
||||
// Not sure how to determine where request headers and and content headers begin,
|
||||
// at least not without a known set of headers (general headers first the content headers)
|
||||
// which seems like a bad way of doing it. So we'll assume if it's a known content header put it there
|
||||
// else use request headers.
|
||||
|
||||
var values = ParseValues(headerValue);
|
||||
var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers;
|
||||
|
||||
if (values.Count > 1)
|
||||
{
|
||||
headersToAddTo.TryAddWithoutValidation(headerName, values);
|
||||
}
|
||||
else
|
||||
{
|
||||
headersToAddTo.TryAddWithoutValidation(headerName, values.First());
|
||||
}
|
||||
}
|
||||
|
||||
private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines)
|
||||
{
|
||||
//Blank line separates headers from content, so read headers until we find blank line.
|
||||
// Blank line separates headers from content, so read headers until we find blank line.
|
||||
int lineIndex = 1;
|
||||
string line = null, nextLine = null;
|
||||
while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++])))
|
||||
{
|
||||
//If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability.
|
||||
//Combine these lines into a single comma separated style header for easier parsing.
|
||||
// If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability.
|
||||
// Combine these lines into a single comma separated style header for easier parsing.
|
||||
while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex])))
|
||||
{
|
||||
if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0]))
|
||||
@@ -130,11 +140,14 @@ namespace Rssdp.Infrastructure
|
||||
lineIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ParseHeader(line, headers, contentHeaders);
|
||||
}
|
||||
|
||||
return lineIndex;
|
||||
}
|
||||
|
||||
@@ -153,7 +166,9 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters);
|
||||
if (indexOfSeparator <= 0)
|
||||
{
|
||||
values.Add(headerValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
var segments = headerValue.Split(SeparatorCharacters);
|
||||
@@ -163,13 +178,17 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
var segment = segments[segmentIndex];
|
||||
if (segment.Trim().StartsWith("\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
segment = CombineQuotedSegments(segments, ref segmentIndex, segment);
|
||||
}
|
||||
|
||||
values.Add(segment);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
values.AddRange(segments);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
@@ -192,17 +211,20 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
|
||||
if (index + 1 < segments.Length)
|
||||
{
|
||||
trimmedSegment += "," + segments[index + 1].TrimEnd();
|
||||
}
|
||||
}
|
||||
|
||||
segmentIndex = segments.Length;
|
||||
if (trimmedSegment.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return trimmedSegment.Substring(1, trimmedSegment.Length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return trimmedSegment;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,10 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public sealed class HttpRequestParser : HttpParserBase<HttpRequestMessage>
|
||||
{
|
||||
|
||||
#region Fields & Constants
|
||||
|
||||
private readonly string[] ContentHeaderNames = new string[]
|
||||
{
|
||||
"Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
{
|
||||
"Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified data into a <see cref="HttpRequestMessage"/> instance.
|
||||
@@ -41,14 +34,12 @@ namespace Rssdp.Infrastructure
|
||||
finally
|
||||
{
|
||||
if (retVal != null)
|
||||
{
|
||||
retVal.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the <paramref name="message"/>.
|
||||
/// </summary>
|
||||
@@ -56,18 +47,32 @@ namespace Rssdp.Infrastructure
|
||||
/// <param name="message">Either a <see cref="HttpResponseMessage"/> or <see cref="HttpRequestMessage"/> to assign the parsed values to.</param>
|
||||
protected override void ParseStatusLine(string data, HttpRequestMessage message)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
var parts = data.Split(' ');
|
||||
if (parts.Length < 2) throw new ArgumentException("Status line is invalid. Insufficient status parts.", nameof(data));
|
||||
if (parts.Length < 2)
|
||||
{
|
||||
throw new ArgumentException("Status line is invalid. Insufficient status parts.", nameof(data));
|
||||
}
|
||||
|
||||
message.Method = new HttpMethod(parts[0].Trim());
|
||||
Uri requestUri;
|
||||
if (Uri.TryCreate(parts[1].Trim(), UriKind.RelativeOrAbsolute, out requestUri))
|
||||
{
|
||||
message.RequestUri = requestUri;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(parts[1]);
|
||||
}
|
||||
|
||||
if (parts.Length >= 3)
|
||||
{
|
||||
@@ -83,8 +88,5 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,10 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public sealed class HttpResponseParser : HttpParserBase<HttpResponseMessage>
|
||||
{
|
||||
|
||||
#region Fields & Constants
|
||||
|
||||
private readonly string[] ContentHeaderNames = new string[]
|
||||
{
|
||||
"Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
{
|
||||
"Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified data into a <see cref="HttpResponseMessage"/> instance.
|
||||
@@ -41,16 +34,14 @@ namespace Rssdp.Infrastructure
|
||||
catch
|
||||
{
|
||||
if (retVal != null)
|
||||
{
|
||||
retVal.Dispose();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false).
|
||||
/// </summary>
|
||||
@@ -68,17 +59,29 @@ namespace Rssdp.Infrastructure
|
||||
/// <param name="message">Either a <see cref="HttpResponseMessage"/> or <see cref="HttpRequestMessage"/> to assign the parsed values to.</param>
|
||||
protected override void ParseStatusLine(string data, HttpResponseMessage message)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
var parts = data.Split(' ');
|
||||
if (parts.Length < 2) throw new ArgumentException("data status line is invalid. Insufficient status parts.", nameof(data));
|
||||
if (parts.Length < 2)
|
||||
{
|
||||
throw new ArgumentException("data status line is invalid. Insufficient status parts.", nameof(data));
|
||||
}
|
||||
|
||||
message.Version = ParseHttpVersion(parts[0].Trim());
|
||||
|
||||
int statusCode = -1;
|
||||
if (!Int32.TryParse(parts[1].Trim(), out statusCode))
|
||||
{
|
||||
throw new ArgumentException("data status line is invalid. Status code is not a valid integer.", nameof(data));
|
||||
}
|
||||
|
||||
message.StatusCode = (HttpStatusCode)statusCode;
|
||||
|
||||
@@ -87,7 +90,5 @@ namespace Rssdp.Infrastructure
|
||||
message.ReasonPhrase = parts[2].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,15 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (selector == null) throw new ArgumentNullException(nameof(selector));
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
if (selector == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selector));
|
||||
}
|
||||
|
||||
return !source.Any() ? source :
|
||||
source.Concat(
|
||||
|
||||
@@ -10,9 +10,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public interface ISsdpCommunicationsServer : IDisposable
|
||||
{
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
|
||||
/// </summary>
|
||||
@@ -23,10 +20,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
event EventHandler<ResponseReceivedEventArgs> ResponseReceived;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications.
|
||||
/// </summary>
|
||||
@@ -48,10 +41,6 @@ namespace Rssdp.Infrastructure
|
||||
Task SendMulticastMessage(string message, IPAddress fromLocalIpAddress, CancellationToken cancellationToken);
|
||||
Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances.
|
||||
/// </summary>
|
||||
@@ -59,8 +48,5 @@ namespace Rssdp.Infrastructure
|
||||
/// <para>If true, disposing an instance of a <see cref="SsdpDeviceLocatorBase"/>or a <see cref="ISsdpDevicePublisher"/> will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server.</para>
|
||||
/// </remarks>
|
||||
bool IsShared { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="ISsdpDevicePublisher"/>
|
||||
public interface ISsdpDeviceLocator
|
||||
{
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a device becomes available or is found by a search request.
|
||||
/// </summary>
|
||||
@@ -34,10 +31,6 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="StopListeningForNotifications"/>
|
||||
event EventHandler<DeviceUnavailableEventArgs> DeviceUnavailable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="DeviceAvailable"/> or <see cref="DeviceUnavailable"/> events.
|
||||
/// </summary>
|
||||
@@ -58,12 +51,6 @@ namespace Rssdp.Infrastructure
|
||||
set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
#region SearchAsync Overloads
|
||||
|
||||
/// <summary>
|
||||
/// Aynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results.
|
||||
/// </summary>
|
||||
@@ -108,8 +95,6 @@ namespace Rssdp.Infrastructure
|
||||
/// <returns>A task whose result is an <see cref="System.Collections.Generic.IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
|
||||
System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for broadcast notifications of service availability.
|
||||
/// </summary>
|
||||
@@ -134,8 +119,5 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="DeviceUnavailable"/>
|
||||
/// <seealso cref="NotificationFilter"/>
|
||||
void StopListeningForNotifications();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,17 +9,12 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public sealed class RequestReceivedEventArgs : EventArgs
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly HttpRequestMessage _Message;
|
||||
|
||||
private readonly IPEndPoint _ReceivedFrom;
|
||||
|
||||
#endregion
|
||||
|
||||
public IPAddress LocalIpAddress { get; private set; }
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor.
|
||||
/// </summary>
|
||||
@@ -30,10 +25,6 @@ namespace Rssdp.Infrastructure
|
||||
LocalIpAddress = localIpAddress;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HttpRequestMessage"/> that was received.
|
||||
/// </summary>
|
||||
@@ -49,7 +40,5 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
get { return _ReceivedFrom; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,12 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public sealed class ResponseReceivedEventArgs : EventArgs
|
||||
{
|
||||
|
||||
public IPAddress LocalIpAddress { get; set; }
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly HttpResponseMessage _Message;
|
||||
|
||||
private readonly IPEndPoint _ReceivedFrom;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Full constructor.
|
||||
/// </summary>
|
||||
@@ -30,10 +24,6 @@ namespace Rssdp.Infrastructure
|
||||
_ReceivedFrom = receivedFrom;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HttpResponseMessage"/> that was received.
|
||||
/// </summary>
|
||||
@@ -49,7 +39,5 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
get { return _ReceivedFrom; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public sealed class SsdpCommunicationsServer : DisposableManagedObjectBase, ISsdpCommunicationsServer
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
/* We could technically use one socket listening on port 1900 for everything.
|
||||
* This should get both multicast (notifications) and unicast (search response) messages, however
|
||||
* this often doesn't work under Windows because the MS SSDP service is running. If that service
|
||||
@@ -53,10 +50,6 @@ namespace Rssdp.Infrastructure
|
||||
private bool _IsShared;
|
||||
private readonly bool _enableMultiSocketBinding;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a HTTPU request message is received by a socket (unicast or multicast).
|
||||
/// </summary>
|
||||
@@ -67,10 +60,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public event EventHandler<ResponseReceivedEventArgs> ResponseReceived;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Minimum constructor.
|
||||
/// </summary>
|
||||
@@ -89,8 +78,15 @@ namespace Rssdp.Infrastructure
|
||||
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception>
|
||||
public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding)
|
||||
{
|
||||
if (socketFactory == null) throw new ArgumentNullException(nameof(socketFactory));
|
||||
if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
|
||||
if (socketFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(socketFactory));
|
||||
}
|
||||
|
||||
if (multicastTimeToLive <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero.");
|
||||
}
|
||||
|
||||
_BroadcastListenSocketSynchroniser = new object();
|
||||
_SendSocketSynchroniser = new object();
|
||||
@@ -107,10 +103,6 @@ namespace Rssdp.Infrastructure
|
||||
_enableMultiSocketBinding = enableMultiSocketBinding;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications.
|
||||
/// </summary>
|
||||
@@ -164,7 +156,10 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public async Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (messageData == null) throw new ArgumentNullException(nameof(messageData));
|
||||
if (messageData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(messageData));
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
@@ -193,11 +188,9 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -249,7 +242,10 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public async Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
byte[] messageData = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
@@ -298,10 +294,6 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple <see cref="SsdpDeviceLocatorBase"/> and/or <see cref="ISsdpDevicePublisher"/> instances.
|
||||
/// </summary>
|
||||
@@ -311,13 +303,10 @@ namespace Rssdp.Infrastructure
|
||||
public bool IsShared
|
||||
{
|
||||
get { return _IsShared; }
|
||||
|
||||
set { _IsShared = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening for requests, disposes this instance and all internal resources.
|
||||
/// </summary>
|
||||
@@ -332,10 +321,6 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private Task SendMessageIfSocketNotDisposed(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIpAddress, CancellationToken cancellationToken)
|
||||
{
|
||||
var sockets = _sendSockets;
|
||||
@@ -483,9 +468,9 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnLocalIpAddress)
|
||||
{
|
||||
//SSDP specification says only * is currently used but other uri's might
|
||||
//be implemented in the future and should be ignored unless understood.
|
||||
//Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11
|
||||
// SSDP specification says only * is currently used but other uri's might
|
||||
// be implemented in the future and should be ignored unless understood.
|
||||
// Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11
|
||||
if (data.RequestUri.ToString() != "*")
|
||||
{
|
||||
return;
|
||||
@@ -493,20 +478,21 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
var handlers = this.RequestReceived;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnLocalIpAddress));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIpAddress)
|
||||
{
|
||||
var handlers = this.ResponseReceived;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new ResponseReceivedEventArgs(data, endPoint)
|
||||
{
|
||||
LocalIpAddress = localIpAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,5 @@ namespace Rssdp.Infrastructure
|
||||
internal const string SsdpByeByeNotification = "ssdp:byebye";
|
||||
|
||||
internal const int UdpResendCount = 3;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@ namespace Rssdp
|
||||
/// <seealso cref="SsdpEmbeddedDevice"/>
|
||||
public abstract class SsdpDevice
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
private string _Udn;
|
||||
private string _DeviceType;
|
||||
private string _DeviceTypeNamespace;
|
||||
@@ -25,10 +22,6 @@ namespace Rssdp
|
||||
|
||||
private IList<SsdpDevice> _Devices;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a new child device is added.
|
||||
/// </summary>
|
||||
@@ -43,10 +36,6 @@ namespace Rssdp
|
||||
/// <seealso cref="DeviceRemoved"/>
|
||||
public event EventHandler<DeviceEventArgs> DeviceRemoved;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Derived type constructor, allows constructing a device with no parent. Should only be used from derived types that are or inherit from <see cref="SsdpRootDevice"/>.
|
||||
/// </summary>
|
||||
@@ -60,23 +49,19 @@ namespace Rssdp
|
||||
this.Devices = new ReadOnlyCollection<SsdpDevice>(_Devices);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public SsdpRootDevice ToRootDevice()
|
||||
{
|
||||
var device = this;
|
||||
|
||||
var rootDevice = device as SsdpRootDevice;
|
||||
if (rootDevice == null)
|
||||
{
|
||||
rootDevice = ((SsdpEmbeddedDevice)device).RootDevice;
|
||||
}
|
||||
|
||||
return rootDevice;
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
#region UPnP Device Description Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns the core device type (not including namespace, version etc.). Required.
|
||||
/// </summary>
|
||||
@@ -90,6 +75,7 @@ namespace Rssdp
|
||||
{
|
||||
return _DeviceType;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_DeviceType = value;
|
||||
@@ -111,6 +97,7 @@ namespace Rssdp
|
||||
{
|
||||
return _DeviceTypeNamespace;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_DeviceTypeNamespace = value;
|
||||
@@ -130,6 +117,7 @@ namespace Rssdp
|
||||
{
|
||||
return _DeviceVersion;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_DeviceVersion = value;
|
||||
@@ -177,10 +165,15 @@ namespace Rssdp
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrEmpty(_Udn) && !String.IsNullOrEmpty(this.Uuid))
|
||||
{
|
||||
return "uuid:" + this.Uuid;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _Udn;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_Udn = value;
|
||||
@@ -248,8 +241,6 @@ namespace Rssdp
|
||||
/// </remarks>
|
||||
public Uri PresentationUrl { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns a read-only enumerable set of <see cref="SsdpDevice"/> objects representing children of this device. Child devices are optional.
|
||||
/// </summary>
|
||||
@@ -261,10 +252,6 @@ namespace Rssdp
|
||||
private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds a child device to the <see cref="Devices"/> collection.
|
||||
/// </summary>
|
||||
@@ -278,9 +265,20 @@ namespace Rssdp
|
||||
/// <seealso cref="DeviceAdded"/>
|
||||
public void AddDevice(SsdpEmbeddedDevice device)
|
||||
{
|
||||
if (device == null) throw new ArgumentNullException(nameof(device));
|
||||
if (device.RootDevice != null && device.RootDevice != this.ToRootDevice()) throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch).");
|
||||
if (device == this) throw new InvalidOperationException("Can't add device to itself.");
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
if (device.RootDevice != null && device.RootDevice != this.ToRootDevice())
|
||||
{
|
||||
throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch).");
|
||||
}
|
||||
|
||||
if (device == this)
|
||||
{
|
||||
throw new InvalidOperationException("Can't add device to itself.");
|
||||
}
|
||||
|
||||
bool wasAdded = false;
|
||||
lock (_Devices)
|
||||
@@ -291,7 +289,9 @@ namespace Rssdp
|
||||
}
|
||||
|
||||
if (wasAdded)
|
||||
{
|
||||
OnDeviceAdded(device);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -306,7 +306,10 @@ namespace Rssdp
|
||||
/// <seealso cref="DeviceRemoved"/>
|
||||
public void RemoveDevice(SsdpEmbeddedDevice device)
|
||||
{
|
||||
if (device == null) throw new ArgumentNullException(nameof(device));
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
bool wasRemoved = false;
|
||||
lock (_Devices)
|
||||
@@ -319,7 +322,9 @@ namespace Rssdp
|
||||
}
|
||||
|
||||
if (wasRemoved)
|
||||
{
|
||||
OnDeviceRemoved(device);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -332,7 +337,9 @@ namespace Rssdp
|
||||
{
|
||||
var handlers = this.DeviceAdded;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new DeviceEventArgs(device));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -345,10 +352,9 @@ namespace Rssdp
|
||||
{
|
||||
var handlers = this.DeviceRemoved;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new DeviceEventArgs(device));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Rssdp.Infrastructure
|
||||
/// </summary>
|
||||
public class SsdpDeviceLocator : DisposableManagedObjectBase
|
||||
{
|
||||
|
||||
#region Fields & Constants
|
||||
|
||||
private List<DiscoveredSsdpDevice> _Devices;
|
||||
private ISsdpCommunicationsServer _CommunicationsServer;
|
||||
|
||||
@@ -25,16 +22,15 @@ namespace Rssdp.Infrastructure
|
||||
private readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4);
|
||||
private readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer)
|
||||
{
|
||||
if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
|
||||
if (communicationsServer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(communicationsServer));
|
||||
}
|
||||
|
||||
_CommunicationsServer = communicationsServer;
|
||||
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
|
||||
@@ -42,10 +38,6 @@ namespace Rssdp.Infrastructure
|
||||
_Devices = new List<DiscoveredSsdpDevice>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Raised for when
|
||||
/// <list type="bullet">
|
||||
@@ -76,12 +68,6 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="StopListeningForNotifications"/>
|
||||
public event EventHandler<DeviceUnavailableEventArgs> DeviceUnavailable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#region Search Overloads
|
||||
|
||||
public void RestartBroadcastTimer(TimeSpan dueTime, TimeSpan period)
|
||||
{
|
||||
lock (_timerLock)
|
||||
@@ -120,7 +106,6 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,18 +143,31 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private Task SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
|
||||
{
|
||||
if (searchTarget == null) throw new ArgumentNullException(nameof(searchTarget));
|
||||
if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", nameof(searchTarget));
|
||||
if (searchWaitTime.TotalSeconds < 0) throw new ArgumentException("searchWaitTime must be a positive time.");
|
||||
if (searchWaitTime.TotalSeconds > 0 && searchWaitTime.TotalSeconds <= 1) throw new ArgumentException("searchWaitTime must be zero (if you are not using the result and relying entirely in the events), or greater than one second.");
|
||||
if (searchTarget == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(searchTarget));
|
||||
}
|
||||
|
||||
if (searchTarget.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("searchTarget cannot be an empty string.", nameof(searchTarget));
|
||||
}
|
||||
|
||||
if (searchWaitTime.TotalSeconds < 0)
|
||||
{
|
||||
throw new ArgumentException("searchWaitTime must be a positive time.");
|
||||
}
|
||||
|
||||
if (searchWaitTime.TotalSeconds > 0 && searchWaitTime.TotalSeconds <= 1)
|
||||
{
|
||||
throw new ArgumentException("searchWaitTime must be zero (if you are not using the result and relying entirely in the events), or greater than one second.");
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
return BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening for broadcast notifications of service availability.
|
||||
/// </summary>
|
||||
@@ -212,14 +210,19 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="DeviceAvailable"/>
|
||||
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.DeviceAvailable;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
|
||||
{
|
||||
LocalIpAddress = localIpAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,17 +233,18 @@ namespace Rssdp.Infrastructure
|
||||
/// <seealso cref="DeviceUnavailable"/>
|
||||
protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired)
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = this.DeviceUnavailable;
|
||||
if (handlers != null)
|
||||
{
|
||||
handlers(this, new DeviceUnavailableEventArgs(device, expired));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="ISsdpDeviceLocator.DeviceAvailable"/> or <see cref="ISsdpDeviceLocator.DeviceUnavailable"/> events.
|
||||
/// </summary>
|
||||
@@ -262,10 +266,6 @@ namespace Rssdp.Infrastructure
|
||||
set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
/// <summary>
|
||||
/// Disposes this object and all internal resources. Stops listening for all network messages.
|
||||
/// </summary>
|
||||
@@ -286,12 +286,6 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
#region Discovery/Device Add
|
||||
|
||||
private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress)
|
||||
{
|
||||
bool isNewDevice = false;
|
||||
@@ -315,7 +309,10 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress)
|
||||
{
|
||||
if (!NotificationTypeMatchesFilter(device)) return;
|
||||
if (!NotificationTypeMatchesFilter(device))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnDeviceAvailable(device, isNewDevice, localIpAddress);
|
||||
}
|
||||
@@ -327,17 +324,13 @@ namespace Rssdp.Infrastructure
|
||||
|| device.NotificationType == this.NotificationFilter;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Message Processing
|
||||
|
||||
private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, CancellationToken cancellationToken)
|
||||
{
|
||||
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
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["X-EMBY-SERVERID"] = _appHost.SystemId;
|
||||
|
||||
values["MAN"] = "\"ssdp:discover\"";
|
||||
|
||||
@@ -356,7 +349,10 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress)
|
||||
{
|
||||
if (!message.IsSuccessStatusCode) return;
|
||||
if (!message.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var location = GetFirstHeaderUriValue("Location", message);
|
||||
if (location != null)
|
||||
@@ -377,13 +373,20 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress)
|
||||
{
|
||||
if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) return;
|
||||
if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notificationType = GetFirstHeaderStringValue("NTS", message);
|
||||
if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
ProcessAliveNotification(message, localIpAddress);
|
||||
}
|
||||
else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
ProcessByeByeNotification(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress)
|
||||
@@ -425,13 +428,13 @@ namespace Rssdp.Infrastructure
|
||||
};
|
||||
|
||||
if (NotificationTypeMatchesFilter(deadDevice))
|
||||
{
|
||||
OnDeviceUnavailable(deadDevice, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Header/Message Processing Utilities
|
||||
|
||||
private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message)
|
||||
{
|
||||
string retVal = null;
|
||||
@@ -440,7 +443,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
message.Headers.TryGetValues(headerName, out values);
|
||||
if (values != null)
|
||||
{
|
||||
retVal = values.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
@@ -454,7 +459,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
message.Headers.TryGetValues(headerName, out values);
|
||||
if (values != null)
|
||||
{
|
||||
retVal = values.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
@@ -468,7 +475,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
request.Headers.TryGetValues(headerName, out values);
|
||||
if (values != null)
|
||||
{
|
||||
value = values.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
Uri retVal;
|
||||
@@ -484,7 +493,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
response.Headers.TryGetValues(headerName, out values);
|
||||
if (values != null)
|
||||
{
|
||||
value = values.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
Uri retVal;
|
||||
@@ -494,20 +505,20 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
private TimeSpan CacheAgeFromHeader(System.Net.Http.Headers.CacheControlHeaderValue headerValue)
|
||||
{
|
||||
if (headerValue == null) return TimeSpan.Zero;
|
||||
if (headerValue == null)
|
||||
{
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
return (TimeSpan)(headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Expiry and Device Removal
|
||||
|
||||
private void RemoveExpiredDevicesFromCache()
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DiscoveredSsdpDevice[] expiredDevices = null;
|
||||
lock (_Devices)
|
||||
@@ -516,7 +527,10 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
foreach (var device in expiredDevices)
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_Devices.Remove(device);
|
||||
}
|
||||
@@ -527,7 +541,10 @@ namespace Rssdp.Infrastructure
|
||||
// problems.
|
||||
foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct())
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceDied(expiredUsn, true);
|
||||
}
|
||||
@@ -541,7 +558,10 @@ namespace Rssdp.Infrastructure
|
||||
existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn);
|
||||
foreach (var existingDevice in existingDevices)
|
||||
{
|
||||
if (this.IsDisposed) return true;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_Devices.Remove(existingDevice);
|
||||
}
|
||||
@@ -552,7 +572,9 @@ namespace Rssdp.Infrastructure
|
||||
foreach (var removedDevice in existingDevices)
|
||||
{
|
||||
if (NotificationTypeMatchesFilter(removedDevice))
|
||||
{
|
||||
OnDeviceUnavailable(removedDevice, expired);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -561,14 +583,16 @@ namespace Rssdp.Infrastructure
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private TimeSpan SearchTimeToMXValue(TimeSpan searchWaitTime)
|
||||
{
|
||||
if (searchWaitTime.TotalSeconds < 2 || searchWaitTime == TimeSpan.Zero)
|
||||
{
|
||||
return OneSecond;
|
||||
}
|
||||
else
|
||||
{
|
||||
return searchWaitTime.Subtract(OneSecond);
|
||||
}
|
||||
}
|
||||
|
||||
private DiscoveredSsdpDevice FindExistingDeviceNotification(IEnumerable<DiscoveredSsdpDevice> devices, string notificationType, string usn)
|
||||
@@ -580,6 +604,7 @@ namespace Rssdp.Infrastructure
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -598,10 +623,6 @@ namespace Rssdp.Infrastructure
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void CommsServer_ResponseReceived(object sender, ResponseReceivedEventArgs e)
|
||||
{
|
||||
ProcessSearchResponseMessage(e.Message, e.LocalIpAddress);
|
||||
@@ -611,8 +632,5 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
ProcessNotificationMessage(e.Message, e.LocalIpAddress);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,35 @@ namespace Rssdp.Infrastructure
|
||||
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, INetworkManager networkManager,
|
||||
string osName, string osVersion, bool sendOnlyMatchedHost)
|
||||
{
|
||||
if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer));
|
||||
if (networkManager == null) throw new ArgumentNullException(nameof(networkManager));
|
||||
if (osName == null) throw new ArgumentNullException(nameof(osName));
|
||||
if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
|
||||
if (osVersion == null) throw new ArgumentNullException(nameof(osVersion));
|
||||
if (osVersion.Length == 0) throw new ArgumentException("osVersion cannot be an empty string.", nameof(osName));
|
||||
if (communicationsServer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(communicationsServer));
|
||||
}
|
||||
|
||||
if (networkManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(networkManager));
|
||||
}
|
||||
|
||||
if (osName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(osName));
|
||||
}
|
||||
|
||||
if (osName.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("osName cannot be an empty string.", nameof(osName));
|
||||
}
|
||||
|
||||
if (osVersion == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(osVersion));
|
||||
}
|
||||
|
||||
if (osVersion.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("osVersion cannot be an empty string.", nameof(osName));
|
||||
}
|
||||
|
||||
_SupportPnpRootDevice = true;
|
||||
_Devices = new List<SsdpRootDevice>();
|
||||
@@ -82,7 +105,10 @@ namespace Rssdp.Infrastructure
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable supresses compiler warning, but task is not really needed.")]
|
||||
public void AddDevice(SsdpRootDevice device)
|
||||
{
|
||||
if (device == null) throw new ArgumentNullException(nameof(device));
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
@@ -115,7 +141,10 @@ namespace Rssdp.Infrastructure
|
||||
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception>
|
||||
public async Task RemoveDevice(SsdpRootDevice device)
|
||||
{
|
||||
if (device == null) throw new ArgumentNullException(nameof(device));
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
bool wasRemoved = false;
|
||||
lock (_Devices)
|
||||
@@ -156,6 +185,7 @@ namespace Rssdp.Infrastructure
|
||||
public bool SupportPnpRootDevice
|
||||
{
|
||||
get { return _SupportPnpRootDevice; }
|
||||
|
||||
set
|
||||
{
|
||||
_SupportPnpRootDevice = value;
|
||||
@@ -185,7 +215,9 @@ namespace Rssdp.Infrastructure
|
||||
if (commsServer != null)
|
||||
{
|
||||
if (!commsServer.IsShared)
|
||||
{
|
||||
commsServer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_RecentSearchRequests = null;
|
||||
@@ -205,53 +237,66 @@ namespace Rssdp.Infrastructure
|
||||
return;
|
||||
}
|
||||
|
||||
//WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget));
|
||||
// WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget));
|
||||
|
||||
if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint))
|
||||
{
|
||||
//WriteTrace("Search Request is Duplicate, ignoring.");
|
||||
// WriteTrace("Search Request is Duplicate, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Wait on random interval up to MX, as per SSDP spec.
|
||||
//Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120.
|
||||
//Using 16 as minimum as that's often the minimum system clock frequency anyway.
|
||||
// Wait on random interval up to MX, as per SSDP spec.
|
||||
// Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120.
|
||||
// Using 16 as minimum as that's often the minimum system clock frequency anyway.
|
||||
int maxWaitInterval = 0;
|
||||
if (String.IsNullOrEmpty(mx))
|
||||
{
|
||||
//Windows Explorer is poorly behaved and doesn't supply an MX header value.
|
||||
//if (this.SupportPnpRootDevice)
|
||||
// Windows Explorer is poorly behaved and doesn't supply an MX header value.
|
||||
// if (this.SupportPnpRootDevice)
|
||||
mx = "1";
|
||||
//else
|
||||
//return;
|
||||
// else
|
||||
// return;
|
||||
}
|
||||
|
||||
if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) return;
|
||||
if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxWaitInterval > 120)
|
||||
{
|
||||
maxWaitInterval = _Random.Next(0, 120);
|
||||
}
|
||||
|
||||
//Do not block synchronously as that may tie up a threadpool thread for several seconds.
|
||||
// Do not block synchronously as that may tie up a threadpool thread for several seconds.
|
||||
Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) =>
|
||||
{
|
||||
//Copying devices to local array here to avoid threading issues/enumerator exceptions.
|
||||
// Copying devices to local array here to avoid threading issues/enumerator exceptions.
|
||||
IEnumerable<SsdpDevice> devices = null;
|
||||
lock (_Devices)
|
||||
{
|
||||
if (String.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
devices = GetAllDevicesAsFlatEnumerable().ToArray();
|
||||
}
|
||||
else if (String.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (this.SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0))
|
||||
{
|
||||
devices = _Devices.ToArray();
|
||||
}
|
||||
else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray();
|
||||
}
|
||||
else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
devices = (from device in GetAllDevicesAsFlatEnumerable() where String.Compare(device.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 select device).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
if (devices != null)
|
||||
{
|
||||
var deviceList = devices.ToList();
|
||||
//WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
|
||||
// WriteTrace(String.Format("Sending {0} search responses", deviceList.Count));
|
||||
|
||||
foreach (var device in deviceList)
|
||||
{
|
||||
@@ -264,7 +309,7 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
else
|
||||
{
|
||||
//WriteTrace(String.Format("Sending 0 search responses."));
|
||||
// WriteTrace(String.Format("Sending 0 search responses."));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -285,7 +330,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
|
||||
if (this.SupportPnpRootDevice)
|
||||
{
|
||||
SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress, cancellationToken);
|
||||
@@ -308,7 +355,7 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
var rootDevice = device.ToRootDevice();
|
||||
|
||||
//var additionalheaders = FormatCustomHeadersForResponse(device);
|
||||
// var additionalheaders = FormatCustomHeadersForResponse(device);
|
||||
|
||||
const string header = "HTTP/1.1 200 OK";
|
||||
|
||||
@@ -335,10 +382,9 @@ namespace Rssdp.Infrastructure
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device);
|
||||
// WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device);
|
||||
}
|
||||
|
||||
private bool IsDuplicateSearchRequest(string searchTarget, IPEndPoint endPoint)
|
||||
@@ -352,15 +398,21 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
var lastRequest = _RecentSearchRequests[newRequest.Key];
|
||||
if (lastRequest.IsOld())
|
||||
{
|
||||
_RecentSearchRequests[newRequest.Key] = newRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
isDuplicateRequest = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_RecentSearchRequests.Add(newRequest.Key, newRequest);
|
||||
if (_RecentSearchRequests.Count > 10)
|
||||
{
|
||||
CleanUpRecentSearchRequestsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +434,12 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//WriteTrace("Begin Sending Alive Notifications For All Devices");
|
||||
// WriteTrace("Begin Sending Alive Notifications For All Devices");
|
||||
|
||||
SsdpRootDevice[] devices;
|
||||
lock (_Devices)
|
||||
@@ -394,12 +449,15 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
if (IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SendAliveNotifications(device, true, CancellationToken.None);
|
||||
}
|
||||
|
||||
//WriteTrace("Completed Sending Alive Notifications For All Devices");
|
||||
// WriteTrace("Completed Sending Alive Notifications For All Devices");
|
||||
}
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
@@ -414,7 +472,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
|
||||
if (this.SupportPnpRootDevice)
|
||||
{
|
||||
SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
SendAliveNotification(device, device.Udn, device.Udn, cancellationToken);
|
||||
@@ -448,7 +508,7 @@ namespace Rssdp.Infrastructure
|
||||
|
||||
_CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken);
|
||||
|
||||
//WriteTrace(String.Format("Sent alive notification"), device);
|
||||
// WriteTrace(String.Format("Sent alive notification"), device);
|
||||
}
|
||||
|
||||
private Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
|
||||
@@ -458,7 +518,9 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken));
|
||||
if (this.SupportPnpRootDevice)
|
||||
{
|
||||
tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
||||
tasks.Add(SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken));
|
||||
@@ -499,20 +561,27 @@ namespace Rssdp.Infrastructure
|
||||
var timer = _RebroadcastAliveNotificationsTimer;
|
||||
_RebroadcastAliveNotificationsTimer = null;
|
||||
if (timer != null)
|
||||
{
|
||||
timer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetMinimumNonZeroCacheLifetime()
|
||||
{
|
||||
var nonzeroCacheLifetimesQuery = (from device
|
||||
in _Devices
|
||||
where device.CacheLifetime != TimeSpan.Zero
|
||||
select device.CacheLifetime).ToList();
|
||||
var nonzeroCacheLifetimesQuery = (
|
||||
from device
|
||||
in _Devices
|
||||
where device.CacheLifetime != TimeSpan.Zero
|
||||
select device.CacheLifetime).ToList();
|
||||
|
||||
if (nonzeroCacheLifetimesQuery.Any())
|
||||
{
|
||||
return nonzeroCacheLifetimesQuery.Min();
|
||||
}
|
||||
else
|
||||
{
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFirstHeaderValue(System.Net.Http.Headers.HttpRequestHeaders httpRequestHeaders, string headerName)
|
||||
@@ -520,7 +589,9 @@ namespace Rssdp.Infrastructure
|
||||
string retVal = null;
|
||||
IEnumerable<String> values = null;
|
||||
if (httpRequestHeaders.TryGetValues(headerName, out values) && values != null)
|
||||
{
|
||||
retVal = values.FirstOrDefault();
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
@@ -533,31 +604,38 @@ namespace Rssdp.Infrastructure
|
||||
{
|
||||
LogFunction(text);
|
||||
}
|
||||
//System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher");
|
||||
// System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher");
|
||||
}
|
||||
|
||||
private void WriteTrace(string text, SsdpDevice device)
|
||||
{
|
||||
var rootDevice = device as SsdpRootDevice;
|
||||
if (rootDevice != null)
|
||||
{
|
||||
WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid + " - " + rootDevice.Location);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e)
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
if (this.IsDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(e.Message.Method.Method, SsdpConstants.MSearchMethod, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//According to SSDP/UPnP spec, ignore message if missing these headers.
|
||||
// According to SSDP/UPnP spec, ignore message if missing these headers.
|
||||
// Edit: But some devices do it anyway
|
||||
//if (!e.Message.Headers.Contains("MX"))
|
||||
// if (!e.Message.Headers.Contains("MX"))
|
||||
// WriteTrace("Ignoring search request - missing MX header.");
|
||||
//else if (!e.Message.Headers.Contains("MAN"))
|
||||
// else if (!e.Message.Headers.Contains("MAN"))
|
||||
// WriteTrace("Ignoring search request - missing MAN header.");
|
||||
//else
|
||||
// else
|
||||
ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
@@ -565,7 +643,9 @@ namespace Rssdp.Infrastructure
|
||||
private class SearchRequest
|
||||
{
|
||||
public IPEndPoint EndPoint { get; set; }
|
||||
|
||||
public DateTime Received { get; set; }
|
||||
|
||||
public string SearchTarget { get; set; }
|
||||
|
||||
public string Key
|
||||
|
||||
@@ -5,14 +5,8 @@ namespace Rssdp
|
||||
/// </summary>
|
||||
public class SsdpEmbeddedDevice : SsdpDevice
|
||||
{
|
||||
|
||||
#region Fields
|
||||
private SsdpRootDevice _RootDevice;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
@@ -20,10 +14,6 @@ namespace Rssdp
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="SsdpRootDevice"/> that is this device's first ancestor. If this device is itself an <see cref="SsdpRootDevice"/>, then returns a reference to itself.
|
||||
/// </summary>
|
||||
@@ -33,6 +23,7 @@ namespace Rssdp
|
||||
{
|
||||
return _RootDevice;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
_RootDevice = value;
|
||||
@@ -45,7 +36,5 @@ namespace Rssdp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,8 @@ namespace Rssdp
|
||||
/// </remarks>
|
||||
public class SsdpRootDevice : SsdpDevice
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private Uri _UrlBase;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
@@ -27,10 +21,6 @@ namespace Rssdp
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how long clients can cache this device's details for. Optional but defaults to <see cref="TimeSpan.Zero"/> which means no-caching. Recommended value is half an hour.
|
||||
/// </summary>
|
||||
@@ -77,7 +67,5 @@ namespace Rssdp
|
||||
_UrlBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user