diff options
Diffstat (limited to 'RSSDP')
43 files changed, 9989 insertions, 0 deletions
diff --git a/RSSDP/CustomHttpHeaders.cs b/RSSDP/CustomHttpHeaders.cs new file mode 100644 index 000000000..9250d612f --- /dev/null +++ b/RSSDP/CustomHttpHeaders.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + /// <summary> + /// Represents a custom HTTP header sent on device search response or notification messages. + /// </summary> + public sealed class CustomHttpHeader + { + + #region Fields + + private string _Name; + private string _Value; + + #endregion + + #region Constructors + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="name">The field name of the header.</param> + /// <param name="value">The value of the header</param> + /// <remarks> + /// <para>As per RFC 822 and 2616, the name must contain only printable ASCII characters (33-126) excluding colon (:). The value may contain any ASCII characters except carriage return or line feed.</para> + /// </remarks> + /// <exception cref="System.ArgumentNullException">Thrown if the name is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the name is an empty value, or contains an invalid character. Also thrown if the value contains a \r or \n character.</exception> + public CustomHttpHeader(string name, string value) + { + Name = name; + Value = value; + } + + #endregion + + #region Public Properties + + /// <summary> + /// Return the name of this header. + /// </summary> + public string Name + { + get { return _Name; } + private set + { + EnsureValidName(value); + _Name = value; + } + } + + /// <summary> + /// Returns the value of this header. + /// </summary> + public string Value + { + get { return _Value; } + private set + { + EnsureValidValue(value); + _Value = value; + } + } + + #endregion + + #region Overrides + + /// <summary> + /// Returns the header formatted for use in an HTTP message. + /// </summary> + /// <returns>A string representing this header in the format of 'name: value'.</returns> + public override string ToString() + { + return this.Name + ": " + this.Value; + } + + #endregion + + #region Private Methods + + private static void EnsureValidName(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name), "Name cannot be null."); + if (name.Length == 0) throw new ArgumentException("Name cannot be blank.", nameof(name)); + + foreach (var c in name) + { + var b = (byte)c; + if (c == ':' || b < 33 || b > 126) throw new ArgumentException("Name contains illegal characters.", nameof(name)); + } + } + + private static void EnsureValidValue(string value) + { + if (String.IsNullOrEmpty(value)) return; + + if (value.Contains("\r") || value.Contains("\n")) throw new ArgumentException("Invalid value.", nameof(value)); + } + + #endregion + + } + + /// <summary> + /// Represents a collection of custom HTTP headers, keyed by name. + /// </summary> + public class CustomHttpHeadersCollection : IEnumerable<CustomHttpHeader> + { + #region Fields + + private IDictionary<string, CustomHttpHeader> _Headers; + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + public CustomHttpHeadersCollection() + { + _Headers = new Dictionary<string, CustomHttpHeader>(); + } + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="capacity">Specifies the initial capacity of the collection.</param> + public CustomHttpHeadersCollection(int capacity) + { + _Headers = new Dictionary<string, CustomHttpHeader>(capacity); + } + + #endregion + + #region Public Methpds + + /// <summary> + /// Adds a <see cref="CustomHttpHeader"/> instance to the collection. + /// </summary> + /// <param name="header">The <see cref="CustomHttpHeader"/> instance to add to the collection.</param> + /// <remarks> + /// <para></para> + /// </remarks> + /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="header"/> is null.</exception> + public void Add(CustomHttpHeader header) + { + if (header == null) throw new ArgumentNullException(nameof(header)); + + lock (_Headers) + { + _Headers.Add(header.Name, header); + } + } + + #region Remove Overloads + + /// <summary> + /// Removes the specified header instance from the collection. + /// </summary> + /// <param name="header">The <see cref="CustomHttpHeader"/> instance to remove from the collection.</param> + /// <remarks> + /// <para>Only removes the specified header if that instance was in the collection, if another header with the same name exists in the collection it is not removed.</para> + /// </remarks> + /// <returns>True if an item was removed from the collection, otherwise false (because it did not exist or was not the same instance).</returns> + /// <seealso cref="Remove(string)"/> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="header"/> is null.</exception> + public bool Remove(CustomHttpHeader header) + { + if (header == null) throw new ArgumentNullException(nameof(header)); + + lock (_Headers) + { + if (_Headers.ContainsKey(header.Name) && _Headers[header.Name] == header) + return _Headers.Remove(header.Name); + } + + return false; + } + + /// <summary> + /// Removes the property with the specified key (<see cref="CustomHttpHeader.Name"/> from the collection. + /// </summary> + /// <param name="headerName">The name of the <see cref="CustomHttpHeader"/> instance to remove from the collection.</param> + /// <returns>True if an item was removed from the collection, otherwise false (because no item exists in the collection with that key).</returns> + /// <exception cref="System.ArgumentException">Thrown if the <paramref name="headerName"/> argument is null or empty string.</exception> + public bool Remove(string headerName) + { + if (String.IsNullOrEmpty(headerName)) throw new ArgumentException("headerName cannot be null or empty.", nameof(headerName)); + + lock (_Headers) + { + return _Headers.Remove(headerName); + } + } + + #endregion + + /// <summary> + /// Returns a boolean indicating whether or not the specified <see cref="CustomHttpHeader"/> instance is in the collection. + /// </summary> + /// <param name="header">An <see cref="CustomHttpHeader"/> instance to check the collection for.</param> + /// <returns>True if the specified instance exists in the collection, otherwise false.</returns> + public bool Contains(CustomHttpHeader header) + { + if (header == null) throw new ArgumentNullException(nameof(header)); + + lock (_Headers) + { + if (_Headers.ContainsKey(header.Name)) + return _Headers[header.Name] == header; + } + + return false; + } + + /// <summary> + /// Returns a boolean indicating whether or not a <see cref="CustomHttpHeader"/> instance with the specified full name value exists in the collection. + /// </summary> + /// <param name="headerName">A string containing the full name of the <see cref="CustomHttpHeader"/> instance to check for.</param> + /// <returns>True if an item with the specified full name exists in the collection, otherwise false.</returns> + public bool Contains(string headerName) + { + if (String.IsNullOrEmpty(headerName)) throw new ArgumentException("headerName cannot be null or empty.", nameof(headerName)); + + lock (_Headers) + { + return _Headers.ContainsKey(headerName); + } + } + + #endregion + + #region Public Properties + + /// <summary> + /// Returns the number of items in the collection. + /// </summary> + public int Count + { + get { return _Headers.Count; } + } + + /// <summary> + /// Returns the <see cref="CustomHttpHeader"/> instance from the collection that has the specified <see cref="CustomHttpHeader.Name"/> value. + /// </summary> + /// <param name="name">The full name of the property to return.</param> + /// <returns>A <see cref="CustomHttpHeader"/> instance from the collection.</returns> + /// <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if no item exists in the collection with the specified <paramref name="name"/> value.</exception> + public CustomHttpHeader this[string name] + { + get + { + return _Headers[name]; + } + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator of <see cref="CustomHttpHeader"/> instances in this collection. + /// </summary> + /// <returns>An enumerator of <see cref="CustomHttpHeader"/> instances in this collection.</returns> + public IEnumerator<CustomHttpHeader> GetEnumerator() + { + lock (_Headers) + { + return _Headers.Values.GetEnumerator(); + } + } + + /// <summary> + /// Returns an enumerator of <see cref="CustomHttpHeader"/> instances in this collection. + /// </summary> + /// <returns>An enumerator of <see cref="CustomHttpHeader"/> instances in this collection.</returns> + IEnumerator IEnumerable.GetEnumerator() + { + lock (_Headers) + { + return _Headers.Values.GetEnumerator(); + } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs new file mode 100644 index 000000000..39f07e1d7 --- /dev/null +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp +{ + /// <summary> + /// Event arguments for the <see cref="Rssdp.Infrastructure.SsdpDeviceLocatorBase.DeviceAvailable"/> event. + /// </summary> + public sealed class DeviceAvailableEventArgs : EventArgs + { + + #region Fields + + private readonly DiscoveredSsdpDevice _DiscoveredDevice; + private readonly bool _IsNewlyDiscovered; + + #endregion + + #region Constructors + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="discoveredDevice">A <see cref="DiscoveredSsdpDevice"/> instance representing the available device.</param> + /// <param name="isNewlyDiscovered">A boolean value indicating whether or not this device came from the cache. See <see cref="IsNewlyDiscovered"/> for more detail.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception> + public DeviceAvailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool isNewlyDiscovered) + { + if (discoveredDevice == null) throw new ArgumentNullException("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> + public bool IsNewlyDiscovered + { + get { return _IsNewlyDiscovered; } + } + + /// <summary> + /// A reference to a <see cref="Rssdp.DiscoveredSsdpDevice"/> instance containing the discovered details and allowing access to the full device description. + /// </summary> + public DiscoveredSsdpDevice DiscoveredDevice + { + get { return _DiscoveredDevice; } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/DeviceEventArgs.cs b/RSSDP/DeviceEventArgs.cs new file mode 100644 index 000000000..e0c19c4c4 --- /dev/null +++ b/RSSDP/DeviceEventArgs.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + /// <summary> + /// Event arguments for the <see cref="SsdpDevice.DeviceAdded"/> and <see cref="SsdpDevice.DeviceRemoved"/> events. + /// </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> + /// <param name="device">The <see cref="SsdpDevice"/> associated with the event this argument class is being used for.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + public DeviceEventArgs(SsdpDevice device) + { + if (device == null) throw new ArgumentNullException("device"); + + _Device = device; + } + + #endregion + + #region Public Properties + + /// <summary> + /// Returns the <see cref="SsdpDevice"/> instance the event being raised for. + /// </summary> + public SsdpDevice Device + { + get { return _Device; } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/DeviceUnavailableEventArgs.cs b/RSSDP/DeviceUnavailableEventArgs.cs new file mode 100644 index 000000000..5b7c1437a --- /dev/null +++ b/RSSDP/DeviceUnavailableEventArgs.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp +{ + /// <summary> + /// Event arguments for the <see cref="Rssdp.Infrastructure.SsdpDeviceLocatorBase.DeviceUnavailable"/> event. + /// </summary> + public sealed class DeviceUnavailableEventArgs : EventArgs + { + + #region Fields + + private readonly DiscoveredSsdpDevice _DiscoveredDevice; + private readonly bool _Expired; + + #endregion + + #region Constructors + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="discoveredDevice">A <see cref="DiscoveredSsdpDevice"/> instance representing the device that has become unavailable.</param> + /// <param name="expired">A boolean value indicating whether this device is unavailable because it expired, or because it explicitly sent a byebye notification.. See <see cref="Expired"/> for more detail.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="discoveredDevice"/> parameter is null.</exception> + public DeviceUnavailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool expired) + { + if (discoveredDevice == null) throw new ArgumentNullException("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> + public bool Expired + { + get { return _Expired; } + } + + /// <summary> + /// A reference to a <see cref="Rssdp.DiscoveredSsdpDevice"/> instance containing the discovery details of the removed device. + /// </summary> + public DiscoveredSsdpDevice DiscoveredDevice + { + get { return _DiscoveredDevice; } + } + + #endregion + } +}
\ No newline at end of file diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs new file mode 100644 index 000000000..58f0acfa5 --- /dev/null +++ b/RSSDP/DiscoveredSsdpDevice.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Net.Http.Headers; + +namespace Rssdp +{ + /// <summary> + /// Represents a discovered device, containing basic information about the device and the location of it's full device description document. Also provides convenience methods for retrieving the device description document. + /// </summary> + /// <seealso cref="SsdpDevice"/> + /// <seealso cref="Rssdp.Infrastructure.ISsdpDeviceLocator"/> + public sealed class DiscoveredSsdpDevice + { + + #region Fields + + private SsdpRootDevice _Device; + private DateTimeOffset _AsAt; + + private static HttpClient s_DefaultHttpClient; + + #endregion + + #region Public Properties + + /// <summary> + /// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice. + /// </summary> + public string NotificationType { get; set; } + + /// <summary> + /// Sets or returns the universal service name (USN) of the device. + /// </summary> + public string Usn { get; set; } + + /// <summary> + /// Sets or returns a URL pointing to the device description document for this device. + /// </summary> + public Uri DescriptionLocation { get; set; } + + /// <summary> + /// Sets or returns the length of time this information is valid for (from the <see cref="AsAt"/> time). + /// </summary> + public TimeSpan CacheLifetime { get; set; } + + /// <summary> + /// Sets or returns the date and time this information was received. + /// </summary> + public DateTimeOffset AsAt + { + get { return _AsAt; } + set + { + if (_AsAt != value) + { + _AsAt = value; + _Device = null; + } + } + } + + /// <summary> + /// 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> + /// <returns></returns> + public bool IsExpired() + { + return this.CacheLifetime == TimeSpan.Zero || this.AsAt.Add(this.CacheLifetime) <= DateTimeOffset.Now; + } + + /// <summary> + /// Retrieves the device description document specified by the <see cref="DescriptionLocation"/> property. + /// </summary> + /// <remarks> + /// <para>This method may choose to cache (or return cached) information if called multiple times within the <see cref="CacheLifetime"/> period.</para> + /// </remarks> + /// <returns>An <see cref="SsdpDevice"/> instance describing the full device details.</returns> + public async Task<SsdpDevice> GetDeviceInfo() + { + var device = _Device; + if (device == null || this.IsExpired()) + return await GetDeviceInfo(GetDefaultClient()); + else + return device; + } + + /// <summary> + /// Retrieves the device description document specified by the <see cref="DescriptionLocation"/> property using the provided <see cref="System.Net.Http.HttpClient"/> instance. + /// </summary> + /// <remarks> + /// <para>This method may choose to cache (or return cached) information if called multiple times within the <see cref="CacheLifetime"/> period.</para> + /// <para>This method performs no error handling, if an exception occurs downloading or parsing the document it will be thrown to the calling code. Ensure you setup correct error handling for these scenarios.</para> + /// </remarks> + /// <param name="downloadHttpClient">A <see cref="System.Net.Http.HttpClient"/> to use when downloading the document data.</param> + /// <returns>An <see cref="SsdpDevice"/> instance describing the full device details.</returns> + public async Task<SsdpRootDevice> GetDeviceInfo(HttpClient downloadHttpClient) + { + if (_Device == null || this.IsExpired()) + { + var rawDescriptionDocument = await downloadHttpClient.GetAsync(this.DescriptionLocation); + rawDescriptionDocument.EnsureSuccessStatusCode(); + + // Not using ReadAsStringAsync() here as some devices return the content type as utf-8 not UTF-8, + // which causes an (unneccesary) exception. + var data = await rawDescriptionDocument.Content.ReadAsByteArrayAsync(); + _Device = new SsdpRootDevice(this.DescriptionLocation, this.CacheLifetime, System.Text.UTF8Encoding.UTF8.GetString(data, 0, data.Length)); + } + + return _Device; + } + + #endregion + + #region Overrides + + /// <summary> + /// Returns the device's <see cref="Usn"/> value. + /// </summary> + /// <returns>A string containing the device's universal service name.</returns> + public override string ToString() + { + return this.Usn; + } + + #endregion + + #region Private Methods + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Can't call dispose on the handler since we pass it to the HttpClient, which outlives the scope of this method.")] + private static HttpClient GetDefaultClient() + { + if (s_DefaultHttpClient == null) + { + var handler = new System.Net.Http.HttpClientHandler(); + try + { + if (handler.SupportsAutomaticDecompression) + handler.AutomaticDecompression = System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.GZip; + + s_DefaultHttpClient = new HttpClient(handler); + } + catch + { + if (handler != null) + handler.Dispose(); + + throw; + } + } + + return s_DefaultHttpClient; + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs new file mode 100644 index 000000000..87f2aa71c --- /dev/null +++ b/RSSDP/DisposableManagedObjectBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Correclty implements the <see cref="IDisposable"/> interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an <see cref="IsDisposed"/> property. + /// </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> + /// <param name="disposing">True if managed objects should be disposed, if false, only unmanaged resources should be released.</param> + protected abstract void Dispose(bool disposing); + + /// <summary> + /// Throws and <see cref="System.ObjectDisposedException"/> if the <see cref="IsDisposed"/> property is true. + /// </summary> + /// <seealso cref="IsDisposed"/> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="IsDisposed"/> property is true.</exception> + /// <seealso cref="Dispose()"/> + protected virtual void ThrowIfDisposed() + { + 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> + /// <seealso cref="Dispose()"/> + public bool IsDisposed + { + get; + private set; + } + + #endregion + + #region IDisposable Members + + /// <summary> + /// Disposes this object instance and all internally managed resources. + /// </summary> + /// <remarks> + /// <para>Sets the <see cref="IsDisposed"/> property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.</para> + /// </remarks> + /// <seealso cref="IsDisposed"/> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification="We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] + public void Dispose() + { + try + { + IsDisposed = true; + + Dispose(true); + } + finally + { + GC.SuppressFinalize(this); + } + } + + #endregion + } +}
\ No newline at end of file diff --git a/RSSDP/GlobalSuppressions.cs b/RSSDP/GlobalSuppressions.cs Binary files differnew file mode 100644 index 000000000..b9aa0c369 --- /dev/null +++ b/RSSDP/GlobalSuppressions.cs diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs new file mode 100644 index 000000000..7934419b0 --- /dev/null +++ b/RSSDP/HttpParserBase.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// A base class for the <see cref="HttpResponseParser"/> and <see cref="HttpRequestParser"/> classes. Not intended for direct use. + /// </summary> + /// <typeparam name="T"></typeparam> + public abstract class HttpParserBase<T> where T : new() + { + + #region Fields + + private static readonly string[] LineTerminators = new string[] { "\r\n", "\n" }; + private static readonly char[] SeparatorCharacters = new char[] { ',', ';' }; + + #endregion + + #region Public Methods + + /// <summary> + /// Parses the <paramref name="data"/> provided into either a <see cref="System.Net.Http.HttpRequestMessage"/> or <see cref="System.Net.Http.HttpResponseMessage"/> object. + /// </summary> + /// <param name="data">A string containing the HTTP message to parse.</param> + /// <returns>Either a <see cref="System.Net.Http.HttpRequestMessage"/> or <see cref="System.Net.Http.HttpResponseMessage"/> object containing the parsed data.</returns> + public abstract T Parse(string data); + + /// <summary> + /// Parses a string containing either an HTTP request or response into a <see cref="System.Net.Http.HttpRequestMessage"/> or <see cref="System.Net.Http.HttpResponseMessage"/> object. + /// </summary> + /// <param name="message">A <see cref="System.Net.Http.HttpRequestMessage"/> or <see cref="System.Net.Http.HttpResponseMessage"/> object representing the parsed message.</param> + /// <param name="headers">A reference to the <see cref="System.Net.Http.Headers.HttpHeaders"/> collection for the <paramref name="message"/> object.</param> + /// <param name="data">A string containing the data to be parsed.</param> + /// <returns>An <see cref="System.Net.Http.HttpContent"/> object containing the content of the parsed message.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="Honestly, it's fine. MemoryStream doesn't mind.")] + protected virtual HttpContent Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data) + { + if (data == null) throw new ArgumentNullException("data"); + if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", "data"); + if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", "data"); + + HttpContent retVal = null; + try + { + var contentStream = new System.IO.MemoryStream(); + try + { + retVal = new StreamContent(contentStream); + + var lines = data.Split(LineTerminators, StringSplitOptions.None); + + //First line is the 'request' line containing http protocol details like method, uri, http version etc. + ParseStatusLine(lines[0], message); + + int lineIndex = ParseHeaders(headers, retVal.Headers, lines); + + if (lineIndex < lines.Length - 1) + { + //Read rest of any remaining data as content. + if (lineIndex < lines.Length - 1) + { + //This is inefficient in multiple ways, but not sure of a good way of correcting. Revisit. + var body = System.Text.UTF8Encoding.UTF8.GetBytes(String.Join(null, lines, lineIndex, lines.Length - lineIndex)); + contentStream.Write(body, 0, body.Length); + contentStream.Seek(0, System.IO.SeekOrigin.Begin); + } + } + } + catch + { + if (contentStream != null) + contentStream.Dispose(); + + throw; + } + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + + return retVal; + } + + /// <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> + /// <param name="data">The first line of the HTTP message to be parsed.</param> + /// <param name="message">Either a <see cref="System.Net.Http.HttpResponseMessage"/> or <see cref="System.Net.Http.HttpRequestMessage"/> to assign the parsed values to.</param> + protected abstract void ParseStatusLine(string data, T message); + + /// <summary> + /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). + /// </summary> + /// <param name="headerName">A string containing the name of the header to return the type of.</param> + protected abstract bool IsContentHeader(string headerName); + + /// <summary> + /// Parses the HTTP version text from an HTTP request or response status line and returns a <see cref="Version"/> object representing the parsed values. + /// </summary> + /// <param name="versionData">A string containing the HTTP version, from the message status line.</param> + /// <returns>A <see cref="Version"/> object containing the parsed version data.</returns> + protected static Version ParseHttpVersion(string versionData) + { + if (versionData == null) throw new ArgumentNullException("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.", "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> + /// <param name="line">A string containing the data to be parsed.</param> + /// <param name="headers">A reference to a <see cref="System.Net.Http.Headers.HttpHeaders"/> collection to which the parsed header will be added.</param> + /// <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 + 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. + + 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. + 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. + while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex]))) + { + if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0])) + { + line += "," + nextLine.TrimStart(); + lineIndex++; + } + else + break; + } + + ParseHeader(line, headers, contentHeaders); + } + return lineIndex; + } + + private static IList<string> ParseValues(string headerValue) + { + // This really should be better and match the HTTP 1.1 spec, + // but this should actually be good enough for SSDP implementations + // I think. + var values = new List<string>(); + + if (headerValue == "\"\"") + { + values.Add(String.Empty); + return values; + } + + var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters); + if (indexOfSeparator <= 0) + values.Add(headerValue); + else + { + var segments = headerValue.Split(SeparatorCharacters); + if (headerValue.Contains("\"")) + { + for (int segmentIndex = 0; segmentIndex < segments.Length; segmentIndex++) + { + 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; + } + + private static string CombineQuotedSegments(string[] segments, ref int segmentIndex, string segment) + { + var trimmedSegment = segment.Trim(); + for (int index = segmentIndex; index < segments.Length; index++) + { + if (trimmedSegment == "\"\"" || + ( + trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase) + && !trimmedSegment.EndsWith("\"\"", StringComparison.OrdinalIgnoreCase) + && !trimmedSegment.EndsWith("\\\"", StringComparison.OrdinalIgnoreCase)) + ) + { + segmentIndex = index; + return trimmedSegment.Substring(1, trimmedSegment.Length - 2); + } + + 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 + + } +}
\ No newline at end of file diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs new file mode 100644 index 000000000..0923f291f --- /dev/null +++ b/RSSDP/HttpRequestParser.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Parses a string into a <see cref="System.Net.Http.HttpRequestMessage"/> or throws an exception. + /// </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 + + /// <summary> + /// Parses the specified data into a <see cref="System.Net.Http.HttpRequestMessage"/> instance. + /// </summary> + /// <param name="data">A string containing the data to parse.</param> + /// <returns>A <see cref="System.Net.Http.HttpRequestMessage"/> instance containing the parsed data.</returns> + public override System.Net.Http.HttpRequestMessage Parse(string data) + { + System.Net.Http.HttpRequestMessage retVal = null; + + try + { + retVal = new System.Net.Http.HttpRequestMessage(); + + retVal.Content = Parse(retVal, retVal.Headers, data); + + return retVal; + } + 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> + /// <param name="data">The first line of the HTTP message to be parsed.</param> + /// <param name="message">Either a <see cref="System.Net.Http.HttpResponseMessage"/> or <see cref="System.Net.Http.HttpRequestMessage"/> to assign the parsed values to.</param> + protected override void ParseStatusLine(string data, HttpRequestMessage message) + { + if (data == null) throw new ArgumentNullException("data"); + if (message == null) throw new ArgumentNullException("message"); + + var parts = data.Split(' '); + if (parts.Length < 3) throw new ArgumentException("Status line is invalid. Insufficient status parts.", "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]); + + message.Version = ParseHttpVersion(parts[2].Trim()); + } + + /// <summary> + /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). + /// </summary> + /// <param name="headerName">A string containing the name of the header to return the type of.</param> + protected override bool IsContentHeader(string headerName) + { + return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase); + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/HttpResponseParser.cs b/RSSDP/HttpResponseParser.cs new file mode 100644 index 000000000..ba85a1657 --- /dev/null +++ b/RSSDP/HttpResponseParser.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Parses a string into a <see cref="System.Net.Http.HttpResponseMessage"/> or throws an exception. + /// </summary> + public sealed class HttpResponseParser : HttpParserBase<System.Net.Http.HttpResponseMessage> + { + + #region Fields & Constants + + private static 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 + + /// <summary> + /// Parses the specified data into a <see cref="System.Net.Http.HttpResponseMessage"/> instance. + /// </summary> + /// <param name="data">A string containing the data to parse.</param> + /// <returns>A <see cref="System.Net.Http.HttpResponseMessage"/> instance containing the parsed data.</returns> + public override HttpResponseMessage Parse(string data) + { + System.Net.Http.HttpResponseMessage retVal = null; + try + { + retVal = new System.Net.Http.HttpResponseMessage(); + + retVal.Content = Parse(retVal, retVal.Headers, data); + + return retVal; + } + 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> + /// <param name="headerName">A string containing the name of the header to return the type of.</param> + /// <returns>A boolean, true if th specified header relates to HTTP content, otherwise false.</returns> + protected override bool IsContentHeader(string headerName) + { + return ContentHeaderNames.Contains(headerName, StringComparer.OrdinalIgnoreCase); + } + + /// <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> + /// <param name="data">The first line of the HTTP message to be parsed.</param> + /// <param name="message">Either a <see cref="System.Net.Http.HttpResponseMessage"/> or <see cref="System.Net.Http.HttpRequestMessage"/> to assign the parsed values to.</param> + protected override void ParseStatusLine(string data, HttpResponseMessage message) + { + if (data == null) throw new ArgumentNullException("data"); + if (message == null) throw new ArgumentNullException("message"); + + var parts = data.Split(' '); + if (parts.Length < 3) throw new ArgumentException("data status line is invalid. Insufficient status parts.", "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.", "data"); + + message.StatusCode = (HttpStatusCode)statusCode; + message.ReasonPhrase = parts[2].Trim(); + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/IEnumerableExtensions.cs b/RSSDP/IEnumerableExtensions.cs new file mode 100644 index 000000000..f72073949 --- /dev/null +++ b/RSSDP/IEnumerableExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp.Infrastructure +{ + internal static class IEnumerableExtensions + { + public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector) + { + if (source == null) throw new ArgumentNullException("source"); + if (selector == null) throw new ArgumentNullException("selector"); + + return !source.Any() ? source : + source.Concat( + source + .SelectMany(i => selector(i).EmptyIfNull()) + .SelectManyRecursive(selector) + ); + } + + public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source) + { + return source ?? Enumerable.Empty<T>(); + } + } +} diff --git a/RSSDP/ISocketFactory.cs b/RSSDP/ISocketFactory.cs new file mode 100644 index 000000000..3e7d7facb --- /dev/null +++ b/RSSDP/ISocketFactory.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform <see cref="IUdpSocket"/> interface. + /// </summary> + public interface ISocketFactory + { + + /// <summary> + /// Createa a new unicast socket using the specified local port number. + /// </summary> + /// <param name="localPort">The local port to bind to.</param> + /// <returns>A <see cref="IUdpSocket"/> implementation.</returns> + IUdpSocket CreateUdpSocket(int localPort); + + /// <summary> + /// Createa a new multicast socket using the specified multicast IP address, multicast time to live and local port. + /// </summary> + /// <param name="ipAddress">The multicast IP address to bind to.</param> + /// <param name="multicastTimeToLive">The multicast time to live value. Actually a maximum number of network hops for UDP packets.</param> + /// <param name="localPort">The local port to bind to.</param> + /// <returns>A <see cref="IUdpSocket"/> implementation.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Justification="IP is a well known and understood abbreviation and the full name is excessive.")] + IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort); + + } +} diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs new file mode 100644 index 000000000..990b21d05 --- /dev/null +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Interface for a component that manages network communication (sending and receiving HTTPU messages) for the SSDP protocol. + /// </summary> + public interface ISsdpCommunicationsServer : IDisposable + { + + #region Events + + /// <summary> + /// Raised when a HTTPU request message is received by a socket (unicast or multicast). + /// </summary> + event EventHandler<RequestReceivedEventArgs> RequestReceived; + + /// <summary> + /// Raised when an HTTPU response message is received by a socket (unicast or multicast). + /// </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> + void BeginListeningForBroadcasts(); + + /// <summary> + /// Causes the server to stop listening for multicast messages, being SSDP search requests and notifications. + /// </summary> + void StopListeningForBroadcasts(); + + /// <summary> + /// Stops listening for search responses on the local, unicast socket. + /// </summary> + void StopListeningForResponses(); + + /// <summary> + /// Sends a message to a particular address (uni or multicast) and port. + /// </summary> + /// <param name="messageData">A byte array containing the data to send.</param> + /// <param name="destination">A <see cref="UdpEndPoint"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param> + Task SendMessage(byte[] messageData, UdpEndPoint destination); + + /// <summary> + /// Sends a message to the SSDP multicast address and port. + /// </summary> + /// <param name="messageData">A byte array containing the data to send.</param> + Task SendMulticastMessage(byte[] messageData); + + #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> + /// <remarks> + /// <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 + + } +}
\ No newline at end of file diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs new file mode 100644 index 000000000..4b7d10796 --- /dev/null +++ b/RSSDP/ISsdpDeviceLocator.cs @@ -0,0 +1,146 @@ +using System; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Interface for components that discover the existence of SSDP devices. + /// </summary> + /// <remarks> + /// <para>Discovering devices includes explicit search requests as well as listening for broadcast status notifications.</para> + /// </remarks> + /// <seealso cref="DiscoveredSsdpDevice"/> + /// <seealso cref="SsdpDevice"/> + /// <seealso cref="ISsdpDevicePublisher"/> + public interface ISsdpDeviceLocator + { + + #region Events + + /// <summary> + /// Event raised when a device becomes available or is found by a search request. + /// </summary> + /// <seealso cref="NotificationFilter"/> + /// <seealso cref="DeviceUnavailable"/> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="StopListeningForNotifications"/> + event EventHandler<DeviceAvailableEventArgs> DeviceAvailable; + + /// <summary> + /// Event raised when a device explicitly notifies of shutdown or a device expires from the cache. + /// </summary> + /// <seeseealso cref="NotificationFilter"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="StartListeningForNotifications"/> + /// <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> + /// <remarks> + /// <para>Device alive/byebye notifications whose NT header does not match this filter value will still be captured and cached internally, but will not raise events about device availability. Usually used with either a device type of uuid NT header value.</para> + /// <para>Example filters follow;</para> + /// <example>upnp:rootdevice</example> + /// <example>urn:schemas-upnp-org:device:WANDevice:1</example> + /// <example>"uuid:9F15356CC-95FA-572E-0E99-85B456BD3012"</example> + /// </remarks> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="DeviceUnavailable"/> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="StopListeningForNotifications"/> + string NotificationFilter + { + get; + set; + } + + /// <summary> + /// Returns a boolean indicating whether or not a search is currently active. + /// </summary> + bool IsSearching { get; } + + #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> + /// <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(); + + /// <summary> + /// Performs a search for the specified search target (criteria) and default search timeout. + /// </summary> + /// <param name="searchTarget">The criteria for the search. Value can be; + /// <list type="table"> + /// <item><term>Root devices</term><description>upnp:rootdevice</description></item> + /// <item><term>Specific device by UUID</term><description>uuid:<device uuid></description></item> + /// <item><term>Device type</term><description>Fully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1</description></item> + /// </list> + /// </param> + /// <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(string searchTarget); + + /// <summary> + /// Performs a search for the specified search target (criteria) and search timeout. + /// </summary> + /// <param name="searchTarget">The criteria for the search. Value can be; + /// <list type="table"> + /// <item><term>Root devices</term><description>upnp:rootdevice</description></item> + /// <item><term>Specific device by UUID</term><description>uuid:<device uuid></description></item> + /// <item><term>Device type</term><description>A device namespace and type in format of urn:<device namespace>:device:<device type>:<device version> i.e urn:schemas-upnp-org:device:Basic:1</description></item> + /// <item><term>Service type</term><description>A service namespace and type in format of urn:<service namespace>:service:<servicetype>:<service version> i.e urn:my-namespace:service:MyCustomService:1</description></item> + /// </list> + /// </param> + /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache.</param> + /// <remarks> + /// <para>By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type.</para> + /// </remarks> + /// <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(string searchTarget, TimeSpan searchWaitTime); + + /// <summary> + /// Performs a search for all devices using the specified search timeout. + /// </summary> + /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache.</param> + /// <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> + /// <remarks> + /// <para>When called the system will listen for 'alive' and 'byebye' notifications. This can speed up searching, as well as provide dynamic notification of new devices appearing on the network, and previously discovered devices disappearing.</para> + /// </remarks> + /// <seealso cref="StopListeningForNotifications"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="DeviceUnavailable"/> + /// <seealso cref="NotificationFilter"/> + void StartListeningForNotifications(); + + /// <summary> + /// Stops listening for broadcast notifications of service availability. + /// </summary> + /// <remarks> + /// <para>Does nothing if this instance is not already listening for notifications.</para> + /// </remarks> + /// <exception cref="System.ObjectDisposedException">Throw if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true.</exception> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="DeviceUnavailable"/> + /// <seealso cref="NotificationFilter"/> + void StopListeningForNotifications(); + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/ISsdpDevicePublisher.cs b/RSSDP/ISsdpDevicePublisher.cs new file mode 100644 index 000000000..b6ebc4176 --- /dev/null +++ b/RSSDP/ISsdpDevicePublisher.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Interface for components that publish the existence of SSDP devices. + /// </summary> + /// <remarks> + /// <para>Publishing a device includes sending notifications (alive and byebye) as well as responding to search requests when appropriate.</para> + /// </remarks> + /// <seealso cref="SsdpRootDevice"/> + /// <seealso cref="ISsdpDeviceLocator"/> + public interface ISsdpDevicePublisher + { + /// <summary> + /// Adds a device (and it's children) to the list of devices being published by this server, making them discoverable to SSDP clients. + /// </summary> + /// <param name="device">The <see cref="SsdpRootDevice"/> instance to add.</param> + /// <returns>An awaitable <see cref="System.Threading.Tasks.Task"/>.</returns> + void AddDevice(SsdpRootDevice device); + + /// <summary> + /// Removes a device (and it's children) from the list of devices being published by this server, making them undiscoverable. + /// </summary> + /// <param name="device">The <see cref="SsdpRootDevice"/> instance to add.</param> + /// <returns>An awaitable <see cref="System.Threading.Tasks.Task"/>.</returns> + Task RemoveDevice(SsdpRootDevice device); + + /// <summary> + /// Returns a read only list of devices being published by this instance. + /// </summary> + /// <seealso cref="SsdpDevice"/> + System.Collections.Generic.IEnumerable<SsdpRootDevice> Devices { get; } + + } +} diff --git a/RSSDP/IUPnPDeviceValidator.cs b/RSSDP/IUPnPDeviceValidator.cs new file mode 100644 index 000000000..39b80742e --- /dev/null +++ b/RSSDP/IUPnPDeviceValidator.cs @@ -0,0 +1,27 @@ +using System; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Interface for components that check an <see cref="SsdpDevice"/> object's properties meet the UPnP specification for a particular version. + /// </summary> + public interface IUpnpDeviceValidator + { + /// <summary> + /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified root device. + /// </summary> + /// <param name="device">The <see cref="SsdpRootDevice"/> to validate.</param> + System.Collections.Generic.IEnumerable<string> GetValidationErrors(SsdpRootDevice device); + + /// <summary> + /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified device. + /// </summary> + /// <param name="device">The <see cref="SsdpDevice"/> to validate.</param> + System.Collections.Generic.IEnumerable<string> GetValidationErrors(SsdpDevice device); + + /// <summary> + /// Validates the specified device and throws an <see cref="System.InvalidOperationException"/> if there are any validation errors. + /// </summary> + void ThrowIfDeviceInvalid(SsdpDevice device); + } +} diff --git a/RSSDP/IUdpSocket.cs b/RSSDP/IUdpSocket.cs new file mode 100644 index 000000000..bcab4ecf1 --- /dev/null +++ b/RSSDP/IUdpSocket.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides a common interface across platforms for UDP sockets used by this SSDP implementation. + /// </summary> + public interface IUdpSocket : IDisposable + { + /// <summary> + /// Waits for and returns the next UDP message sent to this socket (uni or multicast). + /// </summary> + /// <returns></returns> + System.Threading.Tasks.Task<ReceivedUdpData> ReceiveAsync(); + + /// <summary> + /// Sends a UDP message to a particular end point (uni or multicast). + /// </summary> + /// <param name="messageData">The data to send.</param> + /// <param name="endPoint">The <see cref="UdpEndPoint"/> providing the address and port to send to.</param> + Task SendTo(byte[] messageData, UdpEndPoint endPoint); + + } +}
\ No newline at end of file diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..63f3af083 --- /dev/null +++ b/RSSDP/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RSSDP")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c227adb7-e256-4e70-a8b9-22b9e0cf4f55")] diff --git a/RSSDP/RSSDP.xproj b/RSSDP/RSSDP.xproj new file mode 100644 index 000000000..d0b2b2cbf --- /dev/null +++ b/RSSDP/RSSDP.xproj @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> + <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> + </PropertyGroup> + + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> + <PropertyGroup Label="Globals"> + <ProjectGuid>c227adb7-e256-4e70-a8b9-22b9e0cf4f55</ProjectGuid> + <RootNamespace>RSSDP</RootNamespace> + <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> + <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> + <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + </PropertyGroup> + + <PropertyGroup> + <SchemaVersion>2.0</SchemaVersion> + </PropertyGroup> + <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> +</Project> diff --git a/RSSDP/ReadOnlyEnumerable.cs b/RSSDP/ReadOnlyEnumerable.cs new file mode 100644 index 000000000..1a69f8837 --- /dev/null +++ b/RSSDP/ReadOnlyEnumerable.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + internal sealed class ReadOnlyEnumerable<T> : System.Collections.Generic.IEnumerable<T> + { + + #region Fields + + private IEnumerable<T> _Items; + + #endregion + + #region Constructors + + public ReadOnlyEnumerable(IEnumerable<T> items) + { + if (items == null) throw new ArgumentNullException("items"); + + _Items = items; + } + + #endregion + + #region IEnumerable<T> Members + + public IEnumerator<T> GetEnumerator() + { + return _Items.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _Items.GetEnumerator(); + } + + #endregion + } +} diff --git a/RSSDP/ReceivedUdpData.cs b/RSSDP/ReceivedUdpData.cs new file mode 100644 index 000000000..d1c2ca3c9 --- /dev/null +++ b/RSSDP/ReceivedUdpData.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Used by the sockets wrapper to hold raw data received from a UDP socket. + /// </summary> + public sealed class ReceivedUdpData + { + /// <summary> + /// The buffer to place received data into. + /// </summary> + public byte[] Buffer { get; set; } + + /// <summary> + /// The number of bytes received. + /// </summary> + public int ReceivedBytes { get; set; } + + /// <summary> + /// The <see cref="UdpEndPoint"/> the data was received from. + /// </summary> + public UdpEndPoint ReceivedFrom { get; set; } + } +} diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs new file mode 100644 index 000000000..a78f1b91d --- /dev/null +++ b/RSSDP/RequestReceivedEventArgs.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides arguments for the <see cref="ISsdpCommunicationsServer.RequestReceived"/> event. + /// </summary> + public sealed class RequestReceivedEventArgs : EventArgs + { + + #region Fields + + private readonly HttpRequestMessage _Message; + private readonly UdpEndPoint _ReceivedFrom; + + #endregion + + #region Constructors + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="message">The <see cref="HttpRequestMessage"/> that was received.</param> + /// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param> + public RequestReceivedEventArgs(HttpRequestMessage message, UdpEndPoint receivedFrom) + { + _Message = message; + _ReceivedFrom = receivedFrom; + } + + #endregion + + #region Public Properties + + /// <summary> + /// The <see cref="HttpRequestMessage"/> that was received. + /// </summary> + public HttpRequestMessage Message + { + get { return _Message; } + } + + /// <summary> + /// The <see cref="UdpEndPoint"/> the request came from. + /// </summary> + public UdpEndPoint ReceivedFrom + { + get { return _ReceivedFrom; } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/ResponseReceivedEventArgs.cs b/RSSDP/ResponseReceivedEventArgs.cs new file mode 100644 index 000000000..f88330802 --- /dev/null +++ b/RSSDP/ResponseReceivedEventArgs.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides arguments for the <see cref="ISsdpCommunicationsServer.ResponseReceived"/> event. + /// </summary> + public sealed class ResponseReceivedEventArgs : EventArgs + { + + #region Fields + + private readonly HttpResponseMessage _Message; + private readonly UdpEndPoint _ReceivedFrom; + + #endregion + + #region Constructors + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="message">The <see cref="HttpResponseMessage"/> that was received.</param> + /// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param> + public ResponseReceivedEventArgs(HttpResponseMessage message, UdpEndPoint receivedFrom) + { + _Message = message; + _ReceivedFrom = receivedFrom; + } + + #endregion + + #region Public Properties + + /// <summary> + /// The <see cref="HttpResponseMessage"/> that was received. + /// </summary> + public HttpResponseMessage Message + { + get { return _Message; } + } + + /// <summary> + /// The <see cref="UdpEndPoint"/> the response came from. + /// </summary> + public UdpEndPoint ReceivedFrom + { + get { return _ReceivedFrom; } + } + + #endregion + + } +} diff --git a/RSSDP/Rssdp.Portable.csproj b/RSSDP/Rssdp.Portable.csproj new file mode 100644 index 000000000..a8169f4b3 --- /dev/null +++ b/RSSDP/Rssdp.Portable.csproj @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Rssdp</RootNamespace> + <AssemblyName>Rssdp.Portable</AssemblyName> + <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <TargetFrameworkProfile>Profile44</TargetFrameworkProfile> + <TargetFrameworkVersion>v4.6</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <NuGetPackageImportStamp>1c5b2aa5</NuGetPackageImportStamp> + <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> + <RestorePackages>true</RestorePackages> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <RunCodeAnalysis>true</RunCodeAnalysis> + <CodeAnalysisRuleSet>..\RssdpRuleset.ruleset</CodeAnalysisRuleSet> + <DocumentationFile>bin\Debug\Rssdp.Portable.XML</DocumentationFile> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\lib\portable-net45+win+wpa81+wp80\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <CodeAnalysisRuleSet>..\RssdpRuleset.ruleset</CodeAnalysisRuleSet> + <DocumentationFile>..\lib\portable-net45+win+wpa81+wp80\Rssdp.Portable.XML</DocumentationFile> + <RunCodeAnalysis>true</RunCodeAnalysis> + </PropertyGroup> + <ItemGroup> + <Compile Include="..\Shared\AssemblyInfoCommon.cs"> + <Link>Properties\AssemblyInfoCommon.cs</Link> + </Compile> + <Compile Include="CustomHttpHeaders.cs" /> + <Compile Include="DeviceAvailableEventArgs.cs" /> + <Compile Include="DeviceEventArgs.cs" /> + <Compile Include="DeviceUnavailableEventArgs.cs" /> + <Compile Include="DisposableManagedObjectBase.cs" /> + <Compile Include="HttpParserBase.cs" /> + <Compile Include="IEnumerableExtensions.cs" /> + <Compile Include="ISsdpCommunicationsServer.cs" /> + <Compile Include="ISsdpDeviceLocator.cs" /> + <Compile Include="ISsdpDevicePublisher.cs" /> + <Compile Include="IUPnPDeviceValidator.cs" /> + <Compile Include="ReadOnlyEnumerable.cs" /> + <Compile Include="SsdpDeviceExtensions.cs" /> + <Compile Include="SsdpDeviceLocatorBase.cs" /> + <Compile Include="DiscoveredSsdpDevice.cs" /> + <Compile Include="GlobalSuppressions.cs" /> + <Compile Include="HttpRequestParser.cs" /> + <Compile Include="HttpResponseParser.cs" /> + <Compile Include="ISocketFactory.cs" /> + <Compile Include="IUdpSocket.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="ReceivedUdpData.cs" /> + <Compile Include="RequestReceivedEventArgs.cs" /> + <Compile Include="ResponseReceivedEventArgs.cs" /> + <Compile Include="SsdpCommunicationsServer.cs" /> + <Compile Include="SsdpConstants.cs" /> + <Compile Include="SsdpDevice.cs" /> + <Compile Include="SsdpDeviceIcon.cs" /> + <Compile Include="SsdpDeviceProperties.cs" /> + <Compile Include="SsdpDeviceProperty.cs" /> + <Compile Include="SsdpDevicePublisherBase.cs" /> + <Compile Include="SsdpEmbeddedDevice.cs" /> + <Compile Include="SsdpRootDevice.cs" /> + <Compile Include="UdpEndPoint.cs" /> + <Compile Include="UPnP10DeviceValidator.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="app.config" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <CodeAnalysisDictionary Include="..\Shared\CodeAnalysisDictionary.xml"> + <Link>Properties\CodeAnalysisDictionary.xml</Link> + <SubType>Designer</SubType> + </CodeAnalysisDictionary> + </ItemGroup> + <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> + </Target> + <Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/RSSDP/SocketFactory.cs b/RSSDP/SocketFactory.cs new file mode 100644 index 000000000..1a9c981d0 --- /dev/null +++ b/RSSDP/SocketFactory.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security; +using System.Text; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + // Not entirely happy with this. Would have liked to have done something more generic/reusable, + // but that wasn't really the point so kept to YAGNI principal for now, even if the + // interfaces are a bit ugly, specific and make assumptions. + + /// <summary> + /// Used by RSSDP components to create implementations of the <see cref="IUdpSocket"/> interface, to perform platform agnostic socket communications. + /// </summary> + public sealed class SocketFactory : ISocketFactory + { + private IPAddress _LocalIP; + + /// <summary> + /// Default constructor. + /// </summary> + /// <param name="localIP">A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use <see cref="IPAddress.Any"/>.</param> + public SocketFactory(string localIP) + { + if (String.IsNullOrEmpty(localIP)) + _LocalIP = IPAddress.Any; + else + _LocalIP = IPAddress.Parse(localIP); + } + + #region ISocketFactory Members + + /// <summary> + /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port. + /// </summary> + /// <param name="localPort">An integer specifying the local port to bind the socket to.</param> + /// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, SsdpConstants.SsdpDefaultMulticastTimeToLive); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress), _LocalIP)); + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + /// <summary> + /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port. + /// </summary> + /// <param name="ipAddress">The multicast IP address to make the socket a member of.</param> + /// <param name="multicastTimeToLive">The multicast time to live value for the socket.</param> + /// <param name="localPort">The number of the local port to bind to.</param> + /// <returns></returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] + public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) + { + if (ipAddress == null) throw new ArgumentNullException("ipAddress"); + if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress"); + if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive"); + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + try + { +#if NETSTANDARD1_3 + // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket + // See https://github.com/dotnet/corefx/pull/11509 for more details + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) + { + retVal.ExclusiveAddressUse = false; + } +#else + retVal.ExclusiveAddressUse = false; +#endif + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP)); + retVal.MulticastLoopback = true; + + return new UdpSocket(retVal, localPort, _LocalIP.ToString()); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + + #endregion + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs new file mode 100644 index 000000000..df23cae50 --- /dev/null +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides the platform independent logic for publishing device existence and responding to search requests. + /// </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 + is running then it will steal the unicast messages and we will never see search responses. + Since stopping the service would be a bad idea (might not be allowed security wise and might + break other apps running on the system) the only other work around is to use two sockets. + + We use one socket to listen for/receive notifications and search requests (_BroadcastListenSocket). + We use a second socket, bound to a different local port, to send search requests and listen for + responses (_SendSocket). The responses are sent to the local port this socket is bound to, + which isn't port 1900 so the MS service doesn't steal them. While the caller can specify a local + port to use, we will default to 0 which allows the underlying system to auto-assign a free port. + + */ + + private object _BroadcastListenSocketSynchroniser = new object(); + private IUdpSocket _BroadcastListenSocket; + + private object _SendSocketSynchroniser = new object(); + private IUdpSocket _SendSocket; + + private HttpRequestParser _RequestParser; + private HttpResponseParser _ResponseParser; + + private ISocketFactory _SocketFactory; + + private int _LocalPort; + private int _MulticastTtl; + + private bool _IsShared; + + #endregion + + #region Events + + /// <summary> + /// Raised when a HTTPU request message is received by a socket (unicast or multicast). + /// </summary> + public event EventHandler<RequestReceivedEventArgs> RequestReceived; + + /// <summary> + /// Raised when an HTTPU response message is received by a socket (unicast or multicast). + /// </summary> + public event EventHandler<ResponseReceivedEventArgs> ResponseReceived; + + #endregion + + #region Constructors + + /// <summary> + /// Minimum constructor. + /// </summary> + /// <param name="socketFactory">An implementation of the <see cref="ISocketFactory"/> interface that can be used to make new unicast and multicast sockets. Cannot be null.</param> + /// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> + public SsdpCommunicationsServer(ISocketFactory socketFactory) + : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive) + { + } + + /// <summary> + /// Partial constructor. + /// </summary> + /// <param name="socketFactory">An implementation of the <see cref="ISocketFactory"/> interface that can be used to make new unicast and multicast sockets. Cannot be null.</param> + /// <param name="localPort">The specific local port to use for all sockets created by this instance. Specify zero to indicate the system should choose a free port itself.</param> + /// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> + public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort) + : this(socketFactory, localPort, SsdpConstants.SsdpDefaultMulticastTimeToLive) + { + } + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="socketFactory">An implementation of the <see cref="ISocketFactory"/> interface that can be used to make new unicast and multicast sockets. Cannot be null.</param> + /// <param name="localPort">The specific local port to use for all sockets created by this instance. Specify zero to indicate the system should choose a free port itself.</param> + /// <param name="multicastTimeToLive">The multicast time to live value for multicast sockets. Technically this is a number of router hops, not a 'Time'. Must be greater than zero.</param> + /// <exception cref="System.ArgumentNullException">The <paramref name="socketFactory"/> argument is null.</exception> + /// <exception cref="System.ArgumentOutOfRangeException">The <paramref name="multicastTimeToLive"/> argument is less than or equal to zero.</exception> + public SsdpCommunicationsServer(ISocketFactory socketFactory, int localPort, int multicastTimeToLive) + { + if (socketFactory == null) throw new ArgumentNullException("socketFactory"); + if (multicastTimeToLive <= 0) throw new ArgumentOutOfRangeException("multicastTimeToLive", "multicastTimeToLive must be greater than zero."); + + _BroadcastListenSocketSynchroniser = new object(); + _SendSocketSynchroniser = new object(); + + _LocalPort = localPort; + _SocketFactory = socketFactory; + + _RequestParser = new HttpRequestParser(); + _ResponseParser = new HttpResponseParser(); + + _MulticastTtl = multicastTimeToLive; + } + + #endregion + + #region Public Methods + + /// <summary> + /// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications. + /// </summary> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> + public void BeginListeningForBroadcasts() + { + ThrowIfDisposed(); + + if (_BroadcastListenSocket == null) + { + lock (_BroadcastListenSocketSynchroniser) + { + if (_BroadcastListenSocket == null) + _BroadcastListenSocket = ListenForBroadcastsAsync(); + } + } + } + + /// <summary> + /// Causes the server to stop listening for multicast messages, being SSDP search requests and notifications. + /// </summary> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> + public void StopListeningForBroadcasts() + { + ThrowIfDisposed(); + + lock (_BroadcastListenSocketSynchroniser) + { + if (_BroadcastListenSocket != null) + { + _BroadcastListenSocket.Dispose(); + _BroadcastListenSocket = null; + } + } + } + + /// <summary> + /// Sends a message to a particular address (uni or multicast) and port. + /// </summary> + /// <param name="messageData">A byte array containing the data to send.</param> + /// <param name="destination">A <see cref="UdpEndPoint"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> + public async Task SendMessage(byte[] messageData, UdpEndPoint destination) + { + if (messageData == null) throw new ArgumentNullException("messageData"); + + ThrowIfDisposed(); + + EnsureSendSocketCreated(); + + // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. + await Repeat(SsdpConstants.UdpResendCount, TimeSpan.FromMilliseconds(100), () => SendMessageIfSocketNotDisposed(messageData, destination)).ConfigureAwait(false); + } + + /// <summary> + /// Sends a message to the SSDP multicast address and port. + /// </summary> + /// <param name="messageData">A byte array containing the data to send.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> + public async Task SendMulticastMessage(byte[] messageData) + { + if (messageData == null) throw new ArgumentNullException("messageData"); + + ThrowIfDisposed(); + + EnsureSendSocketCreated(); + + // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. + await Repeat(SsdpConstants.UdpResendCount, TimeSpan.FromMilliseconds(100), + () => SendMessageIfSocketNotDisposed(messageData, new UdpEndPoint() { IPAddress = SsdpConstants.MulticastLocalAdminAddress, Port = SsdpConstants.MulticastPort })).ConfigureAwait(false); + } + + /// <summary> + /// Stops listening for search responses on the local, unicast socket. + /// </summary> + /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception> + public void StopListeningForResponses() + { + ThrowIfDisposed(); + + lock (_SendSocketSynchroniser) + { + var socket = _SendSocket; + _SendSocket = null; + if (socket != null) + socket.Dispose(); + } + } + + #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> + /// <remarks> + /// <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> + 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> + /// <param name="disposing"></param> + protected override void Dispose(bool disposing) + { + if (disposing) + { + lock (_BroadcastListenSocketSynchroniser) + { + if (_BroadcastListenSocket != null) + _BroadcastListenSocket.Dispose(); + } + + lock (_SendSocketSynchroniser) + { + if (_SendSocket != null) + _SendSocket.Dispose(); + } + } + } + + #endregion + + #region Private Methods + + private async Task SendMessageIfSocketNotDisposed(byte[] messageData, UdpEndPoint destination) + { + var socket = _SendSocket; + if (socket != null) + { + await _SendSocket.SendTo(messageData, destination).ConfigureAwait(false); + } + else + { + ThrowIfDisposed(); + } + } + + private static async Task Repeat(int repetitions, TimeSpan delay, Func<Task> work) + { + for (int cnt = 0; cnt < repetitions; cnt++) + { + await work().ConfigureAwait(false); + + if (delay != TimeSpan.Zero) + await Task.Delay(delay).ConfigureAwait(false); + } + } + + private IUdpSocket ListenForBroadcastsAsync() + { + var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort); + + ListenToSocket(socket); + + return socket; + } + + private IUdpSocket CreateSocketAndListenForResponsesAsync() + { + _SendSocket = _SocketFactory.CreateUdpSocket(_LocalPort); + + ListenToSocket(_SendSocket); + + return _SendSocket; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")] + private void ListenToSocket(IUdpSocket socket) + { + // Tasks are captured to local variables even if we don't use them just to avoid compiler warnings. + var t = Task.Run(async () => + { + + var cancelled = false; + while (!cancelled) + { + try + { + var result = await socket.ReceiveAsync(); + + if (result.ReceivedBytes > 0) + { + // Strange cannot convert compiler error here if I don't explicitly + // assign or cast to Action first. Assignment is easier to read, + // so went with that. + Action processWork = () => ProcessMessage(System.Text.UTF8Encoding.UTF8.GetString(result.Buffer, 0, result.ReceivedBytes), result.ReceivedFrom); + var processTask = Task.Run(processWork); + } + } + catch (ObjectDisposedException) + { + cancelled = true; + } + catch (TaskCanceledException) + { + cancelled = true; + } + } + }); + } + + private void EnsureSendSocketCreated() + { + if (_SendSocket == null) + { + lock (_SendSocketSynchroniser) + { + if (_SendSocket == null) + _SendSocket = CreateSocketAndListenForResponsesAsync(); + } + } + } + + private void ProcessMessage(string data, UdpEndPoint endPoint) + { + //Responses start with the HTTP version, prefixed with HTTP/ while + //requests start with a method which can vary and might be one we haven't + //seen/don't know. We'll check if this message is a request or a response + //by checking for the static HTTP/ prefix on the start of the message. + if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) + { + HttpResponseMessage responseMessage = null; + try + { + responseMessage = _ResponseParser.Parse(data); + } + catch (ArgumentException) { } // Ignore invalid packets. + + if (responseMessage != null) + OnResponseReceived(responseMessage, endPoint); + } + else + { + HttpRequestMessage requestMessage = null; + try + { + requestMessage = _RequestParser.Parse(data); + } + catch (ArgumentException) { } // Ignore invalid packets. + + if (requestMessage != null) + OnRequestReceived(requestMessage, endPoint); + } + } + + private void OnRequestReceived(HttpRequestMessage data, UdpEndPoint endPoint) + { + //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; + + var handlers = this.RequestReceived; + if (handlers != null) + handlers(this, new RequestReceivedEventArgs(data, endPoint)); + } + + private void OnResponseReceived(HttpResponseMessage data, UdpEndPoint endPoint) + { + var handlers = this.ResponseReceived; + if (handlers != null) + handlers(this, new ResponseReceivedEventArgs(data, endPoint)); + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpConstants.cs b/RSSDP/SsdpConstants.cs new file mode 100644 index 000000000..c839d9e0b --- /dev/null +++ b/RSSDP/SsdpConstants.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides constants for common values related to the SSDP protocols. + /// </summary> + public static class SsdpConstants + { + + /// <summary> + /// Multicast IP Address used for SSDP multicast messages. Values is 239.255.255.250. + /// </summary> + public const string MulticastLocalAdminAddress = "239.255.255.250"; + /// <summary> + /// The UDP port used for SSDP multicast messages. Values is 1900. + /// </summary> + public const int MulticastPort = 1900; + /// <summary> + /// The default multicase TTL for SSDP multicast messages. Value is 4. + /// </summary> + public const int SsdpDefaultMulticastTimeToLive = 4; + + internal const string MSearchMethod = "M-SEARCH"; + + internal const string SsdpDiscoverMessage = "ssdp:discover"; + internal const string SsdpDiscoverAllSTHeader = "ssdp:all"; + + internal const string SsdpDeviceDescriptionXmlNamespace = "urn:schemas-upnp-org:device-1-0"; + + /// <summary> + /// Default buffer size for receiving SSDP broadcasts. Value is 8192 (bytes). + /// </summary> + public const int DefaultUdpSocketBufferSize = 8192; + /// <summary> + /// The maximum possible buffer size for a UDP message. Value is 65507 (bytes). + /// </summary> + public const int MaxUdpSocketBufferSize = 65507; // Max possible UDP packet size on IPv4 without using 'jumbograms'. + + /// <summary> + /// Namespace/prefix for UPnP device types. Values is schemas-upnp-org. + /// </summary> + public const string UpnpDeviceTypeNamespace = "schemas-upnp-org"; + /// <summary> + /// UPnP Root Device type. Value is upnp:rootdevice. + /// </summary> + public const string UpnpDeviceTypeRootDevice = "upnp:rootdevice"; + /// <summary> + /// The value is used by Windows Explorer for device searches instead of the UPNPDeviceTypeRootDevice constant. + /// Not sure why (different spec, bug, alternate protocol etc). Used to enable Windows Explorer support. + /// </summary> + public const string PnpDeviceTypeRootDevice = "pnp:rootdevice"; + /// <summary> + /// UPnP Basic Device type. Value is Basic. + /// </summary> + public const string UpnpDeviceTypeBasicDevice = "Basic"; + + internal const string SsdpKeepAliveNotification = "ssdp:alive"; + internal const string SsdpByeByeNotification = "ssdp:byebye"; + + internal const int UdpResendCount = 3; + + } +} diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs new file mode 100644 index 000000000..8a4992239 --- /dev/null +++ b/RSSDP/SsdpDevice.cs @@ -0,0 +1,783 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + /// <summary> + /// Base class representing the common details of a (root or embedded) device, either to be published or that has been located. + /// </summary> + /// <remarks> + /// <para>Do not derive new types directly from this class. New device classes should derive from either <see cref="SsdpRootDevice"/> or <see cref="SsdpEmbeddedDevice"/>.</para> + /// </remarks> + /// <seealso cref="SsdpRootDevice"/> + /// <seealso cref="SsdpEmbeddedDevice"/> + public abstract class SsdpDevice + { + + #region Fields + + private string _Udn; + private string _DeviceType; + private string _DeviceTypeNamespace; + private int _DeviceVersion; + private SsdpDevicePropertiesCollection _CustomProperties; + private CustomHttpHeadersCollection _CustomResponseHeaders; + + private IList<SsdpDevice> _Devices; + + #endregion + + #region Events + + /// <summary> + /// Raised when a new child device is added. + /// </summary> + /// <seealso cref="AddDevice"/> + /// <seealso cref="DeviceAdded"/> + public event EventHandler<DeviceEventArgs> DeviceAdded; + + /// <summary> + /// Raised when a child device is removed. + /// </summary> + /// <seealso cref="RemoveDevice"/> + /// <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> + protected SsdpDevice() + { + _DeviceTypeNamespace = SsdpConstants.UpnpDeviceTypeNamespace; + _DeviceType = SsdpConstants.UpnpDeviceTypeBasicDevice; + _DeviceVersion = 1; + + this.Icons = new List<SsdpDeviceIcon>(); + _Devices = new List<SsdpDevice>(); + this.Devices = new ReadOnlyEnumerable<SsdpDevice>(_Devices); + _CustomResponseHeaders = new CustomHttpHeadersCollection(); + _CustomProperties = new SsdpDevicePropertiesCollection(); + } + + /// <summary> + /// Deserialisation constructor. + /// </summary> + /// <remarks><para>Uses the provided XML string and parent device properties to set the properties of the object. The XML provided must be a valid UPnP device description document.</para></remarks> + /// <param name="deviceDescriptionXml">A UPnP device description XML document.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is empty.</exception> + protected SsdpDevice(string deviceDescriptionXml) + : this() + { + if (deviceDescriptionXml == null) throw new ArgumentNullException("deviceDescriptionXml"); + if (deviceDescriptionXml.Length == 0) throw new ArgumentException("deviceDescriptionXml cannot be an empty string.", "deviceDescriptionXml"); + + using (var ms = new System.IO.MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(deviceDescriptionXml))) + { + var reader = XmlReader.Create(ms); + + LoadDeviceProperties(reader, this); + } + } + + #endregion + + #region Public Properties + + #region UPnP Device Description Properties + + /// <summary> + /// Sets or returns the core device type (not including namespace, version etc.). Required. + /// </summary> + /// <remarks><para>Defaults to the UPnP basic device type.</para></remarks> + /// <seealso cref="DeviceTypeNamespace"/> + /// <seealso cref="DeviceVersion"/> + /// <seealso cref="FullDeviceType"/> + public string DeviceType + { + get + { + return _DeviceType; + } + set + { + _DeviceType = value; + } + } + + public string DeviceClass { get; set; } + + /// <summary> + /// Sets or returns the namespace for the <see cref="DeviceType"/> of this device. Optional, but defaults to UPnP schema so should be changed if <see cref="DeviceType"/> is not a UPnP device type. + /// </summary> + /// <remarks><para>Defaults to the UPnP standard namespace.</para></remarks> + /// <seealso cref="DeviceType"/> + /// <seealso cref="DeviceVersion"/> + /// <seealso cref="FullDeviceType"/> + public string DeviceTypeNamespace + { + get + { + return _DeviceTypeNamespace; + } + set + { + _DeviceTypeNamespace = value; + } + } + + /// <summary> + /// Sets or returns the version of the device type. Optional, defaults to 1. + /// </summary> + /// <remarks><para>Defaults to a value of 1.</para></remarks> + /// <seealso cref="DeviceType"/> + /// <seealso cref="DeviceTypeNamespace"/> + /// <seealso cref="FullDeviceType"/> + public int DeviceVersion + { + get + { + return _DeviceVersion; + } + set + { + _DeviceVersion = value; + } + } + + /// <summary> + /// Returns the full device type string. + /// </summary> + /// <remarks> + /// <para>The format used is urn:<see cref="DeviceTypeNamespace"/>:device:<see cref="DeviceType"/>:<see cref="DeviceVersion"/></para> + /// </remarks> + public string FullDeviceType + { + get + { + return String.Format("urn:{0}:{3}:{1}:{2}", + this.DeviceTypeNamespace ?? String.Empty, + this.DeviceType ?? String.Empty, + this.DeviceVersion, + this.DeviceClass ?? "device"); + } + } + + /// <summary> + /// Sets or returns the universally unique identifier for this device (without the uuid: prefix). Required. + /// </summary> + /// <remarks> + /// <para>Must be the same over time for a specific device instance (i.e. must survive reboots).</para> + /// <para>For UPnP 1.0 this can be any unique string. For UPnP 1.1 this should be a 128 bit number formatted in a specific way, preferably generated using the time and MAC based algorithm. See section 1.1.4 of http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf for details.</para> + /// <para>Technically this library implements UPnP 1.0, so any value is allowed, but we advise using UPnP 1.1 compatible values for good behaviour and forward compatibility with future versions.</para> + /// </remarks> + public string Uuid { get; set; } + + /// <summary> + /// Returns (or sets*) a unique device name for this device. Optional, not recommended to be explicitly set. + /// </summary> + /// <remarks> + /// <para>* In general you should not explicitly set this property. If it is not set (or set to null/empty string) the property will return a UDN value that is correct as per the UPnP specification, based on the other device properties.</para> + /// <para>The setter is provided to allow for devices that do not correctly follow the specification (when we discover them), rather than to intentionally deviate from the specification.</para> + /// <para>If a value is explicitly set, it is used verbatim, and so any prefix (such as uuid:) must be provided in the value.</para> + /// </remarks> + public string Udn + { + get + { + if (String.IsNullOrEmpty(_Udn) && !String.IsNullOrEmpty(this.Uuid)) + return "uuid:" + this.Uuid; + else + return _Udn; + } + set + { + _Udn = value; + } + } + + /// <summary> + /// Sets or returns a friendly/display name for this device on the network. Something the user can identify the device/instance by, i.e Lounge Main Light. Required. + /// </summary> + /// <remarks><para>A short description for the end user. </para></remarks> + public string FriendlyName { get; set; } + + /// <summary> + /// Sets or returns the name of the manufacturer of this device. Required. + /// </summary> + public string Manufacturer { get; set; } + + /// <summary> + /// Sets or returns a URL to the manufacturers web site. Optional. + /// </summary> + public Uri ManufacturerUrl { get; set; } + + /// <summary> + /// Sets or returns a description of this device model. Recommended. + /// </summary> + /// <remarks><para>A long description for the end user.</para></remarks> + public string ModelDescription { get; set; } + + /// <summary> + /// Sets or returns the name of this model. Required. + /// </summary> + public string ModelName { get; set; } + + /// <summary> + /// Sets or returns the number of this model. Recommended. + /// </summary> + public string ModelNumber { get; set; } + + /// <summary> + /// Sets or returns a URL to a web page with details of this device model. Optional. + /// </summary> + /// <remarks> + /// <para>Optional. May be relative to base URL.</para> + /// </remarks> + public Uri ModelUrl { get; set; } + + /// <summary> + /// Sets or returns the serial number for this device. Recommended. + /// </summary> + public string SerialNumber { get; set; } + + /// <summary> + /// Sets or returns the universal product code of the device, if any. Optional. + /// </summary> + /// <remarks> + /// <para>If not blank, must be exactly 12 numeric digits.</para> + /// </remarks> + public string Upc { get; set; } + + /// <summary> + /// Sets or returns the URL to a web page that can be used to configure/manager/use the device. Recommended. + /// </summary> + /// <remarks> + /// <para>May be relative to base URL. </para> + /// </remarks> + public Uri PresentationUrl { get; set; } + + #endregion + + /// <summary> + /// Returns a list of icons (images) that can be used to display this device. Optional, but recommended you provide at least one at 48x48 pixels. + /// </summary> + public IList<SsdpDeviceIcon> Icons + { + get; + private set; + } + + /// <summary> + /// Returns a read-only enumerable set of <see cref="SsdpDevice"/> objects representing children of this device. Child devices are optional. + /// </summary> + /// <seealso cref="AddDevice"/> + /// <seealso cref="RemoveDevice"/> + public IEnumerable<SsdpDevice> Devices + { + get; + private set; + } + + /// <summary> + /// Returns a dictionary of <see cref="SsdpDeviceProperty"/> objects keyed by <see cref="SsdpDeviceProperty.FullName"/>. Each value represents a custom property in the device description document. + /// </summary> + public SsdpDevicePropertiesCollection CustomProperties + { + get + { + return _CustomProperties; + } + } + + /// <summary> + /// Provides a list of additional information to provide about this device in search response and notification messages. + /// </summary> + /// <remarks> + /// <para>The headers included here are included in the (HTTP headers) for search response and alive notifications sent in relation to this device.</para> + /// <para>Only values specified directly on this <see cref="SsdpDevice"/> instance will be included, headers from ancestors are not automatically included.</para> + /// </remarks> + public CustomHttpHeadersCollection CustomResponseHeaders + { + get + { + return _CustomResponseHeaders; + } + } + + #endregion + + #region Public Methods + + /// <summary> + /// Adds a child device to the <see cref="Devices"/> collection. + /// </summary> + /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance to add.</param> + /// <remarks> + /// <para>If the device is already a member of the <see cref="Devices"/> collection, this method does nothing.</para> + /// <para>Also sets the <see cref="SsdpEmbeddedDevice.RootDevice"/> property of the added device and all descendant devices to the relevant <see cref="SsdpRootDevice"/> instance.</para> + /// </remarks> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <exception cref="System.InvalidOperationException">Thrown if the <paramref name="device"/> is already associated with a different <see cref="SsdpRootDevice"/> instance than used in this tree. Can occur if you try to add the same device instance to more than one tree. Also thrown if you try to add a device to itself.</exception> + /// <seealso cref="DeviceAdded"/> + public void AddDevice(SsdpEmbeddedDevice device) + { + if (device == null) throw new ArgumentNullException("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) + { + device.RootDevice = this.ToRootDevice(); + _Devices.Add(device); + wasAdded = true; + } + + if (wasAdded) + OnDeviceAdded(device); + } + + /// <summary> + /// Removes a child device from the <see cref="Devices"/> collection. + /// </summary> + /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance to remove.</param> + /// <remarks> + /// <para>If the device is not a member of the <see cref="Devices"/> collection, this method does nothing.</para> + /// <para>Also sets the <see cref="SsdpEmbeddedDevice.RootDevice"/> property to null for the removed device and all descendant devices.</para> + /// </remarks> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <seealso cref="DeviceRemoved"/> + public void RemoveDevice(SsdpEmbeddedDevice device) + { + if (device == null) throw new ArgumentNullException("device"); + + bool wasRemoved = false; + lock (_Devices) + { + wasRemoved = _Devices.Remove(device); + if (wasRemoved) + { + device.RootDevice = null; + } + } + + if (wasRemoved) + OnDeviceRemoved(device); + } + + /// <summary> + /// Raises the <see cref="DeviceAdded"/> event. + /// </summary> + /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance added to the <see cref="Devices"/> collection.</param> + /// <seealso cref="AddDevice"/> + /// <seealso cref="DeviceAdded"/> + protected virtual void OnDeviceAdded(SsdpEmbeddedDevice device) + { + var handlers = this.DeviceAdded; + if (handlers != null) + handlers(this, new DeviceEventArgs(device)); + } + + /// <summary> + /// Raises the <see cref="DeviceRemoved"/> event. + /// </summary> + /// <param name="device">The <see cref="SsdpEmbeddedDevice"/> instance removed from the <see cref="Devices"/> collection.</param> + /// <seealso cref="RemoveDevice"/> + /// <see cref="DeviceRemoved"/> + protected virtual void OnDeviceRemoved(SsdpEmbeddedDevice device) + { + var handlers = this.DeviceRemoved; + if (handlers != null) + handlers(this, new DeviceEventArgs(device)); + } + + /// <summary> + /// Writes this device to the specified <see cref="System.Xml.XmlWriter"/> as a device node and it's content. + /// </summary> + /// <param name="writer">The <see cref="System.Xml.XmlWriter"/> to output to.</param> + /// <param name="device">The <see cref="SsdpDevice"/> to write out.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="writer"/> or <paramref name="device"/> argument is null.</exception> + protected virtual void WriteDeviceDescriptionXml(XmlWriter writer, SsdpDevice device) + { + if (writer == null) throw new ArgumentNullException("writer"); + if (device == null) throw new ArgumentNullException("device"); + + writer.WriteStartElement("device"); + + if (!String.IsNullOrEmpty(device.FullDeviceType)) + WriteNodeIfNotEmpty(writer, "deviceType", device.FullDeviceType); + + WriteNodeIfNotEmpty(writer, "friendlyName", device.FriendlyName); + WriteNodeIfNotEmpty(writer, "manufacturer", device.Manufacturer); + WriteNodeIfNotEmpty(writer, "manufacturerURL", device.ManufacturerUrl); + WriteNodeIfNotEmpty(writer, "modelDescription", device.ModelDescription); + WriteNodeIfNotEmpty(writer, "modelName", device.ModelName); + WriteNodeIfNotEmpty(writer, "modelNumber", device.ModelNumber); + WriteNodeIfNotEmpty(writer, "modelURL", device.ModelUrl); + WriteNodeIfNotEmpty(writer, "presentationURL", device.PresentationUrl); + WriteNodeIfNotEmpty(writer, "serialNumber", device.SerialNumber); + WriteNodeIfNotEmpty(writer, "UDN", device.Udn); + WriteNodeIfNotEmpty(writer, "UPC", device.Upc); + + WriteCustomProperties(writer, device); + WriteIcons(writer, device); + WriteChildDevices(writer, device); + + writer.WriteEndElement(); + } + + /// <summary> + /// Converts a string to a <see cref="Uri"/>, or returns null if the string provided is null. + /// </summary> + /// <param name="value">The string value to convert.</param> + /// <returns>A <see cref="Uri"/>.</returns> + protected static Uri StringToUri(string value) + { + if (!String.IsNullOrEmpty(value)) + return new Uri(value, UriKind.RelativeOrAbsolute); + + return null; + } + + #endregion + + #region Private Methods + + #region Serialisation Methods + + private static void WriteCustomProperties(XmlWriter writer, SsdpDevice device) + { + foreach (var prop in device.CustomProperties) + { + writer.WriteElementString(prop.Namespace, prop.Name, SsdpConstants.SsdpDeviceDescriptionXmlNamespace, prop.Value); + } + } + + private static void WriteIcons(XmlWriter writer, SsdpDevice device) + { + if (device.Icons.Any()) + { + writer.WriteStartElement("iconList"); + + foreach (var icon in device.Icons) + { + writer.WriteStartElement("icon"); + + writer.WriteElementString("mimetype", icon.MimeType); + writer.WriteElementString("width", icon.Width.ToString()); + writer.WriteElementString("height", icon.Height.ToString()); + writer.WriteElementString("depth", icon.ColorDepth.ToString()); + writer.WriteElementString("url", icon.Url.ToString()); + + writer.WriteEndElement(); + } + + writer.WriteEndElement(); + } + } + + private void WriteChildDevices(XmlWriter writer, SsdpDevice parentDevice) + { + if (parentDevice.Devices.Any()) + { + writer.WriteStartElement("deviceList"); + + foreach (var device in parentDevice.Devices) + { + WriteDeviceDescriptionXml(writer, device); + } + + writer.WriteEndElement(); + } + } + + private static void WriteNodeIfNotEmpty(XmlWriter writer, string nodeName, string value) + { + if (!String.IsNullOrEmpty(value)) + writer.WriteElementString(nodeName, value); + } + + private static void WriteNodeIfNotEmpty(XmlWriter writer, string nodeName, Uri value) + { + if (value != null) + writer.WriteElementString(nodeName, value.ToString()); + } + + #endregion + + #region Deserialisation Methods + + private void LoadDeviceProperties(XmlReader reader, SsdpDevice device) + { + ReadUntilDeviceNode(reader); + + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "device") + { + reader.Read(); + break; + } + + if (!SetPropertyFromReader(reader, device)) + reader.Read(); + } + } + + private static void ReadUntilDeviceNode(XmlReader reader) + { + while (!reader.EOF && (reader.LocalName != "device" || reader.NodeType != XmlNodeType.Element)) + { + reader.Read(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Yes, there is a large switch statement, not it's not really complex and doesn't really need to be rewritten at this point.")] + private bool SetPropertyFromReader(XmlReader reader, SsdpDevice device) + { + switch (reader.LocalName) + { + case "friendlyName": + device.FriendlyName = reader.ReadElementContentAsString(); + break; + + case "manufacturer": + device.Manufacturer = reader.ReadElementContentAsString(); + break; + + case "manufacturerURL": + device.ManufacturerUrl = StringToUri(reader.ReadElementContentAsString()); + break; + + case "modelDescription": + device.ModelDescription = reader.ReadElementContentAsString(); + break; + + case "modelName": + device.ModelName = reader.ReadElementContentAsString(); + break; + + case "modelNumber": + device.ModelNumber = reader.ReadElementContentAsString(); + break; + + case "modelURL": + device.ModelUrl = StringToUri(reader.ReadElementContentAsString()); + break; + + case "presentationURL": + device.PresentationUrl = StringToUri(reader.ReadElementContentAsString()); + break; + + case "serialNumber": + device.SerialNumber = reader.ReadElementContentAsString(); + break; + + case "UDN": + device.Udn = reader.ReadElementContentAsString(); + SetUuidFromUdn(device); + break; + + case "UPC": + device.Upc = reader.ReadElementContentAsString(); + break; + + case "deviceType": + SetDeviceTypePropertiesFromFullDeviceType(device, reader.ReadElementContentAsString()); + break; + + case "iconList": + reader.Read(); + LoadIcons(reader, device); + break; + + case "deviceList": + reader.Read(); + LoadChildDevices(reader, device); + break; + + case "serviceList": + reader.Skip(); + break; + + default: + if (reader.NodeType == XmlNodeType.Element && reader.Name != "device" && reader.Name != "icon") + { + AddCustomProperty(reader, device); + break; + } + else + return false; + } + return true; + } + + private static void SetDeviceTypePropertiesFromFullDeviceType(SsdpDevice device, string value) + { + if (String.IsNullOrEmpty(value) || !value.Contains(":")) + device.DeviceType = value; + else + { + var parts = value.Split(':'); + if (parts.Length == 5) + { + int deviceVersion = 1; + if (Int32.TryParse(parts[4], out deviceVersion)) + { + device.DeviceTypeNamespace = parts[1]; + device.DeviceType = parts[3]; + device.DeviceVersion = deviceVersion; + } + else + device.DeviceType = value; + } + else + device.DeviceType = value; + } + } + + private static void SetUuidFromUdn(SsdpDevice device) + { + if (device.Udn != null && device.Udn.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) + device.Uuid = device.Udn.Substring(5).Trim(); + else + device.Uuid = device.Udn; + } + + private static void LoadIcons(XmlReader reader, SsdpDevice device) + { + while (!reader.EOF) + { + while (!reader.EOF && reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + } + + if (reader.LocalName != "icon") break; + + while (reader.Name == "icon") + { + var icon = new SsdpDeviceIcon(); + LoadIconProperties(reader, icon); + device.Icons.Add(icon); + + reader.Read(); + } + } + } + + private static void LoadIconProperties(XmlReader reader, SsdpDeviceIcon icon) + { + while (!reader.EOF) + { + if (reader.NodeType != XmlNodeType.Element) + { + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "icon") break; + + reader.Read(); + continue; + } + + switch (reader.LocalName) + { + case "depth": + icon.ColorDepth = reader.ReadElementContentAsInt(); + break; + + case "height": + icon.Height = reader.ReadElementContentAsInt(); + break; + + case "width": + icon.Width = reader.ReadElementContentAsInt(); + break; + + case "mimetype": + icon.MimeType = reader.ReadElementContentAsString(); + break; + + case "url": + icon.Url = StringToUri(reader.ReadElementContentAsString()); + break; + + } + + reader.Read(); + } + } + + private void LoadChildDevices(XmlReader reader, SsdpDevice device) + { + while (!reader.EOF && reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + } + + while (!reader.EOF) + { + while (!reader.EOF && reader.NodeType != XmlNodeType.Element) + { + reader.Read(); + } + + if (reader.LocalName == "device") + { + var childDevice = new SsdpEmbeddedDevice(); + LoadDeviceProperties(reader, childDevice); + device.AddDevice(childDevice); + } + else + break; + } + } + + private static void AddCustomProperty(XmlReader reader, SsdpDevice device) + { + var newProp = new SsdpDeviceProperty() { Namespace = reader.Prefix, Name = reader.LocalName }; + int depth = reader.Depth; + reader.Read(); + while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment) + { + reader.Read(); + } + + if (reader.NodeType != XmlNodeType.CDATA && reader.NodeType != XmlNodeType.Text) + { + while (!reader.EOF && (reader.NodeType != XmlNodeType.EndElement || reader.Name != newProp.Name || reader.Prefix != newProp.Namespace || reader.Depth != depth)) + { + reader.Read(); + } + if (!reader.EOF) + reader.Read(); + return; + } + + newProp.Value = reader.Value; + + // We don't support complex nested types or repeat/multi-value properties + if (!device.CustomProperties.Contains(newProp.FullName)) + device.CustomProperties.Add(newProp); + } + + #endregion + + //private bool ChildDeviceExists(SsdpDevice device) + //{ + // return (from d in _Devices where device.Uuid == d.Uuid select d).Any(); + //} + + #endregion + + } +} diff --git a/RSSDP/SsdpDeviceExtensions.cs b/RSSDP/SsdpDeviceExtensions.cs new file mode 100644 index 000000000..0ad710a6b --- /dev/null +++ b/RSSDP/SsdpDeviceExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + /// <summary> + /// Extensions for <see cref="SsdpDevice"/> and derived types. + /// </summary> + public static class SsdpDeviceExtensions + { + + /// <summary> + /// Returns the root device associated with a device instance derived from <see cref="SsdpDevice"/>. + /// </summary> + /// <param name="device">The device instance to find the <see cref="SsdpRootDevice"/> for.</param> + /// <remarks> + /// <para>The <paramref name="device"/> must be or inherit from <see cref="SsdpRootDevice"/> or <see cref="SsdpEmbeddedDevice"/>, otherwise an <see cref="System.InvalidCastException"/> will occur.</para> + /// <para>May return null if the <paramref name="device"/> instance is an embedded device not yet associated with a <see cref="SsdpRootDevice"/> instance yet.</para> + /// <para>If <paramref name="device"/> is an instance of <see cref="SsdpRootDevice"/> (or derives from it), returns the same instance cast to <see cref="SsdpRootDevice"/>.</para> + /// </remarks> + /// <returns>The <see cref="SsdpRootDevice"/> instance associated with the device instance specified, or null otherwise.</returns> + /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="device"/> is null.</exception> + /// <exception cref="System.InvalidCastException">Thrown if <paramref name="device"/> is not an instance of or dervied from either <see cref="SsdpRootDevice"/> or <see cref="SsdpEmbeddedDevice"/>.</exception> + public static SsdpRootDevice ToRootDevice(this SsdpDevice device) + { + if (device == null) throw new System.ArgumentNullException("device"); + + var rootDevice = device as SsdpRootDevice; + if (rootDevice == null) + rootDevice = ((SsdpEmbeddedDevice)device).RootDevice; + + return rootDevice; + } + } +} diff --git a/RSSDP/SsdpDeviceIcon.cs b/RSSDP/SsdpDeviceIcon.cs new file mode 100644 index 000000000..4ffda58ff --- /dev/null +++ b/RSSDP/SsdpDeviceIcon.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp +{ + /// <summary> + /// Represents an icon published by an <see cref="SsdpDevice"/>. + /// </summary> + public sealed class SsdpDeviceIcon + { + /// <summary> + /// The mime type for the image data returned by the <see cref="Url"/> property. + /// </summary> + /// <remarks> + /// <para>Required. Icon's MIME type (cf. RFC 2045, 2046, and 2387). Single MIME image type. At least one icon should be of type “image/png” (Portable Network Graphics, see IETF RFC 2083).</para> + /// </remarks> + /// <seealso cref="Url"/> + public string MimeType { get; set; } + + /// <summary> + /// The URL that can be called with an HTTP GET command to retrieve the image data. + /// </summary> + /// <remarks> + /// <para>Required. May be relative to base URL. Specified by UPnP vendor. Single URL.</para> + /// </remarks> + /// <seealso cref="MimeType"/> + public Uri Url { get; set; } + + /// <summary> + /// The width of the image in pixels. + /// </summary> + /// <remarks><para>Required, must be greater than zero.</para></remarks> + public int Width { get; set; } + + /// <summary> + /// The height of the image in pixels. + /// </summary> + /// <remarks><para>Required, must be greater than zero.</para></remarks> + public int Height { get; set; } + + /// <summary> + /// The colour depth of the image. + /// </summary> + /// <remarks><para>Required, must be greater than zero.</para></remarks> + public int ColorDepth { get; set; } + + } +} diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs new file mode 100644 index 000000000..42d20d332 --- /dev/null +++ b/RSSDP/SsdpDeviceLocator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + /// <summary> + /// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status. + /// </summary> + public sealed class SsdpDeviceLocator : SsdpDeviceLocatorBase + { + + /// <summary> + /// Default constructor. Constructs a new instance using the default <see cref="ISsdpCommunicationsServer"/> and <see cref="ISocketFactory"/> implementations for this platform. + /// </summary> + [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() : base(new SsdpCommunicationsServer(new SocketFactory(null))) + { + // This is not the problem you are looking for; + // Yes, this is poor man's dependency injection which some call an anti-pattern. + // However, it makes the library really simple to get started with or to use if the calling code isn't using IoC/DI. + // The fact we have injected dependencies is really an internal architectural implementation detail to allow for the + // cross platform and testing concerns of this library. It shouldn't be something calling code worries about and is + // not a deliberate extension point, except where adding new platform support in which case... + // There is a constructor that takes a manually injected dependency anyway, so proper DI using + // a container or whatever can be done anyway. + } + + /// <summary> + /// Full constructor. Constructs a new instance using the provided <see cref="ISsdpCommunicationsServer"/> implementation. + /// </summary> + public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer) + : base(communicationsServer) + { + } + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpDeviceLocatorBase.cs b/RSSDP/SsdpDeviceLocatorBase.cs new file mode 100644 index 000000000..0c5166eec --- /dev/null +++ b/RSSDP/SsdpDeviceLocatorBase.cs @@ -0,0 +1,732 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status. + /// </summary> + public abstract class SsdpDeviceLocatorBase : DisposableManagedObjectBase, ISsdpDeviceLocator + { + + #region Fields & Constants + + private List<DiscoveredSsdpDevice> _Devices; + private ISsdpCommunicationsServer _CommunicationsServer; + + private IList<DiscoveredSsdpDevice> _SearchResults; + private object _SearchResultsSynchroniser; + + private System.Threading.Timer _ExpireCachedDevicesTimer; + + 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); + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + /// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/> implementation to use for network communications.</param> + protected SsdpDeviceLocatorBase(ISsdpCommunicationsServer communicationsServer) + { + if (communicationsServer == null) throw new ArgumentNullException("communicationsServer"); + + _CommunicationsServer = communicationsServer; + _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; + + _SearchResultsSynchroniser = new object(); + _Devices = new List<DiscoveredSsdpDevice>(); + } + + #endregion + + #region Events + + /// <summary> + /// Raised for when + /// <list type="bullet"> + /// <item>An 'alive' notification is received that a device, regardless of whether or not that device is not already in the cache or has previously raised this event.</item> + /// <item>For each item found during a device <see cref="SearchAsync()"/> (cached or not), allowing clients to respond to found devices before the entire search is complete.</item> + /// <item>Only if the notification type matches the <see cref="NotificationFilter"/> property. By default the filter is null, meaning all notifications raise events (regardless of ant </item> + /// </list> + /// <para>This event may be raised from a background thread, if interacting with UI or other objects with specific thread affinity invoking to the relevant thread is required.</para> + /// </summary> + /// <seealso cref="NotificationFilter"/> + /// <seealso cref="DeviceUnavailable"/> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="StopListeningForNotifications"/> + public event EventHandler<DeviceAvailableEventArgs> DeviceAvailable; + + /// <summary> + /// Raised when a notification is received that indicates a device has shutdown or otherwise become unavailable. + /// </summary> + /// <remarks> + /// <para>Devices *should* broadcast these types of notifications, but not all devices do and sometimes (in the event of power loss for example) it might not be possible for a device to do so. You should also implement error handling when trying to contact a device, even if RSSDP is reporting that device as available.</para> + /// <para>This event is only raised if the notification type matches the <see cref="NotificationFilter"/> property. A null or empty string for the <see cref="NotificationFilter"/> will be treated as no filter and raise the event for all notifications.</para> + /// <para>The <see cref="DeviceUnavailableEventArgs.DiscoveredDevice"/> property may contain either a fully complete <see cref="DiscoveredSsdpDevice"/> instance, or one containing just a USN and NotificationType property. Full information is available if the device was previously discovered and cached, but only partial information if a byebye notification was received for a previously unseen or expired device.</para> + /// <para>This event may be raised from a background thread, if interacting with UI or other objects with specific thread affinity invoking to the relevant thread is required.</para> + /// </remarks> + /// <seealso cref="NotificationFilter"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="StopListeningForNotifications"/> + public event EventHandler<DeviceUnavailableEventArgs> DeviceUnavailable; + + #endregion + + #region Public Methods + + #region Search Overloads + + /// <summary> + /// Performs a search for all devices using the default search timeout. + /// </summary> + /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns> + public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync() + { + return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime); + } + + /// <summary> + /// Performs a search for the specified search target (criteria) and default search timeout. + /// </summary> + /// <param name="searchTarget">The criteria for the search. Value can be; + /// <list type="table"> + /// <item><term>Root devices</term><description>upnp:rootdevice</description></item> + /// <item><term>Specific device by UUID</term><description>uuid:<device uuid></description></item> + /// <item><term>Device type</term><description>Fully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1</description></item> + /// </list> + /// </param> + /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns> + public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget) + { + return SearchAsync(searchTarget, DefaultSearchWaitTime); + } + + /// <summary> + /// Performs a search for all devices using the specified search timeout. + /// </summary> + /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param> + /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns> + public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime) + { + return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime); + } + + /// <summary> + /// Performs a search for the specified search target (criteria) and search timeout. + /// </summary> + /// <param name="searchTarget">The criteria for the search. Value can be; + /// <list type="table"> + /// <item><term>Root devices</term><description>upnp:rootdevice</description></item> + /// <item><term>Specific device by UUID</term><description>uuid:<device uuid></description></item> + /// <item><term>Device type</term><description>A device namespace and type in format of urn:<device namespace>:device:<device type>:<device version> i.e urn:schemas-upnp-org:device:Basic:1</description></item> + /// <item><term>Service type</term><description>A service namespace and type in format of urn:<service namespace>:service:<servicetype>:<service version> i.e urn:my-namespace:service:MyCustomService:1</description></item> + /// </list> + /// </param> + /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param> + /// <remarks> + /// <para>By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type.</para> + /// </remarks> + /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "expireTask", Justification = "Task is not actually required, but capturing to local variable suppresses compiler warning")] + public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime) + { + if (searchTarget == null) throw new ArgumentNullException("searchTarget"); + if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "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(); + + if (_SearchResults != null) throw new InvalidOperationException("Search already in progress. Only one search at a time is allowed."); + _SearchResults = new List<DiscoveredSsdpDevice>(); + + // If searchWaitTime == 0 then we are only going to report unexpired cached items, not actually do a search. + if (searchWaitTime > TimeSpan.Zero) + await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false); + + await Task.Run(() => + { + lock (_SearchResultsSynchroniser) + { + foreach (var device in GetUnexpiredDevices().Where((d) => NotificationTypeMatchesFilter(d))) + { + if (this.IsDisposed) return; + + DeviceFound(device, false); + } + } + }).ConfigureAwait(false); + + if (searchWaitTime != TimeSpan.Zero) + await Task.Delay(searchWaitTime); + + IEnumerable<DiscoveredSsdpDevice> retVal = null; + + try + { + lock (_SearchResultsSynchroniser) + { + retVal = _SearchResults; + _SearchResults = null; + } + + var expireTask = RemoveExpiredDevicesFromCacheAsync(); + } + finally + { + var server = _CommunicationsServer; + try + { + if (server != null) // In case we were disposed while searching. + server.StopListeningForResponses(); + } + catch (ObjectDisposedException) { } + } + + return retVal; + } + + #endregion + + /// <summary> + /// Starts listening for broadcast notifications of service availability. + /// </summary> + /// <remarks> + /// <para>When called the system will listen for 'alive' and 'byebye' notifications. This can speed up searching, as well as provide dynamic notification of new devices appearing on the network, and previously discovered devices disappearing.</para> + /// </remarks> + /// <seealso cref="StopListeningForNotifications"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="DeviceUnavailable"/> + /// <exception cref="System.ObjectDisposedException">Throw if the <see cref="DisposableManagedObjectBase.IsDisposed"/> ty is true.</exception> + public void StartListeningForNotifications() + { + ThrowIfDisposed(); + + _CommunicationsServer.RequestReceived -= CommsServer_RequestReceived; + _CommunicationsServer.RequestReceived += CommsServer_RequestReceived; + _CommunicationsServer.BeginListeningForBroadcasts(); + } + + /// <summary> + /// Stops listening for broadcast notifications of service availability. + /// </summary> + /// <remarks> + /// <para>Does nothing if this instance is not already listening for notifications.</para> + /// </remarks> + /// <seealso cref="StartListeningForNotifications"/> + /// <seealso cref="DeviceAvailable"/> + /// <seealso cref="DeviceUnavailable"/> + /// <exception cref="System.ObjectDisposedException">Throw if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true.</exception> + public void StopListeningForNotifications() + { + ThrowIfDisposed(); + + _CommunicationsServer.RequestReceived -= CommsServer_RequestReceived; + } + + /// <summary> + /// Raises the <see cref="DeviceAvailable"/> event. + /// </summary> + /// <param name="device">A <see cref="DiscoveredSsdpDevice"/> representing the device that is now available.</param> + /// <param name="isNewDevice">True if the device was not currently in the cahce before this event was raised.</param> + /// <seealso cref="DeviceAvailable"/> + protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice) + { + if (this.IsDisposed) return; + + var handlers = this.DeviceAvailable; + if (handlers != null) + handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)); + } + + /// <summary> + /// Raises the <see cref="DeviceUnavailable"/> event. + /// </summary> + /// <param name="device">A <see cref="DiscoveredSsdpDevice"/> representing the device that is no longer available.</param> + /// <param name="expired">True if the device expired from the cache without being renewed, otherwise false to indicate the device explicitly notified us it was being shutdown.</param> + /// <seealso cref="DeviceUnavailable"/> + protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired) + { + if (this.IsDisposed) return; + + var handlers = this.DeviceUnavailable; + if (handlers != null) + handlers(this, new DeviceUnavailableEventArgs(device, expired)); + } + + #endregion + + #region Public Properties + + /// <summary> + /// Returns a boolean indicating whether or not a search is currently in progress. + /// </summary> + /// <remarks> + /// <para>Only one search can be performed at a time, per <see cref="SsdpDeviceLocatorBase"/> instance.</para> + /// </remarks> + public bool IsSearching + { + get { return _SearchResults != null; } + } + + /// <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> + /// <remarks> + /// <para>Device alive/byebye notifications whose NT header does not match this filter value will still be captured and cached internally, but will not raise events about device availability. Usually used with either a device type of uuid NT header value.</para> + /// <para>If the value is null or empty string then, all notifications are reported.</para> + /// <para>Example filters follow;</para> + /// <example>upnp:rootdevice</example> + /// <example>urn:schemas-upnp-org:device:WANDevice:1</example> + /// <example>uuid:9F15356CC-95FA-572E-0E99-85B456BD3012</example> + /// </remarks> + /// <seealso cref="ISsdpDeviceLocator.DeviceAvailable"/> + /// <seealso cref="ISsdpDeviceLocator.DeviceUnavailable"/> + /// <seealso cref="ISsdpDeviceLocator.StartListeningForNotifications"/> + /// <seealso cref="ISsdpDeviceLocator.StopListeningForNotifications"/> + public string NotificationFilter + { + get; + set; + } + + #endregion + + #region Overrides + + /// <summary> + /// Disposes this object and all internal resources. Stops listening for all network messages. + /// </summary> + /// <param name="disposing">True if managed resources should be disposed, or false is only unmanaged resources should be cleaned up.</param> + protected override void Dispose(bool disposing) + { + + if (disposing) + { + var timer = _ExpireCachedDevicesTimer; + if (timer != null) + timer.Dispose(); + + var commsServer = _CommunicationsServer; + _CommunicationsServer = null; + if (commsServer != null) + { + commsServer.ResponseReceived -= this.CommsServer_ResponseReceived; + commsServer.RequestReceived -= this.CommsServer_RequestReceived; + if (!commsServer.IsShared) + commsServer.Dispose(); + } + } + } + + #endregion + + #region Private Methods + + #region Discovery/Device Add + + private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device) + { + bool isNewDevice = false; + lock (_Devices) + { + var existingDevice = FindExistingDeviceNotification(_Devices, device.NotificationType, device.Usn); + if (existingDevice == null) + { + _Devices.Add(device); + isNewDevice = true; + } + else + { + _Devices.Remove(existingDevice); + _Devices.Add(device); + } + } + + DeviceFound(device, isNewDevice); + } + + private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice) + { + // Don't raise the event if we've already done it for a cached + // version of this device, and the cached version isn't + // "significantly" different, i.e location and cachelifetime + // haven't changed. + var raiseEvent = false; + + if (!NotificationTypeMatchesFilter(device)) return; + + lock (_SearchResultsSynchroniser) + { + if (_SearchResults != null) + { + var existingDevice = FindExistingDeviceNotification(_SearchResults, device.NotificationType, device.Usn); + if (existingDevice == null) + { + _SearchResults.Add(device); + raiseEvent = true; + } + else + { + if (existingDevice.DescriptionLocation != device.DescriptionLocation || existingDevice.CacheLifetime != device.CacheLifetime) + { + _SearchResults.Remove(existingDevice); + _SearchResults.Add(device); + raiseEvent = true; + } + } + } + else + raiseEvent = true; + } + + if (raiseEvent) + OnDeviceAvailable(device, isNewDevice); + } + + private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device) + { + return String.IsNullOrEmpty(this.NotificationFilter) + || this.NotificationFilter == SsdpConstants.SsdpDiscoverAllSTHeader + || device.NotificationType == this.NotificationFilter; + } + + #endregion + + #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); + + return _CommunicationsServer.SendMulticastMessage(broadcastMessage); + } + + private void ProcessSearchResponseMessage(HttpResponseMessage message) + { + if (!message.IsSuccessStatusCode) return; + + var location = GetFirstHeaderUriValue("Location", message); + if (location != null) + { + var device = new DiscoveredSsdpDevice() + { + DescriptionLocation = location, + Usn = GetFirstHeaderStringValue("USN", message), + NotificationType = GetFirstHeaderStringValue("ST", message), + CacheLifetime = CacheAgeFromHeader(message.Headers.CacheControl), + AsAt = DateTimeOffset.Now, + ResponseHeaders = message.Headers + }; + + AddOrUpdateDiscoveredDevice(device); + } + } + + private void ProcessNotificationMessage(HttpRequestMessage message) + { + 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); + else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0) + ProcessByeByeNotification(message); + } + + private void ProcessAliveNotification(HttpRequestMessage message) + { + var location = GetFirstHeaderUriValue("Location", message); + if (location != null) + { + var device = new DiscoveredSsdpDevice() + { + DescriptionLocation = location, + Usn = GetFirstHeaderStringValue("USN", message), + NotificationType = GetFirstHeaderStringValue("NT", message), + CacheLifetime = CacheAgeFromHeader(message.Headers.CacheControl), + AsAt = DateTimeOffset.Now, + ResponseHeaders = message.Headers + }; + + AddOrUpdateDiscoveredDevice(device); + + ResetExpireCachedDevicesTimer(); + } + } + + private void ProcessByeByeNotification(HttpRequestMessage message) + { + var notficationType = GetFirstHeaderStringValue("NT", message); + if (!String.IsNullOrEmpty(notficationType)) + { + var usn = GetFirstHeaderStringValue("USN", message); + + if (!DeviceDied(usn, false)) + { + var deadDevice = new DiscoveredSsdpDevice() + { + AsAt = DateTime.UtcNow, + CacheLifetime = TimeSpan.Zero, + DescriptionLocation = null, + NotificationType = GetFirstHeaderStringValue("NT", message), + Usn = usn, + ResponseHeaders = message.Headers + }; + + if (NotificationTypeMatchesFilter(deadDevice)) + OnDeviceUnavailable(deadDevice, false); + } + + ResetExpireCachedDevicesTimer(); + } + } + + private void ResetExpireCachedDevicesTimer() + { + if (IsDisposed) return; + + if (_ExpireCachedDevicesTimer == null) + _ExpireCachedDevicesTimer = new Timer(this.ExpireCachedDevices, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + + _ExpireCachedDevicesTimer.Change(60000, System.Threading.Timeout.Infinite); + } + + private void ExpireCachedDevices(object state) + { + RemoveExpiredDevicesFromCache(); + } + + #region Header/Message Processing Utilities + + private static string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message) + { + string retVal = null; + IEnumerable<string> values; + if (message.Headers.Contains(headerName)) + { + message.Headers.TryGetValues(headerName, out values); + if (values != null) + retVal = values.FirstOrDefault(); + } + + return retVal; + } + + private static string GetFirstHeaderStringValue(string headerName, HttpRequestMessage message) + { + string retVal = null; + IEnumerable<string> values; + if (message.Headers.Contains(headerName)) + { + message.Headers.TryGetValues(headerName, out values); + if (values != null) + retVal = values.FirstOrDefault(); + } + + return retVal; + } + + private static Uri GetFirstHeaderUriValue(string headerName, HttpRequestMessage request) + { + string value = null; + IEnumerable<string> values; + if (request.Headers.Contains(headerName)) + { + request.Headers.TryGetValues(headerName, out values); + if (values != null) + value = values.FirstOrDefault(); + } + + Uri retVal; + Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out retVal); + return retVal; + } + + private static Uri GetFirstHeaderUriValue(string headerName, HttpResponseMessage response) + { + string value = null; + IEnumerable<string> values; + if (response.Headers.Contains(headerName)) + { + response.Headers.TryGetValues(headerName, out values); + if (values != null) + value = values.FirstOrDefault(); + } + + Uri retVal; + Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out retVal); + return retVal; + } + + private static TimeSpan CacheAgeFromHeader(System.Net.Http.Headers.CacheControlHeaderValue headerValue) + { + if (headerValue == null) return TimeSpan.Zero; + + return (TimeSpan)(headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero); + } + + #endregion + + #endregion + + #region Expiry and Device Removal + + private Task RemoveExpiredDevicesFromCacheAsync() + { + return Task.Run(() => + { + RemoveExpiredDevicesFromCache(); + }); + } + + private void RemoveExpiredDevicesFromCache() + { + if (this.IsDisposed) return; + + IEnumerable<DiscoveredSsdpDevice> expiredDevices = null; + lock (_Devices) + { + expiredDevices = (from device in _Devices where device.IsExpired() select device).ToArray(); + + foreach (var device in expiredDevices) + { + if (this.IsDisposed) return; + + _Devices.Remove(device); + } + } + + // Don't do this inside lock because DeviceDied raises an event + // which means public code may execute during lock and cause + // problems. + foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct()) + { + if (this.IsDisposed) return; + + DeviceDied(expiredUsn, true); + } + } + + private IEnumerable<DiscoveredSsdpDevice> GetUnexpiredDevices() + { + lock (_Devices) + { + return (from device in _Devices where !device.IsExpired() select device).ToArray(); + } + } + + private bool DeviceDied(string deviceUsn, bool expired) + { + IEnumerable<DiscoveredSsdpDevice> existingDevices = null; + lock (_Devices) + { + existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn); + foreach (var existingDevice in existingDevices) + { + if (this.IsDisposed) return true; + + _Devices.Remove(existingDevice); + } + } + + if (existingDevices != null && existingDevices.Any()) + { + lock (_SearchResultsSynchroniser) + { + if (_SearchResults != null) + { + var resultsToRemove = (from result in _SearchResults where result.Usn == deviceUsn select result).ToArray(); + foreach (var result in resultsToRemove) + { + if (this.IsDisposed) return true; + + _SearchResults.Remove(result); + } + } + } + + foreach (var removedDevice in existingDevices) + { + if (NotificationTypeMatchesFilter(removedDevice)) + OnDeviceUnavailable(removedDevice, expired); + } + + return true; + } + + return false; + } + + #endregion + + private static TimeSpan SearchTimeToMXValue(TimeSpan searchWaitTime) + { + if (searchWaitTime.TotalSeconds < 2 || searchWaitTime == TimeSpan.Zero) + return OneSecond; + else + return searchWaitTime.Subtract(OneSecond); + } + + private static DiscoveredSsdpDevice FindExistingDeviceNotification(IEnumerable<DiscoveredSsdpDevice> devices, string notificationType, string usn) + { + return (from d in devices where d.NotificationType == notificationType && d.Usn == usn select d).FirstOrDefault(); + } + + private static IEnumerable<DiscoveredSsdpDevice> FindExistingDeviceNotifications(IList<DiscoveredSsdpDevice> devices, string usn) + { + return (from d in devices where d.Usn == usn select d).ToArray(); + } + + #endregion + + #region Event Handlers + + private void CommsServer_ResponseReceived(object sender, ResponseReceivedEventArgs e) + { + ProcessSearchResponseMessage(e.Message); + } + + private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) + { + ProcessNotificationMessage(e.Message); + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpDeviceProperties.cs b/RSSDP/SsdpDeviceProperties.cs new file mode 100644 index 000000000..850dfb0ba --- /dev/null +++ b/RSSDP/SsdpDeviceProperties.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + /// <summary> + /// Represents a collection of <see cref="SsdpDeviceProperty"/> instances keyed by the <see cref="SsdpDeviceProperty.FullName"/> property value. + /// </summary> + /// <remarks> + /// <para>Items added to this collection are keyed by their <see cref="SsdpDeviceProperty.FullName"/> property value, at the time they were added. If the name changes after they were added to the collection, the key is not updated unless the item is manually removed and re-added to the collection.</para> + /// </remarks> + public class SsdpDevicePropertiesCollection : IEnumerable<SsdpDeviceProperty> + { + + #region Fields + + private IDictionary<string, SsdpDeviceProperty> _Properties; + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + public SsdpDevicePropertiesCollection() + { + _Properties = new Dictionary<string, SsdpDeviceProperty>(); + } + + /// <summary> + /// Full constructor. + /// </summary> + /// <param name="capacity">Specifies the initial capacity of the collection.</param> + public SsdpDevicePropertiesCollection(int capacity) + { + _Properties = new Dictionary<string, SsdpDeviceProperty>(capacity); + } + + #endregion + + #region Public Methpds + + /// <summary> + /// Adds a <see cref="SsdpDeviceProperty"/> instance to the collection. + /// </summary> + /// <param name="customDeviceProperty">The property instance to add to the collection.</param> + /// <remarks> + /// <para></para> + /// </remarks> + /// <exception cref="System.ArgumentNullException">Thrown if <paramref name="customDeviceProperty"/> is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the <see cref="SsdpDeviceProperty.FullName"/> property of the <paramref name="customDeviceProperty"/> argument is null or empty string, or if the collection already contains an item with the same key.</exception> + public void Add(SsdpDeviceProperty customDeviceProperty) + { + if (customDeviceProperty == null) throw new ArgumentNullException("customDeviceProperty"); + if (String.IsNullOrEmpty(customDeviceProperty.FullName)) throw new ArgumentException("customDeviceProperty.FullName cannot be null or empty."); + + lock (_Properties) + { + _Properties.Add(customDeviceProperty.FullName, customDeviceProperty); + } + } + + #region Remove Overloads + + /// <summary> + /// Removes the specified property instance from the collection. + /// </summary> + /// <param name="customDeviceProperty">The <see cref="SsdpDeviceProperty"/> instance to remove from the collection.</param> + /// <remarks> + /// <para>Only remove the specified property if that instance was in the collection, if another property with the same full name exists in the collection it is not removed.</para> + /// </remarks> + /// <returns>True if an item was removed from the collection, otherwise false (because it did not exist or was not the same instance).</returns> + /// <seealso cref="Remove(string)"/> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="customDeviceProperty"/> is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the <see cref="SsdpDeviceProperty.FullName"/> property of the <paramref name="customDeviceProperty"/> argument is null or empty string, or if the collection already contains an item with the same key.</exception> + public bool Remove(SsdpDeviceProperty customDeviceProperty) + { + if (customDeviceProperty == null) throw new ArgumentNullException("customDeviceProperty"); + if (String.IsNullOrEmpty(customDeviceProperty.FullName)) throw new ArgumentException("customDeviceProperty.FullName cannot be null or empty."); + + lock (_Properties) + { + if (_Properties.ContainsKey(customDeviceProperty.FullName) && _Properties[customDeviceProperty.FullName] == customDeviceProperty) + return _Properties.Remove(customDeviceProperty.FullName); + } + + return false; + } + + /// <summary> + /// Removes the property with the specified key (<see cref="SsdpDeviceProperty.FullName"/> from the collection. + /// </summary> + /// <param name="customDevicePropertyFullName">The full name of the <see cref="SsdpDeviceProperty"/> instance to remove from the collection.</param> + /// <returns>True if an item was removed from the collection, otherwise false (because no item exists in the collection with that key).</returns> + /// <exception cref="System.ArgumentException">Thrown if the <paramref name="customDevicePropertyFullName"/> argument is null or empty string.</exception> + public bool Remove(string customDevicePropertyFullName) + { + if (String.IsNullOrEmpty(customDevicePropertyFullName)) throw new ArgumentException("customDevicePropertyFullName cannot be null or empty."); + + lock (_Properties) + { + return _Properties.Remove(customDevicePropertyFullName); + } + } + + #endregion + + /// <summary> + /// Returns a boolean indicating whether or not the specified <see cref="SsdpDeviceProperty"/> instance is in the collection. + /// </summary> + /// <param name="customDeviceProperty">An <see cref="SsdpDeviceProperty"/> instance to check the collection for.</param> + /// <returns>True if the specified instance exists in the collection, otherwise false.</returns> + public bool Contains(SsdpDeviceProperty customDeviceProperty) + { + if (customDeviceProperty == null) throw new ArgumentNullException("customDeviceProperty"); + if (String.IsNullOrEmpty(customDeviceProperty.FullName)) throw new ArgumentException("customDeviceProperty.FullName cannot be null or empty."); + + lock (_Properties) + { + if (_Properties.ContainsKey(customDeviceProperty.FullName)) + return _Properties[customDeviceProperty.FullName] == customDeviceProperty; + } + + return false; + } + + /// <summary> + /// Returns a boolean indicating whether or not a <see cref="SsdpDeviceProperty"/> instance with the specified full name value exists in the collection. + /// </summary> + /// <param name="customDevicePropertyFullName">A string containing the full name of the <see cref="SsdpDeviceProperty"/> instance to check for.</param> + /// <returns>True if an item with the specified full name exists in the collection, otherwise false.</returns> + public bool Contains(string customDevicePropertyFullName) + { + if (String.IsNullOrEmpty(customDevicePropertyFullName)) throw new ArgumentException("customDevicePropertyFullName cannot be null or empty."); + + lock (_Properties) + { + return _Properties.ContainsKey(customDevicePropertyFullName); + } + } + + #endregion + + #region Public Properties + + /// <summary> + /// Returns the number of items in the collection. + /// </summary> + public int Count + { + get { return _Properties.Count; } + } + + /// <summary> + /// Returns the <see cref="SsdpDeviceProperty"/> instance from the collection that has the specified <see cref="SsdpDeviceProperty.FullName"/> value. + /// </summary> + /// <param name="fullName">The full name of the property to return.</param> + /// <returns>A <see cref="SsdpDeviceProperty"/> instance from the collection.</returns> + /// <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if no item exists in the collection with the specified <paramref name="fullName"/> value.</exception> + public SsdpDeviceProperty this[string fullName] + { + get + { + return _Properties[fullName]; + } + } + + #endregion + + #region IEnumerable<SsdpDeviceProperty> Members + + /// <summary> + /// Returns an enumerator of <see cref="SsdpDeviceProperty"/> instances in this collection. + /// </summary> + /// <returns>An enumerator of <see cref="SsdpDeviceProperty"/> instances in this collection.</returns> + public IEnumerator<SsdpDeviceProperty> GetEnumerator() + { + lock (_Properties) + { + return _Properties.Values.GetEnumerator(); + } + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator of <see cref="SsdpDeviceProperty"/> instances in this collection. + /// </summary> + /// <returns>An enumerator of <see cref="SsdpDeviceProperty"/> instances in this collection.</returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + lock (_Properties) + { + return _Properties.Values.GetEnumerator(); + } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpDeviceProperty.cs b/RSSDP/SsdpDeviceProperty.cs new file mode 100644 index 000000000..3a8dd2ec7 --- /dev/null +++ b/RSSDP/SsdpDeviceProperty.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp +{ + /// <summary> + /// Represents a custom property of an <see cref="SsdpDevice"/>. + /// </summary> + public sealed class SsdpDeviceProperty + { + + /// <summary> + /// Sets or returns the namespace this property exists in. + /// </summary> + public string Namespace { get; set; } + + /// <summary> + /// Sets or returns the name of this property. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Returns the full name of this property (namespace and name). + /// </summary> + public string FullName { get { return String.IsNullOrEmpty(this.Namespace) ? this.Name : this.Namespace + ":" + this.Name; } } + + /// <summary> + /// Sets or returns the value of this property. + /// </summary> + public string Value { get; set; } + + } +} diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs new file mode 100644 index 000000000..9667ed809 --- /dev/null +++ b/RSSDP/SsdpDevicePublisher.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + /// <summary> + /// Allows publishing devices both as notification and responses to search requests. + /// </summary> + /// <remarks> + /// This is the 'server' part of the system. You add your devices to an instance of this class so clients can find them. + /// </remarks> + public class SsdpDevicePublisher : SsdpDevicePublisherBase + { + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + /// <remarks> + /// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification.</para> + /// </remarks> + [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() + : this(new SsdpCommunicationsServer(new SocketFactory(null))) + { + + } + + /// <summary> + /// Full constructor. + /// </summary> + /// <remarks> + /// <para>Allows the caller to specify their own <see cref="ISsdpCommunicationsServer"/> implementation for full control over the networking, or for mocking/testing purposes..</para> + /// </remarks> + public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer) + : base(communicationsServer, GetOSName(), GetOSVersion()) + { + + } + + /// <summary> + /// Partial constructor. + /// </summary> + /// <param name="localPort">The local port to use for socket communications, specify 0 to have the system choose it's own.</param> + /// <remarks> + /// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification, but specifies the local port to use for socket communications. Specify 0 to indicate the system should choose it's own port.</para> + /// </remarks> + [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(int localPort) + : this(new SsdpCommunicationsServer(new SocketFactory(null), localPort)) + { + + } + + /// <summary> + /// Partial constructor. + /// </summary> + /// <param name="localPort">The local port to use for socket communications, specify 0 to have the system choose it's own.</param> + /// <param name="multicastTimeToLive">The number of hops a multicast packet can make before it expires. Must be 1 or greater.</param> + /// <remarks> + /// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification, but specifies the local port to use and multicast time to live setting for socket communications.</para> + /// <para>Specify 0 for the <paramref name="localPort"/> argument to indicate the system should choose it's own port.</para> + /// <para>The <paramref name="multicastTimeToLive"/> is actually a number of 'hops' on the network and not a time based argument.</para> + /// </remarks> + [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(int localPort, int multicastTimeToLive) + : this(new SsdpCommunicationsServer(new SocketFactory(null), localPort, multicastTimeToLive)) + { + } + + #endregion + + #region Private Methods + + private static string GetOSName() + { +#if NET46 + return Environment.OSVersion.Platform.ToString(); +#elif NETSTANDARD1_6 + return System.Runtime.InteropServices.RuntimeInformation.OSDescription; +#endif + return "Operating System"; + } + + private static string GetOSVersion() + { +#if NET46 + return Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString(); +#elif NETSTANDARD1_6 + return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; +#endif + return "1.0"; + } + +#endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpDevicePublisherBase.cs b/RSSDP/SsdpDevicePublisherBase.cs new file mode 100644 index 000000000..ecf1ac132 --- /dev/null +++ b/RSSDP/SsdpDevicePublisherBase.cs @@ -0,0 +1,725 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Provides the platform independent logic for publishing SSDP devices (notifications and search responses). + /// </summary> + public abstract class SsdpDevicePublisherBase : DisposableManagedObjectBase, ISsdpDevicePublisher + { + + #region Fields & Constants + + private ISsdpCommunicationsServer _CommsServer; + private string _OSName; + private string _OSVersion; + + private bool _SupportPnpRootDevice; + + private IList<SsdpRootDevice> _Devices; + private ReadOnlyEnumerable<SsdpRootDevice> _ReadOnlyDevices; + + private System.Threading.Timer _RebroadcastAliveNotificationsTimer; + //private TimeSpan _RebroadcastAliveNotificationsTimeSpan; + private DateTime _LastNotificationTime; + + private IDictionary<string, SearchRequest> _RecentSearchRequests; + private IUpnpDeviceValidator _DeviceValidator; + + private Random _Random; + //private TimeSpan _MinCacheTime; + + private const string ServerVersion = "1.0"; + + #endregion + + #region Message Format Constants + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + /// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/> implementation, used to send and receive SSDP network messages.</param> + /// <param name="osName">Then name of the operating system running the server.</param> + /// <param name="osVersion">The version of the operating system running the server.</param> + protected SsdpDevicePublisherBase(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion) + { + if (communicationsServer == null) throw new ArgumentNullException("communicationsServer"); + if (osName == null) throw new ArgumentNullException("osName"); + if (osName.Length == 0) throw new ArgumentException("osName cannot be an empty string.", "osName"); + if (osVersion == null) throw new ArgumentNullException("osVersion"); + if (osVersion.Length == 0) throw new ArgumentException("osVersion cannot be an empty string.", "osName"); + + _SupportPnpRootDevice = true; + _Devices = new List<SsdpRootDevice>(); + _ReadOnlyDevices = new ReadOnlyEnumerable<SsdpRootDevice>(_Devices); + _RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase); + _Random = new Random(); + _DeviceValidator = new Upnp10DeviceValidator(); //Should probably inject this later, but for now we only support 1.0. + + _CommsServer = communicationsServer; + _CommsServer.RequestReceived += CommsServer_RequestReceived; + _OSName = osName; + _OSVersion = osVersion; + + _CommsServer.BeginListeningForBroadcasts(); + } + + #endregion + + #region Public Methods + + /// <summary> + /// Adds a device (and it's children) to the list of devices being published by this server, making them discoverable to SSDP clients. + /// </summary> + /// <remarks> + /// <para>Adding a device causes "alive" notification messages to be sent immediately, or very soon after. Ensure your device/description service is running before adding the device object here.</para> + /// <para>Devices added here with a non-zero cache life time will also have notifications broadcast periodically.</para> + /// <para>This method ignores duplicate device adds (if the same device instance is added multiple times, the second and subsequent add calls do nothing).</para> + /// </remarks> + /// <param name="device">The <see cref="SsdpDevice"/> instance to add.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <exception cref="System.InvalidOperationException">Thrown if the <paramref name="device"/> contains property values that are not acceptable to the UPnP 1.0 specification.</exception> + [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("device"); + + ThrowIfDisposed(); + + _DeviceValidator.ThrowIfDeviceInvalid(device); + + TimeSpan minCacheTime = TimeSpan.Zero; + bool wasAdded = false; + lock (_Devices) + { + if (!_Devices.Contains(device)) + { + _Devices.Add(device); + wasAdded = true; + minCacheTime = GetMinimumNonZeroCacheLifetime(); + } + } + + if (wasAdded) + { + //_MinCacheTime = minCacheTime; + + ConnectToDeviceEvents(device); + + WriteTrace("Device Added", device); + + SetRebroadcastAliveNotificationsTimer(minCacheTime); + + SendAliveNotifications(device, true); + } + } + + /// <summary> + /// Removes a device (and it's children) from the list of devices being published by this server, making them undiscoverable. + /// </summary> + /// <remarks> + /// <para>Removing a device causes "byebye" notification messages to be sent immediately, advising clients of the device/service becoming unavailable. We recommend removing the device from the published list before shutting down the actual device/service, if possible.</para> + /// <para>This method does nothing if the device was not found in the collection.</para> + /// </remarks> + /// <param name="device">The <see cref="SsdpDevice"/> instance to add.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + public async Task RemoveDevice(SsdpRootDevice device) + { + if (device == null) throw new ArgumentNullException("device"); + + ThrowIfDisposed(); + + bool wasRemoved = false; + TimeSpan minCacheTime = TimeSpan.Zero; + lock (_Devices) + { + if (_Devices.Contains(device)) + { + _Devices.Remove(device); + wasRemoved = true; + minCacheTime = GetMinimumNonZeroCacheLifetime(); + } + } + + if (wasRemoved) + { + //_MinCacheTime = minCacheTime; + + DisconnectFromDeviceEvents(device); + + WriteTrace("Device Removed", device); + + await SendByeByeNotifications(device, true).ConfigureAwait(false); + + SetRebroadcastAliveNotificationsTimer(minCacheTime); + } + } + + #endregion + + #region Public Properties + + /// <summary> + /// Returns a read only list of devices being published by this instance. + /// </summary> + public IEnumerable<SsdpRootDevice> Devices + { + get + { + return _ReadOnlyDevices; + } + } + + /// <summary> + /// If true (default) treats root devices as both upnp:rootdevice and pnp:rootdevice types. + /// </summary> + /// <remarks> + /// <para>Enabling this option will cause devices to show up in Microsoft Windows Explorer's network screens (if discovery is enabled etc.). Windows Explorer appears to search only for pnp:rootdeivce and not upnp:rootdevice.</para> + /// <para>If false, the system will only use upnp:rootdevice for notifiation broadcasts and and search responses, which is correct according to the UPnP/SSDP spec.</para> + /// </remarks> + public bool SupportPnpRootDevice + { + get { return _SupportPnpRootDevice; } + set + { + _SupportPnpRootDevice = value; + } + } + + #endregion + + #region Overrides + + /// <summary> + /// Stops listening for requests, stops sending periodic broadcasts, disposes all internal resources. + /// </summary> + /// <param name="disposing"></param> + protected override void Dispose(bool disposing) + { + if (disposing) + { + var commsServer = _CommsServer; + _CommsServer = null; + + if (commsServer != null) + { + commsServer.RequestReceived -= this.CommsServer_RequestReceived; + if (!commsServer.IsShared) + commsServer.Dispose(); + } + + DisposeRebroadcastTimer(); + + foreach (var device in this.Devices) + { + DisconnectFromDeviceEvents(device); + } + + _RecentSearchRequests = null; + } + } + + #endregion + + #region Private Methods + + #region Search Related Methods + + private void ProcessSearchRequest(string mx, string searchTarget, UdpEndPoint endPoint) + { + if (String.IsNullOrEmpty(searchTarget)) + { + WriteTrace(String.Format("Invalid search request received From {0}, Target is null/empty.", endPoint.ToString())); + return; + } + + WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", endPoint.ToString(), searchTarget)); + + if (IsDuplicateSearchRequest(searchTarget, endPoint)) + { + 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. + int maxWaitInterval = 0; + if (String.IsNullOrEmpty(mx)) + { + //Windows Explorer is poorly behaved and doesn't supply an MX header value. + //if (this.SupportPnpRootDevice) + mx = "1"; + //else + //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. + Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) => + { + //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)); + + foreach (var device in deviceList) + { + SendDeviceSearchResponses(device, endPoint); + } + } + else + WriteTrace(String.Format("Sending 0 search responses.")); + }); + } + + private IEnumerable<SsdpDevice> GetAllDevicesAsFlatEnumerable() + { + return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices)); + } + + private void SendDeviceSearchResponses(SsdpDevice device, UdpEndPoint endPoint) + { + bool isRootDevice = (device as SsdpRootDevice) != null; + if (isRootDevice) + { + SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint); + if (this.SupportPnpRootDevice) + SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint); + } + + SendSearchResponse(device.Udn, device, device.Udn, endPoint); + + SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint); + } + + private static string GetUsn(string udn, string fullDeviceType) + { + return String.Format("{0}::{1}", udn, fullDeviceType); + } + + private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint) + { + var rootDevice = device.ToRootDevice(); + + //var additionalheaders = FormatCustomHeadersForResponse(device); + + const string header = "HTTP/1.1 200 OK"; + + var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + values["EXT"] = ""; + values["DATE"] = DateTime.UtcNow.ToString("r"); + values["CACHE-CONTROL"] = "max-age = 600"; + values["ST"] = searchTarget; + values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["USN"] = uniqueServiceName; + values["LOCATION"] = rootDevice.Location.ToString(); + + var message = BuildMessage(header, values); + + try + { + await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint).ConfigureAwait(false); + } + catch (Exception ex) + { + + } + + WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); + } + + private bool IsDuplicateSearchRequest(string searchTarget, UdpEndPoint endPoint) + { + var isDuplicateRequest = false; + + var newRequest = new SearchRequest() { EndPoint = endPoint, SearchTarget = searchTarget, Received = DateTime.UtcNow }; + lock (_RecentSearchRequests) + { + if (_RecentSearchRequests.ContainsKey(newRequest.Key)) + { + 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(); + } + } + + return isDuplicateRequest; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable avoids compiler warning, but value is otherwise not required.")] + private void CleanUpRecentSearchRequestsAsync() + { + var t = Task.Run(() => + { + lock (_RecentSearchRequests) + { + foreach (var requestKey in (from r in _RecentSearchRequests where r.Value.IsOld() select r.Key).ToArray()) + { + _RecentSearchRequests.Remove(requestKey); + } + } + }); + } + + #endregion + + #region Notification Related Methods + + #region Alive + + private void SendAllAliveNotifications(object state) + { + try + { + if (IsDisposed) return; + + //DisposeRebroadcastTimer(); + + WriteTrace("Begin Sending Alive Notifications For All Devices"); + + _LastNotificationTime = DateTime.Now; + + IEnumerable<SsdpRootDevice> devices; + lock (_Devices) + { + devices = _Devices.ToArray(); + } + + foreach (var device in devices) + { + if (IsDisposed) return; + + SendAliveNotifications(device, true); + } + + WriteTrace("Completed Sending Alive Notifications For All Devices"); + } + catch (ObjectDisposedException ex) + { + WriteTrace("Publisher stopped, exception " + ex.Message); + Dispose(); + } + //finally + //{ + // // This is causing all notifications to stop + // //if (!this.IsDisposed) + // //SetRebroadcastAliveNotificationsTimer(_MinCacheTime); + //} + } + + private void SendAliveNotifications(SsdpDevice device, bool isRoot) + { + if (isRoot) + { + SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice)); + if (this.SupportPnpRootDevice) + SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice)); + } + + SendAliveNotification(device, device.Udn, device.Udn); + SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType)); + + foreach (var childDevice in device.Devices) + { + SendAliveNotifications(childDevice, false); + } + } + + private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName) + { + var rootDevice = device.ToRootDevice(); + + const string header = "NOTIFY * HTTP/1.1"; + + var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + // If needed later for non-server devices, these headers will need to be dynamic + values["HOST"] = "239.255.255.250:1900"; + values["DATE"] = DateTime.UtcNow.ToString("r"); + values["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds; + values["LOCATION"] = rootDevice.Location.ToString(); + values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["NTS"] = "ssdp:alive"; + values["NT"] = notificationType; + values["USN"] = uniqueServiceName; + + var message = BuildMessage(header, values); + + _CommsServer.SendMulticastMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message)); + + WriteTrace(String.Format("Sent alive notification"), device); + } + + private string BuildMessage(string header, Dictionary<string, string> 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(); + } + + #endregion + + #region ByeBye + + private async Task SendByeByeNotifications(SsdpDevice device, bool isRoot) + { + if (isRoot) + { + await SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice)).ConfigureAwait(false); + if (this.SupportPnpRootDevice) + await SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice")).ConfigureAwait(false); ; + } + + await SendByeByeNotification(device, device.Udn, device.Udn).ConfigureAwait(false); ; + await SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType)).ConfigureAwait(false); ; + + foreach (var childDevice in device.Devices) + { + await SendByeByeNotifications(childDevice, false).ConfigureAwait(false); ; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "byebye", Justification = "Correct value for this type of notification in SSDP.")] + private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName) + { + const string header = "NOTIFY * HTTP/1.1"; + + var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + // If needed later for non-server devices, these headers will need to be dynamic + values["HOST"] = "239.255.255.250:1900"; + values["DATE"] = DateTime.UtcNow.ToString("r"); + values["SERVER"] = string.Format("{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, ServerVersion); + values["NTS"] = "ssdp:byebye"; + values["NT"] = notificationType; + values["USN"] = uniqueServiceName; + + var message = BuildMessage(header, values); + + return _CommsServer.SendMulticastMessage(System.Text.UTF8Encoding.UTF8.GetBytes(message)); + + //WriteTrace(String.Format("Sent byebye notification"), device); + } + + #endregion + + #region Rebroadcast Timer + + private void DisposeRebroadcastTimer() + { + var timer = _RebroadcastAliveNotificationsTimer; + _RebroadcastAliveNotificationsTimer = null; + if (timer != null) + timer.Dispose(); + } + + private void SetRebroadcastAliveNotificationsTimer(TimeSpan minCacheTime) + { + //if (minCacheTime == _RebroadcastAliveNotificationsTimeSpan) return; + + DisposeRebroadcastTimer(); + + if (minCacheTime == TimeSpan.Zero) return; + + // According to UPnP/SSDP spec, we should randomise the interval at + // which we broadcast notifications, to help with network congestion. + // Specs also advise to choose a random interval up to *half* the cache time. + // Here we do that, but using the minimum non-zero cache time of any device we are publishing. + var rebroadCastInterval = new TimeSpan(minCacheTime.Ticks); + + // If we were already setup to rebroadcast someime in the future, + // don't just blindly reset the next broadcast time to the new interval + // as repeatedly changing the interval might end up causing us to over + // delay in sending the next one. + var nextBroadcastInterval = rebroadCastInterval; + if (_LastNotificationTime != DateTime.MinValue) + { + nextBroadcastInterval = rebroadCastInterval.Subtract(DateTime.Now.Subtract(_LastNotificationTime)); + if (nextBroadcastInterval.Ticks < 0) + nextBroadcastInterval = TimeSpan.Zero; + else if (nextBroadcastInterval > rebroadCastInterval) + nextBroadcastInterval = rebroadCastInterval; + } + + //_RebroadcastAliveNotificationsTimeSpan = rebroadCastInterval; + _RebroadcastAliveNotificationsTimer = new System.Threading.Timer(SendAllAliveNotifications, null, nextBroadcastInterval, rebroadCastInterval); + + WriteTrace(String.Format("Rebroadcast Interval = {0}, Next Broadcast At = {1}", rebroadCastInterval.ToString(), nextBroadcastInterval.ToString())); + } + + private TimeSpan GetMinimumNonZeroCacheLifetime() + { + 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; + } + + #endregion + + #endregion + + private static string GetFirstHeaderValue(System.Net.Http.Headers.HttpRequestHeaders httpRequestHeaders, string headerName) + { + string retVal = null; + IEnumerable<String> values = null; + if (httpRequestHeaders.TryGetValues(headerName, out values) && values != null) + retVal = values.FirstOrDefault(); + + return retVal; + } + + public static Action<string> LogFunction { get; set; } + + private static void WriteTrace(string text) + { + if (LogFunction != null) + { + LogFunction(text); + } + //System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher"); + } + + private static 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 ConnectToDeviceEvents(SsdpDevice device) + { + device.DeviceAdded += device_DeviceAdded; + device.DeviceRemoved += device_DeviceRemoved; + + foreach (var childDevice in device.Devices) + { + ConnectToDeviceEvents(childDevice); + } + } + + private void DisconnectFromDeviceEvents(SsdpDevice device) + { + device.DeviceAdded -= device_DeviceAdded; + device.DeviceRemoved -= device_DeviceRemoved; + + foreach (var childDevice in device.Devices) + { + DisconnectFromDeviceEvents(childDevice); + } + } + + #endregion + + #region Event Handlers + + private void device_DeviceAdded(object sender, DeviceEventArgs e) + { + SendAliveNotifications(e.Device, false); + ConnectToDeviceEvents(e.Device); + } + + private void device_DeviceRemoved(object sender, DeviceEventArgs e) + { + var task = SendByeByeNotifications(e.Device, false); + Task.WaitAll(task); + DisconnectFromDeviceEvents(e.Device); + } + + private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) + { + if (this.IsDisposed) return; + + if (e.Message.Method.Method == SsdpConstants.MSearchMethod) + { + //According to SSDP/UPnP spec, ignore message if missing these headers. + // Edit: But some devices do it anyway + //if (!e.Message.Headers.Contains("MX")) + // WriteTrace("Ignoring search request - missing MX header."); + //else if (!e.Message.Headers.Contains("MAN")) + // WriteTrace("Ignoring search request - missing MAN header."); + //else + ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom); + } + } + + #endregion + + #region Private Classes + + private class SearchRequest + { + public UdpEndPoint EndPoint { get; set; } + public DateTime Received { get; set; } + public string SearchTarget { get; set; } + + public string Key + { + get { return this.SearchTarget + ":" + this.EndPoint.ToString(); } + } + + public bool IsOld() + { + return DateTime.UtcNow.Subtract(this.Received).TotalMilliseconds > 500; + } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpEmbeddedDevice.cs b/RSSDP/SsdpEmbeddedDevice.cs new file mode 100644 index 000000000..c03106b2d --- /dev/null +++ b/RSSDP/SsdpEmbeddedDevice.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Rssdp +{ + /// <summary> + /// Represents a device that is a descendant of a <see cref="SsdpRootDevice"/> instance. + /// </summary> + public class SsdpEmbeddedDevice : SsdpDevice + { + + #region Fields + + private SsdpRootDevice _RootDevice; + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + public SsdpEmbeddedDevice() + { + } + + /// <summary> + /// Deserialisation constructor. + /// </summary> + /// <param name="deviceDescriptionXml">A UPnP device description XML document.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is empty.</exception> + public SsdpEmbeddedDevice(string deviceDescriptionXml) + : base(deviceDescriptionXml) + { + } + + #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> + public SsdpRootDevice RootDevice + { + get + { + return _RootDevice; + } + internal set + { + _RootDevice = value; + lock (this.Devices) + { + foreach (var embeddedDevice in this.Devices) + { + ((SsdpEmbeddedDevice)embeddedDevice).RootDevice = _RootDevice; + } + } + } + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs new file mode 100644 index 000000000..faf851bcb --- /dev/null +++ b/RSSDP/SsdpRootDevice.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + /// <summary> + /// Represents a 'root' device, a device that has no parent. Used for publishing devices and for the root device in a tree of discovered devices. + /// </summary> + /// <remarks> + /// <para>Child (embedded) devices are represented by the <see cref="SsdpDevice"/> in the <see cref="SsdpDevice.Devices"/> property.</para> + /// <para>Root devices contain some information that applies to the whole device tree and is therefore not present on child devices, such as <see cref="CacheLifetime"/> and <see cref="Location"/>.</para> + /// </remarks> + public class SsdpRootDevice : SsdpDevice + { + + #region Fields + + private Uri _UrlBase; + + #endregion + + #region Constructors + + /// <summary> + /// Default constructor. + /// </summary> + public SsdpRootDevice() : base() + { + } + + /// <summary> + /// Deserialisation constructor. + /// </summary> + /// <param name="location">The url from which the device description document was retrieved.</param> + /// <param name="cacheLifetime">A <see cref="System.TimeSpan"/> representing the time maximum period of time the device description can be cached for.</param> + /// <param name="deviceDescriptionXml">The device description XML as a string.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="deviceDescriptionXml"/> or <paramref name="location"/> arguments are null.</exception> + /// <exception cref="System.ArgumentException">Thrown if the <paramref name="deviceDescriptionXml"/> argument is empty.</exception> + public SsdpRootDevice(Uri location, TimeSpan cacheLifetime, string deviceDescriptionXml) + : base(deviceDescriptionXml) + { + if (location == null) throw new ArgumentNullException("location"); + + this.CacheLifetime = cacheLifetime; + this.Location = location; + + LoadFromDescriptionDocument(deviceDescriptionXml); + } + + #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> + /// <remarks> + /// <para>Specifiy <see cref="TimeSpan.Zero"/> to indicate no caching allowed.</para> + /// <para>Also used to specify how often to rebroadcast alive notifications.</para> + /// <para>The UPnP/SSDP specifications indicate this should not be less than 1800 seconds (half an hour), but this is not enforced by this library.</para> + /// </remarks> + public TimeSpan CacheLifetime + { + get; set; + } + + /// <summary> + /// Gets or sets the URL used to retrieve the description document for this device/tree. Required. + /// </summary> + public Uri Location { get; set; } + + + /// <summary> + /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional. + /// </summary> + /// <remarks> + /// <para>Defines the base URL. Used to construct fully-qualified URLs. All relative URLs that appear elsewhere in the description are combined with this base URL. If URLBase is empty or not given, the base URL is the URL from which the device description was retrieved (which is the preferred implementation; use of URLBase is no longer recommended). Specified by UPnP vendor. Single URL.</para> + /// </remarks> + public Uri UrlBase + { + get + { + return _UrlBase ?? this.Location; + } + + set + { + _UrlBase = value; + } + } + + #endregion + + #region Public Methods + + /// <summary> + /// Saves the property values of this device object to an a string in the full UPnP device description XML format, including child devices and outer root node and XML document declaration. + /// </summary> + /// <returns>A string containing XML in the UPnP device description format</returns> + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispsoing memory stream twice is 'safe' and easier to read than correct code for ensuring it is only closed once.")] + public virtual string ToDescriptionDocument() + { + if (String.IsNullOrEmpty(this.Uuid)) throw new InvalidOperationException("Must provide a UUID value."); + + //This would have been so much nicer with Xml.Linq, but that's + //not available until .NET 4.03 at the earliest, and I want to + //target 4.0 :( + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(ms, new XmlWriterSettings() { Encoding = System.Text.UTF8Encoding.UTF8, Indent = true, NamespaceHandling = NamespaceHandling.OmitDuplicates }); + writer.WriteStartDocument(); + writer.WriteStartElement("root", SsdpConstants.SsdpDeviceDescriptionXmlNamespace); + + writer.WriteStartElement("specVersion"); + writer.WriteElementString("major", "1"); + writer.WriteElementString("minor", "0"); + writer.WriteEndElement(); + + if (this.UrlBase != null && this.UrlBase != this.Location) + writer.WriteElementString("URLBase", this.UrlBase.ToString()); + + WriteDeviceDescriptionXml(writer, this); + + writer.WriteEndElement(); + writer.Flush(); + + ms.Seek(0, System.IO.SeekOrigin.Begin); + using (var reader = new System.IO.StreamReader(ms)) + { + return reader.ReadToEnd(); + } + } + } + + #endregion + + #region Private Methods + + #region Deserialisation Methods + + private void LoadFromDescriptionDocument(string deviceDescriptionXml) + { + using (var ms = new System.IO.MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(deviceDescriptionXml))) + { + var reader = XmlReader.Create(ms); + while (!reader.EOF) + { + reader.Read(); + if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "root") continue; + + while (!reader.EOF) + { + reader.Read(); + + if (reader.NodeType != XmlNodeType.Element) continue; + + if (reader.LocalName == "URLBase") + { + this.UrlBase = StringToUri(reader.ReadElementContentAsString()); + break; + } + } + } + } + } + + #endregion + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/UPnP10DeviceValidator.cs b/RSSDP/UPnP10DeviceValidator.cs new file mode 100644 index 000000000..f802b7639 --- /dev/null +++ b/RSSDP/UPnP10DeviceValidator.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Validates a <see cref="SsdpDevice"/> object's properties meet the UPnP 1.0 specification. + /// </summary> + /// <remarks> + /// <para>This is a best effort validation for known rules, it doesn't guarantee 100% compatibility with the specification. Reading the specification yourself is the best way to ensure compatibility.</para> + /// </remarks> + public class Upnp10DeviceValidator : IUpnpDeviceValidator + { + + #region Public Methods + + /// <summary> + /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified root device. + /// </summary> + /// <remarks> + /// <para>If no errors are found, an empty (but non-null) enumerable is returned.</para> + /// </remarks> + /// <param name="device">The <see cref="SsdpRootDevice"/> to validate.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <returns>A non-null enumerable set of strings, empty if there are no validation errors, otherwise each string represents a discrete problem.</returns> + public IEnumerable<string> GetValidationErrors(SsdpRootDevice device) + { + if (device == null) throw new ArgumentNullException("device"); + + var retVal = GetValidationErrors((SsdpDevice)device) as IList<string>; + + if (device.Location == null) + retVal.Add("Location cannot be null."); + else if (!device.Location.IsAbsoluteUri) + retVal.Add("Location must be an absolute URL."); + + return retVal; + } + + /// <summary> + /// Returns an enumerable set of strings, each one being a description of an invalid property on the specified device. + /// </summary> + /// <remarks> + /// <para>If no errors are found, an empty (but non-null) enumerable is returned.</para> + /// </remarks> + /// <param name="device">The <see cref="SsdpDevice"/> to validate.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <returns>A non-null enumerable set of strings, empty if there are no validation errors, otherwise each string represents a discrete problem.</returns> + public IEnumerable<string> GetValidationErrors(SsdpDevice device) + { + if (device == null) throw new ArgumentNullException("device"); + + var retVal = new List<string>(); + + if (String.IsNullOrEmpty(device.Uuid)) + retVal.Add("Uuid is not set."); + + if (!String.IsNullOrEmpty(device.Upc)) + ValidateUpc(device, retVal); + + if (String.IsNullOrEmpty(device.Udn)) + retVal.Add("UDN is not set."); + else + ValidateUdn(device, retVal); + + if (String.IsNullOrEmpty(device.DeviceType)) + retVal.Add("DeviceType is not set."); + + if (String.IsNullOrEmpty(device.DeviceTypeNamespace)) + retVal.Add("DeviceTypeNamespace is not set."); + else + { + if (IsOverLength(device.DeviceTypeNamespace, 64)) + retVal.Add("DeviceTypeNamespace cannot be longer than 64 characters."); + + //if (device.DeviceTypeNamespace.Contains(".")) + // retVal.Add("Period (.) characters in the DeviceTypeNamespace property must be replaced with hyphens (-)."); + } + + if (device.DeviceVersion <= 0) + retVal.Add("DeviceVersion must be 1 or greater."); + + if (IsOverLength(device.ModelName, 32)) + retVal.Add("ModelName cannot be longer than 32 characters."); + + if (IsOverLength(device.ModelNumber, 32)) + retVal.Add("ModelNumber cannot be longer than 32 characters."); + + if (IsOverLength(device.FriendlyName, 64)) + retVal.Add("FriendlyName cannot be longer than 64 characters."); + + if (IsOverLength(device.Manufacturer, 64)) + retVal.Add("Manufacturer cannot be longer than 64 characters."); + + if (IsOverLength(device.SerialNumber, 64)) + retVal.Add("SerialNumber cannot be longer than 64 characters."); + + if (IsOverLength(device.ModelDescription, 128)) + retVal.Add("ModelDescription cannot be longer than 128 characters."); + + if (String.IsNullOrEmpty(device.FriendlyName)) + retVal.Add("FriendlyName is required."); + + if (String.IsNullOrEmpty(device.Manufacturer)) + retVal.Add("Manufacturer is required."); + + if (String.IsNullOrEmpty(device.ModelName)) + retVal.Add("ModelName is required."); + + if (device.Icons.Any()) + ValidateIcons(device, retVal); + + ValidateChildDevices(device, retVal); + + return retVal; + } + + /// <summary> + /// Validates the specified device and throws an <see cref="System.InvalidOperationException"/> if there are any validation errors. + /// </summary> + /// <param name="device">The <see cref="SsdpDevice"/> to validate.</param> + /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="device"/> argument is null.</exception> + /// <exception cref="System.InvalidOperationException">Thrown if the device object does not pass validation.</exception> + public void ThrowIfDeviceInvalid(SsdpDevice device) + { + var errors = this.GetValidationErrors(device); + if (errors != null && errors.Any()) throw new InvalidOperationException("Invalid device settings : " + String.Join(Environment.NewLine, errors)); + } + + #endregion + + #region Private Methods + + private static void ValidateUpc(SsdpDevice device, List<string> retVal) + { + if (device.Upc.Length != 12) + retVal.Add("Upc, if provided, should be 12 digits."); + + foreach (char c in device.Upc) + { + if (!Char.IsDigit(c)) + { + retVal.Add("Upc, if provided, should contain only digits (numeric characters)."); + break; + } + } + } + + private static void ValidateUdn(SsdpDevice device, List<string> retVal) + { + if (!device.Udn.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) + retVal.Add("UDN must begin with uuid:. Correct format is uuid:<uuid>"); + else if (device.Udn.Substring(5).Trim() != device.Uuid) + retVal.Add("UDN incorrect. Correct format is uuid:<uuid>"); + } + + private static void ValidateIcons(SsdpDevice device, List<string> retVal) + { + if (device.Icons.Any((di) => di.Url == null)) + retVal.Add("Device icon is missing URL."); + + if (device.Icons.Any((di) => String.IsNullOrEmpty(di.MimeType))) + retVal.Add("Device icon is missing mime type."); + + if (device.Icons.Any((di) => di.Width <= 0 || di.Height <= 0)) + retVal.Add("Device icon has zero (or negative) height, width or both."); + + if (device.Icons.Any((di) => di.ColorDepth <= 0)) + retVal.Add("Device icon has zero (or negative) colordepth."); + } + + private void ValidateChildDevices(SsdpDevice device, List<string> retVal) + { + foreach (var childDevice in device.Devices) + { + foreach (var validationError in this.GetValidationErrors(childDevice)) + { + retVal.Add("Embedded Device : " + childDevice.Uuid + ": " + validationError); + } + } + } + + private static bool IsOverLength(string value, int maxLength) + { + return !String.IsNullOrEmpty(value) && value.Length > maxLength; + } + + #endregion + + } +} diff --git a/RSSDP/UdpEndPoint.cs b/RSSDP/UdpEndPoint.cs new file mode 100644 index 000000000..617769cf4 --- /dev/null +++ b/RSSDP/UdpEndPoint.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rssdp.Infrastructure +{ + /// <summary> + /// Cross platform representation of a UDP end point, being an IP address (either IPv4 or IPv6) and a port. + /// </summary> + public sealed class UdpEndPoint + { + + /// <summary> + /// The IP Address of the end point. + /// </summary> + /// <remarks> + /// <para>Can be either IPv4 or IPv6, up to the code using this instance to determine which was provided.</para> + /// </remarks> + public string IPAddress { get; set; } + + /// <summary> + /// The port of the end point. + /// </summary> + public int Port { get; set; } + + /// <summary> + /// Returns the <see cref="IPAddress"/> and <see cref="Port"/> values separated by a colon. + /// </summary> + /// <returns>A string containing <see cref="IPAddress"/>:<see cref="Port"/>.</returns> + public override string ToString() + { + return (this.IPAddress ?? String.Empty) + ":" + this.Port.ToString(); + } + } +} diff --git a/RSSDP/UdpSocket.cs b/RSSDP/UdpSocket.cs new file mode 100644 index 000000000..ea5d6ae97 --- /dev/null +++ b/RSSDP/UdpSocket.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Rssdp.Infrastructure; + +namespace Rssdp +{ + // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS + // Be careful to check any changes compile and work for all platform projects it is shared in. + + internal sealed class UdpSocket : DisposableManagedObjectBase, IUdpSocket + { + + #region Fields + + private System.Net.Sockets.Socket _Socket; + private int _LocalPort; + + #endregion + + #region Constructors + + public UdpSocket(System.Net.Sockets.Socket socket, int localPort, string ipAddress) + { + if (socket == null) throw new ArgumentNullException("socket"); + + _Socket = socket; + _LocalPort = localPort; + + IPAddress ip = null; + if (String.IsNullOrEmpty(ipAddress)) + ip = IPAddress.Any; + else + ip = IPAddress.Parse(ipAddress); + + _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + if (_LocalPort == 0) + _LocalPort = (_Socket.LocalEndPoint as IPEndPoint).Port; + } + + #endregion + + #region IUdpSocket Members + + public System.Threading.Tasks.Task<ReceivedUdpData> ReceiveAsync() + { + ThrowIfDisposed(); + + var tcs = new TaskCompletionSource<ReceivedUdpData>(); + + System.Net.EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); + var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + state.TaskCompletionSource = tcs; + +#if NETSTANDARD1_6 + _Socket.ReceiveFromAsync(new System.ArraySegment<Byte>(state.Buffer), System.Net.Sockets.SocketFlags.None, state.EndPoint) + .ContinueWith((task, asyncState) => + { + if (task.Status != TaskStatus.Faulted) + { + var receiveState = asyncState as AsyncReceiveState; + receiveState.EndPoint = task.Result.RemoteEndPoint; + ProcessResponse(receiveState, () => task.Result.ReceivedBytes); + } + }, state); +#else + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, System.Net.Sockets.SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); +#endif + + return tcs.Task; + } + + public Task SendTo(byte[] messageData, UdpEndPoint endPoint) + { + ThrowIfDisposed(); + + if (messageData == null) throw new ArgumentNullException("messageData"); + if (endPoint == null) throw new ArgumentNullException("endPoint"); + +#if NETSTANDARD1_6 + _Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + return Task.FromResult(true); +#else + var taskSource = new TaskCompletionSource<bool>(); + + try + { + _Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port), result => + { + try + { + _Socket.EndSend(result); + taskSource.TrySetResult(true); + } + catch (SocketException ex) + { + taskSource.TrySetException(ex); + } + catch (ObjectDisposedException ex) + { + taskSource.TrySetException(ex); + } + catch (InvalidOperationException ex) + { + taskSource.TrySetException(ex); + } + catch (SecurityException ex) + { + taskSource.TrySetException(ex); + } + }, null); + } + catch (SocketException ex) + { + taskSource.TrySetException(ex); + } + catch (ObjectDisposedException ex) + { + taskSource.TrySetException(ex); + } + catch (SecurityException ex) + { + taskSource.TrySetException(ex); + } + + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port)); + + return taskSource.Task; +#endif + } + + #endregion + + #region Overrides + + protected override void Dispose(bool disposing) + { + if (disposing) + { + var socket = _Socket; + if (socket != null) + socket.Dispose(); + } + } + + #endregion + + #region Private Methods + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] + private static void ProcessResponse(AsyncReceiveState state, Func<int> receiveData) + { + try + { + var bytesRead = receiveData(); + + var ipEndPoint = state.EndPoint as IPEndPoint; + state.TaskCompletionSource.SetResult( + new ReceivedUdpData() + { + Buffer = state.Buffer, + ReceivedBytes = bytesRead, + ReceivedFrom = new UdpEndPoint() + { + IPAddress = ipEndPoint.Address.ToString(), + Port = ipEndPoint.Port + } + } + ); + } + catch (ObjectDisposedException) + { + state.TaskCompletionSource.SetCanceled(); + } + catch (SocketException se) + { + if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) + state.TaskCompletionSource.SetException(se); + else + state.TaskCompletionSource.SetCanceled(); + } + catch (Exception ex) + { + state.TaskCompletionSource.SetException(ex); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] + private void ProcessResponse(IAsyncResult asyncResult) + { +#if NET46 + var state = asyncResult.AsyncState as AsyncReceiveState; + try + { + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.EndPoint); + + var ipEndPoint = state.EndPoint as IPEndPoint; + state.TaskCompletionSource.SetResult( + new ReceivedUdpData() + { + Buffer = state.Buffer, + ReceivedBytes = bytesRead, + ReceivedFrom = new UdpEndPoint() + { + IPAddress = ipEndPoint.Address.ToString(), + Port = ipEndPoint.Port + } + } + ); + } + catch (ObjectDisposedException) + { + state.TaskCompletionSource.SetCanceled(); + } + catch (SocketException se) + { + if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) + state.TaskCompletionSource.SetException(se); + else + state.TaskCompletionSource.SetCanceled(); + } + catch (Exception ex) + { + state.TaskCompletionSource.SetException(ex); + } +#endif + } + + #endregion + + #region Private Classes + + private class AsyncReceiveState + { + public AsyncReceiveState(System.Net.Sockets.Socket socket, EndPoint endPoint) + { + this.Socket = socket; + this.EndPoint = endPoint; + } + + public EndPoint EndPoint; + public byte[] Buffer = new byte[SsdpConstants.DefaultUdpSocketBufferSize]; + + public System.Net.Sockets.Socket Socket { get; private set; } + + public TaskCompletionSource<ReceivedUdpData> TaskCompletionSource { get; set; } + + } + + #endregion + + } +}
\ No newline at end of file diff --git a/RSSDP/project.json b/RSSDP/project.json new file mode 100644 index 000000000..0ec2f45d6 --- /dev/null +++ b/RSSDP/project.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + + }, + + "frameworks": { + "net46": { + "frameworkAssemblies": { + "System.Collections": "4.0.0.0", + "System.Net": "4.0.0.0", + "System.Net.Http": "4.0.0.0", + "System.Runtime": "4.0.0.0", + "System.Threading": "4.0.0.0", + "System.Threading.Tasks": "4.0.0.0", + "System.Xml": "4.0.0.0" + }, + "dependencies": { + + } + }, + "netstandard1.6": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.Net.Sockets": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Timer": "4.0.1", + "System.Xml.ReaderWriter": "4.0.11" + } + } + } +} diff --git a/RSSDP/project.lock.json b/RSSDP/project.lock.json new file mode 100644 index 000000000..6bd7d14fd --- /dev/null +++ b/RSSDP/project.lock.json @@ -0,0 +1,4054 @@ +{ + "locked": false, + "version": 2, + "targets": { + ".NETFramework,Version=v4.6": {}, + ".NETStandard,Version=v1.6": { + "Microsoft.NETCore.Platforms/1.0.1": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Microsoft.NETCore.Targets/1.0.1": { + "type": "package", + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "Microsoft.Win32.Primitives/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/Microsoft.Win32.Primitives.dll": {} + } + }, + "NETStandard.Library/1.6.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.Win32.Primitives": "4.0.1", + "System.AppContext": "4.1.0", + "System.Collections": "4.0.11", + "System.Collections.Concurrent": "4.0.12", + "System.Console": "4.0.0", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Globalization.Calendars": "4.0.1", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.IO.Compression.ZipFile": "4.0.1", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.Net.Sockets": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", + "System.Runtime.Numerics": "4.0.1", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Security.Cryptography.X509Certificates": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Timer": "4.0.1", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XDocument": "4.0.11" + } + }, + "runtime.native.System/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + }, + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "runtime.native.System.IO.Compression/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + }, + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "runtime.native.System.Net.Http/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + }, + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "runtime.native.System.Security.Cryptography/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + }, + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/_._": {} + } + }, + "System.AppContext/4.1.0": { + "type": "package", + "dependencies": { + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.6/System.AppContext.dll": {} + }, + "runtime": { + "lib/netstandard1.6/System.AppContext.dll": {} + } + }, + "System.Buffers/4.0.0": { + "type": "package", + "dependencies": { + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tracing": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11" + }, + "compile": { + "lib/netstandard1.1/_._": {} + }, + "runtime": { + "lib/netstandard1.1/System.Buffers.dll": {} + } + }, + "System.Collections/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Collections.dll": {} + } + }, + "System.Collections.Concurrent/4.0.12": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Collections.Concurrent.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Collections.Concurrent.dll": {} + } + }, + "System.Console/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Console.dll": {} + } + }, + "System.Diagnostics.Debug/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Diagnostics.Debug.dll": {} + } + }, + "System.Diagnostics.DiagnosticSource/4.0.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Tracing": "4.1.0", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11" + }, + "compile": { + "lib/netstandard1.3/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": {} + } + }, + "System.Diagnostics.Tools/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/System.Diagnostics.Tools.dll": {} + } + }, + "System.Diagnostics.Tracing/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.5/System.Diagnostics.Tracing.dll": {} + } + }, + "System.Globalization/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Globalization.dll": {} + } + }, + "System.Globalization.Calendars/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Globalization.Calendars.dll": {} + } + }, + "System.Globalization.Extensions/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Globalization": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/_._": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.3/System.Globalization.Extensions.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.3/System.Globalization.Extensions.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.IO/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.5/System.IO.dll": {} + } + }, + "System.IO.Compression/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.IO": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "runtime.native.System": "4.0.0", + "runtime.native.System.IO.Compression": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.IO.Compression.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.3/System.IO.Compression.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.3/System.IO.Compression.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.IO.Compression.ZipFile/4.0.1": { + "type": "package", + "dependencies": { + "System.Buffers": "4.0.0", + "System.IO": "4.1.0", + "System.IO.Compression": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Text.Encoding": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.IO.Compression.ZipFile.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.IO.Compression.ZipFile.dll": {} + } + }, + "System.IO.FileSystem/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Text.Encoding": "4.0.11", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.IO.FileSystem.dll": {} + } + }, + "System.IO.FileSystem.Primitives/4.0.1": { + "type": "package", + "dependencies": { + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": {} + } + }, + "System.Linq/4.1.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0" + }, + "compile": { + "ref/netstandard1.6/System.Linq.dll": {} + }, + "runtime": { + "lib/netstandard1.6/System.Linq.dll": {} + } + }, + "System.Linq.Expressions/4.1.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Linq": "4.1.0", + "System.ObjectModel": "4.0.12", + "System.Reflection": "4.1.0", + "System.Reflection.Emit": "4.0.1", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Emit.Lightweight": "4.0.1", + "System.Reflection.Extensions": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Reflection.TypeExtensions": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + }, + "compile": { + "ref/netstandard1.6/System.Linq.Expressions.dll": {} + }, + "runtime": { + "lib/netstandard1.6/System.Linq.Expressions.dll": {} + } + }, + "System.Net.Http/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.DiagnosticSource": "4.0.0", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Globalization.Extensions": "4.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.Net.Primitives": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.OpenSsl": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Security.Cryptography.X509Certificates": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11", + "runtime.native.System": "4.0.0", + "runtime.native.System.Net.Http": "4.0.1", + "runtime.native.System.Security.Cryptography": "4.0.0" + }, + "compile": { + "ref/netstandard1.3/System.Net.Http.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.6/System.Net.Http.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.3/System.Net.Http.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Net.Primitives/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1" + }, + "compile": { + "ref/netstandard1.3/System.Net.Primitives.dll": {} + } + }, + "System.Net.Sockets/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Net.Sockets.dll": {} + } + }, + "System.ObjectModel/4.0.12": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.ObjectModel.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.ObjectModel.dll": {} + } + }, + "System.Reflection/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.5/System.Reflection.dll": {} + } + }, + "System.Reflection.Emit/4.0.1": { + "type": "package", + "dependencies": { + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.1/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.dll": {} + } + }, + "System.Reflection.Emit.ILGeneration/4.0.1": { + "type": "package", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": {} + } + }, + "System.Reflection.Emit.Lightweight/4.0.1": { + "type": "package", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Reflection.Emit.ILGeneration": "4.0.1", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": {} + } + }, + "System.Reflection.Extensions/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Extensions.dll": {} + } + }, + "System.Reflection.Primitives/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/System.Reflection.Primitives.dll": {} + } + }, + "System.Reflection.TypeExtensions/4.1.0": { + "type": "package", + "dependencies": { + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.5/_._": {} + }, + "runtime": { + "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": {} + } + }, + "System.Resources.ResourceManager/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Globalization": "4.0.11", + "System.Reflection": "4.1.0", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.0/System.Resources.ResourceManager.dll": {} + } + }, + "System.Runtime/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1" + }, + "compile": { + "ref/netstandard1.5/System.Runtime.dll": {} + } + }, + "System.Runtime.Extensions/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.5/System.Runtime.Extensions.dll": {} + } + }, + "System.Runtime.Handles/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Runtime.Handles.dll": {} + } + }, + "System.Runtime.InteropServices/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Handles": "4.0.1" + }, + "compile": { + "ref/netstandard1.5/System.Runtime.InteropServices.dll": {} + } + }, + "System.Runtime.InteropServices.RuntimeInformation/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Threading": "4.0.11", + "runtime.native.System": "4.0.0" + }, + "compile": { + "ref/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Runtime.Numerics/4.0.1": { + "type": "package", + "dependencies": { + "System.Globalization": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0" + }, + "compile": { + "ref/netstandard1.1/System.Runtime.Numerics.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Runtime.Numerics.dll": {} + } + }, + "System.Security.Cryptography.Algorithms/4.2.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.IO": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11", + "runtime.native.System.Security.Cryptography": "4.0.0" + }, + "compile": { + "ref/netstandard1.6/System.Security.Cryptography.Algorithms.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.Algorithms.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Algorithms.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Cryptography.Cng/4.2.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.IO": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11" + }, + "compile": { + "ref/netstandard1.6/_._": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.Cng.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Cng.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Cryptography.Csp/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/_._": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.3/System.Security.Cryptography.Csp.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.3/System.Security.Cryptography.Csp.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Cryptography.Encoding/4.0.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Collections.Concurrent": "4.0.12", + "System.Linq": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11", + "runtime.native.System.Security.Cryptography": "4.0.0" + }, + "compile": { + "ref/netstandard1.3/System.Security.Cryptography.Encoding.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.3/System.Security.Cryptography.Encoding.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.3/System.Security.Cryptography.Encoding.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Security.Cryptography.OpenSsl/4.0.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.IO": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11", + "runtime.native.System.Security.Cryptography": "4.0.0" + }, + "compile": { + "ref/netstandard1.6/_._": {} + }, + "runtime": { + "lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll": { + "assetType": "runtime", + "rid": "unix" + } + } + }, + "System.Security.Cryptography.Primitives/4.0.0": { + "type": "package", + "dependencies": { + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Threading": "4.0.11", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Security.Cryptography.Primitives.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": {} + } + }, + "System.Security.Cryptography.X509Certificates/4.1.0": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.Globalization.Calendars": "4.0.1", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.Handles": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Runtime.Numerics": "4.0.1", + "System.Security.Cryptography.Algorithms": "4.2.0", + "System.Security.Cryptography.Cng": "4.2.0", + "System.Security.Cryptography.Csp": "4.0.0", + "System.Security.Cryptography.Encoding": "4.0.0", + "System.Security.Cryptography.OpenSsl": "4.0.0", + "System.Security.Cryptography.Primitives": "4.0.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "runtime.native.System": "4.0.0", + "runtime.native.System.Net.Http": "4.0.1", + "runtime.native.System.Security.Cryptography": "4.0.0" + }, + "compile": { + "ref/netstandard1.4/System.Security.Cryptography.X509Certificates.dll": {} + }, + "runtimeTargets": { + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.X509Certificates.dll": { + "assetType": "runtime", + "rid": "unix" + }, + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.X509Certificates.dll": { + "assetType": "runtime", + "rid": "win" + } + } + }, + "System.Text.Encoding/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Text.Encoding.dll": {} + } + }, + "System.Text.Encoding.Extensions/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0", + "System.Text.Encoding": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Text.Encoding.Extensions.dll": {} + } + }, + "System.Text.RegularExpressions/4.1.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Globalization": "4.0.11", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Threading": "4.0.11" + }, + "compile": { + "ref/netstandard1.6/System.Text.RegularExpressions.dll": {} + }, + "runtime": { + "lib/netstandard1.6/System.Text.RegularExpressions.dll": {} + } + }, + "System.Threading/4.0.11": { + "type": "package", + "dependencies": { + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Threading.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Threading.dll": {} + } + }, + "System.Threading.Tasks/4.0.11": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.3/System.Threading.Tasks.dll": {} + } + }, + "System.Threading.Tasks.Extensions/4.0.0": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Runtime": "4.1.0", + "System.Threading.Tasks": "4.0.11" + }, + "compile": { + "lib/netstandard1.0/_._": {} + }, + "runtime": { + "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": {} + } + }, + "System.Threading.Timer/4.0.1": { + "type": "package", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.NETCore.Targets": "1.0.1", + "System.Runtime": "4.1.0" + }, + "compile": { + "ref/netstandard1.2/System.Threading.Timer.dll": {} + } + }, + "System.Xml.ReaderWriter/4.0.11": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.IO.FileSystem": "4.0.1", + "System.IO.FileSystem.Primitives": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Runtime.InteropServices": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Text.Encoding.Extensions": "4.0.11", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading.Tasks": "4.0.11", + "System.Threading.Tasks.Extensions": "4.0.0" + }, + "compile": { + "ref/netstandard1.3/System.Xml.ReaderWriter.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Xml.ReaderWriter.dll": {} + } + }, + "System.Xml.XDocument/4.0.11": { + "type": "package", + "dependencies": { + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Globalization": "4.0.11", + "System.IO": "4.1.0", + "System.Reflection": "4.1.0", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime": "4.1.0", + "System.Runtime.Extensions": "4.1.0", + "System.Text.Encoding": "4.0.11", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + }, + "compile": { + "ref/netstandard1.3/System.Xml.XDocument.dll": {} + }, + "runtime": { + "lib/netstandard1.3/System.Xml.XDocument.dll": {} + } + } + } + }, + "libraries": { + "Microsoft.NETCore.Platforms/1.0.1": { + "sha512": "2G6OjjJzwBfNOO8myRV/nFrbTw5iA+DEm0N+qUqhrOmaVtn4pC77h38I1jsXGw5VH55+dPfQsqHD0We9sCl9FQ==", + "type": "package", + "path": "Microsoft.NETCore.Platforms/1.0.1", + "files": [ + "Microsoft.NETCore.Platforms.1.0.1.nupkg.sha512", + "Microsoft.NETCore.Platforms.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.json" + ] + }, + "Microsoft.NETCore.Targets/1.0.1": { + "sha512": "rkn+fKobF/cbWfnnfBOQHKVKIOpxMZBvlSHkqDWgBpwGDcLRduvs3D9OLGeV6GWGvVwNlVi2CBbTjuPmtHvyNw==", + "type": "package", + "path": "Microsoft.NETCore.Targets/1.0.1", + "files": [ + "Microsoft.NETCore.Targets.1.0.1.nupkg.sha512", + "Microsoft.NETCore.Targets.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.json" + ] + }, + "Microsoft.Win32.Primitives/4.0.1": { + "sha512": "fQnBHO9DgcmkC9dYSJoBqo6sH1VJwJprUHh8F3hbcRlxiQiBUuTntdk8tUwV490OqC2kQUrinGwZyQHTieuXRA==", + "type": "package", + "path": "Microsoft.Win32.Primitives/4.0.1", + "files": [ + "Microsoft.Win32.Primitives.4.0.1.nupkg.sha512", + "Microsoft.Win32.Primitives.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/Microsoft.Win32.Primitives.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/Microsoft.Win32.Primitives.dll", + "ref/netstandard1.3/Microsoft.Win32.Primitives.dll", + "ref/netstandard1.3/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/de/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/es/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/fr/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/it/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/ja/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/ko/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/ru/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/zh-hans/Microsoft.Win32.Primitives.xml", + "ref/netstandard1.3/zh-hant/Microsoft.Win32.Primitives.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "NETStandard.Library/1.6.0": { + "sha512": "ypsCvIdCZ4IoYASJHt6tF2fMo7N30NLgV1EbmC+snO490OMl9FvVxmumw14rhReWU3j3g7BYudG6YCrchwHJlA==", + "type": "package", + "path": "NETStandard.Library/1.6.0", + "files": [ + "NETStandard.Library.1.6.0.nupkg.sha512", + "NETStandard.Library.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt" + ] + }, + "runtime.native.System/4.0.0": { + "sha512": "QfS/nQI7k/BLgmLrw7qm7YBoULEvgWnPI+cYsbfCVFTW8Aj+i8JhccxcFMu1RWms0YZzF+UHguNBK4Qn89e2Sg==", + "type": "package", + "path": "runtime.native.System/4.0.0", + "files": [ + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.native.System.4.0.0.nupkg.sha512", + "runtime.native.System.nuspec" + ] + }, + "runtime.native.System.IO.Compression/4.1.0": { + "sha512": "Ob7nvnJBox1aaB222zSVZSkf4WrebPG4qFscfK7vmD7P7NxoSxACQLtO7ytWpqXDn2wcd/+45+EAZ7xjaPip8A==", + "type": "package", + "path": "runtime.native.System.IO.Compression/4.1.0", + "files": [ + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.native.System.IO.Compression.4.1.0.nupkg.sha512", + "runtime.native.System.IO.Compression.nuspec" + ] + }, + "runtime.native.System.Net.Http/4.0.1": { + "sha512": "Nh0UPZx2Vifh8r+J+H2jxifZUD3sBrmolgiFWJd2yiNrxO0xTa6bAw3YwRn1VOiSen/tUXMS31ttNItCZ6lKuA==", + "type": "package", + "path": "runtime.native.System.Net.Http/4.0.1", + "files": [ + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.native.System.Net.Http.4.0.1.nupkg.sha512", + "runtime.native.System.Net.Http.nuspec" + ] + }, + "runtime.native.System.Security.Cryptography/4.0.0": { + "sha512": "2CQK0jmO6Eu7ZeMgD+LOFbNJSXHFVQbCJJkEyEwowh1SCgYnrn9W9RykMfpeeVGw7h4IBvYikzpGUlmZTUafJw==", + "type": "package", + "path": "runtime.native.System.Security.Cryptography/4.0.0", + "files": [ + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/_._", + "runtime.native.System.Security.Cryptography.4.0.0.nupkg.sha512", + "runtime.native.System.Security.Cryptography.nuspec" + ] + }, + "System.AppContext/4.1.0": { + "sha512": "3QjO4jNV7PdKkmQAVp9atA+usVnKRwI3Kx1nMwJ93T0LcQfx7pKAYk0nKz5wn1oP5iqlhZuy6RXOFdhr7rDwow==", + "type": "package", + "path": "System.AppContext/4.1.0", + "files": [ + "System.AppContext.4.1.0.nupkg.sha512", + "System.AppContext.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.AppContext.dll", + "lib/net463/System.AppContext.dll", + "lib/netcore50/System.AppContext.dll", + "lib/netstandard1.6/System.AppContext.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.AppContext.dll", + "ref/net463/System.AppContext.dll", + "ref/netstandard/_._", + "ref/netstandard1.3/System.AppContext.dll", + "ref/netstandard1.3/System.AppContext.xml", + "ref/netstandard1.3/de/System.AppContext.xml", + "ref/netstandard1.3/es/System.AppContext.xml", + "ref/netstandard1.3/fr/System.AppContext.xml", + "ref/netstandard1.3/it/System.AppContext.xml", + "ref/netstandard1.3/ja/System.AppContext.xml", + "ref/netstandard1.3/ko/System.AppContext.xml", + "ref/netstandard1.3/ru/System.AppContext.xml", + "ref/netstandard1.3/zh-hans/System.AppContext.xml", + "ref/netstandard1.3/zh-hant/System.AppContext.xml", + "ref/netstandard1.6/System.AppContext.dll", + "ref/netstandard1.6/System.AppContext.xml", + "ref/netstandard1.6/de/System.AppContext.xml", + "ref/netstandard1.6/es/System.AppContext.xml", + "ref/netstandard1.6/fr/System.AppContext.xml", + "ref/netstandard1.6/it/System.AppContext.xml", + "ref/netstandard1.6/ja/System.AppContext.xml", + "ref/netstandard1.6/ko/System.AppContext.xml", + "ref/netstandard1.6/ru/System.AppContext.xml", + "ref/netstandard1.6/zh-hans/System.AppContext.xml", + "ref/netstandard1.6/zh-hant/System.AppContext.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.AppContext.dll" + ] + }, + "System.Buffers/4.0.0": { + "sha512": "msXumHfjjURSkvxUjYuq4N2ghHoRi2VpXcKMA7gK6ujQfU3vGpl+B6ld0ATRg+FZFpRyA6PgEPA+VlIkTeNf2w==", + "type": "package", + "path": "System.Buffers/4.0.0", + "files": [ + "System.Buffers.4.0.0.nupkg.sha512", + "System.Buffers.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.1/.xml", + "lib/netstandard1.1/System.Buffers.dll" + ] + }, + "System.Collections/4.0.11": { + "sha512": "YUJGz6eFKqS0V//mLt25vFGrrCvOnsXjlvFQs+KimpwNxug9x0Pzy4PlFMU3Q2IzqAa9G2L4LsK3+9vCBK7oTg==", + "type": "package", + "path": "System.Collections/4.0.11", + "files": [ + "System.Collections.4.0.11.nupkg.sha512", + "System.Collections.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Collections.dll", + "ref/netcore50/System.Collections.xml", + "ref/netcore50/de/System.Collections.xml", + "ref/netcore50/es/System.Collections.xml", + "ref/netcore50/fr/System.Collections.xml", + "ref/netcore50/it/System.Collections.xml", + "ref/netcore50/ja/System.Collections.xml", + "ref/netcore50/ko/System.Collections.xml", + "ref/netcore50/ru/System.Collections.xml", + "ref/netcore50/zh-hans/System.Collections.xml", + "ref/netcore50/zh-hant/System.Collections.xml", + "ref/netstandard1.0/System.Collections.dll", + "ref/netstandard1.0/System.Collections.xml", + "ref/netstandard1.0/de/System.Collections.xml", + "ref/netstandard1.0/es/System.Collections.xml", + "ref/netstandard1.0/fr/System.Collections.xml", + "ref/netstandard1.0/it/System.Collections.xml", + "ref/netstandard1.0/ja/System.Collections.xml", + "ref/netstandard1.0/ko/System.Collections.xml", + "ref/netstandard1.0/ru/System.Collections.xml", + "ref/netstandard1.0/zh-hans/System.Collections.xml", + "ref/netstandard1.0/zh-hant/System.Collections.xml", + "ref/netstandard1.3/System.Collections.dll", + "ref/netstandard1.3/System.Collections.xml", + "ref/netstandard1.3/de/System.Collections.xml", + "ref/netstandard1.3/es/System.Collections.xml", + "ref/netstandard1.3/fr/System.Collections.xml", + "ref/netstandard1.3/it/System.Collections.xml", + "ref/netstandard1.3/ja/System.Collections.xml", + "ref/netstandard1.3/ko/System.Collections.xml", + "ref/netstandard1.3/ru/System.Collections.xml", + "ref/netstandard1.3/zh-hans/System.Collections.xml", + "ref/netstandard1.3/zh-hant/System.Collections.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Collections.Concurrent/4.0.12": { + "sha512": "2gBcbb3drMLgxlI0fBfxMA31ec6AEyYCHygGse4vxceJan8mRIWeKJ24BFzN7+bi/NFTgdIgufzb94LWO5EERQ==", + "type": "package", + "path": "System.Collections.Concurrent/4.0.12", + "files": [ + "System.Collections.Concurrent.4.0.12.nupkg.sha512", + "System.Collections.Concurrent.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Collections.Concurrent.dll", + "lib/netstandard1.3/System.Collections.Concurrent.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Collections.Concurrent.dll", + "ref/netcore50/System.Collections.Concurrent.xml", + "ref/netcore50/de/System.Collections.Concurrent.xml", + "ref/netcore50/es/System.Collections.Concurrent.xml", + "ref/netcore50/fr/System.Collections.Concurrent.xml", + "ref/netcore50/it/System.Collections.Concurrent.xml", + "ref/netcore50/ja/System.Collections.Concurrent.xml", + "ref/netcore50/ko/System.Collections.Concurrent.xml", + "ref/netcore50/ru/System.Collections.Concurrent.xml", + "ref/netcore50/zh-hans/System.Collections.Concurrent.xml", + "ref/netcore50/zh-hant/System.Collections.Concurrent.xml", + "ref/netstandard1.1/System.Collections.Concurrent.dll", + "ref/netstandard1.1/System.Collections.Concurrent.xml", + "ref/netstandard1.1/de/System.Collections.Concurrent.xml", + "ref/netstandard1.1/es/System.Collections.Concurrent.xml", + "ref/netstandard1.1/fr/System.Collections.Concurrent.xml", + "ref/netstandard1.1/it/System.Collections.Concurrent.xml", + "ref/netstandard1.1/ja/System.Collections.Concurrent.xml", + "ref/netstandard1.1/ko/System.Collections.Concurrent.xml", + "ref/netstandard1.1/ru/System.Collections.Concurrent.xml", + "ref/netstandard1.1/zh-hans/System.Collections.Concurrent.xml", + "ref/netstandard1.1/zh-hant/System.Collections.Concurrent.xml", + "ref/netstandard1.3/System.Collections.Concurrent.dll", + "ref/netstandard1.3/System.Collections.Concurrent.xml", + "ref/netstandard1.3/de/System.Collections.Concurrent.xml", + "ref/netstandard1.3/es/System.Collections.Concurrent.xml", + "ref/netstandard1.3/fr/System.Collections.Concurrent.xml", + "ref/netstandard1.3/it/System.Collections.Concurrent.xml", + "ref/netstandard1.3/ja/System.Collections.Concurrent.xml", + "ref/netstandard1.3/ko/System.Collections.Concurrent.xml", + "ref/netstandard1.3/ru/System.Collections.Concurrent.xml", + "ref/netstandard1.3/zh-hans/System.Collections.Concurrent.xml", + "ref/netstandard1.3/zh-hant/System.Collections.Concurrent.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Console/4.0.0": { + "sha512": "qSKUSOIiYA/a0g5XXdxFcUFmv1hNICBD7QZ0QhGYVipPIhvpiydY8VZqr1thmCXvmn8aipMg64zuanB4eotK9A==", + "type": "package", + "path": "System.Console/4.0.0", + "files": [ + "System.Console.4.0.0.nupkg.sha512", + "System.Console.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Console.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Console.dll", + "ref/netstandard1.3/System.Console.dll", + "ref/netstandard1.3/System.Console.xml", + "ref/netstandard1.3/de/System.Console.xml", + "ref/netstandard1.3/es/System.Console.xml", + "ref/netstandard1.3/fr/System.Console.xml", + "ref/netstandard1.3/it/System.Console.xml", + "ref/netstandard1.3/ja/System.Console.xml", + "ref/netstandard1.3/ko/System.Console.xml", + "ref/netstandard1.3/ru/System.Console.xml", + "ref/netstandard1.3/zh-hans/System.Console.xml", + "ref/netstandard1.3/zh-hant/System.Console.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Diagnostics.Debug/4.0.11": { + "sha512": "w5U95fVKHY4G8ASs/K5iK3J5LY+/dLFd4vKejsnI/ZhBsWS9hQakfx3Zr7lRWKg4tAw9r4iktyvsTagWkqYCiw==", + "type": "package", + "path": "System.Diagnostics.Debug/4.0.11", + "files": [ + "System.Diagnostics.Debug.4.0.11.nupkg.sha512", + "System.Diagnostics.Debug.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Diagnostics.Debug.dll", + "ref/netcore50/System.Diagnostics.Debug.xml", + "ref/netcore50/de/System.Diagnostics.Debug.xml", + "ref/netcore50/es/System.Diagnostics.Debug.xml", + "ref/netcore50/fr/System.Diagnostics.Debug.xml", + "ref/netcore50/it/System.Diagnostics.Debug.xml", + "ref/netcore50/ja/System.Diagnostics.Debug.xml", + "ref/netcore50/ko/System.Diagnostics.Debug.xml", + "ref/netcore50/ru/System.Diagnostics.Debug.xml", + "ref/netcore50/zh-hans/System.Diagnostics.Debug.xml", + "ref/netcore50/zh-hant/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/System.Diagnostics.Debug.dll", + "ref/netstandard1.0/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/de/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/es/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/fr/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/it/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/ja/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/ko/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/ru/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/zh-hans/System.Diagnostics.Debug.xml", + "ref/netstandard1.0/zh-hant/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/System.Diagnostics.Debug.dll", + "ref/netstandard1.3/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/de/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/es/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/fr/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/it/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/ja/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/ko/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/ru/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/zh-hans/System.Diagnostics.Debug.xml", + "ref/netstandard1.3/zh-hant/System.Diagnostics.Debug.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Diagnostics.DiagnosticSource/4.0.0": { + "sha512": "YKglnq4BMTJxfcr6nuT08g+yJ0UxdePIHxosiLuljuHIUR6t4KhFsyaHOaOc1Ofqp0PUvJ0EmcgiEz6T7vEx3w==", + "type": "package", + "path": "System.Diagnostics.DiagnosticSource/4.0.0", + "files": [ + "System.Diagnostics.DiagnosticSource.4.0.0.nupkg.sha512", + "System.Diagnostics.DiagnosticSource.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/net46/System.Diagnostics.DiagnosticSource.dll", + "lib/net46/System.Diagnostics.DiagnosticSource.xml", + "lib/netstandard1.1/System.Diagnostics.DiagnosticSource.dll", + "lib/netstandard1.1/System.Diagnostics.DiagnosticSource.xml", + "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll", + "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.xml", + "lib/portable-net45+win8+wpa81/System.Diagnostics.DiagnosticSource.dll", + "lib/portable-net45+win8+wpa81/System.Diagnostics.DiagnosticSource.xml" + ] + }, + "System.Diagnostics.Tools/4.0.1": { + "sha512": "xBfJ8pnd4C17dWaC9FM6aShzbJcRNMChUMD42I6772KGGrqaFdumwhn9OdM68erj1ueNo3xdQ1EwiFjK5k8p0g==", + "type": "package", + "path": "System.Diagnostics.Tools/4.0.1", + "files": [ + "System.Diagnostics.Tools.4.0.1.nupkg.sha512", + "System.Diagnostics.Tools.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Diagnostics.Tools.dll", + "ref/netcore50/System.Diagnostics.Tools.xml", + "ref/netcore50/de/System.Diagnostics.Tools.xml", + "ref/netcore50/es/System.Diagnostics.Tools.xml", + "ref/netcore50/fr/System.Diagnostics.Tools.xml", + "ref/netcore50/it/System.Diagnostics.Tools.xml", + "ref/netcore50/ja/System.Diagnostics.Tools.xml", + "ref/netcore50/ko/System.Diagnostics.Tools.xml", + "ref/netcore50/ru/System.Diagnostics.Tools.xml", + "ref/netcore50/zh-hans/System.Diagnostics.Tools.xml", + "ref/netcore50/zh-hant/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/System.Diagnostics.Tools.dll", + "ref/netstandard1.0/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/de/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/es/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/fr/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/it/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/ja/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/ko/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/ru/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/zh-hans/System.Diagnostics.Tools.xml", + "ref/netstandard1.0/zh-hant/System.Diagnostics.Tools.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Diagnostics.Tracing/4.1.0": { + "sha512": "vDN1PoMZCkkdNjvZLql592oYJZgS7URcJzJ7bxeBgGtx5UtR5leNm49VmfHGqIffX4FKacHbI3H6UyNSHQknBg==", + "type": "package", + "path": "System.Diagnostics.Tracing/4.1.0", + "files": [ + "System.Diagnostics.Tracing.4.1.0.nupkg.sha512", + "System.Diagnostics.Tracing.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Diagnostics.Tracing.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Diagnostics.Tracing.dll", + "ref/netcore50/System.Diagnostics.Tracing.dll", + "ref/netcore50/System.Diagnostics.Tracing.xml", + "ref/netcore50/de/System.Diagnostics.Tracing.xml", + "ref/netcore50/es/System.Diagnostics.Tracing.xml", + "ref/netcore50/fr/System.Diagnostics.Tracing.xml", + "ref/netcore50/it/System.Diagnostics.Tracing.xml", + "ref/netcore50/ja/System.Diagnostics.Tracing.xml", + "ref/netcore50/ko/System.Diagnostics.Tracing.xml", + "ref/netcore50/ru/System.Diagnostics.Tracing.xml", + "ref/netcore50/zh-hans/System.Diagnostics.Tracing.xml", + "ref/netcore50/zh-hant/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/System.Diagnostics.Tracing.dll", + "ref/netstandard1.1/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/de/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/es/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/fr/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/it/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/ja/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/ko/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/ru/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/zh-hans/System.Diagnostics.Tracing.xml", + "ref/netstandard1.1/zh-hant/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/System.Diagnostics.Tracing.dll", + "ref/netstandard1.2/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/de/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/es/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/fr/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/it/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/ja/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/ko/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/ru/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/zh-hans/System.Diagnostics.Tracing.xml", + "ref/netstandard1.2/zh-hant/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/System.Diagnostics.Tracing.dll", + "ref/netstandard1.3/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/de/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/es/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/fr/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/it/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/ja/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/ko/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/ru/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/zh-hans/System.Diagnostics.Tracing.xml", + "ref/netstandard1.3/zh-hant/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/System.Diagnostics.Tracing.dll", + "ref/netstandard1.5/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/de/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/es/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/fr/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/it/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/ja/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/ko/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/ru/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/zh-hans/System.Diagnostics.Tracing.xml", + "ref/netstandard1.5/zh-hant/System.Diagnostics.Tracing.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Globalization/4.0.11": { + "sha512": "B95h0YLEL2oSnwF/XjqSWKnwKOy/01VWkNlsCeMTFJLLabflpGV26nK164eRs5GiaRSBGpOxQ3pKoSnnyZN5pg==", + "type": "package", + "path": "System.Globalization/4.0.11", + "files": [ + "System.Globalization.4.0.11.nupkg.sha512", + "System.Globalization.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Globalization.dll", + "ref/netcore50/System.Globalization.xml", + "ref/netcore50/de/System.Globalization.xml", + "ref/netcore50/es/System.Globalization.xml", + "ref/netcore50/fr/System.Globalization.xml", + "ref/netcore50/it/System.Globalization.xml", + "ref/netcore50/ja/System.Globalization.xml", + "ref/netcore50/ko/System.Globalization.xml", + "ref/netcore50/ru/System.Globalization.xml", + "ref/netcore50/zh-hans/System.Globalization.xml", + "ref/netcore50/zh-hant/System.Globalization.xml", + "ref/netstandard1.0/System.Globalization.dll", + "ref/netstandard1.0/System.Globalization.xml", + "ref/netstandard1.0/de/System.Globalization.xml", + "ref/netstandard1.0/es/System.Globalization.xml", + "ref/netstandard1.0/fr/System.Globalization.xml", + "ref/netstandard1.0/it/System.Globalization.xml", + "ref/netstandard1.0/ja/System.Globalization.xml", + "ref/netstandard1.0/ko/System.Globalization.xml", + "ref/netstandard1.0/ru/System.Globalization.xml", + "ref/netstandard1.0/zh-hans/System.Globalization.xml", + "ref/netstandard1.0/zh-hant/System.Globalization.xml", + "ref/netstandard1.3/System.Globalization.dll", + "ref/netstandard1.3/System.Globalization.xml", + "ref/netstandard1.3/de/System.Globalization.xml", + "ref/netstandard1.3/es/System.Globalization.xml", + "ref/netstandard1.3/fr/System.Globalization.xml", + "ref/netstandard1.3/it/System.Globalization.xml", + "ref/netstandard1.3/ja/System.Globalization.xml", + "ref/netstandard1.3/ko/System.Globalization.xml", + "ref/netstandard1.3/ru/System.Globalization.xml", + "ref/netstandard1.3/zh-hans/System.Globalization.xml", + "ref/netstandard1.3/zh-hant/System.Globalization.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Globalization.Calendars/4.0.1": { + "sha512": "L1c6IqeQ88vuzC1P81JeHmHA8mxq8a18NUBNXnIY/BVb+TCyAaGIFbhpZt60h9FJNmisymoQkHEFSE9Vslja1Q==", + "type": "package", + "path": "System.Globalization.Calendars/4.0.1", + "files": [ + "System.Globalization.Calendars.4.0.1.nupkg.sha512", + "System.Globalization.Calendars.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Globalization.Calendars.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Globalization.Calendars.dll", + "ref/netstandard1.3/System.Globalization.Calendars.dll", + "ref/netstandard1.3/System.Globalization.Calendars.xml", + "ref/netstandard1.3/de/System.Globalization.Calendars.xml", + "ref/netstandard1.3/es/System.Globalization.Calendars.xml", + "ref/netstandard1.3/fr/System.Globalization.Calendars.xml", + "ref/netstandard1.3/it/System.Globalization.Calendars.xml", + "ref/netstandard1.3/ja/System.Globalization.Calendars.xml", + "ref/netstandard1.3/ko/System.Globalization.Calendars.xml", + "ref/netstandard1.3/ru/System.Globalization.Calendars.xml", + "ref/netstandard1.3/zh-hans/System.Globalization.Calendars.xml", + "ref/netstandard1.3/zh-hant/System.Globalization.Calendars.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Globalization.Extensions/4.0.1": { + "sha512": "KKo23iKeOaIg61SSXwjANN7QYDr/3op3OWGGzDzz7mypx0Za0fZSeG0l6cco8Ntp8YMYkIQcAqlk8yhm5/Uhcg==", + "type": "package", + "path": "System.Globalization.Extensions/4.0.1", + "files": [ + "System.Globalization.Extensions.4.0.1.nupkg.sha512", + "System.Globalization.Extensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Globalization.Extensions.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Globalization.Extensions.dll", + "ref/netstandard1.3/System.Globalization.Extensions.dll", + "ref/netstandard1.3/System.Globalization.Extensions.xml", + "ref/netstandard1.3/de/System.Globalization.Extensions.xml", + "ref/netstandard1.3/es/System.Globalization.Extensions.xml", + "ref/netstandard1.3/fr/System.Globalization.Extensions.xml", + "ref/netstandard1.3/it/System.Globalization.Extensions.xml", + "ref/netstandard1.3/ja/System.Globalization.Extensions.xml", + "ref/netstandard1.3/ko/System.Globalization.Extensions.xml", + "ref/netstandard1.3/ru/System.Globalization.Extensions.xml", + "ref/netstandard1.3/zh-hans/System.Globalization.Extensions.xml", + "ref/netstandard1.3/zh-hant/System.Globalization.Extensions.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.3/System.Globalization.Extensions.dll", + "runtimes/win/lib/net46/System.Globalization.Extensions.dll", + "runtimes/win/lib/netstandard1.3/System.Globalization.Extensions.dll" + ] + }, + "System.IO/4.1.0": { + "sha512": "3KlTJceQc3gnGIaHZ7UBZO26SHL1SHE4ddrmiwumFnId+CEHP+O8r386tZKaE6zlk5/mF8vifMBzHj9SaXN+mQ==", + "type": "package", + "path": "System.IO/4.1.0", + "files": [ + "System.IO.4.1.0.nupkg.sha512", + "System.IO.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.IO.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.IO.dll", + "ref/netcore50/System.IO.dll", + "ref/netcore50/System.IO.xml", + "ref/netcore50/de/System.IO.xml", + "ref/netcore50/es/System.IO.xml", + "ref/netcore50/fr/System.IO.xml", + "ref/netcore50/it/System.IO.xml", + "ref/netcore50/ja/System.IO.xml", + "ref/netcore50/ko/System.IO.xml", + "ref/netcore50/ru/System.IO.xml", + "ref/netcore50/zh-hans/System.IO.xml", + "ref/netcore50/zh-hant/System.IO.xml", + "ref/netstandard1.0/System.IO.dll", + "ref/netstandard1.0/System.IO.xml", + "ref/netstandard1.0/de/System.IO.xml", + "ref/netstandard1.0/es/System.IO.xml", + "ref/netstandard1.0/fr/System.IO.xml", + "ref/netstandard1.0/it/System.IO.xml", + "ref/netstandard1.0/ja/System.IO.xml", + "ref/netstandard1.0/ko/System.IO.xml", + "ref/netstandard1.0/ru/System.IO.xml", + "ref/netstandard1.0/zh-hans/System.IO.xml", + "ref/netstandard1.0/zh-hant/System.IO.xml", + "ref/netstandard1.3/System.IO.dll", + "ref/netstandard1.3/System.IO.xml", + "ref/netstandard1.3/de/System.IO.xml", + "ref/netstandard1.3/es/System.IO.xml", + "ref/netstandard1.3/fr/System.IO.xml", + "ref/netstandard1.3/it/System.IO.xml", + "ref/netstandard1.3/ja/System.IO.xml", + "ref/netstandard1.3/ko/System.IO.xml", + "ref/netstandard1.3/ru/System.IO.xml", + "ref/netstandard1.3/zh-hans/System.IO.xml", + "ref/netstandard1.3/zh-hant/System.IO.xml", + "ref/netstandard1.5/System.IO.dll", + "ref/netstandard1.5/System.IO.xml", + "ref/netstandard1.5/de/System.IO.xml", + "ref/netstandard1.5/es/System.IO.xml", + "ref/netstandard1.5/fr/System.IO.xml", + "ref/netstandard1.5/it/System.IO.xml", + "ref/netstandard1.5/ja/System.IO.xml", + "ref/netstandard1.5/ko/System.IO.xml", + "ref/netstandard1.5/ru/System.IO.xml", + "ref/netstandard1.5/zh-hans/System.IO.xml", + "ref/netstandard1.5/zh-hant/System.IO.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.IO.Compression/4.1.0": { + "sha512": "TjnBS6eztThSzeSib+WyVbLzEdLKUcEHN69VtS3u8aAsSc18FU6xCZlNWWsEd8SKcXAE+y1sOu7VbU8sUeM0sg==", + "type": "package", + "path": "System.IO.Compression/4.1.0", + "files": [ + "System.IO.Compression.4.1.0.nupkg.sha512", + "System.IO.Compression.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net46/System.IO.Compression.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net46/System.IO.Compression.dll", + "ref/netcore50/System.IO.Compression.dll", + "ref/netcore50/System.IO.Compression.xml", + "ref/netcore50/de/System.IO.Compression.xml", + "ref/netcore50/es/System.IO.Compression.xml", + "ref/netcore50/fr/System.IO.Compression.xml", + "ref/netcore50/it/System.IO.Compression.xml", + "ref/netcore50/ja/System.IO.Compression.xml", + "ref/netcore50/ko/System.IO.Compression.xml", + "ref/netcore50/ru/System.IO.Compression.xml", + "ref/netcore50/zh-hans/System.IO.Compression.xml", + "ref/netcore50/zh-hant/System.IO.Compression.xml", + "ref/netstandard1.1/System.IO.Compression.dll", + "ref/netstandard1.1/System.IO.Compression.xml", + "ref/netstandard1.1/de/System.IO.Compression.xml", + "ref/netstandard1.1/es/System.IO.Compression.xml", + "ref/netstandard1.1/fr/System.IO.Compression.xml", + "ref/netstandard1.1/it/System.IO.Compression.xml", + "ref/netstandard1.1/ja/System.IO.Compression.xml", + "ref/netstandard1.1/ko/System.IO.Compression.xml", + "ref/netstandard1.1/ru/System.IO.Compression.xml", + "ref/netstandard1.1/zh-hans/System.IO.Compression.xml", + "ref/netstandard1.1/zh-hant/System.IO.Compression.xml", + "ref/netstandard1.3/System.IO.Compression.dll", + "ref/netstandard1.3/System.IO.Compression.xml", + "ref/netstandard1.3/de/System.IO.Compression.xml", + "ref/netstandard1.3/es/System.IO.Compression.xml", + "ref/netstandard1.3/fr/System.IO.Compression.xml", + "ref/netstandard1.3/it/System.IO.Compression.xml", + "ref/netstandard1.3/ja/System.IO.Compression.xml", + "ref/netstandard1.3/ko/System.IO.Compression.xml", + "ref/netstandard1.3/ru/System.IO.Compression.xml", + "ref/netstandard1.3/zh-hans/System.IO.Compression.xml", + "ref/netstandard1.3/zh-hant/System.IO.Compression.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.3/System.IO.Compression.dll", + "runtimes/win/lib/net46/System.IO.Compression.dll", + "runtimes/win/lib/netstandard1.3/System.IO.Compression.dll" + ] + }, + "System.IO.Compression.ZipFile/4.0.1": { + "sha512": "hBQYJzfTbQURF10nLhd+az2NHxsU6MU7AB8RUf4IolBP5lOAm4Luho851xl+CqslmhI5ZH/el8BlngEk4lBkaQ==", + "type": "package", + "path": "System.IO.Compression.ZipFile/4.0.1", + "files": [ + "System.IO.Compression.ZipFile.4.0.1.nupkg.sha512", + "System.IO.Compression.ZipFile.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.Compression.ZipFile.dll", + "lib/netstandard1.3/System.IO.Compression.ZipFile.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.Compression.ZipFile.dll", + "ref/netstandard1.3/System.IO.Compression.ZipFile.dll", + "ref/netstandard1.3/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/de/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/es/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/fr/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/it/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/ja/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/ko/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/ru/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/zh-hans/System.IO.Compression.ZipFile.xml", + "ref/netstandard1.3/zh-hant/System.IO.Compression.ZipFile.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.IO.FileSystem/4.0.1": { + "sha512": "IBErlVq5jOggAD69bg1t0pJcHaDbJbWNUZTPI96fkYWzwYbN6D9wRHMULLDd9dHsl7C2YsxXL31LMfPI1SWt8w==", + "type": "package", + "path": "System.IO.FileSystem/4.0.1", + "files": [ + "System.IO.FileSystem.4.0.1.nupkg.sha512", + "System.IO.FileSystem.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.dll", + "ref/netstandard1.3/System.IO.FileSystem.dll", + "ref/netstandard1.3/System.IO.FileSystem.xml", + "ref/netstandard1.3/de/System.IO.FileSystem.xml", + "ref/netstandard1.3/es/System.IO.FileSystem.xml", + "ref/netstandard1.3/fr/System.IO.FileSystem.xml", + "ref/netstandard1.3/it/System.IO.FileSystem.xml", + "ref/netstandard1.3/ja/System.IO.FileSystem.xml", + "ref/netstandard1.3/ko/System.IO.FileSystem.xml", + "ref/netstandard1.3/ru/System.IO.FileSystem.xml", + "ref/netstandard1.3/zh-hans/System.IO.FileSystem.xml", + "ref/netstandard1.3/zh-hant/System.IO.FileSystem.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.IO.FileSystem.Primitives/4.0.1": { + "sha512": "kWkKD203JJKxJeE74p8aF8y4Qc9r9WQx4C0cHzHPrY3fv/L/IhWnyCHaFJ3H1QPOH6A93whlQ2vG5nHlBDvzWQ==", + "type": "package", + "path": "System.IO.FileSystem.Primitives/4.0.1", + "files": [ + "System.IO.FileSystem.Primitives.4.0.1.nupkg.sha512", + "System.IO.FileSystem.Primitives.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.Primitives.dll", + "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.Primitives.dll", + "ref/netstandard1.3/System.IO.FileSystem.Primitives.dll", + "ref/netstandard1.3/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/de/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/es/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/fr/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/it/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/ja/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/ko/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/ru/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/zh-hans/System.IO.FileSystem.Primitives.xml", + "ref/netstandard1.3/zh-hant/System.IO.FileSystem.Primitives.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Linq/4.1.0": { + "sha512": "bQ0iYFOQI0nuTnt+NQADns6ucV4DUvMdwN6CbkB1yj8i7arTGiTN5eok1kQwdnnNWSDZfIUySQY+J3d5KjWn0g==", + "type": "package", + "path": "System.Linq/4.1.0", + "files": [ + "System.Linq.4.1.0.nupkg.sha512", + "System.Linq.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net463/System.Linq.dll", + "lib/netcore50/System.Linq.dll", + "lib/netstandard1.6/System.Linq.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net463/System.Linq.dll", + "ref/netcore50/System.Linq.dll", + "ref/netcore50/System.Linq.xml", + "ref/netcore50/de/System.Linq.xml", + "ref/netcore50/es/System.Linq.xml", + "ref/netcore50/fr/System.Linq.xml", + "ref/netcore50/it/System.Linq.xml", + "ref/netcore50/ja/System.Linq.xml", + "ref/netcore50/ko/System.Linq.xml", + "ref/netcore50/ru/System.Linq.xml", + "ref/netcore50/zh-hans/System.Linq.xml", + "ref/netcore50/zh-hant/System.Linq.xml", + "ref/netstandard1.0/System.Linq.dll", + "ref/netstandard1.0/System.Linq.xml", + "ref/netstandard1.0/de/System.Linq.xml", + "ref/netstandard1.0/es/System.Linq.xml", + "ref/netstandard1.0/fr/System.Linq.xml", + "ref/netstandard1.0/it/System.Linq.xml", + "ref/netstandard1.0/ja/System.Linq.xml", + "ref/netstandard1.0/ko/System.Linq.xml", + "ref/netstandard1.0/ru/System.Linq.xml", + "ref/netstandard1.0/zh-hans/System.Linq.xml", + "ref/netstandard1.0/zh-hant/System.Linq.xml", + "ref/netstandard1.6/System.Linq.dll", + "ref/netstandard1.6/System.Linq.xml", + "ref/netstandard1.6/de/System.Linq.xml", + "ref/netstandard1.6/es/System.Linq.xml", + "ref/netstandard1.6/fr/System.Linq.xml", + "ref/netstandard1.6/it/System.Linq.xml", + "ref/netstandard1.6/ja/System.Linq.xml", + "ref/netstandard1.6/ko/System.Linq.xml", + "ref/netstandard1.6/ru/System.Linq.xml", + "ref/netstandard1.6/zh-hans/System.Linq.xml", + "ref/netstandard1.6/zh-hant/System.Linq.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Linq.Expressions/4.1.0": { + "sha512": "I+y02iqkgmCAyfbqOmSDOgqdZQ5tTj80Akm5BPSS8EeB0VGWdy6X1KCoYe8Pk6pwDoAKZUOdLVxnTJcExiv5zw==", + "type": "package", + "path": "System.Linq.Expressions/4.1.0", + "files": [ + "System.Linq.Expressions.4.1.0.nupkg.sha512", + "System.Linq.Expressions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net463/System.Linq.Expressions.dll", + "lib/netcore50/System.Linq.Expressions.dll", + "lib/netstandard1.6/System.Linq.Expressions.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net463/System.Linq.Expressions.dll", + "ref/netcore50/System.Linq.Expressions.dll", + "ref/netcore50/System.Linq.Expressions.xml", + "ref/netcore50/de/System.Linq.Expressions.xml", + "ref/netcore50/es/System.Linq.Expressions.xml", + "ref/netcore50/fr/System.Linq.Expressions.xml", + "ref/netcore50/it/System.Linq.Expressions.xml", + "ref/netcore50/ja/System.Linq.Expressions.xml", + "ref/netcore50/ko/System.Linq.Expressions.xml", + "ref/netcore50/ru/System.Linq.Expressions.xml", + "ref/netcore50/zh-hans/System.Linq.Expressions.xml", + "ref/netcore50/zh-hant/System.Linq.Expressions.xml", + "ref/netstandard1.0/System.Linq.Expressions.dll", + "ref/netstandard1.0/System.Linq.Expressions.xml", + "ref/netstandard1.0/de/System.Linq.Expressions.xml", + "ref/netstandard1.0/es/System.Linq.Expressions.xml", + "ref/netstandard1.0/fr/System.Linq.Expressions.xml", + "ref/netstandard1.0/it/System.Linq.Expressions.xml", + "ref/netstandard1.0/ja/System.Linq.Expressions.xml", + "ref/netstandard1.0/ko/System.Linq.Expressions.xml", + "ref/netstandard1.0/ru/System.Linq.Expressions.xml", + "ref/netstandard1.0/zh-hans/System.Linq.Expressions.xml", + "ref/netstandard1.0/zh-hant/System.Linq.Expressions.xml", + "ref/netstandard1.3/System.Linq.Expressions.dll", + "ref/netstandard1.3/System.Linq.Expressions.xml", + "ref/netstandard1.3/de/System.Linq.Expressions.xml", + "ref/netstandard1.3/es/System.Linq.Expressions.xml", + "ref/netstandard1.3/fr/System.Linq.Expressions.xml", + "ref/netstandard1.3/it/System.Linq.Expressions.xml", + "ref/netstandard1.3/ja/System.Linq.Expressions.xml", + "ref/netstandard1.3/ko/System.Linq.Expressions.xml", + "ref/netstandard1.3/ru/System.Linq.Expressions.xml", + "ref/netstandard1.3/zh-hans/System.Linq.Expressions.xml", + "ref/netstandard1.3/zh-hant/System.Linq.Expressions.xml", + "ref/netstandard1.6/System.Linq.Expressions.dll", + "ref/netstandard1.6/System.Linq.Expressions.xml", + "ref/netstandard1.6/de/System.Linq.Expressions.xml", + "ref/netstandard1.6/es/System.Linq.Expressions.xml", + "ref/netstandard1.6/fr/System.Linq.Expressions.xml", + "ref/netstandard1.6/it/System.Linq.Expressions.xml", + "ref/netstandard1.6/ja/System.Linq.Expressions.xml", + "ref/netstandard1.6/ko/System.Linq.Expressions.xml", + "ref/netstandard1.6/ru/System.Linq.Expressions.xml", + "ref/netstandard1.6/zh-hans/System.Linq.Expressions.xml", + "ref/netstandard1.6/zh-hant/System.Linq.Expressions.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Linq.Expressions.dll" + ] + }, + "System.Net.Http/4.1.0": { + "sha512": "ULq9g3SOPVuupt+Y3U+A37coXzdNisB1neFCSKzBwo182u0RDddKJF8I5+HfyXqK6OhJPgeoAwWXrbiUXuRDsg==", + "type": "package", + "path": "System.Net.Http/4.1.0", + "files": [ + "System.Net.Http.4.1.0.nupkg.sha512", + "System.Net.Http.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/Xamarinmac20/_._", + "lib/monoandroid10/_._", + "lib/monotouch10/_._", + "lib/net45/_._", + "lib/net46/System.Net.Http.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/Xamarinmac20/_._", + "ref/monoandroid10/_._", + "ref/monotouch10/_._", + "ref/net45/_._", + "ref/net46/System.Net.Http.dll", + "ref/net46/System.Net.Http.xml", + "ref/net46/de/System.Net.Http.xml", + "ref/net46/es/System.Net.Http.xml", + "ref/net46/fr/System.Net.Http.xml", + "ref/net46/it/System.Net.Http.xml", + "ref/net46/ja/System.Net.Http.xml", + "ref/net46/ko/System.Net.Http.xml", + "ref/net46/ru/System.Net.Http.xml", + "ref/net46/zh-hans/System.Net.Http.xml", + "ref/net46/zh-hant/System.Net.Http.xml", + "ref/netcore50/System.Net.Http.dll", + "ref/netcore50/System.Net.Http.xml", + "ref/netcore50/de/System.Net.Http.xml", + "ref/netcore50/es/System.Net.Http.xml", + "ref/netcore50/fr/System.Net.Http.xml", + "ref/netcore50/it/System.Net.Http.xml", + "ref/netcore50/ja/System.Net.Http.xml", + "ref/netcore50/ko/System.Net.Http.xml", + "ref/netcore50/ru/System.Net.Http.xml", + "ref/netcore50/zh-hans/System.Net.Http.xml", + "ref/netcore50/zh-hant/System.Net.Http.xml", + "ref/netstandard1.1/System.Net.Http.dll", + "ref/netstandard1.1/System.Net.Http.xml", + "ref/netstandard1.1/de/System.Net.Http.xml", + "ref/netstandard1.1/es/System.Net.Http.xml", + "ref/netstandard1.1/fr/System.Net.Http.xml", + "ref/netstandard1.1/it/System.Net.Http.xml", + "ref/netstandard1.1/ja/System.Net.Http.xml", + "ref/netstandard1.1/ko/System.Net.Http.xml", + "ref/netstandard1.1/ru/System.Net.Http.xml", + "ref/netstandard1.1/zh-hans/System.Net.Http.xml", + "ref/netstandard1.1/zh-hant/System.Net.Http.xml", + "ref/netstandard1.3/System.Net.Http.dll", + "ref/netstandard1.3/System.Net.Http.xml", + "ref/netstandard1.3/de/System.Net.Http.xml", + "ref/netstandard1.3/es/System.Net.Http.xml", + "ref/netstandard1.3/fr/System.Net.Http.xml", + "ref/netstandard1.3/it/System.Net.Http.xml", + "ref/netstandard1.3/ja/System.Net.Http.xml", + "ref/netstandard1.3/ko/System.Net.Http.xml", + "ref/netstandard1.3/ru/System.Net.Http.xml", + "ref/netstandard1.3/zh-hans/System.Net.Http.xml", + "ref/netstandard1.3/zh-hant/System.Net.Http.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.6/System.Net.Http.dll", + "runtimes/win/lib/net46/System.Net.Http.dll", + "runtimes/win/lib/netcore50/System.Net.Http.dll", + "runtimes/win/lib/netstandard1.3/System.Net.Http.dll" + ] + }, + "System.Net.Primitives/4.0.11": { + "sha512": "hVvfl4405DRjA2408luZekbPhplJK03j2Y2lSfMlny7GHXlkByw1iLnc9mgKW0GdQn73vvMcWrWewAhylXA4Nw==", + "type": "package", + "path": "System.Net.Primitives/4.0.11", + "files": [ + "System.Net.Primitives.4.0.11.nupkg.sha512", + "System.Net.Primitives.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Net.Primitives.dll", + "ref/netcore50/System.Net.Primitives.xml", + "ref/netcore50/de/System.Net.Primitives.xml", + "ref/netcore50/es/System.Net.Primitives.xml", + "ref/netcore50/fr/System.Net.Primitives.xml", + "ref/netcore50/it/System.Net.Primitives.xml", + "ref/netcore50/ja/System.Net.Primitives.xml", + "ref/netcore50/ko/System.Net.Primitives.xml", + "ref/netcore50/ru/System.Net.Primitives.xml", + "ref/netcore50/zh-hans/System.Net.Primitives.xml", + "ref/netcore50/zh-hant/System.Net.Primitives.xml", + "ref/netstandard1.0/System.Net.Primitives.dll", + "ref/netstandard1.0/System.Net.Primitives.xml", + "ref/netstandard1.0/de/System.Net.Primitives.xml", + "ref/netstandard1.0/es/System.Net.Primitives.xml", + "ref/netstandard1.0/fr/System.Net.Primitives.xml", + "ref/netstandard1.0/it/System.Net.Primitives.xml", + "ref/netstandard1.0/ja/System.Net.Primitives.xml", + "ref/netstandard1.0/ko/System.Net.Primitives.xml", + "ref/netstandard1.0/ru/System.Net.Primitives.xml", + "ref/netstandard1.0/zh-hans/System.Net.Primitives.xml", + "ref/netstandard1.0/zh-hant/System.Net.Primitives.xml", + "ref/netstandard1.1/System.Net.Primitives.dll", + "ref/netstandard1.1/System.Net.Primitives.xml", + "ref/netstandard1.1/de/System.Net.Primitives.xml", + "ref/netstandard1.1/es/System.Net.Primitives.xml", + "ref/netstandard1.1/fr/System.Net.Primitives.xml", + "ref/netstandard1.1/it/System.Net.Primitives.xml", + "ref/netstandard1.1/ja/System.Net.Primitives.xml", + "ref/netstandard1.1/ko/System.Net.Primitives.xml", + "ref/netstandard1.1/ru/System.Net.Primitives.xml", + "ref/netstandard1.1/zh-hans/System.Net.Primitives.xml", + "ref/netstandard1.1/zh-hant/System.Net.Primitives.xml", + "ref/netstandard1.3/System.Net.Primitives.dll", + "ref/netstandard1.3/System.Net.Primitives.xml", + "ref/netstandard1.3/de/System.Net.Primitives.xml", + "ref/netstandard1.3/es/System.Net.Primitives.xml", + "ref/netstandard1.3/fr/System.Net.Primitives.xml", + "ref/netstandard1.3/it/System.Net.Primitives.xml", + "ref/netstandard1.3/ja/System.Net.Primitives.xml", + "ref/netstandard1.3/ko/System.Net.Primitives.xml", + "ref/netstandard1.3/ru/System.Net.Primitives.xml", + "ref/netstandard1.3/zh-hans/System.Net.Primitives.xml", + "ref/netstandard1.3/zh-hant/System.Net.Primitives.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Net.Sockets/4.1.0": { + "sha512": "xAz0N3dAV/aR/9g8r0Y5oEqU1JRsz29F5EGb/WVHmX3jVSLqi2/92M5hTad2aNWovruXrJpJtgZ9fccPMG9uSw==", + "type": "package", + "path": "System.Net.Sockets/4.1.0", + "files": [ + "System.Net.Sockets.4.1.0.nupkg.sha512", + "System.Net.Sockets.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Net.Sockets.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Net.Sockets.dll", + "ref/netstandard1.3/System.Net.Sockets.dll", + "ref/netstandard1.3/System.Net.Sockets.xml", + "ref/netstandard1.3/de/System.Net.Sockets.xml", + "ref/netstandard1.3/es/System.Net.Sockets.xml", + "ref/netstandard1.3/fr/System.Net.Sockets.xml", + "ref/netstandard1.3/it/System.Net.Sockets.xml", + "ref/netstandard1.3/ja/System.Net.Sockets.xml", + "ref/netstandard1.3/ko/System.Net.Sockets.xml", + "ref/netstandard1.3/ru/System.Net.Sockets.xml", + "ref/netstandard1.3/zh-hans/System.Net.Sockets.xml", + "ref/netstandard1.3/zh-hant/System.Net.Sockets.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.ObjectModel/4.0.12": { + "sha512": "tAgJM1xt3ytyMoW4qn4wIqgJYm7L7TShRZG4+Q4Qsi2PCcj96pXN7nRywS9KkB3p/xDUjc2HSwP9SROyPYDYKQ==", + "type": "package", + "path": "System.ObjectModel/4.0.12", + "files": [ + "System.ObjectModel.4.0.12.nupkg.sha512", + "System.ObjectModel.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.ObjectModel.dll", + "lib/netstandard1.3/System.ObjectModel.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.ObjectModel.dll", + "ref/netcore50/System.ObjectModel.xml", + "ref/netcore50/de/System.ObjectModel.xml", + "ref/netcore50/es/System.ObjectModel.xml", + "ref/netcore50/fr/System.ObjectModel.xml", + "ref/netcore50/it/System.ObjectModel.xml", + "ref/netcore50/ja/System.ObjectModel.xml", + "ref/netcore50/ko/System.ObjectModel.xml", + "ref/netcore50/ru/System.ObjectModel.xml", + "ref/netcore50/zh-hans/System.ObjectModel.xml", + "ref/netcore50/zh-hant/System.ObjectModel.xml", + "ref/netstandard1.0/System.ObjectModel.dll", + "ref/netstandard1.0/System.ObjectModel.xml", + "ref/netstandard1.0/de/System.ObjectModel.xml", + "ref/netstandard1.0/es/System.ObjectModel.xml", + "ref/netstandard1.0/fr/System.ObjectModel.xml", + "ref/netstandard1.0/it/System.ObjectModel.xml", + "ref/netstandard1.0/ja/System.ObjectModel.xml", + "ref/netstandard1.0/ko/System.ObjectModel.xml", + "ref/netstandard1.0/ru/System.ObjectModel.xml", + "ref/netstandard1.0/zh-hans/System.ObjectModel.xml", + "ref/netstandard1.0/zh-hant/System.ObjectModel.xml", + "ref/netstandard1.3/System.ObjectModel.dll", + "ref/netstandard1.3/System.ObjectModel.xml", + "ref/netstandard1.3/de/System.ObjectModel.xml", + "ref/netstandard1.3/es/System.ObjectModel.xml", + "ref/netstandard1.3/fr/System.ObjectModel.xml", + "ref/netstandard1.3/it/System.ObjectModel.xml", + "ref/netstandard1.3/ja/System.ObjectModel.xml", + "ref/netstandard1.3/ko/System.ObjectModel.xml", + "ref/netstandard1.3/ru/System.ObjectModel.xml", + "ref/netstandard1.3/zh-hans/System.ObjectModel.xml", + "ref/netstandard1.3/zh-hant/System.ObjectModel.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Reflection/4.1.0": { + "sha512": "JCKANJ0TI7kzoQzuwB/OoJANy1Lg338B6+JVacPl4TpUwi3cReg3nMLplMq2uqYfHFQpKIlHAUVAJlImZz/4ng==", + "type": "package", + "path": "System.Reflection/4.1.0", + "files": [ + "System.Reflection.4.1.0.nupkg.sha512", + "System.Reflection.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Reflection.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Reflection.dll", + "ref/netcore50/System.Reflection.dll", + "ref/netcore50/System.Reflection.xml", + "ref/netcore50/de/System.Reflection.xml", + "ref/netcore50/es/System.Reflection.xml", + "ref/netcore50/fr/System.Reflection.xml", + "ref/netcore50/it/System.Reflection.xml", + "ref/netcore50/ja/System.Reflection.xml", + "ref/netcore50/ko/System.Reflection.xml", + "ref/netcore50/ru/System.Reflection.xml", + "ref/netcore50/zh-hans/System.Reflection.xml", + "ref/netcore50/zh-hant/System.Reflection.xml", + "ref/netstandard1.0/System.Reflection.dll", + "ref/netstandard1.0/System.Reflection.xml", + "ref/netstandard1.0/de/System.Reflection.xml", + "ref/netstandard1.0/es/System.Reflection.xml", + "ref/netstandard1.0/fr/System.Reflection.xml", + "ref/netstandard1.0/it/System.Reflection.xml", + "ref/netstandard1.0/ja/System.Reflection.xml", + "ref/netstandard1.0/ko/System.Reflection.xml", + "ref/netstandard1.0/ru/System.Reflection.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.xml", + "ref/netstandard1.3/System.Reflection.dll", + "ref/netstandard1.3/System.Reflection.xml", + "ref/netstandard1.3/de/System.Reflection.xml", + "ref/netstandard1.3/es/System.Reflection.xml", + "ref/netstandard1.3/fr/System.Reflection.xml", + "ref/netstandard1.3/it/System.Reflection.xml", + "ref/netstandard1.3/ja/System.Reflection.xml", + "ref/netstandard1.3/ko/System.Reflection.xml", + "ref/netstandard1.3/ru/System.Reflection.xml", + "ref/netstandard1.3/zh-hans/System.Reflection.xml", + "ref/netstandard1.3/zh-hant/System.Reflection.xml", + "ref/netstandard1.5/System.Reflection.dll", + "ref/netstandard1.5/System.Reflection.xml", + "ref/netstandard1.5/de/System.Reflection.xml", + "ref/netstandard1.5/es/System.Reflection.xml", + "ref/netstandard1.5/fr/System.Reflection.xml", + "ref/netstandard1.5/it/System.Reflection.xml", + "ref/netstandard1.5/ja/System.Reflection.xml", + "ref/netstandard1.5/ko/System.Reflection.xml", + "ref/netstandard1.5/ru/System.Reflection.xml", + "ref/netstandard1.5/zh-hans/System.Reflection.xml", + "ref/netstandard1.5/zh-hant/System.Reflection.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Reflection.Emit/4.0.1": { + "sha512": "P2wqAj72fFjpP6wb9nSfDqNBMab+2ovzSDzUZK7MVIm54tBJEPr9jWfSjjoTpPwj1LeKcmX3vr0ttyjSSFM47g==", + "type": "package", + "path": "System.Reflection.Emit/4.0.1", + "files": [ + "System.Reflection.Emit.4.0.1.nupkg.sha512", + "System.Reflection.Emit.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.dll", + "lib/netstandard1.3/System.Reflection.Emit.dll", + "lib/xamarinmac20/_._", + "ref/MonoAndroid10/_._", + "ref/net45/_._", + "ref/netstandard1.1/System.Reflection.Emit.dll", + "ref/netstandard1.1/System.Reflection.Emit.xml", + "ref/netstandard1.1/de/System.Reflection.Emit.xml", + "ref/netstandard1.1/es/System.Reflection.Emit.xml", + "ref/netstandard1.1/fr/System.Reflection.Emit.xml", + "ref/netstandard1.1/it/System.Reflection.Emit.xml", + "ref/netstandard1.1/ja/System.Reflection.Emit.xml", + "ref/netstandard1.1/ko/System.Reflection.Emit.xml", + "ref/netstandard1.1/ru/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hans/System.Reflection.Emit.xml", + "ref/netstandard1.1/zh-hant/System.Reflection.Emit.xml", + "ref/xamarinmac20/_._" + ] + }, + "System.Reflection.Emit.ILGeneration/4.0.1": { + "sha512": "Ov6dU8Bu15Bc7zuqttgHF12J5lwSWyTf1S+FJouUXVMSqImLZzYaQ+vRr1rQ0OZ0HqsrwWl4dsKHELckQkVpgA==", + "type": "package", + "path": "System.Reflection.Emit.ILGeneration/4.0.1", + "files": [ + "System.Reflection.Emit.ILGeneration.4.0.1.nupkg.sha512", + "System.Reflection.Emit.ILGeneration.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.ILGeneration.dll", + "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll", + "lib/portable-net45+wp8/_._", + "lib/wp80/_._", + "ref/net45/_._", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.dll", + "ref/netstandard1.0/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/de/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/es/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/fr/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/it/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ja/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ko/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/ru/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Emit.ILGeneration.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Emit.ILGeneration.xml", + "ref/portable-net45+wp8/_._", + "ref/wp80/_._", + "runtimes/aot/lib/netcore50/_._" + ] + }, + "System.Reflection.Emit.Lightweight/4.0.1": { + "sha512": "sSzHHXueZ5Uh0OLpUQprhr+ZYJrLPA2Cmr4gn0wj9+FftNKXx8RIMKvO9qnjk2ebPYUjZ+F2ulGdPOsvj+MEjA==", + "type": "package", + "path": "System.Reflection.Emit.Lightweight/4.0.1", + "files": [ + "System.Reflection.Emit.Lightweight.4.0.1.nupkg.sha512", + "System.Reflection.Emit.Lightweight.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Emit.Lightweight.dll", + "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll", + "lib/portable-net45+wp8/_._", + "lib/wp80/_._", + "ref/net45/_._", + "ref/netstandard1.0/System.Reflection.Emit.Lightweight.dll", + "ref/netstandard1.0/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/de/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/es/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/fr/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/it/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/ja/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/ko/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/ru/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Emit.Lightweight.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Emit.Lightweight.xml", + "ref/portable-net45+wp8/_._", + "ref/wp80/_._", + "runtimes/aot/lib/netcore50/_._" + ] + }, + "System.Reflection.Extensions/4.0.1": { + "sha512": "GYrtRsZcMuHF3sbmRHfMYpvxZoIN2bQGrYGerUiWLEkqdEUQZhH3TRSaC/oI4wO0II1RKBPlpIa1TOMxIcOOzQ==", + "type": "package", + "path": "System.Reflection.Extensions/4.0.1", + "files": [ + "System.Reflection.Extensions.4.0.1.nupkg.sha512", + "System.Reflection.Extensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Extensions.dll", + "ref/netcore50/System.Reflection.Extensions.xml", + "ref/netcore50/de/System.Reflection.Extensions.xml", + "ref/netcore50/es/System.Reflection.Extensions.xml", + "ref/netcore50/fr/System.Reflection.Extensions.xml", + "ref/netcore50/it/System.Reflection.Extensions.xml", + "ref/netcore50/ja/System.Reflection.Extensions.xml", + "ref/netcore50/ko/System.Reflection.Extensions.xml", + "ref/netcore50/ru/System.Reflection.Extensions.xml", + "ref/netcore50/zh-hans/System.Reflection.Extensions.xml", + "ref/netcore50/zh-hant/System.Reflection.Extensions.xml", + "ref/netstandard1.0/System.Reflection.Extensions.dll", + "ref/netstandard1.0/System.Reflection.Extensions.xml", + "ref/netstandard1.0/de/System.Reflection.Extensions.xml", + "ref/netstandard1.0/es/System.Reflection.Extensions.xml", + "ref/netstandard1.0/fr/System.Reflection.Extensions.xml", + "ref/netstandard1.0/it/System.Reflection.Extensions.xml", + "ref/netstandard1.0/ja/System.Reflection.Extensions.xml", + "ref/netstandard1.0/ko/System.Reflection.Extensions.xml", + "ref/netstandard1.0/ru/System.Reflection.Extensions.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Extensions.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Extensions.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Reflection.Primitives/4.0.1": { + "sha512": "4inTox4wTBaDhB7V3mPvp9XlCbeGYWVEM9/fXALd52vNEAVisc1BoVWQPuUuD0Ga//dNbA/WeMy9u9mzLxGTHQ==", + "type": "package", + "path": "System.Reflection.Primitives/4.0.1", + "files": [ + "System.Reflection.Primitives.4.0.1.nupkg.sha512", + "System.Reflection.Primitives.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/netcore50/de/System.Reflection.Primitives.xml", + "ref/netcore50/es/System.Reflection.Primitives.xml", + "ref/netcore50/fr/System.Reflection.Primitives.xml", + "ref/netcore50/it/System.Reflection.Primitives.xml", + "ref/netcore50/ja/System.Reflection.Primitives.xml", + "ref/netcore50/ko/System.Reflection.Primitives.xml", + "ref/netcore50/ru/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hans/System.Reflection.Primitives.xml", + "ref/netcore50/zh-hant/System.Reflection.Primitives.xml", + "ref/netstandard1.0/System.Reflection.Primitives.dll", + "ref/netstandard1.0/System.Reflection.Primitives.xml", + "ref/netstandard1.0/de/System.Reflection.Primitives.xml", + "ref/netstandard1.0/es/System.Reflection.Primitives.xml", + "ref/netstandard1.0/fr/System.Reflection.Primitives.xml", + "ref/netstandard1.0/it/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ja/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ko/System.Reflection.Primitives.xml", + "ref/netstandard1.0/ru/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hans/System.Reflection.Primitives.xml", + "ref/netstandard1.0/zh-hant/System.Reflection.Primitives.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Reflection.TypeExtensions/4.1.0": { + "sha512": "tsQ/ptQ3H5FYfON8lL4MxRk/8kFyE0A+tGPXmVP967cT/gzLHYxIejIYSxp4JmIeFHVP78g/F2FE1mUUTbDtrg==", + "type": "package", + "path": "System.Reflection.TypeExtensions/4.1.0", + "files": [ + "System.Reflection.TypeExtensions.4.1.0.nupkg.sha512", + "System.Reflection.TypeExtensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Reflection.TypeExtensions.dll", + "lib/net462/System.Reflection.TypeExtensions.dll", + "lib/netcore50/System.Reflection.TypeExtensions.dll", + "lib/netstandard1.5/System.Reflection.TypeExtensions.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Reflection.TypeExtensions.dll", + "ref/net462/System.Reflection.TypeExtensions.dll", + "ref/netstandard1.3/System.Reflection.TypeExtensions.dll", + "ref/netstandard1.3/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/de/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/es/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/fr/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/it/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/ja/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/ko/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/ru/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/zh-hans/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.3/zh-hant/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/System.Reflection.TypeExtensions.dll", + "ref/netstandard1.5/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/de/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/es/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/fr/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/it/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/ja/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/ko/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/ru/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/zh-hans/System.Reflection.TypeExtensions.xml", + "ref/netstandard1.5/zh-hant/System.Reflection.TypeExtensions.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Reflection.TypeExtensions.dll" + ] + }, + "System.Resources.ResourceManager/4.0.1": { + "sha512": "TxwVeUNoTgUOdQ09gfTjvW411MF+w9MBYL7AtNVc+HtBCFlutPLhUCdZjNkjbhj3bNQWMdHboF0KIWEOjJssbA==", + "type": "package", + "path": "System.Resources.ResourceManager/4.0.1", + "files": [ + "System.Resources.ResourceManager.4.0.1.nupkg.sha512", + "System.Resources.ResourceManager.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Resources.ResourceManager.dll", + "ref/netcore50/System.Resources.ResourceManager.xml", + "ref/netcore50/de/System.Resources.ResourceManager.xml", + "ref/netcore50/es/System.Resources.ResourceManager.xml", + "ref/netcore50/fr/System.Resources.ResourceManager.xml", + "ref/netcore50/it/System.Resources.ResourceManager.xml", + "ref/netcore50/ja/System.Resources.ResourceManager.xml", + "ref/netcore50/ko/System.Resources.ResourceManager.xml", + "ref/netcore50/ru/System.Resources.ResourceManager.xml", + "ref/netcore50/zh-hans/System.Resources.ResourceManager.xml", + "ref/netcore50/zh-hant/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/System.Resources.ResourceManager.dll", + "ref/netstandard1.0/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/de/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/es/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/fr/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/it/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/ja/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/ko/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/ru/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/zh-hans/System.Resources.ResourceManager.xml", + "ref/netstandard1.0/zh-hant/System.Resources.ResourceManager.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Runtime/4.1.0": { + "sha512": "v6c/4Yaa9uWsq+JMhnOFewrYkgdNHNG2eMKuNqRn8P733rNXeRCGvV5FkkjBXn2dbVkPXOsO0xjsEeM1q2zC0g==", + "type": "package", + "path": "System.Runtime/4.1.0", + "files": [ + "System.Runtime.4.1.0.nupkg.sha512", + "System.Runtime.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Runtime.dll", + "lib/portable-net45+win8+wp80+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Runtime.dll", + "ref/netcore50/System.Runtime.dll", + "ref/netcore50/System.Runtime.xml", + "ref/netcore50/de/System.Runtime.xml", + "ref/netcore50/es/System.Runtime.xml", + "ref/netcore50/fr/System.Runtime.xml", + "ref/netcore50/it/System.Runtime.xml", + "ref/netcore50/ja/System.Runtime.xml", + "ref/netcore50/ko/System.Runtime.xml", + "ref/netcore50/ru/System.Runtime.xml", + "ref/netcore50/zh-hans/System.Runtime.xml", + "ref/netcore50/zh-hant/System.Runtime.xml", + "ref/netstandard1.0/System.Runtime.dll", + "ref/netstandard1.0/System.Runtime.xml", + "ref/netstandard1.0/de/System.Runtime.xml", + "ref/netstandard1.0/es/System.Runtime.xml", + "ref/netstandard1.0/fr/System.Runtime.xml", + "ref/netstandard1.0/it/System.Runtime.xml", + "ref/netstandard1.0/ja/System.Runtime.xml", + "ref/netstandard1.0/ko/System.Runtime.xml", + "ref/netstandard1.0/ru/System.Runtime.xml", + "ref/netstandard1.0/zh-hans/System.Runtime.xml", + "ref/netstandard1.0/zh-hant/System.Runtime.xml", + "ref/netstandard1.2/System.Runtime.dll", + "ref/netstandard1.2/System.Runtime.xml", + "ref/netstandard1.2/de/System.Runtime.xml", + "ref/netstandard1.2/es/System.Runtime.xml", + "ref/netstandard1.2/fr/System.Runtime.xml", + "ref/netstandard1.2/it/System.Runtime.xml", + "ref/netstandard1.2/ja/System.Runtime.xml", + "ref/netstandard1.2/ko/System.Runtime.xml", + "ref/netstandard1.2/ru/System.Runtime.xml", + "ref/netstandard1.2/zh-hans/System.Runtime.xml", + "ref/netstandard1.2/zh-hant/System.Runtime.xml", + "ref/netstandard1.3/System.Runtime.dll", + "ref/netstandard1.3/System.Runtime.xml", + "ref/netstandard1.3/de/System.Runtime.xml", + "ref/netstandard1.3/es/System.Runtime.xml", + "ref/netstandard1.3/fr/System.Runtime.xml", + "ref/netstandard1.3/it/System.Runtime.xml", + "ref/netstandard1.3/ja/System.Runtime.xml", + "ref/netstandard1.3/ko/System.Runtime.xml", + "ref/netstandard1.3/ru/System.Runtime.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.xml", + "ref/netstandard1.5/System.Runtime.dll", + "ref/netstandard1.5/System.Runtime.xml", + "ref/netstandard1.5/de/System.Runtime.xml", + "ref/netstandard1.5/es/System.Runtime.xml", + "ref/netstandard1.5/fr/System.Runtime.xml", + "ref/netstandard1.5/it/System.Runtime.xml", + "ref/netstandard1.5/ja/System.Runtime.xml", + "ref/netstandard1.5/ko/System.Runtime.xml", + "ref/netstandard1.5/ru/System.Runtime.xml", + "ref/netstandard1.5/zh-hans/System.Runtime.xml", + "ref/netstandard1.5/zh-hant/System.Runtime.xml", + "ref/portable-net45+win8+wp80+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Runtime.Extensions/4.1.0": { + "sha512": "CUOHjTT/vgP0qGW22U4/hDlOqXmcPq5YicBaXdUR2UiUoLwBT+olO6we4DVbq57jeX5uXH2uerVZhf0qGj+sVQ==", + "type": "package", + "path": "System.Runtime.Extensions/4.1.0", + "files": [ + "System.Runtime.Extensions.4.1.0.nupkg.sha512", + "System.Runtime.Extensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Runtime.Extensions.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Runtime.Extensions.dll", + "ref/netcore50/System.Runtime.Extensions.dll", + "ref/netcore50/System.Runtime.Extensions.xml", + "ref/netcore50/de/System.Runtime.Extensions.xml", + "ref/netcore50/es/System.Runtime.Extensions.xml", + "ref/netcore50/fr/System.Runtime.Extensions.xml", + "ref/netcore50/it/System.Runtime.Extensions.xml", + "ref/netcore50/ja/System.Runtime.Extensions.xml", + "ref/netcore50/ko/System.Runtime.Extensions.xml", + "ref/netcore50/ru/System.Runtime.Extensions.xml", + "ref/netcore50/zh-hans/System.Runtime.Extensions.xml", + "ref/netcore50/zh-hant/System.Runtime.Extensions.xml", + "ref/netstandard1.0/System.Runtime.Extensions.dll", + "ref/netstandard1.0/System.Runtime.Extensions.xml", + "ref/netstandard1.0/de/System.Runtime.Extensions.xml", + "ref/netstandard1.0/es/System.Runtime.Extensions.xml", + "ref/netstandard1.0/fr/System.Runtime.Extensions.xml", + "ref/netstandard1.0/it/System.Runtime.Extensions.xml", + "ref/netstandard1.0/ja/System.Runtime.Extensions.xml", + "ref/netstandard1.0/ko/System.Runtime.Extensions.xml", + "ref/netstandard1.0/ru/System.Runtime.Extensions.xml", + "ref/netstandard1.0/zh-hans/System.Runtime.Extensions.xml", + "ref/netstandard1.0/zh-hant/System.Runtime.Extensions.xml", + "ref/netstandard1.3/System.Runtime.Extensions.dll", + "ref/netstandard1.3/System.Runtime.Extensions.xml", + "ref/netstandard1.3/de/System.Runtime.Extensions.xml", + "ref/netstandard1.3/es/System.Runtime.Extensions.xml", + "ref/netstandard1.3/fr/System.Runtime.Extensions.xml", + "ref/netstandard1.3/it/System.Runtime.Extensions.xml", + "ref/netstandard1.3/ja/System.Runtime.Extensions.xml", + "ref/netstandard1.3/ko/System.Runtime.Extensions.xml", + "ref/netstandard1.3/ru/System.Runtime.Extensions.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.Extensions.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.Extensions.xml", + "ref/netstandard1.5/System.Runtime.Extensions.dll", + "ref/netstandard1.5/System.Runtime.Extensions.xml", + "ref/netstandard1.5/de/System.Runtime.Extensions.xml", + "ref/netstandard1.5/es/System.Runtime.Extensions.xml", + "ref/netstandard1.5/fr/System.Runtime.Extensions.xml", + "ref/netstandard1.5/it/System.Runtime.Extensions.xml", + "ref/netstandard1.5/ja/System.Runtime.Extensions.xml", + "ref/netstandard1.5/ko/System.Runtime.Extensions.xml", + "ref/netstandard1.5/ru/System.Runtime.Extensions.xml", + "ref/netstandard1.5/zh-hans/System.Runtime.Extensions.xml", + "ref/netstandard1.5/zh-hant/System.Runtime.Extensions.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Runtime.Handles/4.0.1": { + "sha512": "nCJvEKguXEvk2ymk1gqj625vVnlK3/xdGzx0vOKicQkoquaTBJTP13AIYkocSUwHCLNBwUbXTqTWGDxBTWpt7g==", + "type": "package", + "path": "System.Runtime.Handles/4.0.1", + "files": [ + "System.Runtime.Handles.4.0.1.nupkg.sha512", + "System.Runtime.Handles.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/netstandard1.3/System.Runtime.Handles.dll", + "ref/netstandard1.3/System.Runtime.Handles.xml", + "ref/netstandard1.3/de/System.Runtime.Handles.xml", + "ref/netstandard1.3/es/System.Runtime.Handles.xml", + "ref/netstandard1.3/fr/System.Runtime.Handles.xml", + "ref/netstandard1.3/it/System.Runtime.Handles.xml", + "ref/netstandard1.3/ja/System.Runtime.Handles.xml", + "ref/netstandard1.3/ko/System.Runtime.Handles.xml", + "ref/netstandard1.3/ru/System.Runtime.Handles.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.Handles.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.Handles.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Runtime.InteropServices/4.1.0": { + "sha512": "16eu3kjHS633yYdkjwShDHZLRNMKVi/s0bY8ODiqJ2RfMhDMAwxZaUaWVnZ2P71kr/or+X9o/xFWtNqz8ivieQ==", + "type": "package", + "path": "System.Runtime.InteropServices/4.1.0", + "files": [ + "System.Runtime.InteropServices.4.1.0.nupkg.sha512", + "System.Runtime.InteropServices.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net462/System.Runtime.InteropServices.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net462/System.Runtime.InteropServices.dll", + "ref/netcore50/System.Runtime.InteropServices.dll", + "ref/netcore50/System.Runtime.InteropServices.xml", + "ref/netcore50/de/System.Runtime.InteropServices.xml", + "ref/netcore50/es/System.Runtime.InteropServices.xml", + "ref/netcore50/fr/System.Runtime.InteropServices.xml", + "ref/netcore50/it/System.Runtime.InteropServices.xml", + "ref/netcore50/ja/System.Runtime.InteropServices.xml", + "ref/netcore50/ko/System.Runtime.InteropServices.xml", + "ref/netcore50/ru/System.Runtime.InteropServices.xml", + "ref/netcore50/zh-hans/System.Runtime.InteropServices.xml", + "ref/netcore50/zh-hant/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/System.Runtime.InteropServices.dll", + "ref/netstandard1.1/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/de/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/es/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/fr/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/it/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/ja/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/ko/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/ru/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/zh-hans/System.Runtime.InteropServices.xml", + "ref/netstandard1.1/zh-hant/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/System.Runtime.InteropServices.dll", + "ref/netstandard1.2/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/de/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/es/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/fr/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/it/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/ja/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/ko/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/ru/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/zh-hans/System.Runtime.InteropServices.xml", + "ref/netstandard1.2/zh-hant/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/System.Runtime.InteropServices.dll", + "ref/netstandard1.3/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/de/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/es/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/fr/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/it/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/ja/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/ko/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/ru/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/zh-hans/System.Runtime.InteropServices.xml", + "ref/netstandard1.3/zh-hant/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/System.Runtime.InteropServices.dll", + "ref/netstandard1.5/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/de/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/es/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/fr/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/it/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/ja/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/ko/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/ru/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/zh-hans/System.Runtime.InteropServices.xml", + "ref/netstandard1.5/zh-hant/System.Runtime.InteropServices.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Runtime.InteropServices.RuntimeInformation/4.0.0": { + "sha512": "hWPhJxc453RCa8Z29O91EmfGeZIHX1ZH2A8L6lYQVSaKzku2DfArSfMEb1/MYYzPQRJZeu0c9dmYeJKxW5Fgng==", + "type": "package", + "path": "System.Runtime.InteropServices.RuntimeInformation/4.0.0", + "files": [ + "System.Runtime.InteropServices.RuntimeInformation.4.0.0.nupkg.sha512", + "System.Runtime.InteropServices.RuntimeInformation.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/System.Runtime.InteropServices.RuntimeInformation.dll", + "lib/win8/System.Runtime.InteropServices.RuntimeInformation.dll", + "lib/wpa81/System.Runtime.InteropServices.RuntimeInformation.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Runtime.InteropServices.RuntimeInformation.dll", + "runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll", + "runtimes/win/lib/net45/System.Runtime.InteropServices.RuntimeInformation.dll", + "runtimes/win/lib/netcore50/System.Runtime.InteropServices.RuntimeInformation.dll", + "runtimes/win/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll" + ] + }, + "System.Runtime.Numerics/4.0.1": { + "sha512": "+XbKFuzdmLP3d1o9pdHu2nxjNr2OEPqGzKeegPLCUMM71a0t50A/rOcIRmGs9wR7a8KuHX6hYs/7/TymIGLNqg==", + "type": "package", + "path": "System.Runtime.Numerics/4.0.1", + "files": [ + "System.Runtime.Numerics.4.0.1.nupkg.sha512", + "System.Runtime.Numerics.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Runtime.Numerics.dll", + "lib/netstandard1.3/System.Runtime.Numerics.dll", + "lib/portable-net45+win8+wpa81/_._", + "lib/win8/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Runtime.Numerics.dll", + "ref/netcore50/System.Runtime.Numerics.xml", + "ref/netcore50/de/System.Runtime.Numerics.xml", + "ref/netcore50/es/System.Runtime.Numerics.xml", + "ref/netcore50/fr/System.Runtime.Numerics.xml", + "ref/netcore50/it/System.Runtime.Numerics.xml", + "ref/netcore50/ja/System.Runtime.Numerics.xml", + "ref/netcore50/ko/System.Runtime.Numerics.xml", + "ref/netcore50/ru/System.Runtime.Numerics.xml", + "ref/netcore50/zh-hans/System.Runtime.Numerics.xml", + "ref/netcore50/zh-hant/System.Runtime.Numerics.xml", + "ref/netstandard1.1/System.Runtime.Numerics.dll", + "ref/netstandard1.1/System.Runtime.Numerics.xml", + "ref/netstandard1.1/de/System.Runtime.Numerics.xml", + "ref/netstandard1.1/es/System.Runtime.Numerics.xml", + "ref/netstandard1.1/fr/System.Runtime.Numerics.xml", + "ref/netstandard1.1/it/System.Runtime.Numerics.xml", + "ref/netstandard1.1/ja/System.Runtime.Numerics.xml", + "ref/netstandard1.1/ko/System.Runtime.Numerics.xml", + "ref/netstandard1.1/ru/System.Runtime.Numerics.xml", + "ref/netstandard1.1/zh-hans/System.Runtime.Numerics.xml", + "ref/netstandard1.1/zh-hant/System.Runtime.Numerics.xml", + "ref/portable-net45+win8+wpa81/_._", + "ref/win8/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Security.Cryptography.Algorithms/4.2.0": { + "sha512": "8JQFxbLVdrtIOKMDN38Fn0GWnqYZw/oMlwOUG/qz1jqChvyZlnUmu+0s7wLx7JYua/nAXoESpHA3iw11QFWhXg==", + "type": "package", + "path": "System.Security.Cryptography.Algorithms/4.2.0", + "files": [ + "System.Security.Cryptography.Algorithms.4.2.0.nupkg.sha512", + "System.Security.Cryptography.Algorithms.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Algorithms.dll", + "lib/net461/System.Security.Cryptography.Algorithms.dll", + "lib/net463/System.Security.Cryptography.Algorithms.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Algorithms.dll", + "ref/net461/System.Security.Cryptography.Algorithms.dll", + "ref/net463/System.Security.Cryptography.Algorithms.dll", + "ref/netstandard1.3/System.Security.Cryptography.Algorithms.dll", + "ref/netstandard1.4/System.Security.Cryptography.Algorithms.dll", + "ref/netstandard1.6/System.Security.Cryptography.Algorithms.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.Algorithms.dll", + "runtimes/win/lib/net46/System.Security.Cryptography.Algorithms.dll", + "runtimes/win/lib/net461/System.Security.Cryptography.Algorithms.dll", + "runtimes/win/lib/net463/System.Security.Cryptography.Algorithms.dll", + "runtimes/win/lib/netcore50/System.Security.Cryptography.Algorithms.dll", + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Algorithms.dll" + ] + }, + "System.Security.Cryptography.Cng/4.2.0": { + "sha512": "cUJ2h+ZvONDe28Szw3st5dOHdjndhJzQ2WObDEXAWRPEQBtVItVoxbXM/OEsTthl3cNn2dk2k0I3y45igCQcLw==", + "type": "package", + "path": "System.Security.Cryptography.Cng/4.2.0", + "files": [ + "System.Security.Cryptography.Cng.4.2.0.nupkg.sha512", + "System.Security.Cryptography.Cng.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/net46/System.Security.Cryptography.Cng.dll", + "lib/net461/System.Security.Cryptography.Cng.dll", + "lib/net463/System.Security.Cryptography.Cng.dll", + "ref/net46/System.Security.Cryptography.Cng.dll", + "ref/net461/System.Security.Cryptography.Cng.dll", + "ref/net463/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.3/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.4/System.Security.Cryptography.Cng.dll", + "ref/netstandard1.6/System.Security.Cryptography.Cng.dll", + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net46/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net461/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/net463/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.4/System.Security.Cryptography.Cng.dll", + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.Cng.dll" + ] + }, + "System.Security.Cryptography.Csp/4.0.0": { + "sha512": "/i1Usuo4PgAqgbPNC0NjbO3jPW//BoBlTpcWFD1EHVbidH21y4c1ap5bbEMSGAXjAShhMH4abi/K8fILrnu4BQ==", + "type": "package", + "path": "System.Security.Cryptography.Csp/4.0.0", + "files": [ + "System.Security.Cryptography.Csp.4.0.0.nupkg.sha512", + "System.Security.Cryptography.Csp.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Csp.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Csp.dll", + "ref/netstandard1.3/System.Security.Cryptography.Csp.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.3/System.Security.Cryptography.Csp.dll", + "runtimes/win/lib/net46/System.Security.Cryptography.Csp.dll", + "runtimes/win/lib/netcore50/_._", + "runtimes/win/lib/netstandard1.3/System.Security.Cryptography.Csp.dll" + ] + }, + "System.Security.Cryptography.Encoding/4.0.0": { + "sha512": "FbKgE5MbxSQMPcSVRgwM6bXN3GtyAh04NkV8E5zKCBE26X0vYW0UtTa2FIgkH33WVqBVxRgxljlVYumWtU+HcQ==", + "type": "package", + "path": "System.Security.Cryptography.Encoding/4.0.0", + "files": [ + "System.Security.Cryptography.Encoding.4.0.0.nupkg.sha512", + "System.Security.Cryptography.Encoding.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Encoding.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Encoding.dll", + "ref/netstandard1.3/System.Security.Cryptography.Encoding.dll", + "ref/netstandard1.3/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/de/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/es/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/fr/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/it/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/ja/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/ko/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/ru/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/zh-hans/System.Security.Cryptography.Encoding.xml", + "ref/netstandard1.3/zh-hant/System.Security.Cryptography.Encoding.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.3/System.Security.Cryptography.Encoding.dll", + "runtimes/win/lib/net46/System.Security.Cryptography.Encoding.dll", + "runtimes/win/lib/netstandard1.3/System.Security.Cryptography.Encoding.dll" + ] + }, + "System.Security.Cryptography.OpenSsl/4.0.0": { + "sha512": "HUG/zNUJwEiLkoURDixzkzZdB5yGA5pQhDP93ArOpDPQMteURIGERRNzzoJlmTreLBWr5lkFSjjMSk8ySEpQMw==", + "type": "package", + "path": "System.Security.Cryptography.OpenSsl/4.0.0", + "files": [ + "System.Security.Cryptography.OpenSsl.4.0.0.nupkg.sha512", + "System.Security.Cryptography.OpenSsl.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll", + "ref/netstandard1.6/System.Security.Cryptography.OpenSsl.dll", + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll" + ] + }, + "System.Security.Cryptography.Primitives/4.0.0": { + "sha512": "Wkd7QryWYjkQclX0bngpntW5HSlMzeJU24UaLJQ7YTfI8ydAVAaU2J+HXLLABOVJlKTVvAeL0Aj39VeTe7L+oA==", + "type": "package", + "path": "System.Security.Cryptography.Primitives/4.0.0", + "files": [ + "System.Security.Cryptography.Primitives.4.0.0.nupkg.sha512", + "System.Security.Cryptography.Primitives.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.Primitives.dll", + "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.Primitives.dll", + "ref/netstandard1.3/System.Security.Cryptography.Primitives.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Security.Cryptography.X509Certificates/4.1.0": { + "sha512": "4HEfsQIKAhA1+ApNn729Gi09zh+lYWwyIuViihoMDWp1vQnEkL2ct7mAbhBlLYm+x/L4Rr/pyGge1lIY635e0w==", + "type": "package", + "path": "System.Security.Cryptography.X509Certificates/4.1.0", + "files": [ + "System.Security.Cryptography.X509Certificates.4.1.0.nupkg.sha512", + "System.Security.Cryptography.X509Certificates.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Security.Cryptography.X509Certificates.dll", + "lib/net461/System.Security.Cryptography.X509Certificates.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Security.Cryptography.X509Certificates.dll", + "ref/net461/System.Security.Cryptography.X509Certificates.dll", + "ref/netstandard1.3/System.Security.Cryptography.X509Certificates.dll", + "ref/netstandard1.3/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/de/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/es/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/fr/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/it/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/ja/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/ko/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/ru/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/zh-hans/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.3/zh-hant/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/System.Security.Cryptography.X509Certificates.dll", + "ref/netstandard1.4/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/de/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/es/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/fr/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/it/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/ja/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/ko/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/ru/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/zh-hans/System.Security.Cryptography.X509Certificates.xml", + "ref/netstandard1.4/zh-hant/System.Security.Cryptography.X509Certificates.xml", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/unix/lib/netstandard1.6/System.Security.Cryptography.X509Certificates.dll", + "runtimes/win/lib/net46/System.Security.Cryptography.X509Certificates.dll", + "runtimes/win/lib/net461/System.Security.Cryptography.X509Certificates.dll", + "runtimes/win/lib/netcore50/System.Security.Cryptography.X509Certificates.dll", + "runtimes/win/lib/netstandard1.6/System.Security.Cryptography.X509Certificates.dll" + ] + }, + "System.Text.Encoding/4.0.11": { + "sha512": "U3gGeMlDZXxCEiY4DwVLSacg+DFWCvoiX+JThA/rvw37Sqrku7sEFeVBBBMBnfB6FeZHsyDx85HlKL19x0HtZA==", + "type": "package", + "path": "System.Text.Encoding/4.0.11", + "files": [ + "System.Text.Encoding.4.0.11.nupkg.sha512", + "System.Text.Encoding.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Text.Encoding.dll", + "ref/netcore50/System.Text.Encoding.xml", + "ref/netcore50/de/System.Text.Encoding.xml", + "ref/netcore50/es/System.Text.Encoding.xml", + "ref/netcore50/fr/System.Text.Encoding.xml", + "ref/netcore50/it/System.Text.Encoding.xml", + "ref/netcore50/ja/System.Text.Encoding.xml", + "ref/netcore50/ko/System.Text.Encoding.xml", + "ref/netcore50/ru/System.Text.Encoding.xml", + "ref/netcore50/zh-hans/System.Text.Encoding.xml", + "ref/netcore50/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.0/System.Text.Encoding.dll", + "ref/netstandard1.0/System.Text.Encoding.xml", + "ref/netstandard1.0/de/System.Text.Encoding.xml", + "ref/netstandard1.0/es/System.Text.Encoding.xml", + "ref/netstandard1.0/fr/System.Text.Encoding.xml", + "ref/netstandard1.0/it/System.Text.Encoding.xml", + "ref/netstandard1.0/ja/System.Text.Encoding.xml", + "ref/netstandard1.0/ko/System.Text.Encoding.xml", + "ref/netstandard1.0/ru/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.0/zh-hant/System.Text.Encoding.xml", + "ref/netstandard1.3/System.Text.Encoding.dll", + "ref/netstandard1.3/System.Text.Encoding.xml", + "ref/netstandard1.3/de/System.Text.Encoding.xml", + "ref/netstandard1.3/es/System.Text.Encoding.xml", + "ref/netstandard1.3/fr/System.Text.Encoding.xml", + "ref/netstandard1.3/it/System.Text.Encoding.xml", + "ref/netstandard1.3/ja/System.Text.Encoding.xml", + "ref/netstandard1.3/ko/System.Text.Encoding.xml", + "ref/netstandard1.3/ru/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hans/System.Text.Encoding.xml", + "ref/netstandard1.3/zh-hant/System.Text.Encoding.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Text.Encoding.Extensions/4.0.11": { + "sha512": "jtbiTDtvfLYgXn8PTfWI+SiBs51rrmO4AAckx4KR6vFK9Wzf6tI8kcRdsYQNwriUeQ1+CtQbM1W4cMbLXnj/OQ==", + "type": "package", + "path": "System.Text.Encoding.Extensions/4.0.11", + "files": [ + "System.Text.Encoding.Extensions.4.0.11.nupkg.sha512", + "System.Text.Encoding.Extensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Text.Encoding.Extensions.dll", + "ref/netcore50/System.Text.Encoding.Extensions.xml", + "ref/netcore50/de/System.Text.Encoding.Extensions.xml", + "ref/netcore50/es/System.Text.Encoding.Extensions.xml", + "ref/netcore50/fr/System.Text.Encoding.Extensions.xml", + "ref/netcore50/it/System.Text.Encoding.Extensions.xml", + "ref/netcore50/ja/System.Text.Encoding.Extensions.xml", + "ref/netcore50/ko/System.Text.Encoding.Extensions.xml", + "ref/netcore50/ru/System.Text.Encoding.Extensions.xml", + "ref/netcore50/zh-hans/System.Text.Encoding.Extensions.xml", + "ref/netcore50/zh-hant/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/System.Text.Encoding.Extensions.dll", + "ref/netstandard1.0/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/de/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/es/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/fr/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/it/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/ja/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/ko/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/ru/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/zh-hans/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.0/zh-hant/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/System.Text.Encoding.Extensions.dll", + "ref/netstandard1.3/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/de/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/es/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/fr/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/it/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/ja/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/ko/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/ru/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/zh-hans/System.Text.Encoding.Extensions.xml", + "ref/netstandard1.3/zh-hant/System.Text.Encoding.Extensions.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Text.RegularExpressions/4.1.0": { + "sha512": "i88YCXpRTjCnoSQZtdlHkAOx4KNNik4hMy83n0+Ftlb7jvV6ZiZWMpnEZHhjBp6hQVh8gWd/iKNPzlPF7iyA2g==", + "type": "package", + "path": "System.Text.RegularExpressions/4.1.0", + "files": [ + "System.Text.RegularExpressions.4.1.0.nupkg.sha512", + "System.Text.RegularExpressions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/net463/System.Text.RegularExpressions.dll", + "lib/netcore50/System.Text.RegularExpressions.dll", + "lib/netstandard1.6/System.Text.RegularExpressions.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/net463/System.Text.RegularExpressions.dll", + "ref/netcore50/System.Text.RegularExpressions.dll", + "ref/netcore50/System.Text.RegularExpressions.xml", + "ref/netcore50/de/System.Text.RegularExpressions.xml", + "ref/netcore50/es/System.Text.RegularExpressions.xml", + "ref/netcore50/fr/System.Text.RegularExpressions.xml", + "ref/netcore50/it/System.Text.RegularExpressions.xml", + "ref/netcore50/ja/System.Text.RegularExpressions.xml", + "ref/netcore50/ko/System.Text.RegularExpressions.xml", + "ref/netcore50/ru/System.Text.RegularExpressions.xml", + "ref/netcore50/zh-hans/System.Text.RegularExpressions.xml", + "ref/netcore50/zh-hant/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/System.Text.RegularExpressions.dll", + "ref/netstandard1.0/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/de/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/es/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/fr/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/it/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/ja/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/ko/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/ru/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/zh-hans/System.Text.RegularExpressions.xml", + "ref/netstandard1.0/zh-hant/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/System.Text.RegularExpressions.dll", + "ref/netstandard1.3/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/de/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/es/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/fr/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/it/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/ja/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/ko/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/ru/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/zh-hans/System.Text.RegularExpressions.xml", + "ref/netstandard1.3/zh-hant/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/System.Text.RegularExpressions.dll", + "ref/netstandard1.6/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/de/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/es/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/fr/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/it/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/ja/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/ko/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/ru/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/zh-hans/System.Text.RegularExpressions.xml", + "ref/netstandard1.6/zh-hant/System.Text.RegularExpressions.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Threading/4.0.11": { + "sha512": "N+3xqIcg3VDKyjwwCGaZ9HawG9aC6cSDI+s7ROma310GQo8vilFZa86hqKppwTHleR/G0sfOzhvgnUxWCR/DrQ==", + "type": "package", + "path": "System.Threading/4.0.11", + "files": [ + "System.Threading.4.0.11.nupkg.sha512", + "System.Threading.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Threading.dll", + "lib/netstandard1.3/System.Threading.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Threading.dll", + "ref/netcore50/System.Threading.xml", + "ref/netcore50/de/System.Threading.xml", + "ref/netcore50/es/System.Threading.xml", + "ref/netcore50/fr/System.Threading.xml", + "ref/netcore50/it/System.Threading.xml", + "ref/netcore50/ja/System.Threading.xml", + "ref/netcore50/ko/System.Threading.xml", + "ref/netcore50/ru/System.Threading.xml", + "ref/netcore50/zh-hans/System.Threading.xml", + "ref/netcore50/zh-hant/System.Threading.xml", + "ref/netstandard1.0/System.Threading.dll", + "ref/netstandard1.0/System.Threading.xml", + "ref/netstandard1.0/de/System.Threading.xml", + "ref/netstandard1.0/es/System.Threading.xml", + "ref/netstandard1.0/fr/System.Threading.xml", + "ref/netstandard1.0/it/System.Threading.xml", + "ref/netstandard1.0/ja/System.Threading.xml", + "ref/netstandard1.0/ko/System.Threading.xml", + "ref/netstandard1.0/ru/System.Threading.xml", + "ref/netstandard1.0/zh-hans/System.Threading.xml", + "ref/netstandard1.0/zh-hant/System.Threading.xml", + "ref/netstandard1.3/System.Threading.dll", + "ref/netstandard1.3/System.Threading.xml", + "ref/netstandard1.3/de/System.Threading.xml", + "ref/netstandard1.3/es/System.Threading.xml", + "ref/netstandard1.3/fr/System.Threading.xml", + "ref/netstandard1.3/it/System.Threading.xml", + "ref/netstandard1.3/ja/System.Threading.xml", + "ref/netstandard1.3/ko/System.Threading.xml", + "ref/netstandard1.3/ru/System.Threading.xml", + "ref/netstandard1.3/zh-hans/System.Threading.xml", + "ref/netstandard1.3/zh-hant/System.Threading.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "runtimes/aot/lib/netcore50/System.Threading.dll" + ] + }, + "System.Threading.Tasks/4.0.11": { + "sha512": "k1S4Gc6IGwtHGT8188RSeGaX86Qw/wnrgNLshJvsdNUOPP9etMmo8S07c+UlOAx4K/xLuN9ivA1bD0LVurtIxQ==", + "type": "package", + "path": "System.Threading.Tasks/4.0.11", + "files": [ + "System.Threading.Tasks.4.0.11.nupkg.sha512", + "System.Threading.Tasks.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Threading.Tasks.dll", + "ref/netcore50/System.Threading.Tasks.xml", + "ref/netcore50/de/System.Threading.Tasks.xml", + "ref/netcore50/es/System.Threading.Tasks.xml", + "ref/netcore50/fr/System.Threading.Tasks.xml", + "ref/netcore50/it/System.Threading.Tasks.xml", + "ref/netcore50/ja/System.Threading.Tasks.xml", + "ref/netcore50/ko/System.Threading.Tasks.xml", + "ref/netcore50/ru/System.Threading.Tasks.xml", + "ref/netcore50/zh-hans/System.Threading.Tasks.xml", + "ref/netcore50/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.0/System.Threading.Tasks.dll", + "ref/netstandard1.0/System.Threading.Tasks.xml", + "ref/netstandard1.0/de/System.Threading.Tasks.xml", + "ref/netstandard1.0/es/System.Threading.Tasks.xml", + "ref/netstandard1.0/fr/System.Threading.Tasks.xml", + "ref/netstandard1.0/it/System.Threading.Tasks.xml", + "ref/netstandard1.0/ja/System.Threading.Tasks.xml", + "ref/netstandard1.0/ko/System.Threading.Tasks.xml", + "ref/netstandard1.0/ru/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.0/zh-hant/System.Threading.Tasks.xml", + "ref/netstandard1.3/System.Threading.Tasks.dll", + "ref/netstandard1.3/System.Threading.Tasks.xml", + "ref/netstandard1.3/de/System.Threading.Tasks.xml", + "ref/netstandard1.3/es/System.Threading.Tasks.xml", + "ref/netstandard1.3/fr/System.Threading.Tasks.xml", + "ref/netstandard1.3/it/System.Threading.Tasks.xml", + "ref/netstandard1.3/ja/System.Threading.Tasks.xml", + "ref/netstandard1.3/ko/System.Threading.Tasks.xml", + "ref/netstandard1.3/ru/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hans/System.Threading.Tasks.xml", + "ref/netstandard1.3/zh-hant/System.Threading.Tasks.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Threading.Tasks.Extensions/4.0.0": { + "sha512": "pH4FZDsZQ/WmgJtN4LWYmRdJAEeVkyriSwrv2Teoe5FOU0Yxlb6II6GL8dBPOfRmutHGATduj3ooMt7dJ2+i+w==", + "type": "package", + "path": "System.Threading.Tasks.Extensions/4.0.0", + "files": [ + "System.Threading.Tasks.Extensions.4.0.0.nupkg.sha512", + "System.Threading.Tasks.Extensions.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll", + "lib/netstandard1.0/System.Threading.Tasks.Extensions.xml", + "lib/portable-net45+win8+wp8+wpa81/System.Threading.Tasks.Extensions.dll", + "lib/portable-net45+win8+wp8+wpa81/System.Threading.Tasks.Extensions.xml" + ] + }, + "System.Threading.Timer/4.0.1": { + "sha512": "saGfUV8uqVW6LeURiqxcGhZ24PzuRNaUBtbhVeuUAvky1naH395A/1nY0P2bWvrw/BreRtIB/EzTDkGBpqCwEw==", + "type": "package", + "path": "System.Threading.Timer/4.0.1", + "files": [ + "System.Threading.Timer.4.0.1.nupkg.sha512", + "System.Threading.Timer.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net451/_._", + "lib/portable-net451+win81+wpa81/_._", + "lib/win81/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net451/_._", + "ref/netcore50/System.Threading.Timer.dll", + "ref/netcore50/System.Threading.Timer.xml", + "ref/netcore50/de/System.Threading.Timer.xml", + "ref/netcore50/es/System.Threading.Timer.xml", + "ref/netcore50/fr/System.Threading.Timer.xml", + "ref/netcore50/it/System.Threading.Timer.xml", + "ref/netcore50/ja/System.Threading.Timer.xml", + "ref/netcore50/ko/System.Threading.Timer.xml", + "ref/netcore50/ru/System.Threading.Timer.xml", + "ref/netcore50/zh-hans/System.Threading.Timer.xml", + "ref/netcore50/zh-hant/System.Threading.Timer.xml", + "ref/netstandard1.2/System.Threading.Timer.dll", + "ref/netstandard1.2/System.Threading.Timer.xml", + "ref/netstandard1.2/de/System.Threading.Timer.xml", + "ref/netstandard1.2/es/System.Threading.Timer.xml", + "ref/netstandard1.2/fr/System.Threading.Timer.xml", + "ref/netstandard1.2/it/System.Threading.Timer.xml", + "ref/netstandard1.2/ja/System.Threading.Timer.xml", + "ref/netstandard1.2/ko/System.Threading.Timer.xml", + "ref/netstandard1.2/ru/System.Threading.Timer.xml", + "ref/netstandard1.2/zh-hans/System.Threading.Timer.xml", + "ref/netstandard1.2/zh-hant/System.Threading.Timer.xml", + "ref/portable-net451+win81+wpa81/_._", + "ref/win81/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Xml.ReaderWriter/4.0.11": { + "sha512": "ZIiLPsf67YZ9zgr31vzrFaYQqxRPX9cVHjtPSnmx4eN6lbS/yEyYNr2vs1doGDEscF0tjCZFsk9yUg1sC9e8tg==", + "type": "package", + "path": "System.Xml.ReaderWriter/4.0.11", + "files": [ + "System.Xml.ReaderWriter.4.0.11.nupkg.sha512", + "System.Xml.ReaderWriter.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Xml.ReaderWriter.dll", + "lib/netstandard1.3/System.Xml.ReaderWriter.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Xml.ReaderWriter.dll", + "ref/netcore50/System.Xml.ReaderWriter.xml", + "ref/netcore50/de/System.Xml.ReaderWriter.xml", + "ref/netcore50/es/System.Xml.ReaderWriter.xml", + "ref/netcore50/fr/System.Xml.ReaderWriter.xml", + "ref/netcore50/it/System.Xml.ReaderWriter.xml", + "ref/netcore50/ja/System.Xml.ReaderWriter.xml", + "ref/netcore50/ko/System.Xml.ReaderWriter.xml", + "ref/netcore50/ru/System.Xml.ReaderWriter.xml", + "ref/netcore50/zh-hans/System.Xml.ReaderWriter.xml", + "ref/netcore50/zh-hant/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/System.Xml.ReaderWriter.dll", + "ref/netstandard1.0/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/de/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/es/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/fr/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/it/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/ja/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/ko/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/ru/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/zh-hans/System.Xml.ReaderWriter.xml", + "ref/netstandard1.0/zh-hant/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/System.Xml.ReaderWriter.dll", + "ref/netstandard1.3/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/de/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/es/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/fr/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/it/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/ja/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/ko/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/ru/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/zh-hans/System.Xml.ReaderWriter.xml", + "ref/netstandard1.3/zh-hant/System.Xml.ReaderWriter.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + }, + "System.Xml.XDocument/4.0.11": { + "sha512": "Mk2mKmPi0nWaoiYeotq1dgeNK1fqWh61+EK+w4Wu8SWuTYLzpUnschb59bJtGywaPq7SmTuPf44wrXRwbIrukg==", + "type": "package", + "path": "System.Xml.XDocument/4.0.11", + "files": [ + "System.Xml.XDocument.4.0.11.nupkg.sha512", + "System.Xml.XDocument.nuspec", + "ThirdPartyNotices.txt", + "dotnet_library_license.txt", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/netcore50/System.Xml.XDocument.dll", + "lib/netstandard1.3/System.Xml.XDocument.dll", + "lib/portable-net45+win8+wp8+wpa81/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/System.Xml.XDocument.dll", + "ref/netcore50/System.Xml.XDocument.xml", + "ref/netcore50/de/System.Xml.XDocument.xml", + "ref/netcore50/es/System.Xml.XDocument.xml", + "ref/netcore50/fr/System.Xml.XDocument.xml", + "ref/netcore50/it/System.Xml.XDocument.xml", + "ref/netcore50/ja/System.Xml.XDocument.xml", + "ref/netcore50/ko/System.Xml.XDocument.xml", + "ref/netcore50/ru/System.Xml.XDocument.xml", + "ref/netcore50/zh-hans/System.Xml.XDocument.xml", + "ref/netcore50/zh-hant/System.Xml.XDocument.xml", + "ref/netstandard1.0/System.Xml.XDocument.dll", + "ref/netstandard1.0/System.Xml.XDocument.xml", + "ref/netstandard1.0/de/System.Xml.XDocument.xml", + "ref/netstandard1.0/es/System.Xml.XDocument.xml", + "ref/netstandard1.0/fr/System.Xml.XDocument.xml", + "ref/netstandard1.0/it/System.Xml.XDocument.xml", + "ref/netstandard1.0/ja/System.Xml.XDocument.xml", + "ref/netstandard1.0/ko/System.Xml.XDocument.xml", + "ref/netstandard1.0/ru/System.Xml.XDocument.xml", + "ref/netstandard1.0/zh-hans/System.Xml.XDocument.xml", + "ref/netstandard1.0/zh-hant/System.Xml.XDocument.xml", + "ref/netstandard1.3/System.Xml.XDocument.dll", + "ref/netstandard1.3/System.Xml.XDocument.xml", + "ref/netstandard1.3/de/System.Xml.XDocument.xml", + "ref/netstandard1.3/es/System.Xml.XDocument.xml", + "ref/netstandard1.3/fr/System.Xml.XDocument.xml", + "ref/netstandard1.3/it/System.Xml.XDocument.xml", + "ref/netstandard1.3/ja/System.Xml.XDocument.xml", + "ref/netstandard1.3/ko/System.Xml.XDocument.xml", + "ref/netstandard1.3/ru/System.Xml.XDocument.xml", + "ref/netstandard1.3/zh-hans/System.Xml.XDocument.xml", + "ref/netstandard1.3/zh-hant/System.Xml.XDocument.xml", + "ref/portable-net45+win8+wp8+wpa81/_._", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._" + ] + } + }, + "projectFileDependencyGroups": { + "": [], + ".NETFramework,Version=v4.6": [ + "System.Collections >= 4.0.0", + "System.Net >= 4.0.0", + "System.Net.Http >= 4.0.0", + "System.Runtime >= 4.0.0", + "System.Threading >= 4.0.0", + "System.Threading.Tasks >= 4.0.0", + "System.Xml >= 4.0.0" + ], + ".NETStandard,Version=v1.6": [ + "NETStandard.Library >= 1.6.0", + "System.Collections >= 4.0.11", + "System.Diagnostics.Debug >= 4.0.11", + "System.Diagnostics.Tools >= 4.0.1", + "System.IO >= 4.1.0", + "System.Linq >= 4.1.0", + "System.Net.Http >= 4.1.0", + "System.Net.Primitives >= 4.0.11", + "System.Net.Sockets >= 4.1.0", + "System.Resources.ResourceManager >= 4.0.1", + "System.Runtime >= 4.1.0", + "System.Runtime.Extensions >= 4.1.0", + "System.Runtime.InteropServices.RuntimeInformation >= 4.0.0", + "System.Text.Encoding >= 4.0.11", + "System.Text.Encoding.Extensions >= 4.0.11", + "System.Threading >= 4.0.11", + "System.Threading.Tasks >= 4.0.11", + "System.Threading.Timer >= 4.0.1", + "System.Xml.ReaderWriter >= 4.0.11" + ] + }, + "tools": {}, + "projectFileToolGroups": {} +}
\ No newline at end of file |
