Coding Styles

This commit is contained in:
BlubbFish 2019-12-03 18:44:25 +01:00
parent 186792fde8
commit c1e8637516
72 changed files with 15024 additions and 15932 deletions

View File

@ -1,9 +1,8 @@
namespace Unosquare.Swan.Abstractions using System;
{ using System.Threading;
using System; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Abstractions {
/// <summary> /// <summary>
/// A base implementation of an Application service containing a worker task that performs background processing. /// A base implementation of an Application service containing a worker task that performs background processing.
/// </summary> /// </summary>
@ -44,19 +43,17 @@
/// </code> /// </code>
/// </example> /// </example>
public abstract class AppWorkerBase public abstract class AppWorkerBase
: IWorker, IDisposable : IWorker, IDisposable {
{ private readonly Object _syncLock = new Object();
private readonly object _syncLock = new object();
private AppWorkerState _workerState = AppWorkerState.Stopped; private AppWorkerState _workerState = AppWorkerState.Stopped;
private CancellationTokenSource _tokenSource; private CancellationTokenSource _tokenSource;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AppWorkerBase"/> class. /// Initializes a new instance of the <see cref="AppWorkerBase"/> class.
/// </summary> /// </summary>
protected AppWorkerBase() protected AppWorkerBase() {
{ this.State = AppWorkerState.Stopped;
State = AppWorkerState.Stopped; this.IsBusy = false;
IsBusy = false;
} }
/// <summary> /// <summary>
@ -73,20 +70,19 @@
/// <value> /// <value>
/// The state. /// The state.
/// </value> /// </value>
public AppWorkerState State public AppWorkerState State {
{ get => this._workerState;
get => _workerState;
private set private set {
{ lock(this._syncLock) {
lock (_syncLock) if(value == this._workerState) {
{ return;
if (value == _workerState) return; }
$"Service state changing from {State} to {value}".Debug(GetType().Name); $"Service state changing from {this.State} to {value}".Debug(this.GetType().Name);
var newState = value; AppWorkerState newState = value;
var oldState = _workerState; AppWorkerState oldState = this._workerState;
_workerState = value; this._workerState = value;
StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState)); StateChanged?.Invoke(this, new AppWorkerStateChangedEventArgs(oldState, newState));
} }
@ -99,7 +95,7 @@
/// <value> /// <value>
/// The cancellation token. /// The cancellation token.
/// </value> /// </value>
public CancellationToken CancellationToken => _tokenSource?.Token ?? default; public CancellationToken CancellationToken => this._tokenSource?.Token ?? default;
/// <summary> /// <summary>
/// Gets a value indicating whether the thread is busy. /// Gets a value indicating whether the thread is busy.
@ -107,7 +103,9 @@
/// <value> /// <value>
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>. /// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsBusy { get; private set; } public Boolean IsBusy {
get; private set;
}
#endregion #endregion
@ -117,32 +115,31 @@
/// Performs internal service initialization tasks required before starting the service. /// Performs internal service initialization tasks required before starting the service.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception> /// <exception cref="InvalidOperationException">Service cannot be initialized because it seems to be currently running.</exception>
public virtual void Initialize() => CheckIsRunning(); public virtual void Initialize() => this.CheckIsRunning();
/// <inheritdoc/> /// <inheritdoc/>
/// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception> /// <exception cref="InvalidOperationException">Service cannot be started because it seems to be currently running.</exception>
public virtual void Start() public virtual void Start() {
{ this.CheckIsRunning();
CheckIsRunning();
CreateWorker(); this.CreateWorker();
State = AppWorkerState.Running; this.State = AppWorkerState.Running;
} }
/// <inheritdoc/> /// <inheritdoc/>
/// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception> /// <exception cref="InvalidOperationException">Service cannot be stopped because it is not running.</exception>
public virtual void Stop() public virtual void Stop() {
{ if(this.State != AppWorkerState.Running) {
if (State != AppWorkerState.Running)
return; return;
}
_tokenSource?.Cancel(); this._tokenSource?.Cancel();
"Service stop requested.".Debug(GetType().Name); "Service stop requested.".Debug(this.GetType().Name);
State = AppWorkerState.Stopped; this.State = AppWorkerState.Stopped;
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => _tokenSource?.Dispose(); public void Dispose() => this._tokenSource?.Dispose();
#endregion #endregion
@ -153,12 +150,12 @@
/// </summary> /// </summary>
/// <param name="ex">The ex.</param> /// <param name="ex">The ex.</param>
protected virtual void OnWorkerThreadLoopException(Exception ex) protected virtual void OnWorkerThreadLoopException(Exception ex)
=> "Service exception detected.".Debug(GetType().Name, ex); => "Service exception detected.".Debug(this.GetType().Name, ex);
/// <summary> /// <summary>
/// This method is called when the user loop has exited. /// This method is called when the user loop has exited.
/// </summary> /// </summary>
protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(GetType().Name); protected virtual void OnWorkerThreadExit() => "Service thread is stopping.".Debug(this.GetType().Name);
/// <summary> /// <summary>
/// Implement this method as a loop that checks whether CancellationPending has been set to true /// Implement this method as a loop that checks whether CancellationPending has been set to true
@ -167,46 +164,38 @@
/// <returns>A task representing the execution of the worker.</returns> /// <returns>A task representing the execution of the worker.</returns>
protected abstract Task WorkerThreadLoop(); protected abstract Task WorkerThreadLoop();
private void CheckIsRunning() private void CheckIsRunning() {
{ if(this.State != AppWorkerState.Stopped) {
if (State != AppWorkerState.Stopped)
throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running."); throw new InvalidOperationException("Service cannot be initialized because it seems to be currently running.");
} }
}
private void CreateWorker() private void CreateWorker() {
{ this._tokenSource = new CancellationTokenSource();
_tokenSource = new CancellationTokenSource(); _ = this._tokenSource.Token.Register(() => {
_tokenSource.Token.Register(() => this.IsBusy = false;
{ this.OnWorkerThreadExit();
IsBusy = false;
OnWorkerThreadExit();
}); });
Task.Run(async () => _ = Task.Run(async () => {
{ this.IsBusy = true;
IsBusy = true;
try try {
{ while(!this.CancellationToken.IsCancellationRequested) {
while (!CancellationToken.IsCancellationRequested) await this.WorkerThreadLoop().ConfigureAwait(false);
{
await WorkerThreadLoop().ConfigureAwait(false);
} }
} } catch(AggregateException) {
catch (AggregateException)
{
// Ignored // Ignored
} } catch(Exception ex) {
catch (Exception ex) ex.Log(this.GetType().Name);
{ this.OnWorkerThreadLoopException(ex);
ex.Log(GetType().Name);
OnWorkerThreadLoopException(ex);
if (!_tokenSource.IsCancellationRequested) if(!this._tokenSource.IsCancellationRequested) {
_tokenSource.Cancel(); this._tokenSource.Cancel();
}
} }
}, },
_tokenSource.Token); this._tokenSource.Token);
} }
#endregion #endregion

View File

@ -1,21 +1,18 @@
namespace Unosquare.Swan.Abstractions using System;
{
using System;
namespace Unosquare.Swan.Abstractions {
/// <summary> /// <summary>
/// Represents event arguments whenever the state of an application service changes. /// Represents event arguments whenever the state of an application service changes.
/// </summary> /// </summary>
public class AppWorkerStateChangedEventArgs : EventArgs public class AppWorkerStateChangedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AppWorkerStateChangedEventArgs" /> class. /// Initializes a new instance of the <see cref="AppWorkerStateChangedEventArgs" /> class.
/// </summary> /// </summary>
/// <param name="oldState">The old state.</param> /// <param name="oldState">The old state.</param>
/// <param name="newState">The new state.</param> /// <param name="newState">The new state.</param>
public AppWorkerStateChangedEventArgs(AppWorkerState oldState, AppWorkerState newState) public AppWorkerStateChangedEventArgs(AppWorkerState oldState, AppWorkerState newState) {
{ this.OldState = oldState;
OldState = oldState; this.NewState = newState;
NewState = newState;
} }
/// <summary> /// <summary>
@ -24,7 +21,9 @@
/// <value> /// <value>
/// The new state. /// The new state.
/// </value> /// </value>
public AppWorkerState NewState { get; } public AppWorkerState NewState {
get;
}
/// <summary> /// <summary>
/// Gets the old state. /// Gets the old state.
@ -32,6 +31,8 @@
/// <value> /// <value>
/// The old state. /// The old state.
/// </value> /// </value>
public AppWorkerState OldState { get; } public AppWorkerState OldState {
get;
}
} }
} }

View File

@ -1,8 +1,7 @@
namespace Unosquare.Swan.Components using System;
{ using System.Runtime.InteropServices;
using System;
using System.Runtime.InteropServices;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// A fixed-size buffer that acts as an infinite length one. /// A fixed-size buffer that acts as an infinite length one.
/// This buffer is backed by unmanaged, very fast memory so ensure you call /// This buffer is backed by unmanaged, very fast memory so ensure you call
@ -10,24 +9,22 @@ namespace Unosquare.Swan.Components
/// Only for Windows. /// Only for Windows.
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> /// <seealso cref="System.IDisposable" />
public sealed class CircularBuffer : IDisposable public sealed class CircularBuffer : IDisposable {
{ private readonly Object _syncLock = new Object();
private readonly object _syncLock = new object();
private IntPtr _buffer = IntPtr.Zero; private IntPtr _buffer = IntPtr.Zero;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer"/> class. /// Initializes a new instance of the <see cref="CircularBuffer"/> class.
/// </summary> /// </summary>
/// <param name="bufferLength">Length of the buffer.</param> /// <param name="bufferLength">Length of the buffer.</param>
public CircularBuffer(int bufferLength) public CircularBuffer(Int32 bufferLength) {
{
#if !NET452 #if !NET452
if (Runtime.OS != Swan.OperatingSystem.Windows) if (Runtime.OS != Swan.OperatingSystem.Windows)
throw new InvalidOperationException("CircularBuffer component is only available in Windows"); throw new InvalidOperationException("CircularBuffer component is only available in Windows");
#endif #endif
Length = bufferLength; this.Length = bufferLength;
_buffer = Marshal.AllocHGlobal(Length); this._buffer = Marshal.AllocHGlobal(this.Length);
} }
#region Properties #region Properties
@ -38,7 +35,9 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The length. /// The length.
/// </value> /// </value>
public int Length { get; private set; } public Int32 Length {
get; private set;
}
/// <summary> /// <summary>
/// Gets the current, 0-based read index. /// Gets the current, 0-based read index.
@ -46,7 +45,9 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The index of the read. /// The index of the read.
/// </value> /// </value>
public int ReadIndex { get; private set; } public Int32 ReadIndex {
get; private set;
}
/// <summary> /// <summary>
/// Gets the current, 0-based write index. /// Gets the current, 0-based write index.
@ -54,7 +55,9 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The index of the write. /// The index of the write.
/// </value> /// </value>
public int WriteIndex { get; private set; } public Int32 WriteIndex {
get; private set;
}
/// <summary> /// <summary>
/// Gets an the object associated with the last write. /// Gets an the object associated with the last write.
@ -70,7 +73,9 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The readable count. /// The readable count.
/// </value> /// </value>
public int ReadableCount { get; private set; } public Int32 ReadableCount {
get; private set;
}
/// <summary> /// <summary>
/// Gets the number of bytes that can be written. /// Gets the number of bytes that can be written.
@ -78,7 +83,7 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The writable count. /// The writable count.
/// </value> /// </value>
public int WritableCount => Length - ReadableCount; public Int32 WritableCount => this.Length - this.ReadableCount;
/// <summary> /// <summary>
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0). /// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
@ -86,7 +91,7 @@ namespace Unosquare.Swan.Components
/// <value> /// <value>
/// The capacity percent. /// The capacity percent.
/// </value> /// </value>
public double CapacityPercent => 1.0 * ReadableCount / Length; public Double CapacityPercent => 1.0 * this.ReadableCount / this.Length;
#endregion #endregion
@ -101,29 +106,26 @@ namespace Unosquare.Swan.Components
/// <exception cref="System.InvalidOperationException"> /// <exception cref="System.InvalidOperationException">
/// Exception that is thrown when a method call is invalid for the object's current state. /// Exception that is thrown when a method call is invalid for the object's current state.
/// </exception> /// </exception>
public void Read(int requestedBytes, byte[] target, int targetOffset) public void Read(Int32 requestedBytes, Byte[] target, Int32 targetOffset) {
{ lock(this._syncLock) {
lock (_syncLock) if(requestedBytes > this.ReadableCount) {
{
if (requestedBytes > ReadableCount)
{
throw new InvalidOperationException( throw new InvalidOperationException(
$"Unable to read {requestedBytes} bytes. Only {ReadableCount} bytes are available"); $"Unable to read {requestedBytes} bytes. Only {this.ReadableCount} bytes are available");
} }
var readCount = 0; Int32 readCount = 0;
while (readCount < requestedBytes) while(readCount < requestedBytes) {
{ Int32 copyLength = Math.Min(this.Length - this.ReadIndex, requestedBytes - readCount);
var copyLength = Math.Min(Length - ReadIndex, requestedBytes - readCount); IntPtr sourcePtr = this._buffer + this.ReadIndex;
var sourcePtr = _buffer + ReadIndex;
Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength); Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength);
readCount += copyLength; readCount += copyLength;
ReadIndex += copyLength; this.ReadIndex += copyLength;
ReadableCount -= copyLength; this.ReadableCount -= copyLength;
if (ReadIndex >= Length) if(this.ReadIndex >= this.Length) {
ReadIndex = 0; this.ReadIndex = 0;
}
} }
} }
} }
@ -136,58 +138,54 @@ namespace Unosquare.Swan.Components
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <param name="writeTag">The write tag.</param> /// <param name="writeTag">The write tag.</param>
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception> /// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception>
public void Write(IntPtr source, int length, TimeSpan writeTag) public void Write(IntPtr source, Int32 length, TimeSpan writeTag) {
{ lock(this._syncLock) {
lock (_syncLock) if(this.ReadableCount + length > this.Length) {
{
if (ReadableCount + length > Length)
{
throw new InvalidOperationException( throw new InvalidOperationException(
$"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room."); $"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room.");
} }
var writeCount = 0; Int32 writeCount = 0;
while (writeCount < length) while(writeCount < length) {
{ Int32 copyLength = Math.Min(this.Length - this.WriteIndex, length - writeCount);
var copyLength = Math.Min(Length - WriteIndex, length - writeCount); IntPtr sourcePtr = source + writeCount;
var sourcePtr = source + writeCount; IntPtr targetPtr = this._buffer + this.WriteIndex;
var targetPtr = _buffer + WriteIndex; CopyMemory(targetPtr, sourcePtr, (UInt32)copyLength);
CopyMemory(targetPtr, sourcePtr, (uint) copyLength);
writeCount += copyLength; writeCount += copyLength;
WriteIndex += copyLength; this.WriteIndex += copyLength;
ReadableCount += copyLength; this.ReadableCount += copyLength;
if (WriteIndex >= Length) if(this.WriteIndex >= this.Length) {
WriteIndex = 0; this.WriteIndex = 0;
}
} }
WriteTag = writeTag; this.WriteTag = writeTag;
} }
} }
/// <summary> /// <summary>
/// Resets all states as if this buffer had just been created. /// Resets all states as if this buffer had just been created.
/// </summary> /// </summary>
public void Clear() public void Clear() {
{ lock(this._syncLock) {
lock (_syncLock) this.WriteIndex = 0;
{ this.ReadIndex = 0;
WriteIndex = 0; this.WriteTag = TimeSpan.MinValue;
ReadIndex = 0; this.ReadableCount = 0;
WriteTag = TimeSpan.MinValue;
ReadableCount = 0;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._buffer == IntPtr.Zero) {
if (_buffer == IntPtr.Zero) return; return;
}
Marshal.FreeHGlobal(_buffer); Marshal.FreeHGlobal(this._buffer);
_buffer = IntPtr.Zero; this._buffer = IntPtr.Zero;
Length = 0; this.Length = 0;
} }
/// <summary> /// <summary>
@ -197,7 +195,7 @@ namespace Unosquare.Swan.Components
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
[DllImport("kernel32")] [DllImport("kernel32")]
public static extern void CopyMemory(IntPtr destination, IntPtr source, uint length); public static extern void CopyMemory(IntPtr destination, IntPtr source, UInt32 length);
#endregion #endregion
} }

View File

@ -1,10 +1,9 @@
namespace Unosquare.Swan.Components using System;
{ using System.IO;
using System; using System.Linq;
using System.IO; using System.Xml.Linq;
using System.Linq;
using System.Xml.Linq;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents a CsProjFile (and FsProjFile) parser. /// Represents a CsProjFile (and FsProjFile) parser.
/// </summary> /// </summary>
@ -15,19 +14,17 @@
/// <seealso cref="System.IDisposable" /> /// <seealso cref="System.IDisposable" />
public class CsProjFile<T> public class CsProjFile<T>
: IDisposable : IDisposable
where T : CsProjMetadataBase where T : CsProjMetadataBase {
{
private readonly Stream _stream; private readonly Stream _stream;
private readonly bool _leaveOpen; private readonly Boolean _leaveOpen;
private readonly XDocument _xmlDocument; private readonly XDocument _xmlDocument;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CsProjFile{T}"/> class. /// Initializes a new instance of the <see cref="CsProjFile{T}"/> class.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
public CsProjFile(string filename = null) public CsProjFile(String filename = null)
: this(OpenFile(filename)) : this(OpenFile(filename)) {
{
// placeholder // placeholder
} }
@ -37,23 +34,21 @@
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param> /// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception> /// <exception cref="ArgumentException">Project file is not of the new .csproj type.</exception>
public CsProjFile(Stream stream, bool leaveOpen = false) public CsProjFile(Stream stream, Boolean leaveOpen = false) {
{ this._stream = stream;
_stream = stream; this._leaveOpen = leaveOpen;
_leaveOpen = leaveOpen;
_xmlDocument = XDocument.Load(stream); this._xmlDocument = XDocument.Load(stream);
var projectElement = _xmlDocument.Descendants("Project").FirstOrDefault(); XElement projectElement = this._xmlDocument.Descendants("Project").FirstOrDefault();
var sdkAttribute = projectElement?.Attribute("Sdk"); XAttribute sdkAttribute = projectElement?.Attribute("Sdk");
var sdk = sdkAttribute?.Value; String sdk = sdkAttribute?.Value;
if (sdk != "Microsoft.NET.Sdk" && sdk != "Microsoft.NET.Sdk.Web") if(sdk != "Microsoft.NET.Sdk" && sdk != "Microsoft.NET.Sdk.Web") {
{
throw new ArgumentException("Project file is not of the new .csproj type."); throw new ArgumentException("Project file is not of the new .csproj type.");
} }
Metadata = Activator.CreateInstance<T>(); this.Metadata = Activator.CreateInstance<T>();
Metadata.SetData(_xmlDocument); this.Metadata.SetData(this._xmlDocument);
} }
/// <summary> /// <summary>
@ -62,46 +57,43 @@
/// <value> /// <value>
/// The nu get metadata. /// The nu get metadata.
/// </value> /// </value>
public T Metadata { get; } public T Metadata {
get;
}
/// <summary> /// <summary>
/// Saves this instance. /// Saves this instance.
/// </summary> /// </summary>
public void Save() public void Save() {
{ this._stream.SetLength(0);
_stream.SetLength(0); this._stream.Position = 0;
_stream.Position = 0;
_xmlDocument.Save(_stream); this._xmlDocument.Save(this._stream);
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(!this._leaveOpen) {
if (!_leaveOpen) this._stream?.Dispose();
{
_stream?.Dispose();
} }
} }
private static FileStream OpenFile(string filename) private static FileStream OpenFile(String filename) {
{ if(filename == null) {
if (filename == null)
{
filename = Directory filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.csproj", SearchOption.TopDirectoryOnly) .EnumerateFiles(Directory.GetCurrentDirectory(), "*.csproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault(); .FirstOrDefault();
} }
if (filename == null) if(filename == null) {
{
filename = Directory filename = Directory
.EnumerateFiles(Directory.GetCurrentDirectory(), "*.fsproj", SearchOption.TopDirectoryOnly) .EnumerateFiles(Directory.GetCurrentDirectory(), "*.fsproj", SearchOption.TopDirectoryOnly)
.FirstOrDefault(); .FirstOrDefault();
} }
if (string.IsNullOrWhiteSpace(filename)) if(String.IsNullOrWhiteSpace(filename)) {
throw new ArgumentNullException(nameof(filename)); throw new ArgumentNullException(nameof(filename));
}
return File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite); return File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
} }

View File

@ -1,8 +1,9 @@
namespace Unosquare.Swan.Components using System;
{ using System.Linq;
using System.Linq; using System.Xml.Linq;
using System.Xml.Linq;
namespace Unosquare.Swan.Components
{
/// <summary> /// <summary>
/// Represents a CsProj metadata abstract class /// Represents a CsProj metadata abstract class
/// to use with <c>CsProjFile</c> parser. /// to use with <c>CsProjFile</c> parser.
@ -17,7 +18,7 @@
/// <value> /// <value>
/// The package identifier. /// The package identifier.
/// </value> /// </value>
public string PackageId => FindElement(nameof(PackageId))?.Value; public String PackageId => this.FindElement(nameof(this.PackageId))?.Value;
/// <summary> /// <summary>
/// Gets the name of the assembly. /// Gets the name of the assembly.
@ -25,7 +26,7 @@
/// <value> /// <value>
/// The name of the assembly. /// The name of the assembly.
/// </value> /// </value>
public string AssemblyName => FindElement(nameof(AssemblyName))?.Value; public String AssemblyName => this.FindElement(nameof(this.AssemblyName))?.Value;
/// <summary> /// <summary>
/// Gets the target frameworks. /// Gets the target frameworks.
@ -33,7 +34,7 @@
/// <value> /// <value>
/// The target frameworks. /// The target frameworks.
/// </value> /// </value>
public string TargetFrameworks => FindElement(nameof(TargetFrameworks))?.Value; public String TargetFrameworks => this.FindElement(nameof(this.TargetFrameworks))?.Value;
/// <summary> /// <summary>
/// Gets the target framework. /// Gets the target framework.
@ -41,7 +42,7 @@
/// <value> /// <value>
/// The target framework. /// The target framework.
/// </value> /// </value>
public string TargetFramework => FindElement(nameof(TargetFramework))?.Value; public String TargetFramework => this.FindElement(nameof(this.TargetFramework))?.Value;
/// <summary> /// <summary>
/// Gets the version. /// Gets the version.
@ -49,25 +50,25 @@
/// <value> /// <value>
/// The version. /// The version.
/// </value> /// </value>
public string Version => FindElement(nameof(Version))?.Value; public String Version => this.FindElement(nameof(this.Version))?.Value;
/// <summary> /// <summary>
/// Parses the cs proj tags. /// Parses the cs proj tags.
/// </summary> /// </summary>
/// <param name="args">The arguments.</param> /// <param name="args">The arguments.</param>
public abstract void ParseCsProjTags(ref string[] args); public abstract void ParseCsProjTags(ref String[] args);
/// <summary> /// <summary>
/// Sets the data. /// Sets the data.
/// </summary> /// </summary>
/// <param name="xmlDocument">The XML document.</param> /// <param name="xmlDocument">The XML document.</param>
public void SetData(XDocument xmlDocument) => _xmlDocument = xmlDocument; public void SetData(XDocument xmlDocument) => this._xmlDocument = xmlDocument;
/// <summary> /// <summary>
/// Finds the element. /// Finds the element.
/// </summary> /// </summary>
/// <param name="elementName">Name of the element.</param> /// <param name="elementName">Name of the element.</param>
/// <returns>A XElement.</returns> /// <returns>A XElement.</returns>
protected XElement FindElement(string elementName) => _xmlDocument.Descendants(elementName).FirstOrDefault(); protected XElement FindElement(String elementName) => this._xmlDocument.Descendants(elementName).FirstOrDefault();
} }
} }

View File

@ -1,11 +1,10 @@
namespace Unosquare.Swan.Components using System;
{ using System.Diagnostics;
using System; using System.Threading;
using System.Diagnostics; using System.Threading.Tasks;
using System.Threading; using Unosquare.Swan.Abstractions;
using System.Threading.Tasks;
using Abstractions;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents logic providing several delay mechanisms. /// Represents logic providing several delay mechanisms.
/// </summary> /// </summary>
@ -28,28 +27,23 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public sealed class DelayProvider : IDisposable public sealed class DelayProvider : IDisposable {
{ private readonly Object _syncRoot = new Object();
private readonly object _syncRoot = new object();
private readonly Stopwatch _delayStopwatch = new Stopwatch(); private readonly Stopwatch _delayStopwatch = new Stopwatch();
private bool _isDisposed; private Boolean _isDisposed;
private IWaitEvent _delayEvent; private IWaitEvent _delayEvent;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DelayProvider"/> class. /// Initializes a new instance of the <see cref="DelayProvider"/> class.
/// </summary> /// </summary>
/// <param name="strategy">The strategy.</param> /// <param name="strategy">The strategy.</param>
public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay) public DelayProvider(DelayStrategy strategy = DelayStrategy.TaskDelay) => this.Strategy = strategy;
{
Strategy = strategy;
}
/// <summary> /// <summary>
/// Enumerates the different ways of providing delays. /// Enumerates the different ways of providing delays.
/// </summary> /// </summary>
public enum DelayStrategy public enum DelayStrategy {
{
/// <summary> /// <summary>
/// Using the Thread.Sleep(15) mechanism. /// Using the Thread.Sleep(15) mechanism.
/// </summary> /// </summary>
@ -69,22 +63,23 @@
/// <summary> /// <summary>
/// Gets the selected delay strategy. /// Gets the selected delay strategy.
/// </summary> /// </summary>
public DelayStrategy Strategy { get; } public DelayStrategy Strategy {
get;
}
/// <summary> /// <summary>
/// Creates the smallest possible, synchronous delay based on the selected strategy. /// Creates the smallest possible, synchronous delay based on the selected strategy.
/// </summary> /// </summary>
/// <returns>The elapsed time of the delay.</returns> /// <returns>The elapsed time of the delay.</returns>
public TimeSpan WaitOne() public TimeSpan WaitOne() {
{ lock(this._syncRoot) {
lock (_syncRoot) if(this._isDisposed) {
{ return TimeSpan.Zero;
if (_isDisposed) return TimeSpan.Zero; }
_delayStopwatch.Restart(); this._delayStopwatch.Restart();
switch (Strategy) switch(this.Strategy) {
{
case DelayStrategy.ThreadSleep: case DelayStrategy.ThreadSleep:
DelaySleep(); DelaySleep();
break; break;
@ -93,25 +88,26 @@
break; break;
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
case DelayStrategy.ThreadPool: case DelayStrategy.ThreadPool:
DelayThreadPool(); this.DelayThreadPool();
break; break;
#endif #endif
} }
return _delayStopwatch.Elapsed; return this._delayStopwatch.Elapsed;
} }
} }
#region Dispose Pattern #region Dispose Pattern
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ lock(this._syncRoot) {
lock (_syncRoot) if(this._isDisposed) {
{ return;
if (_isDisposed) return; }
_isDisposed = true;
_delayEvent?.Dispose(); this._isDisposed = true;
this._delayEvent?.Dispose();
} }
} }
@ -124,19 +120,18 @@
private static void DelayTask() => Task.Delay(1).Wait(); private static void DelayTask() => Task.Delay(1).Wait();
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
private void DelayThreadPool() private void DelayThreadPool() {
{ if(this._delayEvent == null) {
if (_delayEvent == null) this._delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true);
_delayEvent = WaitEventFactory.Create(isCompleted: true, useSlim: true); }
_delayEvent.Begin(); this._delayEvent.Begin();
ThreadPool.QueueUserWorkItem((s) => _ = ThreadPool.QueueUserWorkItem((s) => {
{
DelaySleep(); DelaySleep();
_delayEvent.Complete(); this._delayEvent.Complete();
}); });
_delayEvent.Wait(); this._delayEvent.Wait();
} }
#endif #endif
#endregion #endregion

View File

@ -1,63 +1,61 @@
namespace Unosquare.Swan.Components using Unosquare.Swan.Exceptions;
{ using System;
using Exceptions; using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.Reflection;
using System.Linq;
using System.Reflection;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// The concrete implementation of a simple IoC container /// The concrete implementation of a simple IoC container
/// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC). /// based largely on TinyIoC (https://github.com/grumpydev/TinyIoC).
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> /// <seealso cref="System.IDisposable" />
public partial class DependencyContainer : IDisposable public partial class DependencyContainer : IDisposable {
{ private readonly Object _autoRegisterLock = new Object();
private readonly object _autoRegisterLock = new object();
private bool _disposed; private Boolean _disposed;
static DependencyContainer() static DependencyContainer() {
{
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DependencyContainer"/> class. /// Initializes a new instance of the <see cref="DependencyContainer"/> class.
/// </summary> /// </summary>
public DependencyContainer() public DependencyContainer() {
{ this.RegisteredTypes = new TypesConcurrentDictionary(this);
RegisteredTypes = new TypesConcurrentDictionary(this); _ = this.Register(this);
Register(this);
// Only register the TinyMessenger singleton if we are the root container // Only register the TinyMessenger singleton if we are the root container
if (Parent == null) if(this.Parent == null) {
Register<IMessageHub, MessageHub>(); _ = this.Register<IMessageHub, MessageHub>();
}
} }
private DependencyContainer(DependencyContainer parent) private DependencyContainer(DependencyContainer parent)
: this() : this() => this.Parent = parent;
{
Parent = parent;
}
/// <summary> /// <summary>
/// Lazy created Singleton instance of the container for simple scenarios. /// Lazy created Singleton instance of the container for simple scenarios.
/// </summary> /// </summary>
public static DependencyContainer Current { get; } = new DependencyContainer(); public static DependencyContainer Current { get; } = new DependencyContainer();
internal DependencyContainer Parent { get; } internal DependencyContainer Parent {
get;
}
internal TypesConcurrentDictionary RegisteredTypes { get; } internal TypesConcurrentDictionary RegisteredTypes {
get;
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._disposed) {
if (_disposed) return; return;
}
_disposed = true; this._disposed = true;
foreach (var disposable in RegisteredTypes.Values.Select(item => item as IDisposable)) foreach(IDisposable disposable in this.RegisteredTypes.Values.Select(item => item as IDisposable)) {
{
disposable?.Dispose(); disposable?.Dispose();
} }
@ -82,13 +80,10 @@
public void AutoRegister( public void AutoRegister(
DependencyContainerDuplicateImplementationActions duplicateAction = DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle, DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func<Type, bool> registrationPredicate = null) Func<Type, Boolean> registrationPredicate = null) => this.AutoRegister(
{
AutoRegister(
Runtime.GetAssemblies().Where(a => !IsIgnoredAssembly(a)), Runtime.GetAssemblies().Where(a => !IsIgnoredAssembly(a)),
duplicateAction, duplicateAction,
registrationPredicate); registrationPredicate);
}
#endif #endif
/// <summary> /// <summary>
@ -102,65 +97,56 @@
IEnumerable<Assembly> assemblies, IEnumerable<Assembly> assemblies,
DependencyContainerDuplicateImplementationActions duplicateAction = DependencyContainerDuplicateImplementationActions duplicateAction =
DependencyContainerDuplicateImplementationActions.RegisterSingle, DependencyContainerDuplicateImplementationActions.RegisterSingle,
Func<Type, bool> registrationPredicate = null) Func<Type, Boolean> registrationPredicate = null) {
{ lock(this._autoRegisterLock) {
lock (_autoRegisterLock) List<Type> types = assemblies
{
var types = assemblies
.SelectMany(a => a.GetAllTypes()) .SelectMany(a => a.GetAllTypes())
.Where(t => !IsIgnoredType(t, registrationPredicate)) .Where(t => !IsIgnoredType(t, registrationPredicate))
.ToList(); .ToList();
var concreteTypes = types List<Type> concreteTypes = types
.Where(type => .Where(type =>
type.IsClass() && !type.IsAbstract() && type.IsClass() && !type.IsAbstract() &&
(type != GetType() && (type.DeclaringType != GetType()) && !type.IsGenericTypeDefinition())) type != this.GetType() && type.DeclaringType != this.GetType() && !type.IsGenericTypeDefinition())
.ToList(); .ToList();
foreach (var type in concreteTypes) foreach(Type type in concreteTypes) {
{ try {
try _ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, type));
{ } catch(MethodAccessException) {
RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, type));
}
catch (MethodAccessException)
{
// Ignore methods we can't access - added for Silverlight // Ignore methods we can't access - added for Silverlight
} }
} }
var abstractInterfaceTypes = types.Where( IEnumerable<Type> abstractInterfaceTypes = types.Where(
type => type =>
((type.IsInterface() || type.IsAbstract()) && (type.DeclaringType != GetType()) && (type.IsInterface() || type.IsAbstract()) && type.DeclaringType != this.GetType() &&
(!type.IsGenericTypeDefinition()))); !type.IsGenericTypeDefinition());
foreach (var type in abstractInterfaceTypes) foreach(Type type in abstractInterfaceTypes) {
{ Type localType = type;
var localType = type; List<Type> implementations = concreteTypes
var implementations = concreteTypes
.Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList(); .Where(implementationType => localType.IsAssignableFrom(implementationType)).ToList();
if (implementations.Skip(1).Any()) if(implementations.Skip(1).Any()) {
{ if(duplicateAction == DependencyContainerDuplicateImplementationActions.Fail) {
if (duplicateAction == DependencyContainerDuplicateImplementationActions.Fail)
throw new DependencyContainerRegistrationException(type, implementations); throw new DependencyContainerRegistrationException(type, implementations);
}
if (duplicateAction == DependencyContainerDuplicateImplementationActions.RegisterMultiple) if(duplicateAction == DependencyContainerDuplicateImplementationActions.RegisterMultiple) {
{ _ = this.RegisterMultiple(type, implementations);
RegisterMultiple(type, implementations);
} }
} }
var firstImplementation = implementations.FirstOrDefault(); Type firstImplementation = implementations.FirstOrDefault();
if (firstImplementation == null) continue; if(firstImplementation == null) {
continue;
try
{
RegisteredTypes.Register(type, string.Empty, GetDefaultObjectFactory(type, firstImplementation));
} }
catch (MethodAccessException)
{ try {
_ = this.RegisteredTypes.Register(type, String.Empty, GetDefaultObjectFactory(type, firstImplementation));
} catch(MethodAccessException) {
// Ignore methods we can't access - added for Silverlight // Ignore methods we can't access - added for Silverlight
} }
} }
@ -173,8 +159,8 @@
/// <param name="registerType">Type to register.</param> /// <param name="registerType">Type to register.</param>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, string name = "") public RegisterOptions Register(Type registerType, String name = "")
=> RegisteredTypes.Register( => this.RegisteredTypes.Register(
registerType, registerType,
name, name,
GetDefaultObjectFactory(registerType, registerType)); GetDefaultObjectFactory(registerType, registerType));
@ -186,8 +172,8 @@
/// <param name="registerImplementation">Type to instantiate that implements RegisterType.</param> /// <param name="registerImplementation">Type to instantiate that implements RegisterType.</param>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, Type registerImplementation, string name = "") => public RegisterOptions Register(Type registerType, Type registerImplementation, String name = "") =>
RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation)); this.RegisteredTypes.Register(registerType, name, GetDefaultObjectFactory(registerType, registerImplementation));
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance. /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
@ -196,8 +182,8 @@
/// <param name="instance">Instance of RegisterType to register.</param> /// <param name="instance">Instance of RegisterType to register.</param>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register(Type registerType, object instance, string name = "") => public RegisterOptions Register(Type registerType, Object instance, String name = "") =>
RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance)); this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerType, instance));
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance. /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
@ -210,9 +196,9 @@
public RegisterOptions Register( public RegisterOptions Register(
Type registerType, Type registerType,
Type registerImplementation, Type registerImplementation,
object instance, Object instance,
string name = "") String name = "")
=> RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance)); => this.RegisteredTypes.Register(registerType, name, new InstanceFactory(registerType, registerImplementation, instance));
/// <summary> /// <summary>
/// Creates/replaces a container class registration with a user specified factory. /// Creates/replaces a container class registration with a user specified factory.
@ -223,9 +209,9 @@
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register( public RegisterOptions Register(
Type registerType, Type registerType,
Func<DependencyContainer, Dictionary<string, object>, object> factory, Func<DependencyContainer, Dictionary<String, Object>, Object> factory,
string name = "") String name = "")
=> RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory)); => this.RegisteredTypes.Register(registerType, name, new DelegateFactory(registerType, factory));
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with default options. /// Creates/replaces a named container class registration with default options.
@ -233,11 +219,8 @@
/// <typeparam name="TRegister">Type to register.</typeparam> /// <typeparam name="TRegister">Type to register.</typeparam>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>(string name = "") public RegisterOptions Register<TRegister>(String name = "")
where TRegister : class where TRegister : class => this.Register(typeof(TRegister), name);
{
return Register(typeof(TRegister), name);
}
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a given implementation and default options. /// Creates/replaces a named container class registration with a given implementation and default options.
@ -246,12 +229,9 @@
/// <typeparam name="TRegisterImplementation">Type to instantiate that implements RegisterType.</typeparam> /// <typeparam name="TRegisterImplementation">Type to instantiate that implements RegisterType.</typeparam>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister, TRegisterImplementation>(string name = "") public RegisterOptions Register<TRegister, TRegisterImplementation>(String name = "")
where TRegister : class where TRegister : class
where TRegisterImplementation : class, TRegister where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), name);
{
return Register(typeof(TRegister), typeof(TRegisterImplementation), name);
}
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance. /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
@ -260,11 +240,8 @@
/// <param name="instance">Instance of RegisterType to register.</param> /// <param name="instance">Instance of RegisterType to register.</param>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>(TRegister instance, string name = "") public RegisterOptions Register<TRegister>(TRegister instance, String name = "")
where TRegister : class where TRegister : class => this.Register(typeof(TRegister), instance, name);
{
return Register(typeof(TRegister), instance, name);
}
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a specific, strong referenced, instance. /// Creates/replaces a named container class registration with a specific, strong referenced, instance.
@ -275,12 +252,9 @@
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister, TRegisterImplementation>(TRegisterImplementation instance, public RegisterOptions Register<TRegister, TRegisterImplementation>(TRegisterImplementation instance,
string name = "") String name = "")
where TRegister : class where TRegister : class
where TRegisterImplementation : class, TRegister where TRegisterImplementation : class, TRegister => this.Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
{
return Register(typeof(TRegister), typeof(TRegisterImplementation), instance, name);
}
/// <summary> /// <summary>
/// Creates/replaces a named container class registration with a user specified factory. /// Creates/replaces a named container class registration with a user specified factory.
@ -290,15 +264,13 @@
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns>RegisterOptions for fluent API.</returns> /// <returns>RegisterOptions for fluent API.</returns>
public RegisterOptions Register<TRegister>( public RegisterOptions Register<TRegister>(
Func<DependencyContainer, Dictionary<string, object>, TRegister> factory, string name = "") Func<DependencyContainer, Dictionary<String, Object>, TRegister> factory, String name = "")
where TRegister : class where TRegister : class {
{ if(factory == null) {
if (factory == null)
{
throw new ArgumentNullException(nameof(factory)); throw new ArgumentNullException(nameof(factory));
} }
return Register(typeof(TRegister), factory, name); return this.Register(typeof(TRegister), factory, name);
} }
/// <summary> /// <summary>
@ -310,7 +282,7 @@
/// <param name="implementationTypes">Types that implement RegisterType.</param> /// <param name="implementationTypes">Types that implement RegisterType.</param>
/// <returns>MultiRegisterOptions for the fluent API.</returns> /// <returns>MultiRegisterOptions for the fluent API.</returns>
public MultiRegisterOptions RegisterMultiple<TRegister>(IEnumerable<Type> implementationTypes) => public MultiRegisterOptions RegisterMultiple<TRegister>(IEnumerable<Type> implementationTypes) =>
RegisterMultiple(typeof(TRegister), implementationTypes); this.RegisterMultiple(typeof(TRegister), implementationTypes);
/// <summary> /// <summary>
/// Register multiple implementations of a type. /// Register multiple implementations of a type.
@ -320,32 +292,30 @@
/// <param name="registrationType">Type that each implementation implements.</param> /// <param name="registrationType">Type that each implementation implements.</param>
/// <param name="implementationTypes">Types that implement RegisterType.</param> /// <param name="implementationTypes">Types that implement RegisterType.</param>
/// <returns>MultiRegisterOptions for the fluent API.</returns> /// <returns>MultiRegisterOptions for the fluent API.</returns>
public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable<Type> implementationTypes) public MultiRegisterOptions RegisterMultiple(Type registrationType, IEnumerable<Type> implementationTypes) {
{ if(implementationTypes == null) {
if (implementationTypes == null)
throw new ArgumentNullException(nameof(implementationTypes), "types is null."); throw new ArgumentNullException(nameof(implementationTypes), "types is null.");
}
foreach (var type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type))) foreach(Type type in implementationTypes.Where(type => !registrationType.IsAssignableFrom(type))) {
{
throw new ArgumentException( throw new ArgumentException(
$"types: The type {registrationType.FullName} is not assignable from {type.FullName}"); $"types: The type {registrationType.FullName} is not assignable from {type.FullName}");
} }
if (implementationTypes.Count() != implementationTypes.Distinct().Count()) if(implementationTypes.Count() != implementationTypes.Distinct().Count()) {
{ IEnumerable<String> queryForDuplicatedTypes = implementationTypes
var queryForDuplicatedTypes = implementationTypes
.GroupBy(i => i) .GroupBy(i => i)
.Where(j => j.Count() > 1) .Where(j => j.Count() > 1)
.Select(j => j.Key.FullName); .Select(j => j.Key.FullName);
var fullNamesOfDuplicatedTypes = string.Join(",\n", queryForDuplicatedTypes.ToArray()); String fullNamesOfDuplicatedTypes = String.Join(",\n", queryForDuplicatedTypes.ToArray());
throw new ArgumentException( throw new ArgumentException(
$"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}"); $"types: The same implementation type cannot be specified multiple times for {registrationType.FullName}\n\n{fullNamesOfDuplicatedTypes}");
} }
var registerOptions = implementationTypes List<RegisterOptions> registerOptions = implementationTypes
.Select(type => Register(registrationType, type, type.FullName)) .Select(type => this.Register(registrationType, type, type.FullName))
.ToList(); .ToList();
return new MultiRegisterOptions(registerOptions); return new MultiRegisterOptions(registerOptions);
@ -361,7 +331,7 @@
/// <typeparam name="TRegister">Type to unregister.</typeparam> /// <typeparam name="TRegister">Type to unregister.</typeparam>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns>
public bool Unregister<TRegister>(string name = "") => Unregister(typeof(TRegister), name); public Boolean Unregister<TRegister>(String name = "") => this.Unregister(typeof(TRegister), name);
/// <summary> /// <summary>
/// Remove a named container class registration. /// Remove a named container class registration.
@ -369,8 +339,8 @@
/// <param name="registerType">Type to unregister.</param> /// <param name="registerType">Type to unregister.</param>
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the registration is successfully found and removed; otherwise, <c>false</c>.</returns>
public bool Unregister(Type registerType, string name = "") => public Boolean Unregister(Type registerType, String name = "") =>
RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name)); this.RegisteredTypes.RemoveRegistration(new TypeRegistration(registerType, name));
#endregion #endregion
@ -387,11 +357,11 @@
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <returns>Instance of type.</returns> /// <returns>Instance of type.</returns>
/// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception> /// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception>
public object Resolve( public Object Resolve(
Type resolveType, Type resolveType,
string name = null, String name = null,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null)
=> RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default); => this.RegisteredTypes.ResolveInternal(new TypeRegistration(resolveType, name), options ?? DependencyContainerResolveOptions.Default);
/// <summary> /// <summary>
/// Attempts to resolve a named type using specified options and the supplied constructor parameters. /// Attempts to resolve a named type using specified options and the supplied constructor parameters.
@ -405,12 +375,9 @@
/// <returns>Instance of type.</returns> /// <returns>Instance of type.</returns>
/// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception> /// <exception cref="DependencyContainerResolutionException">Unable to resolve the type.</exception>
public TResolveType Resolve<TResolveType>( public TResolveType Resolve<TResolveType>(
string name = null, String name = null,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null)
where TResolveType : class where TResolveType : class => (TResolveType)this.Resolve(typeof(TResolveType), name, options);
{
return (TResolveType)Resolve(typeof(TResolveType), name, options);
}
/// <summary> /// <summary>
/// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options. /// Attempts to predict whether a given type can be resolved with the supplied constructor parameters options.
@ -424,11 +391,11 @@
/// <returns> /// <returns>
/// Bool indicating whether the type can be resolved. /// Bool indicating whether the type can be resolved.
/// </returns> /// </returns>
public bool CanResolve( public Boolean CanResolve(
Type resolveType, Type resolveType,
string name = null, String name = null,
DependencyContainerResolveOptions options = null) => DependencyContainerResolveOptions options = null) =>
RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options); this.RegisteredTypes.CanResolve(new TypeRegistration(resolveType, name), options);
/// <summary> /// <summary>
/// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options. /// Attempts to predict whether a given named type can be resolved with the supplied constructor parameters options.
@ -442,13 +409,10 @@
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <returns>Bool indicating whether the type can be resolved.</returns> /// <returns>Bool indicating whether the type can be resolved.</returns>
public bool CanResolve<TResolveType>( public Boolean CanResolve<TResolveType>(
string name = null, String name = null,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null)
where TResolveType : class where TResolveType : class => this.CanResolve(typeof(TResolveType), name, options);
{
return CanResolve(typeof(TResolveType), name, options);
}
/// <summary> /// <summary>
/// Attempts to resolve a type using the default options. /// Attempts to resolve a type using the default options.
@ -456,15 +420,11 @@
/// <param name="resolveType">Type to resolve.</param> /// <param name="resolveType">Type to resolve.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, out object resolvedType) public Boolean TryResolve(Type resolveType, out Object resolvedType) {
{ try {
try resolvedType = this.Resolve(resolveType);
{
resolvedType = Resolve(resolveType);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = null; resolvedType = null;
return false; return false;
} }
@ -477,15 +437,11 @@
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, DependencyContainerResolveOptions options, out object resolvedType) public Boolean TryResolve(Type resolveType, DependencyContainerResolveOptions options, out Object resolvedType) {
{ try {
try resolvedType = this.Resolve(resolveType, options: options);
{
resolvedType = Resolve(resolveType, options: options);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = null; resolvedType = null;
return false; return false;
} }
@ -498,15 +454,11 @@
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve(Type resolveType, string name, out object resolvedType) public Boolean TryResolve(Type resolveType, String name, out Object resolvedType) {
{ try {
try resolvedType = this.Resolve(resolveType, name);
{
resolvedType = Resolve(resolveType, name);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = null; resolvedType = null;
return false; return false;
} }
@ -520,19 +472,15 @@
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve( public Boolean TryResolve(
Type resolveType, Type resolveType,
string name, String name,
DependencyContainerResolveOptions options, DependencyContainerResolveOptions options,
out object resolvedType) out Object resolvedType) {
{ try {
try resolvedType = this.Resolve(resolveType, name, options);
{
resolvedType = Resolve(resolveType, name, options);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = null; resolvedType = null;
return false; return false;
} }
@ -544,16 +492,12 @@
/// <typeparam name="TResolveType">Type to resolve.</typeparam> /// <typeparam name="TResolveType">Type to resolve.</typeparam>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(out TResolveType resolvedType) public Boolean TryResolve<TResolveType>(out TResolveType resolvedType)
where TResolveType : class where TResolveType : class {
{ try {
try resolvedType = this.Resolve<TResolveType>();
{
resolvedType = Resolve<TResolveType>();
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = default; resolvedType = default;
return false; return false;
} }
@ -566,16 +510,12 @@
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(DependencyContainerResolveOptions options, out TResolveType resolvedType) public Boolean TryResolve<TResolveType>(DependencyContainerResolveOptions options, out TResolveType resolvedType)
where TResolveType : class where TResolveType : class {
{ try {
try resolvedType = this.Resolve<TResolveType>(options: options);
{
resolvedType = Resolve<TResolveType>(options: options);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = default; resolvedType = default;
return false; return false;
} }
@ -588,16 +528,12 @@
/// <param name="name">Name of registration.</param> /// <param name="name">Name of registration.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>(string name, out TResolveType resolvedType) public Boolean TryResolve<TResolveType>(String name, out TResolveType resolvedType)
where TResolveType : class where TResolveType : class {
{ try {
try resolvedType = this.Resolve<TResolveType>(name);
{
resolvedType = Resolve<TResolveType>(name);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = default; resolvedType = default;
return false; return false;
} }
@ -611,19 +547,15 @@
/// <param name="options">Resolution options.</param> /// <param name="options">Resolution options.</param>
/// <param name="resolvedType">Resolved type or default if resolve fails.</param> /// <param name="resolvedType">Resolved type or default if resolve fails.</param>
/// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if resolved successfully, <c>false</c> otherwise.</returns>
public bool TryResolve<TResolveType>( public Boolean TryResolve<TResolveType>(
string name, String name,
DependencyContainerResolveOptions options, DependencyContainerResolveOptions options,
out TResolveType resolvedType) out TResolveType resolvedType)
where TResolveType : class where TResolveType : class {
{ try {
try resolvedType = this.Resolve<TResolveType>(name, options);
{
resolvedType = Resolve<TResolveType>(name, options);
return true; return true;
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
resolvedType = default; resolvedType = default;
return false; return false;
} }
@ -635,8 +567,8 @@
/// <param name="resolveType">Type to resolveAll.</param> /// <param name="resolveType">Type to resolveAll.</param>
/// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param> /// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param>
/// <returns>IEnumerable.</returns> /// <returns>IEnumerable.</returns>
public IEnumerable<object> ResolveAll(Type resolveType, bool includeUnnamed = false) public IEnumerable<Object> ResolveAll(Type resolveType, Boolean includeUnnamed = false)
=> RegisteredTypes.Resolve(resolveType, includeUnnamed); => this.RegisteredTypes.Resolve(resolveType, includeUnnamed);
/// <summary> /// <summary>
/// Returns all registrations of a type. /// Returns all registrations of a type.
@ -644,38 +576,31 @@
/// <typeparam name="TResolveType">Type to resolveAll.</typeparam> /// <typeparam name="TResolveType">Type to resolveAll.</typeparam>
/// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param> /// <param name="includeUnnamed">Whether to include un-named (default) registrations.</param>
/// <returns>IEnumerable.</returns> /// <returns>IEnumerable.</returns>
public IEnumerable<TResolveType> ResolveAll<TResolveType>(bool includeUnnamed = true) public IEnumerable<TResolveType> ResolveAll<TResolveType>(Boolean includeUnnamed = true)
where TResolveType : class where TResolveType : class => this.ResolveAll(typeof(TResolveType), includeUnnamed).Cast<TResolveType>();
{
return ResolveAll(typeof(TResolveType), includeUnnamed).Cast<TResolveType>();
}
/// <summary> /// <summary>
/// Attempts to resolve all public property dependencies on the given object using the given resolve options. /// Attempts to resolve all public property dependencies on the given object using the given resolve options.
/// </summary> /// </summary>
/// <param name="input">Object to "build up".</param> /// <param name="input">Object to "build up".</param>
/// <param name="resolveOptions">Resolve options to use.</param> /// <param name="resolveOptions">Resolve options to use.</param>
public void BuildUp(object input, DependencyContainerResolveOptions resolveOptions = null) public void BuildUp(Object input, DependencyContainerResolveOptions resolveOptions = null) {
{ if(resolveOptions == null) {
if (resolveOptions == null)
resolveOptions = DependencyContainerResolveOptions.Default; resolveOptions = DependencyContainerResolveOptions.Default;
}
var properties = input.GetType() IEnumerable<PropertyInfo> properties = input.GetType()
.GetProperties() .GetProperties()
.Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null && .Where(property => property.GetCacheGetMethod() != null && property.GetCacheSetMethod() != null &&
!property.PropertyType.IsValueType()); !property.PropertyType.IsValueType());
foreach (var property in properties.Where(property => property.GetValue(input, null) == null)) foreach(PropertyInfo property in properties.Where(property => property.GetValue(input, null) == null)) {
{ try {
try
{
property.SetValue( property.SetValue(
input, input,
RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions), this.RegisteredTypes.ResolveInternal(new TypeRegistration(property.PropertyType), resolveOptions),
null); null);
} } catch(DependencyContainerResolutionException) {
catch (DependencyContainerResolutionException)
{
// Catch any resolution errors and ignore them // Catch any resolution errors and ignore them
} }
} }
@ -685,30 +610,28 @@
#region Internal Methods #region Internal Methods
internal static bool IsValidAssignment(Type registerType, Type registerImplementation) internal static Boolean IsValidAssignment(Type registerType, Type registerImplementation) {
{ if(!registerType.IsGenericTypeDefinition()) {
if (!registerType.IsGenericTypeDefinition()) if(!registerType.IsAssignableFrom(registerImplementation)) {
{
if (!registerType.IsAssignableFrom(registerImplementation))
return false; return false;
} }
else } else {
{ if(registerType.IsInterface() && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name)) {
if (registerType.IsInterface() && registerImplementation.GetInterfaces().All(t => t.Name != registerType.Name))
return false; return false;
}
if (registerType.IsAbstract() && registerImplementation.BaseType() != registerType) if(registerType.IsAbstract() && registerImplementation.BaseType() != registerType) {
return false; return false;
} }
}
return true; return true;
} }
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
private static bool IsIgnoredAssembly(Assembly assembly) private static Boolean IsIgnoredAssembly(Assembly assembly) {
{
// TODO - find a better way to remove "system" assemblies from the auto registration // TODO - find a better way to remove "system" assemblies from the auto registration
var ignoreChecks = new List<Func<Assembly, bool>> List<Func<Assembly, Boolean>> ignoreChecks = new List<Func<Assembly, Boolean>>
{ {
asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal), asm => asm.FullName.StartsWith("Microsoft.", StringComparison.Ordinal),
asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal), asm => asm.FullName.StartsWith("System.", StringComparison.Ordinal),
@ -724,21 +647,19 @@
} }
#endif #endif
private static bool IsIgnoredType(Type type, Func<Type, bool> registrationPredicate) private static Boolean IsIgnoredType(Type type, Func<Type, Boolean> registrationPredicate) {
{
// TODO - find a better way to remove "system" types from the auto registration // TODO - find a better way to remove "system" types from the auto registration
var ignoreChecks = new List<Func<Type, bool>>() List<Func<Type, Boolean>> ignoreChecks = new List<Func<Type, Boolean>>()
{ {
t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false, t => t.FullName?.StartsWith("System.", StringComparison.Ordinal) ?? false,
t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false, t => t.FullName?.StartsWith("Microsoft.", StringComparison.Ordinal) ?? false,
t => t.IsPrimitive(), t => t.IsPrimitive(),
t => t.IsGenericTypeDefinition(), t => t.IsGenericTypeDefinition(),
t => (t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0) && t => t.GetConstructors(BindingFlags.Instance | BindingFlags.Public).Length == 0 &&
!(t.IsInterface() || t.IsAbstract()), !(t.IsInterface() || t.IsAbstract()),
}; };
if (registrationPredicate != null) if(registrationPredicate != null) {
{
ignoreChecks.Add(t => !registrationPredicate(t)); ignoreChecks.Add(t => !registrationPredicate(t));
} }

View File

@ -1,12 +1,11 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System.Collections.Generic;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Resolution settings. /// Resolution settings.
/// </summary> /// </summary>
public class DependencyContainerResolveOptions public class DependencyContainerResolveOptions {
{
/// <summary> /// <summary>
/// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found). /// Gets the default options (attempt resolution of unregistered types, fail on named resolution if name not found).
/// </summary> /// </summary>
@ -18,7 +17,9 @@
/// <value> /// <value>
/// The unregistered resolution action. /// The unregistered resolution action.
/// </value> /// </value>
public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction { get; set; } = public DependencyContainerUnregisteredResolutionActions UnregisteredResolutionAction {
get; set;
} =
DependencyContainerUnregisteredResolutionActions.AttemptResolve; DependencyContainerUnregisteredResolutionActions.AttemptResolve;
/// <summary> /// <summary>
@ -27,7 +28,9 @@
/// <value> /// <value>
/// The named resolution failure action. /// The named resolution failure action.
/// </value> /// </value>
public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction { get; set; } = public DependencyContainerNamedResolutionFailureActions NamedResolutionFailureAction {
get; set;
} =
DependencyContainerNamedResolutionFailureActions.Fail; DependencyContainerNamedResolutionFailureActions.Fail;
/// <summary> /// <summary>
@ -36,14 +39,13 @@
/// <value> /// <value>
/// The constructor parameters. /// The constructor parameters.
/// </value> /// </value>
public Dictionary<string, object> ConstructorParameters { get; } = new Dictionary<string, object>(); public Dictionary<String, Object> ConstructorParameters { get; } = new Dictionary<String, Object>();
/// <summary> /// <summary>
/// Clones this instance. /// Clones this instance.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions public DependencyContainerResolveOptions Clone() => new DependencyContainerResolveOptions {
{
NamedResolutionFailureAction = NamedResolutionFailureAction, NamedResolutionFailureAction = NamedResolutionFailureAction,
UnregisteredResolutionAction = UnregisteredResolutionAction, UnregisteredResolutionAction = UnregisteredResolutionAction,
}; };
@ -52,8 +54,7 @@
/// <summary> /// <summary>
/// Defines Resolution actions. /// Defines Resolution actions.
/// </summary> /// </summary>
public enum DependencyContainerUnregisteredResolutionActions public enum DependencyContainerUnregisteredResolutionActions {
{
/// <summary> /// <summary>
/// Attempt to resolve type, even if the type isn't registered. /// Attempt to resolve type, even if the type isn't registered.
/// ///
@ -78,8 +79,7 @@
/// <summary> /// <summary>
/// Enumerates failure actions. /// Enumerates failure actions.
/// </summary> /// </summary>
public enum DependencyContainerNamedResolutionFailureActions public enum DependencyContainerNamedResolutionFailureActions {
{
/// <summary> /// <summary>
/// The attempt unnamed resolution /// The attempt unnamed resolution
/// </summary> /// </summary>
@ -94,8 +94,7 @@
/// <summary> /// <summary>
/// Enumerates duplicate definition actions. /// Enumerates duplicate definition actions.
/// </summary> /// </summary>
public enum DependencyContainerDuplicateImplementationActions public enum DependencyContainerDuplicateImplementationActions {
{
/// <summary> /// <summary>
/// The register single /// The register single
/// </summary> /// </summary>

View File

@ -1,13 +1,15 @@
namespace Unosquare.Swan.Components using System;
{
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// A Message to be published/delivered by Messenger. /// A Message to be published/delivered by Messenger.
/// </summary> /// </summary>
public interface IMessageHubMessage public interface IMessageHubMessage {
{
/// <summary> /// <summary>
/// The sender of the message, or null if not supported by the message implementation. /// The sender of the message, or null if not supported by the message implementation.
/// </summary> /// </summary>
object Sender { get; } Object Sender {
get;
}
} }
} }

View File

@ -12,31 +12,31 @@
// FITNESS FOR A PARTICULAR PURPOSE. // FITNESS FOR A PARTICULAR PURPOSE.
// =============================================================================== // ===============================================================================
namespace Unosquare.Swan.Components using System.Threading.Tasks;
{ using System;
using System.Threading.Tasks; using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
namespace Unosquare.Swan.Components {
#region Message Types / Interfaces #region Message Types / Interfaces
/// <summary> /// <summary>
/// Represents a message subscription. /// Represents a message subscription.
/// </summary> /// </summary>
public interface IMessageHubSubscription public interface IMessageHubSubscription {
{
/// <summary> /// <summary>
/// Token returned to the subscribed to reference this subscription. /// Token returned to the subscribed to reference this subscription.
/// </summary> /// </summary>
MessageHubSubscriptionToken SubscriptionToken { get; } MessageHubSubscriptionToken SubscriptionToken {
get;
}
/// <summary> /// <summary>
/// Whether delivery should be attempted. /// Whether delivery should be attempted.
/// </summary> /// </summary>
/// <param name="message">Message that may potentially be delivered.</param> /// <param name="message">Message that may potentially be delivered.</param>
/// <returns><c>true</c> - ok to send, <c>false</c> - should not attempt to send.</returns> /// <returns><c>true</c> - ok to send, <c>false</c> - should not attempt to send.</returns>
bool ShouldAttemptDelivery(IMessageHubMessage message); Boolean ShouldAttemptDelivery(IMessageHubMessage message);
/// <summary> /// <summary>
/// Deliver the message. /// Deliver the message.
@ -51,8 +51,7 @@ namespace Unosquare.Swan.Components
/// A message proxy can be used to intercept/alter messages and/or /// A message proxy can be used to intercept/alter messages and/or
/// marshal delivery actions onto a particular thread. /// marshal delivery actions onto a particular thread.
/// </summary> /// </summary>
public interface IMessageHubProxy public interface IMessageHubProxy {
{
/// <summary> /// <summary>
/// Delivers the specified message. /// Delivers the specified message.
/// </summary> /// </summary>
@ -66,10 +65,8 @@ namespace Unosquare.Swan.Components
/// ///
/// Does nothing other than deliver the message. /// Does nothing other than deliver the message.
/// </summary> /// </summary>
public sealed class MessageHubDefaultProxy : IMessageHubProxy public sealed class MessageHubDefaultProxy : IMessageHubProxy {
{ private MessageHubDefaultProxy() {
private MessageHubDefaultProxy()
{
// placeholder // placeholder
} }
@ -94,8 +91,7 @@ namespace Unosquare.Swan.Components
/// <summary> /// <summary>
/// Messenger hub responsible for taking subscriptions/publications and delivering of messages. /// Messenger hub responsible for taking subscriptions/publications and delivering of messages.
/// </summary> /// </summary>
public interface IMessageHub public interface IMessageHub {
{
/// <summary> /// <summary>
/// Subscribe to a message type with the given destination and delivery action. /// Subscribe to a message type with the given destination and delivery action.
/// Messages will be delivered via the specified proxy. /// Messages will be delivered via the specified proxy.
@ -109,7 +105,7 @@ namespace Unosquare.Swan.Components
/// <returns>MessageSubscription used to unsubscribing.</returns> /// <returns>MessageSubscription used to unsubscribing.</returns>
MessageHubSubscriptionToken Subscribe<TMessage>( MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
bool useStrongReferences, Boolean useStrongReferences,
IMessageHubProxy proxy) IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage; where TMessage : class, IMessageHubMessage;
@ -129,8 +125,8 @@ namespace Unosquare.Swan.Components
/// </returns> /// </returns>
MessageHubSubscriptionToken Subscribe<TMessage>( MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter, Func<TMessage, Boolean> messageFilter,
bool useStrongReferences, Boolean useStrongReferences,
IMessageHubProxy proxy) IMessageHubProxy proxy)
where TMessage : class, IMessageHubMessage; where TMessage : class, IMessageHubMessage;
@ -206,18 +202,16 @@ namespace Unosquare.Swan.Components
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public sealed class MessageHub : IMessageHub public sealed class MessageHub : IMessageHub {
{
#region Private Types and Interfaces #region Private Types and Interfaces
private readonly object _subscriptionsPadlock = new object(); private readonly Object _subscriptionsPadlock = new Object();
private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions = private readonly Dictionary<Type, List<SubscriptionItem>> _subscriptions =
new Dictionary<Type, List<SubscriptionItem>>(); new Dictionary<Type, List<SubscriptionItem>>();
private class WeakMessageSubscription<TMessage> : IMessageHubSubscription private class WeakMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage {
{
private readonly WeakReference _deliveryAction; private readonly WeakReference _deliveryAction;
private readonly WeakReference _messageFilter; private readonly WeakReference _messageFilter;
@ -235,35 +229,30 @@ namespace Unosquare.Swan.Components
public WeakMessageSubscription( public WeakMessageSubscription(
MessageHubSubscriptionToken subscriptionToken, MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter) Func<TMessage, Boolean> messageFilter) {
{ this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken)); this._deliveryAction = new WeakReference(deliveryAction);
_deliveryAction = new WeakReference(deliveryAction); this._messageFilter = new WeakReference(messageFilter);
_messageFilter = new WeakReference(messageFilter);
} }
public MessageHubSubscriptionToken SubscriptionToken { get; } public MessageHubSubscriptionToken SubscriptionToken {
get;
public bool ShouldAttemptDelivery(IMessageHubMessage message)
{
return _deliveryAction.IsAlive && _messageFilter.IsAlive &&
((Func<TMessage, bool>) _messageFilter.Target).Invoke((TMessage) message);
} }
public void Deliver(IMessageHubMessage message) public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._deliveryAction.IsAlive && this._messageFilter.IsAlive &&
{ ((Func<TMessage, Boolean>)this._messageFilter.Target).Invoke((TMessage)message);
if (_deliveryAction.IsAlive)
{ public void Deliver(IMessageHubMessage message) {
((Action<TMessage>) _deliveryAction.Target).Invoke((TMessage) message); if(this._deliveryAction.IsAlive) {
((Action<TMessage>)this._deliveryAction.Target).Invoke((TMessage)message);
} }
} }
} }
private class StrongMessageSubscription<TMessage> : IMessageHubSubscription private class StrongMessageSubscription<TMessage> : IMessageHubSubscription
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage {
{
private readonly Action<TMessage> _deliveryAction; private readonly Action<TMessage> _deliveryAction;
private readonly Func<TMessage, bool> _messageFilter; private readonly Func<TMessage, Boolean> _messageFilter;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StrongMessageSubscription{TMessage}" /> class. /// Initializes a new instance of the <see cref="StrongMessageSubscription{TMessage}" /> class.
@ -279,34 +268,37 @@ namespace Unosquare.Swan.Components
public StrongMessageSubscription( public StrongMessageSubscription(
MessageHubSubscriptionToken subscriptionToken, MessageHubSubscriptionToken subscriptionToken,
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter) Func<TMessage, Boolean> messageFilter) {
{ this.SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken));
SubscriptionToken = subscriptionToken ?? throw new ArgumentNullException(nameof(subscriptionToken)); this._deliveryAction = deliveryAction;
_deliveryAction = deliveryAction; this._messageFilter = messageFilter;
_messageFilter = messageFilter;
} }
public MessageHubSubscriptionToken SubscriptionToken { get; } public MessageHubSubscriptionToken SubscriptionToken {
get;
}
public bool ShouldAttemptDelivery(IMessageHubMessage message) => _messageFilter.Invoke((TMessage) message); public Boolean ShouldAttemptDelivery(IMessageHubMessage message) => this._messageFilter.Invoke((TMessage)message);
public void Deliver(IMessageHubMessage message) => _deliveryAction.Invoke((TMessage) message); public void Deliver(IMessageHubMessage message) => this._deliveryAction.Invoke((TMessage)message);
} }
#endregion #endregion
#region Subscription dictionary #region Subscription dictionary
private class SubscriptionItem private class SubscriptionItem {
{ public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription) {
public SubscriptionItem(IMessageHubProxy proxy, IMessageHubSubscription subscription) this.Proxy = proxy;
{ this.Subscription = subscription;
Proxy = proxy;
Subscription = subscription;
} }
public IMessageHubProxy Proxy { get; } public IMessageHubProxy Proxy {
public IMessageHubSubscription Subscription { get; } get;
}
public IMessageHubSubscription Subscription {
get;
}
} }
#endregion #endregion
@ -326,12 +318,10 @@ namespace Unosquare.Swan.Components
/// <returns>MessageSubscription used to unsubscribing.</returns> /// <returns>MessageSubscription used to unsubscribing.</returns>
public MessageHubSubscriptionToken Subscribe<TMessage>( public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
bool useStrongReferences = true, Boolean useStrongReferences = true,
IMessageHubProxy proxy = null) IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage => this.Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
{
return Subscribe(deliveryAction, m => true, useStrongReferences, proxy);
}
/// <summary> /// <summary>
/// Subscribe to a message type with the given destination and delivery action with the given filter. /// Subscribe to a message type with the given destination and delivery action with the given filter.
@ -347,44 +337,38 @@ namespace Unosquare.Swan.Components
/// <returns> /// <returns>
/// MessageSubscription used to unsubscribing. /// MessageSubscription used to unsubscribing.
/// </returns> /// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
public MessageHubSubscriptionToken Subscribe<TMessage>( public MessageHubSubscriptionToken Subscribe<TMessage>(
Action<TMessage> deliveryAction, Action<TMessage> deliveryAction,
Func<TMessage, bool> messageFilter, Func<TMessage, Boolean> messageFilter,
bool useStrongReferences = true, Boolean useStrongReferences = true,
IMessageHubProxy proxy = null) IMessageHubProxy proxy = null)
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage {
{ if(deliveryAction == null) {
if (deliveryAction == null)
throw new ArgumentNullException(nameof(deliveryAction)); throw new ArgumentNullException(nameof(deliveryAction));
}
if (messageFilter == null) if(messageFilter == null) {
throw new ArgumentNullException(nameof(messageFilter)); throw new ArgumentNullException(nameof(messageFilter));
}
lock (_subscriptionsPadlock) lock(this._subscriptionsPadlock) {
{ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
{
currentSubscriptions = new List<SubscriptionItem>(); currentSubscriptions = new List<SubscriptionItem>();
_subscriptions[typeof(TMessage)] = currentSubscriptions; this._subscriptions[typeof(TMessage)] = currentSubscriptions;
} }
var subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage)); MessageHubSubscriptionToken subscriptionToken = new MessageHubSubscriptionToken(this, typeof(TMessage));
IMessageHubSubscription subscription; IMessageHubSubscription subscription = useStrongReferences
if (useStrongReferences) ? new StrongMessageSubscription<TMessage>(
{ subscriptionToken,
subscription = new StrongMessageSubscription<TMessage>( deliveryAction,
messageFilter)
: (IMessageHubSubscription)new WeakMessageSubscription<TMessage>(
subscriptionToken, subscriptionToken,
deliveryAction, deliveryAction,
messageFilter); messageFilter);
}
else
{
subscription = new WeakMessageSubscription<TMessage>(
subscriptionToken,
deliveryAction,
messageFilter);
}
currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription)); currentSubscriptions.Add(new SubscriptionItem(proxy ?? MessageHubDefaultProxy.Instance, subscription));
@ -394,17 +378,17 @@ namespace Unosquare.Swan.Components
/// <inheritdoc /> /// <inheritdoc />
public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken) public void Unsubscribe<TMessage>(MessageHubSubscriptionToken subscriptionToken)
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage {
{ if(subscriptionToken == null) {
if (subscriptionToken == null)
throw new ArgumentNullException(nameof(subscriptionToken)); throw new ArgumentNullException(nameof(subscriptionToken));
}
lock (_subscriptionsPadlock) lock(this._subscriptionsPadlock) {
{ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return; return;
}
var currentlySubscribed = currentSubscriptions List<SubscriptionItem> currentlySubscribed = currentSubscriptions
.Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken)) .Where(sub => ReferenceEquals(sub.Subscription.SubscriptionToken, subscriptionToken))
.ToList(); .ToList();
@ -418,30 +402,26 @@ namespace Unosquare.Swan.Components
/// <typeparam name="TMessage">Type of message.</typeparam> /// <typeparam name="TMessage">Type of message.</typeparam>
/// <param name="message">Message to deliver.</param> /// <param name="message">Message to deliver.</param>
public void Publish<TMessage>(TMessage message) public void Publish<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage {
{ if(message == null) {
if (message == null)
throw new ArgumentNullException(nameof(message)); throw new ArgumentNullException(nameof(message));
}
List<SubscriptionItem> currentlySubscribed; List<SubscriptionItem> currentlySubscribed;
lock (_subscriptionsPadlock) lock(this._subscriptionsPadlock) {
{ if(!this._subscriptions.TryGetValue(typeof(TMessage), out List<SubscriptionItem> currentSubscriptions)) {
if (!_subscriptions.TryGetValue(typeof(TMessage), out var currentSubscriptions))
return; return;
}
currentlySubscribed = currentSubscriptions currentlySubscribed = currentSubscriptions
.Where(sub => sub.Subscription.ShouldAttemptDelivery(message)) .Where(sub => sub.Subscription.ShouldAttemptDelivery(message))
.ToList(); .ToList();
} }
currentlySubscribed.ForEach(sub => currentlySubscribed.ForEach(sub => {
{ try {
try
{
sub.Proxy.Deliver(message, sub.Subscription); sub.Proxy.Deliver(message, sub.Subscription);
} } catch {
catch
{
// Ignore any errors and carry on // Ignore any errors and carry on
} }
}); });
@ -454,10 +434,7 @@ namespace Unosquare.Swan.Components
/// <param name="message">Message to deliver.</param> /// <param name="message">Message to deliver.</param>
/// <returns>A task with the publish.</returns> /// <returns>A task with the publish.</returns>
public Task PublishAsync<TMessage>(TMessage message) public Task PublishAsync<TMessage>(TMessage message)
where TMessage : class, IMessageHubMessage where TMessage : class, IMessageHubMessage => Task.Run(() => this.Publish(message));
{
return Task.Run(() => Publish(message));
}
#endregion #endregion
} }

View File

@ -1,13 +1,11 @@
namespace Unosquare.Swan.Components using System;
{
using System;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Base class for messages that provides weak reference storage of the sender. /// Base class for messages that provides weak reference storage of the sender.
/// </summary> /// </summary>
public abstract class MessageHubMessageBase public abstract class MessageHubMessageBase
: IMessageHubMessage : IMessageHubMessage {
{
/// <summary> /// <summary>
/// Store a WeakReference to the sender just in case anyone is daft enough to /// Store a WeakReference to the sender just in case anyone is daft enough to
/// keep the message around and prevent the sender from being collected. /// keep the message around and prevent the sender from being collected.
@ -19,18 +17,18 @@
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <exception cref="System.ArgumentNullException">sender.</exception> /// <exception cref="System.ArgumentNullException">sender.</exception>
protected MessageHubMessageBase(object sender) protected MessageHubMessageBase(Object sender) {
{ if(sender == null) {
if (sender == null)
throw new ArgumentNullException(nameof(sender)); throw new ArgumentNullException(nameof(sender));
}
_sender = new WeakReference(sender); this._sender = new WeakReference(sender);
} }
/// <summary> /// <summary>
/// The sender of the message, or null if not supported by the message implementation. /// The sender of the message, or null if not supported by the message implementation.
/// </summary> /// </summary>
public object Sender => _sender?.Target; public Object Sender => this._sender?.Target;
} }
/// <summary> /// <summary>
@ -38,22 +36,20 @@
/// </summary> /// </summary>
/// <typeparam name="TContent">Content type to store.</typeparam> /// <typeparam name="TContent">Content type to store.</typeparam>
public class MessageHubGenericMessage<TContent> public class MessageHubGenericMessage<TContent>
: MessageHubMessageBase : MessageHubMessageBase {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MessageHubGenericMessage{TContent}"/> class. /// Initializes a new instance of the <see cref="MessageHubGenericMessage{TContent}"/> class.
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <param name="content">The content.</param> /// <param name="content">The content.</param>
public MessageHubGenericMessage(object sender, TContent content) public MessageHubGenericMessage(Object sender, TContent content)
: base(sender) : base(sender) => this.Content = content;
{
Content = content;
}
/// <summary> /// <summary>
/// Contents of the message. /// Contents of the message.
/// </summary> /// </summary>
public TContent Content { get; protected set; } public TContent Content {
get; protected set;
}
} }
} }

View File

@ -1,16 +1,16 @@
namespace Unosquare.Swan.Components using System;
{
using System;
#if NETSTANDARD1_3 #if NETSTANDARD1_3
using System.Reflection; using System.Reflection;
#endif #endif
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents an active subscription to a message. /// Represents an active subscription to a message.
/// </summary> /// </summary>
public sealed class MessageHubSubscriptionToken public sealed class MessageHubSubscriptionToken
: IDisposable : IDisposable {
{
private readonly WeakReference _hub; private readonly WeakReference _hub;
private readonly Type _messageType; private readonly Type _messageType;
@ -21,31 +21,26 @@
/// <param name="messageType">Type of the message.</param> /// <param name="messageType">Type of the message.</param>
/// <exception cref="System.ArgumentNullException">hub.</exception> /// <exception cref="System.ArgumentNullException">hub.</exception>
/// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception> /// <exception cref="System.ArgumentOutOfRangeException">messageType.</exception>
public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) public MessageHubSubscriptionToken(IMessageHub hub, Type messageType) {
{ if(hub == null) {
if (hub == null)
{
throw new ArgumentNullException(nameof(hub)); throw new ArgumentNullException(nameof(hub));
} }
if (!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) if(!typeof(IMessageHubMessage).IsAssignableFrom(messageType)) {
{
throw new ArgumentOutOfRangeException(nameof(messageType)); throw new ArgumentOutOfRangeException(nameof(messageType));
} }
_hub = new WeakReference(hub); this._hub = new WeakReference(hub);
_messageType = messageType; this._messageType = messageType;
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._hub.IsAlive && this._hub.Target is IMessageHub hub) {
if (_hub.IsAlive && _hub.Target is IMessageHub hub) System.Reflection.MethodInfo unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe),
{ new[] { typeof(MessageHubSubscriptionToken) });
var unsubscribeMethod = typeof(IMessageHub).GetMethod(nameof(IMessageHub.Unsubscribe), unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(this._messageType);
new[] {typeof(MessageHubSubscriptionToken)}); _ = unsubscribeMethod.Invoke(hub, new Object[] { this });
unsubscribeMethod = unsubscribeMethod.MakeGenericMethod(_messageType);
unsubscribeMethod.Invoke(hub, new object[] {this});
} }
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@ -1,32 +1,34 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System; using System.Reflection;
using System.Collections.Generic; using Unosquare.Swan.Exceptions;
using System.Reflection;
using Exceptions;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents an abstract class for Object Factory. /// Represents an abstract class for Object Factory.
/// </summary> /// </summary>
public abstract class ObjectFactoryBase public abstract class ObjectFactoryBase {
{
/// <summary> /// <summary>
/// Whether to assume this factory successfully constructs its objects /// Whether to assume this factory successfully constructs its objects
/// ///
/// Generally set to true for delegate style factories as CanResolve cannot delve /// Generally set to true for delegate style factories as CanResolve cannot delve
/// into the delegates they contain. /// into the delegates they contain.
/// </summary> /// </summary>
public virtual bool AssumeConstruction => false; public virtual Boolean AssumeConstruction => false;
/// <summary> /// <summary>
/// The type the factory instantiates. /// The type the factory instantiates.
/// </summary> /// </summary>
public abstract Type CreatesType { get; } public abstract Type CreatesType {
get;
}
/// <summary> /// <summary>
/// Constructor to use, if specified. /// Constructor to use, if specified.
/// </summary> /// </summary>
public ConstructorInfo Constructor { get; private set; } public ConstructorInfo Constructor {
get; private set;
}
/// <summary> /// <summary>
/// Gets the singleton variant. /// Gets the singleton variant.
@ -36,7 +38,7 @@
/// </value> /// </value>
/// <exception cref="DependencyContainerRegistrationException">singleton.</exception> /// <exception cref="DependencyContainerRegistrationException">singleton.</exception>
public virtual ObjectFactoryBase SingletonVariant => public virtual ObjectFactoryBase SingletonVariant =>
throw new DependencyContainerRegistrationException(GetType(), "singleton"); throw new DependencyContainerRegistrationException(this.GetType(), "singleton");
/// <summary> /// <summary>
/// Gets the multi instance variant. /// Gets the multi instance variant.
@ -46,7 +48,7 @@
/// </value> /// </value>
/// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception> /// <exception cref="DependencyContainerRegistrationException">multi-instance.</exception>
public virtual ObjectFactoryBase MultiInstanceVariant => public virtual ObjectFactoryBase MultiInstanceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "multi-instance"); throw new DependencyContainerRegistrationException(this.GetType(), "multi-instance");
/// <summary> /// <summary>
/// Gets the strong reference variant. /// Gets the strong reference variant.
@ -56,7 +58,7 @@
/// </value> /// </value>
/// <exception cref="DependencyContainerRegistrationException">strong reference.</exception> /// <exception cref="DependencyContainerRegistrationException">strong reference.</exception>
public virtual ObjectFactoryBase StrongReferenceVariant => public virtual ObjectFactoryBase StrongReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "strong reference"); throw new DependencyContainerRegistrationException(this.GetType(), "strong reference");
/// <summary> /// <summary>
/// Gets the weak reference variant. /// Gets the weak reference variant.
@ -66,7 +68,7 @@
/// </value> /// </value>
/// <exception cref="DependencyContainerRegistrationException">weak reference.</exception> /// <exception cref="DependencyContainerRegistrationException">weak reference.</exception>
public virtual ObjectFactoryBase WeakReferenceVariant => public virtual ObjectFactoryBase WeakReferenceVariant =>
throw new DependencyContainerRegistrationException(GetType(), "weak reference"); throw new DependencyContainerRegistrationException(this.GetType(), "weak reference");
/// <summary> /// <summary>
/// Create the type. /// Create the type.
@ -75,7 +77,7 @@
/// <param name="container">Container that requested the creation.</param> /// <param name="container">Container that requested the creation.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns> Instance of type. </returns> /// <returns> Instance of type. </returns>
public abstract object GetObject( public abstract Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options); DependencyContainerResolveOptions options);
@ -90,60 +92,49 @@
public virtual ObjectFactoryBase GetFactoryForChildContainer( public virtual ObjectFactoryBase GetFactoryForChildContainer(
Type type, Type type,
DependencyContainer parent, DependencyContainer parent,
DependencyContainer child) DependencyContainer child) => this;
{
return this;
}
} }
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// IObjectFactory that creates new instances of types for each resolution. /// IObjectFactory that creates new instances of types for each resolution.
/// </summary> /// </summary>
internal class MultiInstanceFactory : ObjectFactoryBase internal class MultiInstanceFactory : ObjectFactoryBase {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly Type _registerImplementation; private readonly Type _registerImplementation;
public MultiInstanceFactory(Type registerType, Type registerImplementation) public MultiInstanceFactory(Type registerType, Type registerImplementation) {
{ if(registerImplementation.IsAbstract() || registerImplementation.IsInterface()) {
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface())
{
throw new DependencyContainerRegistrationException(registerImplementation, throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory", "MultiInstanceFactory",
true); true);
} }
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
{
throw new DependencyContainerRegistrationException(registerImplementation, throw new DependencyContainerRegistrationException(registerImplementation,
"MultiInstanceFactory", "MultiInstanceFactory",
true); true);
} }
_registerType = registerType; this._registerType = registerType;
_registerImplementation = registerImplementation; this._registerImplementation = registerImplementation;
} }
public override Type CreatesType => _registerImplementation; public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase SingletonVariant => public override ObjectFactoryBase SingletonVariant =>
new SingletonFactory(_registerType, _registerImplementation); new SingletonFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase MultiInstanceVariant => this; public override ObjectFactoryBase MultiInstanceVariant => this;
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ try {
try return container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
{ } catch(DependencyContainerResolutionException ex) {
return container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options); throw new DependencyContainerResolutionException(this._registerType, ex);
}
catch (DependencyContainerResolutionException ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
} }
} }
} }
@ -152,42 +143,36 @@
/// <summary> /// <summary>
/// IObjectFactory that invokes a specified delegate to construct the object. /// IObjectFactory that invokes a specified delegate to construct the object.
/// </summary> /// </summary>
internal class DelegateFactory : ObjectFactoryBase internal class DelegateFactory : ObjectFactoryBase {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly Func<DependencyContainer, Dictionary<string, object>, object> _factory; private readonly Func<DependencyContainer, Dictionary<String, Object>, Object> _factory;
public DelegateFactory( public DelegateFactory(
Type registerType, Type registerType,
Func<DependencyContainer, Func<DependencyContainer,
Dictionary<string, object>, object> factory) Dictionary<String, Object>, Object> factory) {
{ this._factory = factory ?? throw new ArgumentNullException(nameof(factory));
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
_registerType = registerType; this._registerType = registerType;
} }
public override bool AssumeConstruction => true; public override Boolean AssumeConstruction => true;
public override Type CreatesType => _registerType; public override Type CreatesType => this._registerType;
public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(_registerType, _factory); public override ObjectFactoryBase WeakReferenceVariant => new WeakDelegateFactory(this._registerType, this._factory);
public override ObjectFactoryBase StrongReferenceVariant => this; public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ try {
try return this._factory.Invoke(container, options.ConstructorParameters);
{ } catch(Exception ex) {
return _factory.Invoke(container, options.ConstructorParameters); throw new DependencyContainerResolutionException(this._registerType, ex);
}
catch (Exception ex)
{
throw new DependencyContainerResolutionException(_registerType, ex);
} }
} }
} }
@ -197,55 +182,50 @@
/// IObjectFactory that invokes a specified delegate to construct the object /// IObjectFactory that invokes a specified delegate to construct the object
/// Holds the delegate using a weak reference. /// Holds the delegate using a weak reference.
/// </summary> /// </summary>
internal class WeakDelegateFactory : ObjectFactoryBase internal class WeakDelegateFactory : ObjectFactoryBase {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly WeakReference _factory; private readonly WeakReference _factory;
public WeakDelegateFactory(Type registerType, public WeakDelegateFactory(Type registerType,
Func<DependencyContainer, Dictionary<string, object>, object> factory) Func<DependencyContainer, Dictionary<String, Object>, Object> factory) {
{ if(factory == null) {
if (factory == null)
throw new ArgumentNullException(nameof(factory)); throw new ArgumentNullException(nameof(factory));
_factory = new WeakReference(factory);
_registerType = registerType;
} }
public override bool AssumeConstruction => true; this._factory = new WeakReference(factory);
public override Type CreatesType => _registerType; this._registerType = registerType;
}
public override ObjectFactoryBase StrongReferenceVariant public override Boolean AssumeConstruction => true;
{
get
{
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory))
throw new DependencyContainerWeakReferenceException(_registerType);
return new DelegateFactory(_registerType, factory); public override Type CreatesType => this._registerType;
public override ObjectFactoryBase StrongReferenceVariant {
get {
if(!(this._factory.Target is Func<DependencyContainer, Dictionary<global::System.String, global::System.Object>, global::System.Object> factory)) {
throw new DependencyContainerWeakReferenceException(this._registerType);
}
return new DelegateFactory(this._registerType, factory);
} }
} }
public override ObjectFactoryBase WeakReferenceVariant => this; public override ObjectFactoryBase WeakReferenceVariant => this;
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ if(!(this._factory.Target is Func<DependencyContainer, Dictionary<global::System.String, global::System.Object>, global::System.Object> factory)) {
if (!(_factory.Target is Func<DependencyContainer, Dictionary<string, object>, object> factory)) throw new DependencyContainerWeakReferenceException(this._registerType);
throw new DependencyContainerWeakReferenceException(_registerType);
try
{
return factory.Invoke(container, options.ConstructorParameters);
} }
catch (Exception ex)
{ try {
throw new DependencyContainerResolutionException(_registerType, ex); return factory.Invoke(container, options.ConstructorParameters);
} catch(Exception ex) {
throw new DependencyContainerResolutionException(this._registerType, ex);
} }
} }
} }
@ -253,45 +233,40 @@
/// <summary> /// <summary>
/// Stores an particular instance to return for a type. /// Stores an particular instance to return for a type.
/// </summary> /// </summary>
internal class InstanceFactory : ObjectFactoryBase, IDisposable internal class InstanceFactory : ObjectFactoryBase, IDisposable {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly Type _registerImplementation; private readonly Type _registerImplementation;
private readonly object _instance; private readonly Object _instance;
public InstanceFactory(Type registerType, Type registerImplementation, object instance) public InstanceFactory(Type registerType, Type registerImplementation, Object instance) {
{ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true); throw new DependencyContainerRegistrationException(registerImplementation, "InstanceFactory", true);
_registerType = registerType;
_registerImplementation = registerImplementation;
_instance = instance;
} }
public override bool AssumeConstruction => true; this._registerType = registerType;
this._registerImplementation = registerImplementation;
this._instance = instance;
}
public override Type CreatesType => _registerImplementation; public override Boolean AssumeConstruction => true;
public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant => public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation); new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant => public override ObjectFactoryBase WeakReferenceVariant =>
new WeakInstanceFactory(_registerType, _registerImplementation, _instance); new WeakInstanceFactory(this._registerType, this._registerImplementation, this._instance);
public override ObjectFactoryBase StrongReferenceVariant => this; public override ObjectFactoryBase StrongReferenceVariant => this;
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) => this._instance;
{
return _instance;
}
public void Dispose() public void Dispose() {
{ IDisposable disposable = this._instance as IDisposable;
var disposable = _instance as IDisposable;
disposable?.Dispose(); disposable?.Dispose();
} }
@ -300,125 +275,116 @@
/// <summary> /// <summary>
/// Stores the instance with a weak reference. /// Stores the instance with a weak reference.
/// </summary> /// </summary>
internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable internal class WeakInstanceFactory : ObjectFactoryBase, IDisposable {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly Type _registerImplementation; private readonly Type _registerImplementation;
private readonly WeakReference _instance; private readonly WeakReference _instance;
public WeakInstanceFactory(Type registerType, Type registerImplementation, object instance) public WeakInstanceFactory(Type registerType, Type registerImplementation, Object instance) {
{ if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation))
{
throw new DependencyContainerRegistrationException( throw new DependencyContainerRegistrationException(
registerImplementation, registerImplementation,
"WeakInstanceFactory", "WeakInstanceFactory",
true); true);
} }
_registerType = registerType; this._registerType = registerType;
_registerImplementation = registerImplementation; this._registerImplementation = registerImplementation;
_instance = new WeakReference(instance); this._instance = new WeakReference(instance);
} }
public override Type CreatesType => _registerImplementation; public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase MultiInstanceVariant => public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation); new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override ObjectFactoryBase WeakReferenceVariant => this; public override ObjectFactoryBase WeakReferenceVariant => this;
public override ObjectFactoryBase StrongReferenceVariant public override ObjectFactoryBase StrongReferenceVariant {
{ get {
get Object instance = this._instance.Target;
{
var instance = _instance.Target;
if (instance == null) if(instance == null) {
throw new DependencyContainerWeakReferenceException(_registerType); throw new DependencyContainerWeakReferenceException(this._registerType);
}
return new InstanceFactory(_registerType, _registerImplementation, instance); return new InstanceFactory(this._registerType, this._registerImplementation, instance);
} }
} }
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ Object instance = this._instance.Target;
var instance = _instance.Target;
if (instance == null) if(instance == null) {
throw new DependencyContainerWeakReferenceException(_registerType); throw new DependencyContainerWeakReferenceException(this._registerType);
}
return instance; return instance;
} }
public void Dispose() => (_instance.Target as IDisposable)?.Dispose(); public void Dispose() => (this._instance.Target as IDisposable)?.Dispose();
} }
/// <summary> /// <summary>
/// A factory that lazy instantiates a type and always returns the same instance. /// A factory that lazy instantiates a type and always returns the same instance.
/// </summary> /// </summary>
internal class SingletonFactory : ObjectFactoryBase, IDisposable internal class SingletonFactory : ObjectFactoryBase, IDisposable {
{
private readonly Type _registerType; private readonly Type _registerType;
private readonly Type _registerImplementation; private readonly Type _registerImplementation;
private readonly object _singletonLock = new object(); private readonly Object _singletonLock = new Object();
private object _current; private Object _current;
public SingletonFactory(Type registerType, Type registerImplementation) public SingletonFactory(Type registerType, Type registerImplementation) {
{ if(registerImplementation.IsAbstract() || registerImplementation.IsInterface()) {
if (registerImplementation.IsAbstract() || registerImplementation.IsInterface())
{
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true); throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
} }
if (!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) if(!DependencyContainer.IsValidAssignment(registerType, registerImplementation)) {
{
throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true); throw new DependencyContainerRegistrationException(registerImplementation, nameof(SingletonFactory), true);
} }
_registerType = registerType; this._registerType = registerType;
_registerImplementation = registerImplementation; this._registerImplementation = registerImplementation;
} }
public override Type CreatesType => _registerImplementation; public override Type CreatesType => this._registerImplementation;
public override ObjectFactoryBase SingletonVariant => this; public override ObjectFactoryBase SingletonVariant => this;
public override ObjectFactoryBase MultiInstanceVariant => public override ObjectFactoryBase MultiInstanceVariant =>
new MultiInstanceFactory(_registerType, _registerImplementation); new MultiInstanceFactory(this._registerType, this._registerImplementation);
public override object GetObject( public override Object GetObject(
Type requestedType, Type requestedType,
DependencyContainer container, DependencyContainer container,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ if(options.ConstructorParameters.Count != 0) {
if (options.ConstructorParameters.Count != 0)
throw new ArgumentException("Cannot specify parameters for singleton types"); throw new ArgumentException("Cannot specify parameters for singleton types");
lock (_singletonLock)
{
if (_current == null)
_current = container.RegisteredTypes.ConstructType(_registerImplementation, Constructor, options);
} }
return _current; lock(this._singletonLock) {
if(this._current == null) {
this._current = container.RegisteredTypes.ConstructType(this._registerImplementation, this.Constructor, options);
}
}
return this._current;
} }
public override ObjectFactoryBase GetFactoryForChildContainer( public override ObjectFactoryBase GetFactoryForChildContainer(
Type type, Type type,
DependencyContainer parent, DependencyContainer parent,
DependencyContainer child) DependencyContainer child) {
{
// We make sure that the singleton is constructed before the child container takes the factory. // We make sure that the singleton is constructed before the child container takes the factory.
// Otherwise the results would vary depending on whether or not the parent container had resolved // Otherwise the results would vary depending on whether or not the parent container had resolved
// the type before the child container does. // the type before the child container does.
GetObject(type, parent, DependencyContainerResolveOptions.Default); _ = this.GetObject(type, parent, DependencyContainerResolveOptions.Default);
return this; return this;
} }
public void Dispose() => (_current as IDisposable)?.Dispose(); public void Dispose() => (this._current as IDisposable)?.Dispose();
} }
} }

View File

@ -1,22 +1,21 @@
namespace Unosquare.Swan.Components using System;
{
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents the text of the standard output and standard error /// Represents the text of the standard output and standard error
/// of a process, including its exit code. /// of a process, including its exit code.
/// </summary> /// </summary>
public class ProcessResult public class ProcessResult {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProcessResult" /> class. /// Initializes a new instance of the <see cref="ProcessResult" /> class.
/// </summary> /// </summary>
/// <param name="exitCode">The exit code.</param> /// <param name="exitCode">The exit code.</param>
/// <param name="standardOutput">The standard output.</param> /// <param name="standardOutput">The standard output.</param>
/// <param name="standardError">The standard error.</param> /// <param name="standardError">The standard error.</param>
public ProcessResult(int exitCode, string standardOutput, string standardError) public ProcessResult(Int32 exitCode, String standardOutput, String standardError) {
{ this.ExitCode = exitCode;
ExitCode = exitCode; this.StandardOutput = standardOutput;
StandardOutput = standardOutput; this.StandardError = standardError;
StandardError = standardError;
} }
/// <summary> /// <summary>
@ -25,7 +24,9 @@
/// <value> /// <value>
/// The exit code. /// The exit code.
/// </value> /// </value>
public int ExitCode { get; } public Int32 ExitCode {
get;
}
/// <summary> /// <summary>
/// Gets the text of the standard output. /// Gets the text of the standard output.
@ -33,7 +34,9 @@
/// <value> /// <value>
/// The standard output. /// The standard output.
/// </value> /// </value>
public string StandardOutput { get; } public String StandardOutput {
get;
}
/// <summary> /// <summary>
/// Gets the text of the standard error. /// Gets the text of the standard error.
@ -41,6 +44,8 @@
/// <value> /// <value>
/// The standard error. /// The standard error.
/// </value> /// </value>
public string StandardError { get; } public String StandardError {
get;
}
} }
} }

View File

@ -1,26 +1,24 @@
namespace Unosquare.Swan.Components using System;
{ using System.Diagnostics;
using System; using System.IO;
using System.Diagnostics; using System.Linq;
using System.IO; using System.Text;
using System.Linq; using System.Threading;
using System.Text; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Provides methods to help create external processes, and efficiently capture the /// Provides methods to help create external processes, and efficiently capture the
/// standard error and standard output streams. /// standard error and standard output streams.
/// </summary> /// </summary>
public static class ProcessRunner public static class ProcessRunner {
{
/// <summary> /// <summary>
/// Defines a delegate to handle binary data reception from the standard /// Defines a delegate to handle binary data reception from the standard
/// output or standard error streams from a process. /// output or standard error streams from a process.
/// </summary> /// </summary>
/// <param name="processData">The process data.</param> /// <param name="processData">The process data.</param>
/// <param name="process">The process.</param> /// <param name="process">The process.</param>
public delegate void ProcessDataReceivedCallback(byte[] processData, Process process); public delegate void ProcessDataReceivedCallback(Byte[] processData, Process process);
/// <summary> /// <summary>
/// Runs the process asynchronously and if the exit code is 0, /// Runs the process asynchronously and if the exit code is 0,
@ -31,8 +29,8 @@
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>The type of the result produced by this Task.</returns> /// <returns>The type of the result produced by this Task.</returns>
public static Task<string> GetProcessOutputAsync(string filename, CancellationToken ct = default) => public static Task<String> GetProcessOutputAsync(String filename, CancellationToken ct = default) =>
GetProcessOutputAsync(filename, string.Empty, ct); GetProcessOutputAsync(filename, String.Empty, ct);
/// <summary> /// <summary>
/// Runs the process asynchronously and if the exit code is 0, /// Runs the process asynchronously and if the exit code is 0,
@ -46,7 +44,7 @@
/// <returns>The type of the result produced by this Task.</returns> /// <returns>The type of the result produced by this Task.</returns>
/// <example> /// <example>
/// The following code explains how to run an external process using the /// The following code explains how to run an external process using the
/// <see cref="GetProcessOutputAsync(string, string, CancellationToken)"/> method. /// <see cref="GetProcessOutputAsync(String, String, CancellationToken)"/> method.
/// <code> /// <code>
/// class Example /// class Example
/// { /// {
@ -65,12 +63,11 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static async Task<string> GetProcessOutputAsync( public static async Task<String> GetProcessOutputAsync(
string filename, String filename,
string arguments, String arguments,
CancellationToken ct = default) CancellationToken ct = default) {
{ ProcessResult result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
var result = await GetProcessResultAsync(filename, arguments, ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
} }
@ -84,13 +81,12 @@
/// <returns> /// <returns>
/// The type of the result produced by this Task. /// The type of the result produced by this Task.
/// </returns> /// </returns>
public static async Task<string> GetProcessOutputAsync( public static async Task<String> GetProcessOutputAsync(
string filename, String filename,
string arguments, String arguments,
string workingDirectory, String workingDirectory,
CancellationToken ct = default) CancellationToken ct = default) {
{ ProcessResult result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
var result = await GetProcessResultAsync(filename, arguments, workingDirectory, ct: ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
} }
@ -108,13 +104,12 @@
/// <returns> /// <returns>
/// The type of the result produced by this Task. /// The type of the result produced by this Task.
/// </returns> /// </returns>
public static async Task<string> GetProcessEncodedOutputAsync( public static async Task<String> GetProcessEncodedOutputAsync(
string filename, String filename,
string arguments = "", String arguments = "",
Encoding encoding = null, Encoding encoding = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ ProcessResult result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
var result = await GetProcessResultAsync(filename, arguments, null, encoding, ct).ConfigureAwait(false);
return result.ExitCode == 0 ? result.StandardOutput : result.StandardError; return result.ExitCode == 0 ? result.StandardOutput : result.StandardError;
} }
@ -131,8 +126,8 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">filename.</exception> /// <exception cref="ArgumentNullException">filename.</exception>
public static Task<ProcessResult> GetProcessResultAsync( public static Task<ProcessResult> GetProcessResultAsync(
string filename, String filename,
string arguments = "", String arguments = "",
CancellationToken ct = default) => CancellationToken ct = default) =>
GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct); GetProcessResultAsync(filename, arguments, null, Definitions.CurrentAnsiEncoding, ct);
@ -151,7 +146,7 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">filename.</exception> /// <exception cref="ArgumentNullException">filename.</exception>
/// <example> /// <example>
/// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(string, string, string, Encoding, CancellationToken)" /> method. /// The following code describes how to run an external process using the <see cref="GetProcessResultAsync(String, String, String, Encoding, CancellationToken)" /> method.
/// <code> /// <code>
/// class Example /// class Example
/// { /// {
@ -171,27 +166,28 @@
/// } /// }
/// </code></example> /// </code></example>
public static async Task<ProcessResult> GetProcessResultAsync( public static async Task<ProcessResult> GetProcessResultAsync(
string filename, String filename,
string arguments, String arguments,
string workingDirectory, String workingDirectory,
Encoding encoding = null, Encoding encoding = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ if(filename == null) {
if (filename == null)
throw new ArgumentNullException(nameof(filename)); throw new ArgumentNullException(nameof(filename));
}
if (encoding == null) if(encoding == null) {
encoding = Definitions.CurrentAnsiEncoding; encoding = Definitions.CurrentAnsiEncoding;
}
var standardOutputBuilder = new StringBuilder(); StringBuilder standardOutputBuilder = new StringBuilder();
var standardErrorBuilder = new StringBuilder(); StringBuilder standardErrorBuilder = new StringBuilder();
var processReturn = await RunProcessAsync( Int32 processReturn = await RunProcessAsync(
filename, filename,
arguments, arguments,
workingDirectory, workingDirectory,
(data, proc) => { standardOutputBuilder.Append(encoding.GetString(data)); }, (data, proc) => standardOutputBuilder.Append(encoding.GetString(data)),
(data, proc) => { standardErrorBuilder.Append(encoding.GetString(data)); }, (data, proc) => standardErrorBuilder.Append(encoding.GetString(data)),
encoding, encoding,
true, true,
ct) ct)
@ -218,27 +214,24 @@
/// <returns> /// <returns>
/// Value type will be -1 for forceful termination of the process. /// Value type will be -1 for forceful termination of the process.
/// </returns> /// </returns>
public static Task<int> RunProcessAsync( public static Task<Int32> RunProcessAsync(
string filename, String filename,
string arguments, String arguments,
string workingDirectory, String workingDirectory,
ProcessDataReceivedCallback onOutputData, ProcessDataReceivedCallback onOutputData,
ProcessDataReceivedCallback onErrorData, ProcessDataReceivedCallback onErrorData,
Encoding encoding, Encoding encoding,
bool syncEvents = true, Boolean syncEvents = true,
CancellationToken ct = default) CancellationToken ct = default) {
{ if(filename == null) {
if (filename == null)
throw new ArgumentNullException(nameof(filename)); throw new ArgumentNullException(nameof(filename));
}
return Task.Run(() => return Task.Run(() => {
{
// Setup the process and its corresponding start info // Setup the process and its corresponding start info
var process = new Process Process process = new Process {
{
EnableRaisingEvents = false, EnableRaisingEvents = false,
StartInfo = new ProcessStartInfo StartInfo = new ProcessStartInfo {
{
Arguments = arguments, Arguments = arguments,
CreateNoWindow = true, CreateNoWindow = true,
FileName = filename, FileName = filename,
@ -253,8 +246,9 @@
}, },
}; };
if (!string.IsNullOrWhiteSpace(workingDirectory)) if(!String.IsNullOrWhiteSpace(workingDirectory)) {
process.StartInfo.WorkingDirectory = workingDirectory; process.StartInfo.WorkingDirectory = workingDirectory;
}
// Launch the process and discard any buffered data for standard error and standard output // Launch the process and discard any buffered data for standard error and standard output
process.Start(); process.Start();
@ -262,7 +256,7 @@
process.StandardOutput.DiscardBufferedData(); process.StandardOutput.DiscardBufferedData();
// Launch the asynchronous stream reading tasks // Launch the asynchronous stream reading tasks
var readTasks = new Task[2]; Task[] readTasks = new Task[2];
readTasks[0] = CopyStreamAsync( readTasks[0] = CopyStreamAsync(
process, process,
process.StandardOutput.BaseStream, process.StandardOutput.BaseStream,
@ -276,44 +270,34 @@
syncEvents, syncEvents,
ct); ct);
try try {
{
// Wait for all tasks to complete // Wait for all tasks to complete
Task.WaitAll(readTasks, ct); Task.WaitAll(readTasks, ct);
} } catch(TaskCanceledException) {
catch (TaskCanceledException)
{
// ignore // ignore
} } finally {
finally
{
// Wait for the process to exit // Wait for the process to exit
while (ct.IsCancellationRequested == false) while(ct.IsCancellationRequested == false) {
{ if(process.HasExited || process.WaitForExit(5)) {
if (process.HasExited || process.WaitForExit(5))
break; break;
} }
}
// Forcefully kill the process if it do not exit // Forcefully kill the process if it do not exit
try try {
{ if(process.HasExited == false) {
if (process.HasExited == false)
process.Kill(); process.Kill();
} }
catch } catch {
{
// swallow // swallow
} }
} }
try try {
{
// Retrieve and return the exit code. // Retrieve and return the exit code.
// -1 signals error // -1 signals error
return process.HasExited ? process.ExitCode : -1; return process.HasExited ? process.ExitCode : -1;
} } catch {
catch
{
return -1; return -1;
} }
}, ct); }, ct);
@ -335,7 +319,7 @@
/// <returns>Value type will be -1 for forceful termination of the process.</returns> /// <returns>Value type will be -1 for forceful termination of the process.</returns>
/// <example> /// <example>
/// The following example illustrates how to run an external process using the /// The following example illustrates how to run an external process using the
/// <see cref="RunProcessAsync(string, string, ProcessDataReceivedCallback, ProcessDataReceivedCallback, bool, CancellationToken)"/> /// <see cref="RunProcessAsync(String, String, ProcessDataReceivedCallback, ProcessDataReceivedCallback, Boolean, CancellationToken)"/>
/// method. /// method.
/// <code> /// <code>
/// class Example /// class Example
@ -362,12 +346,12 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static Task<int> RunProcessAsync( public static Task<Int32> RunProcessAsync(
string filename, String filename,
string arguments, String arguments,
ProcessDataReceivedCallback onOutputData, ProcessDataReceivedCallback onOutputData,
ProcessDataReceivedCallback onErrorData, ProcessDataReceivedCallback onErrorData,
bool syncEvents = true, Boolean syncEvents = true,
CancellationToken ct = default) CancellationToken ct = default)
=> RunProcessAsync( => RunProcessAsync(
filename, filename,
@ -388,81 +372,71 @@
/// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param> /// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>Total copies stream.</returns> /// <returns>Total copies stream.</returns>
private static Task<ulong> CopyStreamAsync( private static Task<UInt64> CopyStreamAsync(
Process process, Process process,
Stream baseStream, Stream baseStream,
ProcessDataReceivedCallback onDataCallback, ProcessDataReceivedCallback onDataCallback,
bool syncEvents, Boolean syncEvents,
CancellationToken ct) CancellationToken ct) => Task.Factory.StartNew(async () => {
{
return Task.Factory.StartNew(async () =>
{
// define some state variables // define some state variables
var swapBuffer = new byte[2048]; // the buffer to copy data from one stream to the next Byte[] swapBuffer = new Byte[2048]; // the buffer to copy data from one stream to the next
ulong totalCount = 0; // the total amount of bytes read UInt64 totalCount = 0; // the total amount of bytes read
var hasExited = false; Boolean hasExited = false;
while (ct.IsCancellationRequested == false) while(ct.IsCancellationRequested == false) {
{ try {
try
{
// Check if process is no longer valid // Check if process is no longer valid
// if this condition holds, simply read the last bits of data available. // if this condition holds, simply read the last bits of data available.
int readCount; // the bytes read in any given event Int32 readCount; // the bytes read in any given event
if (process.HasExited || process.WaitForExit(1)) if(process.HasExited || process.WaitForExit(1)) {
{ while(true) {
while (true) try {
{
try
{
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct); readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
if (readCount > 0) if(readCount > 0) {
{ totalCount += (UInt64)readCount;
totalCount += (ulong) readCount;
onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process); onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
} } else {
else
{
hasExited = true; hasExited = true;
break; break;
} }
} } catch {
catch
{
hasExited = true; hasExited = true;
break; break;
} }
} }
} }
if (hasExited) break; if(hasExited) {
break;
}
// Try reading from the stream. < 0 means no read occurred. // Try reading from the stream. < 0 means no read occurred.
readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct); readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);
// When no read is done, we need to let is rest for a bit // When no read is done, we need to let is rest for a bit
if (readCount <= 0) if(readCount <= 0) {
{
await Task.Delay(1, ct); // do not hog CPU cycles doing nothing. await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
continue; continue;
} }
totalCount += (ulong) readCount; totalCount += (UInt64)readCount;
if (onDataCallback == null) continue; if(onDataCallback == null) {
continue;
}
// Create the buffer to pass to the callback // Create the buffer to pass to the callback
var eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray(); Byte[] eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();
// Create the data processing callback invocation // Create the data processing callback invocation
var eventTask = Task eventTask =
Task.Factory.StartNew(() => { onDataCallback.Invoke(eventBuffer, process); }, ct); Task.Factory.StartNew(() => onDataCallback.Invoke(eventBuffer, process), ct);
// wait for the event to process before the next read occurs // wait for the event to process before the next read occurs
if (syncEvents) eventTask.Wait(ct); if(syncEvents) {
eventTask.Wait(ct);
} }
catch } catch {
{
break; break;
} }
} }
@ -470,5 +444,4 @@
return totalCount; return totalCount;
}, ct).Unwrap(); }, ct).Unwrap();
} }
}
} }

View File

@ -1,40 +1,32 @@
namespace Unosquare.Swan.Components using System;
{ using System.Diagnostics;
using System; using Unosquare.Swan.Abstractions;
using System.Diagnostics;
using Abstractions;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// A time measurement artifact. /// A time measurement artifact.
/// </summary> /// </summary>
internal sealed class RealTimeClock : IDisposable internal sealed class RealTimeClock : IDisposable {
{
private readonly Stopwatch _chrono = new Stopwatch(); private readonly Stopwatch _chrono = new Stopwatch();
private ISyncLocker _locker = SyncLockerFactory.Create(useSlim: true); private ISyncLocker _locker = SyncLockerFactory.Create(useSlim: true);
private long _offsetTicks; private Int64 _offsetTicks;
private double _speedRatio = 1.0d; private Double _speedRatio = 1.0d;
private bool _isDisposed; private Boolean _isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RealTimeClock"/> class. /// Initializes a new instance of the <see cref="RealTimeClock"/> class.
/// The clock starts paused and at the 0 position. /// The clock starts paused and at the 0 position.
/// </summary> /// </summary>
public RealTimeClock() public RealTimeClock() => this.Reset();
{
Reset();
}
/// <summary> /// <summary>
/// Gets or sets the clock position. /// Gets or sets the clock position.
/// </summary> /// </summary>
public TimeSpan Position public TimeSpan Position {
{ get {
get using(this._locker.AcquireReaderLock()) {
{
using (_locker.AcquireReaderLock())
{
return TimeSpan.FromTicks( return TimeSpan.FromTicks(
_offsetTicks + Convert.ToInt64(_chrono.Elapsed.Ticks * SpeedRatio)); this._offsetTicks + Convert.ToInt64(this._chrono.Elapsed.Ticks * this.SpeedRatio));
} }
} }
} }
@ -42,13 +34,10 @@
/// <summary> /// <summary>
/// Gets a value indicating whether the clock is running. /// Gets a value indicating whether the clock is running.
/// </summary> /// </summary>
public bool IsRunning public Boolean IsRunning {
{ get {
get using(this._locker.AcquireReaderLock()) {
{ return this._chrono.IsRunning;
using (_locker.AcquireReaderLock())
{
return _chrono.IsRunning;
} }
} }
} }
@ -56,26 +45,23 @@
/// <summary> /// <summary>
/// Gets or sets the speed ratio at which the clock runs. /// Gets or sets the speed ratio at which the clock runs.
/// </summary> /// </summary>
public double SpeedRatio public Double SpeedRatio {
{ get {
get using(this._locker.AcquireReaderLock()) {
{ return this._speedRatio;
using (_locker.AcquireReaderLock())
{
return _speedRatio;
} }
} }
set set {
{ using(this._locker.AcquireWriterLock()) {
using (_locker.AcquireWriterLock()) if(value < 0d) {
{ value = 0d;
if (value < 0d) value = 0d; }
// Capture the initial position se we set it even after the speedratio has changed // Capture the initial position se we set it even after the speedratio has changed
// this ensures a smooth position transition // this ensures a smooth position transition
var initialPosition = Position; TimeSpan initialPosition = this.Position;
_speedRatio = value; this._speedRatio = value;
Update(initialPosition); this.Update(initialPosition);
} }
} }
} }
@ -84,37 +70,36 @@
/// Sets a new position value atomically. /// Sets a new position value atomically.
/// </summary> /// </summary>
/// <param name="value">The new value that the position porperty will hold.</param> /// <param name="value">The new value that the position porperty will hold.</param>
public void Update(TimeSpan value) public void Update(TimeSpan value) {
{ using(this._locker.AcquireWriterLock()) {
using (_locker.AcquireWriterLock()) Boolean resume = this._chrono.IsRunning;
{ this._chrono.Reset();
var resume = _chrono.IsRunning; this._offsetTicks = value.Ticks;
_chrono.Reset(); if(resume) {
_offsetTicks = value.Ticks; this._chrono.Start();
if (resume) _chrono.Start(); }
} }
} }
/// <summary> /// <summary>
/// Starts or resumes the clock. /// Starts or resumes the clock.
/// </summary> /// </summary>
public void Play() public void Play() {
{ using(this._locker.AcquireWriterLock()) {
using (_locker.AcquireWriterLock()) if(this._chrono.IsRunning) {
{ return;
if (_chrono.IsRunning) return; }
_chrono.Start();
this._chrono.Start();
} }
} }
/// <summary> /// <summary>
/// Pauses the clock. /// Pauses the clock.
/// </summary> /// </summary>
public void Pause() public void Pause() {
{ using(this._locker.AcquireWriterLock()) {
using (_locker.AcquireWriterLock()) this._chrono.Stop();
{
_chrono.Stop();
} }
} }
@ -122,22 +107,22 @@
/// Sets the clock position to 0 and stops it. /// Sets the clock position to 0 and stops it.
/// The speed ratio is not modified. /// The speed ratio is not modified.
/// </summary> /// </summary>
public void Reset() public void Reset() {
{ using(this._locker.AcquireWriterLock()) {
using (_locker.AcquireWriterLock()) this._offsetTicks = 0;
{ this._chrono.Reset();
_offsetTicks = 0;
_chrono.Reset();
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._isDisposed) {
if (_isDisposed) return; return;
_isDisposed = true; }
_locker?.Dispose();
_locker = null; this._isDisposed = true;
this._locker?.Dispose();
this._locker = null;
} }
} }
} }

View File

@ -1,15 +1,13 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using Unosquare.Swan.Exceptions;
using System.Linq;
using Exceptions;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Registration options for "fluent" API. /// Registration options for "fluent" API.
/// </summary> /// </summary>
public sealed class RegisterOptions public sealed class RegisterOptions {
{
private readonly TypesConcurrentDictionary _registeredTypes; private readonly TypesConcurrentDictionary _registeredTypes;
private readonly DependencyContainer.TypeRegistration _registration; private readonly DependencyContainer.TypeRegistration _registration;
@ -18,10 +16,9 @@
/// </summary> /// </summary>
/// <param name="registeredTypes">The registered types.</param> /// <param name="registeredTypes">The registered types.</param>
/// <param name="registration">The registration.</param> /// <param name="registration">The registration.</param>
public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) public RegisterOptions(TypesConcurrentDictionary registeredTypes, DependencyContainer.TypeRegistration registration) {
{ this._registeredTypes = registeredTypes;
_registeredTypes = registeredTypes; this._registration = registration;
_registration = registration;
} }
/// <summary> /// <summary>
@ -29,14 +26,14 @@
/// </summary> /// </summary>
/// <returns>A registration options for fluent API.</returns> /// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsSingleton() public RegisterOptions AsSingleton() {
{ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null) if(currentFactory == null) {
throw new DependencyContainerRegistrationException(_registration.Type, "singleton"); throw new DependencyContainerRegistrationException(this._registration.Type, "singleton");
}
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.SingletonVariant); return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.SingletonVariant);
} }
/// <summary> /// <summary>
@ -44,14 +41,14 @@
/// </summary> /// </summary>
/// <returns>A registration options for fluent API.</returns> /// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions AsMultiInstance() public RegisterOptions AsMultiInstance() {
{ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null) if(currentFactory == null) {
throw new DependencyContainerRegistrationException(_registration.Type, "multi-instance"); throw new DependencyContainerRegistrationException(this._registration.Type, "multi-instance");
}
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.MultiInstanceVariant); return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.MultiInstanceVariant);
} }
/// <summary> /// <summary>
@ -59,14 +56,14 @@
/// </summary> /// </summary>
/// <returns>A registration options for fluent API.</returns> /// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithWeakReference() public RegisterOptions WithWeakReference() {
{ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null) if(currentFactory == null) {
throw new DependencyContainerRegistrationException(_registration.Type, "weak reference"); throw new DependencyContainerRegistrationException(this._registration.Type, "weak reference");
}
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.WeakReferenceVariant); return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.WeakReferenceVariant);
} }
/// <summary> /// <summary>
@ -74,41 +71,36 @@
/// </summary> /// </summary>
/// <returns>A registration options for fluent API.</returns> /// <returns>A registration options for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic constraint registration exception.</exception>
public RegisterOptions WithStrongReference() public RegisterOptions WithStrongReference() {
{ ObjectFactoryBase currentFactory = this._registeredTypes.GetCurrentFactory(this._registration);
var currentFactory = _registeredTypes.GetCurrentFactory(_registration);
if (currentFactory == null) if(currentFactory == null) {
throw new DependencyContainerRegistrationException(_registration.Type, "strong reference"); throw new DependencyContainerRegistrationException(this._registration.Type, "strong reference");
}
return _registeredTypes.AddUpdateRegistration(_registration, currentFactory.StrongReferenceVariant); return this._registeredTypes.AddUpdateRegistration(this._registration, currentFactory.StrongReferenceVariant);
} }
} }
/// <summary> /// <summary>
/// Registration options for "fluent" API when registering multiple implementations. /// Registration options for "fluent" API when registering multiple implementations.
/// </summary> /// </summary>
public sealed class MultiRegisterOptions public sealed class MultiRegisterOptions {
{
private IEnumerable<RegisterOptions> _registerOptions; private IEnumerable<RegisterOptions> _registerOptions;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MultiRegisterOptions"/> class. /// Initializes a new instance of the <see cref="MultiRegisterOptions"/> class.
/// </summary> /// </summary>
/// <param name="registerOptions">The register options.</param> /// <param name="registerOptions">The register options.</param>
public MultiRegisterOptions(IEnumerable<RegisterOptions> registerOptions) public MultiRegisterOptions(IEnumerable<RegisterOptions> registerOptions) => this._registerOptions = registerOptions;
{
_registerOptions = registerOptions;
}
/// <summary> /// <summary>
/// Make registration a singleton (single instance) if possible. /// Make registration a singleton (single instance) if possible.
/// </summary> /// </summary>
/// <returns>A registration multi-instance for fluent API.</returns> /// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsSingleton() public MultiRegisterOptions AsSingleton() {
{ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsSingleton());
return this; return this;
} }
@ -117,16 +109,12 @@
/// </summary> /// </summary>
/// <returns>A registration multi-instance for fluent API.</returns> /// <returns>A registration multi-instance for fluent API.</returns>
/// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception> /// <exception cref="DependencyContainerRegistrationException">Generic Constraint Registration Exception.</exception>
public MultiRegisterOptions AsMultiInstance() public MultiRegisterOptions AsMultiInstance() {
{ this._registerOptions = this.ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
_registerOptions = ExecuteOnAllRegisterOptions(ro => ro.AsMultiInstance());
return this; return this;
} }
private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions( private IEnumerable<RegisterOptions> ExecuteOnAllRegisterOptions(
Func<RegisterOptions, RegisterOptions> action) Func<RegisterOptions, RegisterOptions> action) => this._registerOptions.Select(action).ToList();
{
return _registerOptions.Select(action).ToList();
}
} }
} }

View File

@ -1,27 +1,23 @@
namespace Unosquare.Swan.Components using System;
{
using System;
public partial class DependencyContainer namespace Unosquare.Swan.Components {
{ public partial class DependencyContainer {
/// <summary> /// <summary>
/// Represents a Type Registration within the IoC Container. /// Represents a Type Registration within the IoC Container.
/// </summary> /// </summary>
public sealed class TypeRegistration public sealed class TypeRegistration {
{ private readonly Int32 _hashCode;
private readonly int _hashCode;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TypeRegistration"/> class. /// Initializes a new instance of the <see cref="TypeRegistration"/> class.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
public TypeRegistration(Type type, string name = null) public TypeRegistration(Type type, String name = null) {
{ this.Type = type;
Type = type; this.Name = name ?? String.Empty;
Name = name ?? string.Empty;
_hashCode = string.Concat(Type.FullName, "|", Name).GetHashCode(); this._hashCode = String.Concat(this.Type.FullName, "|", this.Name).GetHashCode();
} }
/// <summary> /// <summary>
@ -30,7 +26,9 @@
/// <value> /// <value>
/// The type. /// The type.
/// </value> /// </value>
public Type Type { get; } public Type Type {
get;
}
/// <summary> /// <summary>
/// Gets the name. /// Gets the name.
@ -38,7 +36,9 @@
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; } public String Name {
get;
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object" />, is equal to this instance. /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
@ -47,13 +47,9 @@
/// <returns> /// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>. /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(object obj) public override Boolean Equals(Object obj) => !(obj is TypeRegistration typeRegistration) || typeRegistration.Type != this.Type
{ ? false
if (!(obj is TypeRegistration typeRegistration) || typeRegistration.Type != Type) : String.Compare(this.Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
return false;
return string.Compare(Name, typeRegistration.Name, StringComparison.Ordinal) == 0;
}
/// <summary> /// <summary>
/// Returns a hash code for this instance. /// Returns a hash code for this instance.
@ -61,7 +57,7 @@
/// <returns> /// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns> /// </returns>
public override int GetHashCode() => _hashCode; public override Int32 GetHashCode() => this._hashCode;
} }
} }
} }

View File

@ -1,282 +1,239 @@
namespace Unosquare.Swan.Components using System;
{ using System.Linq.Expressions;
using System; using System.Reflection;
using System.Linq.Expressions; using System.Collections.Generic;
using System.Reflection; using System.Linq;
using System.Collections.Generic; using Unosquare.Swan.Exceptions;
using System.Linq; using System.Collections.Concurrent;
using Exceptions;
using System.Collections.Concurrent;
namespace Unosquare.Swan.Components {
/// <summary> /// <summary>
/// Represents a Concurrent Dictionary for TypeRegistration. /// Represents a Concurrent Dictionary for TypeRegistration.
/// </summary> /// </summary>
public class TypesConcurrentDictionary : ConcurrentDictionary<DependencyContainer.TypeRegistration, ObjectFactoryBase> public class TypesConcurrentDictionary : ConcurrentDictionary<DependencyContainer.TypeRegistration, ObjectFactoryBase> {
{
private static readonly ConcurrentDictionary<ConstructorInfo, ObjectConstructor> ObjectConstructorCache = private static readonly ConcurrentDictionary<ConstructorInfo, ObjectConstructor> ObjectConstructorCache =
new ConcurrentDictionary<ConstructorInfo, ObjectConstructor>(); new ConcurrentDictionary<ConstructorInfo, ObjectConstructor>();
private readonly DependencyContainer _dependencyContainer; private readonly DependencyContainer _dependencyContainer;
internal TypesConcurrentDictionary(DependencyContainer dependencyContainer) internal TypesConcurrentDictionary(DependencyContainer dependencyContainer) => this._dependencyContainer = dependencyContainer;
{
_dependencyContainer = dependencyContainer;
}
/// <summary> /// <summary>
/// Represents a delegate to build an object with the parameters. /// Represents a delegate to build an object with the parameters.
/// </summary> /// </summary>
/// <param name="parameters">The parameters.</param> /// <param name="parameters">The parameters.</param>
/// <returns>The built object.</returns> /// <returns>The built object.</returns>
public delegate object ObjectConstructor(params object[] parameters); public delegate Object ObjectConstructor(params Object[] parameters);
internal IEnumerable<object> Resolve(Type resolveType, bool includeUnnamed) internal IEnumerable<Object> Resolve(Type resolveType, Boolean includeUnnamed) {
{ IEnumerable<DependencyContainer.TypeRegistration> registrations = this.Keys.Where(tr => tr.Type == resolveType)
var registrations = Keys.Where(tr => tr.Type == resolveType) .Concat(this.GetParentRegistrationsForType(resolveType)).Distinct();
.Concat(GetParentRegistrationsForType(resolveType)).Distinct();
if (!includeUnnamed) if(!includeUnnamed) {
registrations = registrations.Where(tr => tr.Name != string.Empty); registrations = registrations.Where(tr => tr.Name != String.Empty);
return registrations.Select(registration =>
ResolveInternal(registration, DependencyContainerResolveOptions.Default));
} }
internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration) return registrations.Select(registration =>
{ this.ResolveInternal(registration, DependencyContainerResolveOptions.Default));
TryGetValue(registration, out var current); }
internal ObjectFactoryBase GetCurrentFactory(DependencyContainer.TypeRegistration registration) {
_ = this.TryGetValue(registration, out ObjectFactoryBase current);
return current; return current;
} }
internal RegisterOptions Register(Type registerType, string name, ObjectFactoryBase factory) internal RegisterOptions Register(Type registerType, String name, ObjectFactoryBase factory)
=> AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory); => this.AddUpdateRegistration(new DependencyContainer.TypeRegistration(registerType, name), factory);
internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory) internal RegisterOptions AddUpdateRegistration(DependencyContainer.TypeRegistration typeRegistration, ObjectFactoryBase factory) {
{
this[typeRegistration] = factory; this[typeRegistration] = factory;
return new RegisterOptions(this, typeRegistration); return new RegisterOptions(this, typeRegistration);
} }
internal bool RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration) internal Boolean RemoveRegistration(DependencyContainer.TypeRegistration typeRegistration)
=> TryRemove(typeRegistration, out _); => this.TryRemove(typeRegistration, out _);
internal object ResolveInternal( internal Object ResolveInternal(
DependencyContainer.TypeRegistration registration, DependencyContainer.TypeRegistration registration,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null) {
{ if(options == null) {
if (options == null)
options = DependencyContainerResolveOptions.Default; options = DependencyContainerResolveOptions.Default;
}
// Attempt container resolution // Attempt container resolution
if (TryGetValue(registration, out var factory)) if(this.TryGetValue(registration, out ObjectFactoryBase factory)) {
{ try {
try return factory.GetObject(registration.Type, this._dependencyContainer, options);
{ } catch(DependencyContainerResolutionException) {
return factory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw; throw;
} } catch(Exception ex) {
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex); throw new DependencyContainerResolutionException(registration.Type, ex);
} }
} }
// Attempt to get a factory from parent if we can // Attempt to get a factory from parent if we can
var bubbledObjectFactory = GetParentObjectFactory(registration); ObjectFactoryBase bubbledObjectFactory = this.GetParentObjectFactory(registration);
if (bubbledObjectFactory != null) if(bubbledObjectFactory != null) {
{ try {
try return bubbledObjectFactory.GetObject(registration.Type, this._dependencyContainer, options);
{ } catch(DependencyContainerResolutionException) {
return bubbledObjectFactory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw; throw;
} } catch(Exception ex) {
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex); throw new DependencyContainerResolutionException(registration.Type, ex);
} }
} }
// Fail if requesting named resolution and settings set to fail if unresolved // Fail if requesting named resolution and settings set to fail if unresolved
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.Fail) DependencyContainerNamedResolutionFailureActions.Fail) {
throw new DependencyContainerResolutionException(registration.Type); throw new DependencyContainerResolutionException(registration.Type);
}
// Attempted unnamed fallback container resolution if relevant and requested // Attempted unnamed fallback container resolution if relevant and requested
if (!string.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction == if(!String.IsNullOrEmpty(registration.Name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) {
{ if(this.TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, String.Empty), out factory)) {
if (TryGetValue(new DependencyContainer.TypeRegistration(registration.Type, string.Empty), out factory)) try {
{ return factory.GetObject(registration.Type, this._dependencyContainer, options);
try } catch(DependencyContainerResolutionException) {
{
return factory.GetObject(registration.Type, _dependencyContainer, options);
}
catch (DependencyContainerResolutionException)
{
throw; throw;
} } catch(Exception ex) {
catch (Exception ex)
{
throw new DependencyContainerResolutionException(registration.Type, ex); throw new DependencyContainerResolutionException(registration.Type, ex);
} }
} }
} }
// Attempt unregistered construction if possible and requested // Attempt unregistered construction if possible and requested
var isValid = (options.UnregisteredResolutionAction == Boolean isValid = options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.AttemptResolve) || DependencyContainerUnregisteredResolutionActions.AttemptResolve ||
(registration.Type.IsGenericType() && options.UnregisteredResolutionAction == registration.Type.IsGenericType() && options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.GenericsOnly); DependencyContainerUnregisteredResolutionActions.GenericsOnly;
return isValid && !registration.Type.IsAbstract() && !registration.Type.IsInterface() return isValid && !registration.Type.IsAbstract() && !registration.Type.IsInterface()
? ConstructType(registration.Type, null, options) ? this.ConstructType(registration.Type, null, options)
: throw new DependencyContainerResolutionException(registration.Type); : throw new DependencyContainerResolutionException(registration.Type);
} }
internal bool CanResolve( internal Boolean CanResolve(
DependencyContainer.TypeRegistration registration, DependencyContainer.TypeRegistration registration,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null) {
{ if(options == null) {
if (options == null)
options = DependencyContainerResolveOptions.Default; options = DependencyContainerResolveOptions.Default;
}
var checkType = registration.Type; Type checkType = registration.Type;
var name = registration.Name; String name = registration.Name;
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out var factory)) if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType, name), out ObjectFactoryBase factory)) {
{ return factory.AssumeConstruction
if (factory.AssumeConstruction) ? true
return true; : factory.Constructor == null
? this.GetBestConstructor(factory.CreatesType, options) != null
if (factory.Constructor == null) : this.CanConstruct(factory.Constructor, options);
return GetBestConstructor(factory.CreatesType, options) != null;
return CanConstruct(factory.Constructor, options);
} }
// Fail if requesting named resolution and settings set to fail if unresolved // Fail if requesting named resolution and settings set to fail if unresolved
// Or bubble up if we have a parent // Or bubble up if we have a parent
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.Fail) DependencyContainerNamedResolutionFailureActions.Fail) {
return _dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false; return this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false;
}
// Attempted unnamed fallback container resolution if relevant and requested // Attempted unnamed fallback container resolution if relevant and requested
if (!string.IsNullOrEmpty(name) && options.NamedResolutionFailureAction == if(!String.IsNullOrEmpty(name) && options.NamedResolutionFailureAction ==
DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) DependencyContainerNamedResolutionFailureActions.AttemptUnnamedResolution) {
{ if(this.TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory)) {
if (TryGetValue(new DependencyContainer.TypeRegistration(checkType), out factory)) return factory.AssumeConstruction ? true : this.GetBestConstructor(factory.CreatesType, options) != null;
{
if (factory.AssumeConstruction)
return true;
return GetBestConstructor(factory.CreatesType, options) != null;
} }
} }
// Check if type is an automatic lazy factory request or an IEnumerable<ResolveType> // Check if type is an automatic lazy factory request or an IEnumerable<ResolveType>
if (IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable()) if(IsAutomaticLazyFactoryRequest(checkType) || registration.Type.IsIEnumerable()) {
return true; return true;
}
// Attempt unregistered construction if possible and requested // Attempt unregistered construction if possible and requested
// If we cant', bubble if we have a parent // If we cant', bubble if we have a parent
if ((options.UnregisteredResolutionAction == if(options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.AttemptResolve) || DependencyContainerUnregisteredResolutionActions.AttemptResolve ||
(checkType.IsGenericType() && options.UnregisteredResolutionAction == checkType.IsGenericType() && options.UnregisteredResolutionAction ==
DependencyContainerUnregisteredResolutionActions.GenericsOnly)) DependencyContainerUnregisteredResolutionActions.GenericsOnly) {
{ return this.GetBestConstructor(checkType, options) != null ||
return (GetBestConstructor(checkType, options) != null) || (this._dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
(_dependencyContainer.Parent?.RegisteredTypes.CanResolve(registration, options.Clone()) ?? false);
} }
// Bubble resolution up the container tree if we have a parent // Bubble resolution up the container tree if we have a parent
return _dependencyContainer.Parent != null && _dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone()); return this._dependencyContainer.Parent != null && this._dependencyContainer.Parent.RegisteredTypes.CanResolve(registration, options.Clone());
} }
internal object ConstructType( internal Object ConstructType(
Type implementationType, Type implementationType,
ConstructorInfo constructor, ConstructorInfo constructor,
DependencyContainerResolveOptions options = null) DependencyContainerResolveOptions options = null) {
{ Type typeToConstruct = implementationType;
var typeToConstruct = implementationType;
if (constructor == null) if(constructor == null) {
{
// Try and get the best constructor that we can construct // Try and get the best constructor that we can construct
// if we can't construct any then get the constructor // if we can't construct any then get the constructor
// with the least number of parameters so we can throw a meaningful // with the least number of parameters so we can throw a meaningful
// resolve exception // resolve exception
constructor = GetBestConstructor(typeToConstruct, options) ?? constructor = this.GetBestConstructor(typeToConstruct, options) ??
GetTypeConstructors(typeToConstruct).LastOrDefault(); GetTypeConstructors(typeToConstruct).LastOrDefault();
} }
if (constructor == null) if(constructor == null) {
throw new DependencyContainerResolutionException(typeToConstruct); throw new DependencyContainerResolutionException(typeToConstruct);
var ctorParams = constructor.GetParameters();
var args = new object[ctorParams.Length];
for (var parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++)
{
var currentParam = ctorParams[parameterIndex];
try
{
args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
} }
catch (DependencyContainerResolutionException ex)
{ ParameterInfo[] ctorParams = constructor.GetParameters();
Object[] args = new Object[ctorParams.Length];
for(Int32 parameterIndex = 0; parameterIndex < ctorParams.Length; parameterIndex++) {
ParameterInfo currentParam = ctorParams[parameterIndex];
try {
args[parameterIndex] = options?.ConstructorParameters.GetValueOrDefault(currentParam.Name, this.ResolveInternal(new DependencyContainer.TypeRegistration(currentParam.ParameterType), options.Clone()));
} catch(DependencyContainerResolutionException ex) {
// If a constructor parameter can't be resolved // If a constructor parameter can't be resolved
// it will throw, so wrap it and throw that this can't // it will throw, so wrap it and throw that this can't
// be resolved. // be resolved.
throw new DependencyContainerResolutionException(typeToConstruct, ex); throw new DependencyContainerResolutionException(typeToConstruct, ex);
} } catch(Exception ex) {
catch (Exception ex)
{
throw new DependencyContainerResolutionException(typeToConstruct, ex); throw new DependencyContainerResolutionException(typeToConstruct, ex);
} }
} }
try try {
{
return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args); return CreateObjectConstructionDelegateWithCache(constructor).Invoke(args);
} } catch(Exception ex) {
catch (Exception ex)
{
throw new DependencyContainerResolutionException(typeToConstruct, ex); throw new DependencyContainerResolutionException(typeToConstruct, ex);
} }
} }
private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) private static ObjectConstructor CreateObjectConstructionDelegateWithCache(ConstructorInfo constructor) {
{ if(ObjectConstructorCache.TryGetValue(constructor, out ObjectConstructor objectConstructor)) {
if (ObjectConstructorCache.TryGetValue(constructor, out var objectConstructor))
return objectConstructor; return objectConstructor;
}
// We could lock the cache here, but there's no real side // We could lock the cache here, but there's no real side
// effect to two threads creating the same ObjectConstructor // effect to two threads creating the same ObjectConstructor
// at the same time, compared to the cost of a lock for // at the same time, compared to the cost of a lock for
// every creation. // every creation.
var constructorParams = constructor.GetParameters(); ParameterInfo[] constructorParams = constructor.GetParameters();
var lambdaParams = Expression.Parameter(typeof(object[]), "parameters"); ParameterExpression lambdaParams = Expression.Parameter(typeof(Object[]), "parameters");
var newParams = new Expression[constructorParams.Length]; Expression[] newParams = new Expression[constructorParams.Length];
for (var i = 0; i < constructorParams.Length; i++) for(Int32 i = 0; i < constructorParams.Length; i++) {
{ BinaryExpression paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
var paramsParameter = Expression.ArrayIndex(lambdaParams, Expression.Constant(i));
newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType); newParams[i] = Expression.Convert(paramsParameter, constructorParams[i].ParameterType);
} }
var newExpression = Expression.New(constructor, newParams); NewExpression newExpression = Expression.New(constructor, newParams);
var constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams); LambdaExpression constructionLambda = Expression.Lambda(typeof(ObjectConstructor), newExpression, lambdaParams);
objectConstructor = (ObjectConstructor)constructionLambda.Compile(); objectConstructor = (ObjectConstructor)constructionLambda.Compile();
@ -287,66 +244,65 @@
private static IEnumerable<ConstructorInfo> GetTypeConstructors(Type type) private static IEnumerable<ConstructorInfo> GetTypeConstructors(Type type)
=> type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length); => type.GetConstructors().OrderByDescending(ctor => ctor.GetParameters().Length);
private static bool IsAutomaticLazyFactoryRequest(Type type) private static Boolean IsAutomaticLazyFactoryRequest(Type type) {
{ if(!type.IsGenericType()) {
if (!type.IsGenericType())
return false; return false;
}
var genericType = type.GetGenericTypeDefinition(); Type genericType = type.GetGenericTypeDefinition();
// Just a func // Just a func
if (genericType == typeof(Func<>)) if(genericType == typeof(Func<>)) {
return true; return true;
}
// 2 parameter func with string as first parameter (name) // 2 parameter func with string as first parameter (name)
if (genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(string)) if(genericType == typeof(Func<,>) && type.GetGenericArguments()[0] == typeof(String)) {
return true; return true;
}
// 3 parameter func with string as first parameter (name) and IDictionary<string, object> as second (parameters) // 3 parameter func with string as first parameter (name) and IDictionary<string, object> as second (parameters)
return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(string) && return genericType == typeof(Func<,,>) && type.GetGenericArguments()[0] == typeof(String) &&
type.GetGenericArguments()[1] == typeof(IDictionary<string, object>); type.GetGenericArguments()[1] == typeof(IDictionary<String, Object>);
} }
private ObjectFactoryBase GetParentObjectFactory(DependencyContainer.TypeRegistration registration) private ObjectFactoryBase GetParentObjectFactory(DependencyContainer.TypeRegistration registration) => this._dependencyContainer.Parent == null
{ ? null
if (_dependencyContainer.Parent == null) : this._dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out ObjectFactoryBase factory)
return null; ? factory.GetFactoryForChildContainer(registration.Type, this._dependencyContainer.Parent, this._dependencyContainer)
: this._dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
return _dependencyContainer.Parent.RegisteredTypes.TryGetValue(registration, out var factory)
? factory.GetFactoryForChildContainer(registration.Type, _dependencyContainer.Parent, _dependencyContainer)
: _dependencyContainer.Parent.RegisteredTypes.GetParentObjectFactory(registration);
}
private ConstructorInfo GetBestConstructor( private ConstructorInfo GetBestConstructor(
Type type, Type type,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options)
=> type.IsValueType() ? null : GetTypeConstructors(type).FirstOrDefault(ctor => CanConstruct(ctor, options)); => type.IsValueType() ? null : GetTypeConstructors(type).FirstOrDefault(ctor => this.CanConstruct(ctor, options));
private bool CanConstruct( private Boolean CanConstruct(
ConstructorInfo ctor, ConstructorInfo ctor,
DependencyContainerResolveOptions options) DependencyContainerResolveOptions options) {
{ foreach(ParameterInfo parameter in ctor.GetParameters()) {
foreach (var parameter in ctor.GetParameters()) if(String.IsNullOrEmpty(parameter.Name)) {
{
if (string.IsNullOrEmpty(parameter.Name))
return false; return false;
}
var isParameterOverload = options.ConstructorParameters.ContainsKey(parameter.Name); Boolean isParameterOverload = options.ConstructorParameters.ContainsKey(parameter.Name);
if (parameter.ParameterType.IsPrimitive() && !isParameterOverload) if(parameter.ParameterType.IsPrimitive() && !isParameterOverload) {
return false; return false;
}
if (!isParameterOverload && if(!isParameterOverload &&
!CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone())) !this.CanResolve(new DependencyContainer.TypeRegistration(parameter.ParameterType), options.Clone())) {
return false; return false;
} }
}
return true; return true;
} }
private IEnumerable<DependencyContainer.TypeRegistration> GetParentRegistrationsForType(Type resolveType) private IEnumerable<DependencyContainer.TypeRegistration> GetParentRegistrationsForType(Type resolveType)
=> _dependencyContainer.Parent == null => this._dependencyContainer.Parent == null
? new DependencyContainer.TypeRegistration[] { } ? new DependencyContainer.TypeRegistration[] { }
: _dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(_dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType)); : this._dependencyContainer.Parent.RegisteredTypes.Keys.Where(tr => tr.Type == resolveType).Concat(this._dependencyContainer.Parent.RegisteredTypes.GetParentRegistrationsForType(resolveType));
} }
} }

View File

@ -1,10 +1,8 @@
namespace Unosquare.Swan namespace Unosquare.Swan {
{
/// <summary> /// <summary>
/// Enumerates the possible causes of the DataReceived event occurring. /// Enumerates the possible causes of the DataReceived event occurring.
/// </summary> /// </summary>
public enum ConnectionDataReceivedTrigger public enum ConnectionDataReceivedTrigger {
{
/// <summary> /// <summary>
/// The trigger was a forceful flush of the buffer /// The trigger was a forceful flush of the buffer
/// </summary> /// </summary>

View File

@ -1,24 +1,19 @@
namespace Unosquare.Swan using System;
{ using System.Net;
using System; using System.Net.Sockets;
using System.Net;
using System.Net.Sockets;
namespace Unosquare.Swan {
/// <summary> /// <summary>
/// The event arguments for when connections are accepted. /// The event arguments for when connections are accepted.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionAcceptedEventArgs : EventArgs public class ConnectionAcceptedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionAcceptedEventArgs" /> class. /// Initializes a new instance of the <see cref="ConnectionAcceptedEventArgs" /> class.
/// </summary> /// </summary>
/// <param name="client">The client.</param> /// <param name="client">The client.</param>
/// <exception cref="ArgumentNullException">client.</exception> /// <exception cref="ArgumentNullException">client.</exception>
public ConnectionAcceptedEventArgs(TcpClient client) public ConnectionAcceptedEventArgs(TcpClient client) => this.Client = client ?? throw new ArgumentNullException(nameof(client));
{
Client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary> /// <summary>
/// Gets the client. /// Gets the client.
@ -26,22 +21,22 @@
/// <value> /// <value>
/// The client. /// The client.
/// </value> /// </value>
public TcpClient Client { get; } public TcpClient Client {
get;
}
} }
/// <summary> /// <summary>
/// Occurs before a connection is accepted. Set the Cancel property to true to prevent the connection from being accepted. /// Occurs before a connection is accepted. Set the Cancel property to true to prevent the connection from being accepted.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.ConnectionAcceptedEventArgs" /> /// <seealso cref="Unosquare.Swan.ConnectionAcceptedEventArgs" />
public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs public class ConnectionAcceptingEventArgs : ConnectionAcceptedEventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class. /// Initializes a new instance of the <see cref="ConnectionAcceptingEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="client">The client.</param> /// <param name="client">The client.</param>
public ConnectionAcceptingEventArgs(TcpClient client) public ConnectionAcceptingEventArgs(TcpClient client)
: base(client) : base(client) {
{
} }
/// <summary> /// <summary>
@ -50,24 +45,22 @@
/// <value> /// <value>
/// <c>true</c> if cancel; otherwise, <c>false</c>. /// <c>true</c> if cancel; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Cancel { get; set; } public Boolean Cancel {
get; set;
}
} }
/// <summary> /// <summary>
/// Event arguments for when a server listener is started. /// Event arguments for when a server listener is started.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionListenerStartedEventArgs : EventArgs public class ConnectionListenerStartedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionListenerStartedEventArgs" /> class. /// Initializes a new instance of the <see cref="ConnectionListenerStartedEventArgs" /> class.
/// </summary> /// </summary>
/// <param name="listenerEndPoint">The listener end point.</param> /// <param name="listenerEndPoint">The listener end point.</param>
/// <exception cref="ArgumentNullException">listenerEndPoint.</exception> /// <exception cref="ArgumentNullException">listenerEndPoint.</exception>
public ConnectionListenerStartedEventArgs(IPEndPoint listenerEndPoint) public ConnectionListenerStartedEventArgs(IPEndPoint listenerEndPoint) => this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
{
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
}
/// <summary> /// <summary>
/// Gets the end point. /// Gets the end point.
@ -75,15 +68,16 @@
/// <value> /// <value>
/// The end point. /// The end point.
/// </value> /// </value>
public IPEndPoint EndPoint { get; } public IPEndPoint EndPoint {
get;
}
} }
/// <summary> /// <summary>
/// Event arguments for when a server listener fails to start. /// Event arguments for when a server listener fails to start.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionListenerFailedEventArgs : EventArgs public class ConnectionListenerFailedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class. /// Initializes a new instance of the <see cref="ConnectionListenerFailedEventArgs" /> class.
/// </summary> /// </summary>
@ -94,10 +88,9 @@
/// or /// or
/// ex. /// ex.
/// </exception> /// </exception>
public ConnectionListenerFailedEventArgs(IPEndPoint listenerEndPoint, Exception ex) public ConnectionListenerFailedEventArgs(IPEndPoint listenerEndPoint, Exception ex) {
{ this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint)); this.Error = ex ?? throw new ArgumentNullException(nameof(ex));
Error = ex ?? throw new ArgumentNullException(nameof(ex));
} }
/// <summary> /// <summary>
@ -106,7 +99,9 @@
/// <value> /// <value>
/// The end point. /// The end point.
/// </value> /// </value>
public IPEndPoint EndPoint { get; } public IPEndPoint EndPoint {
get;
}
/// <summary> /// <summary>
/// Gets the error. /// Gets the error.
@ -114,15 +109,16 @@
/// <value> /// <value>
/// The error. /// The error.
/// </value> /// </value>
public Exception Error { get; } public Exception Error {
get;
}
} }
/// <summary> /// <summary>
/// Event arguments for when a server listener stopped. /// Event arguments for when a server listener stopped.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionListenerStoppedEventArgs : EventArgs public class ConnectionListenerStoppedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionListenerStoppedEventArgs" /> class. /// Initializes a new instance of the <see cref="ConnectionListenerStoppedEventArgs" /> class.
/// </summary> /// </summary>
@ -133,10 +129,9 @@
/// or /// or
/// ex. /// ex.
/// </exception> /// </exception>
public ConnectionListenerStoppedEventArgs(IPEndPoint listenerEndPoint, Exception ex = null) public ConnectionListenerStoppedEventArgs(IPEndPoint listenerEndPoint, Exception ex = null) {
{ this.EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint));
EndPoint = listenerEndPoint ?? throw new ArgumentNullException(nameof(listenerEndPoint)); this.Error = ex;
Error = ex;
} }
/// <summary> /// <summary>
@ -145,7 +140,9 @@
/// <value> /// <value>
/// The end point. /// The end point.
/// </value> /// </value>
public IPEndPoint EndPoint { get; } public IPEndPoint EndPoint {
get;
}
/// <summary> /// <summary>
/// Gets the error. /// Gets the error.
@ -153,6 +150,8 @@
/// <value> /// <value>
/// The error. /// The error.
/// </value> /// </value>
public Exception Error { get; } public Exception Error {
get;
}
} }
} }

View File

@ -1,22 +1,17 @@
namespace Unosquare.Swan using System;
{ using System.Text;
using System;
using System.Text;
namespace Unosquare.Swan {
/// <summary> /// <summary>
/// The event arguments for connection failure events. /// The event arguments for connection failure events.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionFailureEventArgs : EventArgs public class ConnectionFailureEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionFailureEventArgs"/> class. /// Initializes a new instance of the <see cref="ConnectionFailureEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="ex">The ex.</param> /// <param name="ex">The ex.</param>
public ConnectionFailureEventArgs(Exception ex) public ConnectionFailureEventArgs(Exception ex) => this.Error = ex;
{
Error = ex;
}
/// <summary> /// <summary>
/// Gets the error. /// Gets the error.
@ -24,26 +19,26 @@
/// <value> /// <value>
/// The error. /// The error.
/// </value> /// </value>
public Exception Error { get; } public Exception Error {
get;
}
} }
/// <summary> /// <summary>
/// Event arguments for when data is received. /// Event arguments for when data is received.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class ConnectionDataReceivedEventArgs : EventArgs public class ConnectionDataReceivedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConnectionDataReceivedEventArgs"/> class. /// Initializes a new instance of the <see cref="ConnectionDataReceivedEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="trigger">The trigger.</param> /// <param name="trigger">The trigger.</param>
/// <param name="moreAvailable">if set to <c>true</c> [more available].</param> /// <param name="moreAvailable">if set to <c>true</c> [more available].</param>
public ConnectionDataReceivedEventArgs(byte[] buffer, ConnectionDataReceivedTrigger trigger, bool moreAvailable) public ConnectionDataReceivedEventArgs(Byte[] buffer, ConnectionDataReceivedTrigger trigger, Boolean moreAvailable) {
{ this.Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); this.Trigger = trigger;
Trigger = trigger; this.HasMoreAvailable = moreAvailable;
HasMoreAvailable = moreAvailable;
} }
/// <summary> /// <summary>
@ -52,7 +47,9 @@
/// <value> /// <value>
/// The buffer. /// The buffer.
/// </value> /// </value>
public byte[] Buffer { get; } public Byte[] Buffer {
get;
}
/// <summary> /// <summary>
/// Gets the cause as to why this event was thrown. /// Gets the cause as to why this event was thrown.
@ -60,7 +57,9 @@
/// <value> /// <value>
/// The trigger. /// The trigger.
/// </value> /// </value>
public ConnectionDataReceivedTrigger Trigger { get; } public ConnectionDataReceivedTrigger Trigger {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the receive buffer has more bytes available. /// Gets a value indicating whether the receive buffer has more bytes available.
@ -68,7 +67,9 @@
/// <value> /// <value>
/// <c>true</c> if this instance has more available; otherwise, <c>false</c>. /// <c>true</c> if this instance has more available; otherwise, <c>false</c>.
/// </value> /// </value>
public bool HasMoreAvailable { get; } public Boolean HasMoreAvailable {
get;
}
/// <summary> /// <summary>
/// Gets the string from the given buffer. /// Gets the string from the given buffer.
@ -76,7 +77,7 @@
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static string GetStringFromBuffer(byte[] buffer, Encoding encoding) public static String GetStringFromBuffer(Byte[] buffer, Encoding encoding)
=> encoding.GetString(buffer).TrimEnd('\r', '\n'); => encoding.GetString(buffer).TrimEnd('\r', '\n');
/// <summary> /// <summary>
@ -84,7 +85,7 @@
/// </summary> /// </summary>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public string GetStringFromBuffer(Encoding encoding) public String GetStringFromBuffer(Encoding encoding)
=> GetStringFromBuffer(Buffer, encoding); => GetStringFromBuffer(this.Buffer, encoding);
} }
} }

View File

@ -1,19 +1,17 @@
namespace Unosquare.Swan.Exceptions using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// Generic Constraint Registration Exception. /// Generic Constraint Registration Exception.
/// </summary> /// </summary>
/// <seealso cref="Exception" /> /// <seealso cref="Exception" />
public class DependencyContainerRegistrationException : Exception public class DependencyContainerRegistrationException : Exception {
{ private const String ConvertErrorText = "Cannot convert current registration of {0} to {1}";
private const string ConvertErrorText = "Cannot convert current registration of {0} to {1}"; private const String RegisterErrorText =
private const string RegisterErrorText =
"Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}."; "Cannot register type {0} - abstract classes or interfaces are not valid implementation types for {1}.";
private const string ErrorText = "Duplicate implementation of type {0} found ({1})."; private const String ErrorText = "Duplicate implementation of type {0} found ({1}).";
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class. /// Initializes a new instance of the <see cref="DependencyContainerRegistrationException"/> class.
@ -21,8 +19,7 @@
/// <param name="registerType">Type of the register.</param> /// <param name="registerType">Type of the register.</param>
/// <param name="types">The types.</param> /// <param name="types">The types.</param>
public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types) public DependencyContainerRegistrationException(Type registerType, IEnumerable<Type> types)
: base(string.Format(ErrorText, registerType, GetTypesString(types))) : base(String.Format(ErrorText, registerType, GetTypesString(types))) {
{
} }
/// <summary> /// <summary>
@ -31,16 +28,12 @@
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="method">The method.</param> /// <param name="method">The method.</param>
/// <param name="isTypeFactory">if set to <c>true</c> [is type factory].</param> /// <param name="isTypeFactory">if set to <c>true</c> [is type factory].</param>
public DependencyContainerRegistrationException(Type type, string method, bool isTypeFactory = false) public DependencyContainerRegistrationException(Type type, String method, Boolean isTypeFactory = false)
: base(isTypeFactory : base(isTypeFactory
? string.Format(RegisterErrorText, type.FullName, method) ? String.Format(RegisterErrorText, type.FullName, method)
: string.Format(ConvertErrorText, type.FullName, method)) : String.Format(ConvertErrorText, type.FullName, method)) {
{
} }
private static string GetTypesString(IEnumerable<Type> types) private static String GetTypesString(IEnumerable<Type> types) => String.Join(",", types.Select(type => type.FullName));
{
return string.Join(",", types.Select(type => type.FullName));
}
} }
} }

View File

@ -1,22 +1,19 @@
namespace Unosquare.Swan.Exceptions using System;
{
using System;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// An exception for dependency resolutions. /// An exception for dependency resolutions.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <seealso cref="System.Exception" />
public class DependencyContainerResolutionException : Exception public class DependencyContainerResolutionException : Exception {
{ private const String ErrorText = "Unable to resolve type: {0}";
private const string ErrorText = "Unable to resolve type: {0}";
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class. /// Initializes a new instance of the <see cref="DependencyContainerResolutionException"/> class.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
public DependencyContainerResolutionException(Type type) public DependencyContainerResolutionException(Type type)
: base(string.Format(ErrorText, type.FullName)) : base(String.Format(ErrorText, type.FullName)) {
{
} }
/// <summary> /// <summary>
@ -25,8 +22,7 @@
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="innerException">The inner exception.</param> /// <param name="innerException">The inner exception.</param>
public DependencyContainerResolutionException(Type type, Exception innerException) public DependencyContainerResolutionException(Type type, Exception innerException)
: base(string.Format(ErrorText, type.FullName), innerException) : base(String.Format(ErrorText, type.FullName), innerException) {
{
} }
} }
} }

View File

@ -1,22 +1,19 @@
namespace Unosquare.Swan.Exceptions using System;
{
using System;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// Weak Reference Exception. /// Weak Reference Exception.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <seealso cref="System.Exception" />
public class DependencyContainerWeakReferenceException : Exception public class DependencyContainerWeakReferenceException : Exception {
{ private const String ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
private const string ErrorText = "Unable to instantiate {0} - referenced object has been reclaimed";
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DependencyContainerWeakReferenceException"/> class. /// Initializes a new instance of the <see cref="DependencyContainerWeakReferenceException"/> class.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
public DependencyContainerWeakReferenceException(Type type) public DependencyContainerWeakReferenceException(Type type)
: base(string.Format(ErrorText, type.FullName)) : base(String.Format(ErrorText, type.FullName)) {
{
} }
} }
} }

View File

@ -1,40 +1,31 @@
namespace Unosquare.Swan.Exceptions using System;
{ using Unosquare.Swan.Networking;
using System;
using Networking;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// An exception thrown when the DNS query fails. /// An exception thrown when the DNS query fails.
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <seealso cref="System.Exception" />
public class DnsQueryException : Exception public class DnsQueryException : Exception {
{ internal DnsQueryException(String message)
internal DnsQueryException(string message) : base(message) {
: base(message)
{
} }
internal DnsQueryException(string message, Exception e) internal DnsQueryException(String message, Exception e)
: base(message, e) : base(message, e) {
{
} }
internal DnsQueryException(DnsClient.IDnsResponse response) internal DnsQueryException(DnsClient.IDnsResponse response)
: this(response, Format(response)) : this(response, Format(response)) {
{
} }
internal DnsQueryException(DnsClient.IDnsResponse response, string message) internal DnsQueryException(DnsClient.IDnsResponse response, String message)
: base(message) : base(message) => this.Response = response;
{
Response = response; internal DnsClient.IDnsResponse Response {
get;
} }
internal DnsClient.IDnsResponse Response { get; } private static String Format(DnsClient.IDnsResponse response) => $"Invalid response received with code {response.ResponseCode}";
private static string Format(DnsClient.IDnsResponse response)
{
return $"Invalid response received with code {response.ResponseCode}";
}
} }
} }

View File

@ -1,7 +1,6 @@
namespace Unosquare.Swan.Exceptions using System;
{
using System;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// Represents errors that occurs requesting a JSON file through HTTP. /// Represents errors that occurs requesting a JSON file through HTTP.
/// </summary> /// </summary>
@ -10,8 +9,7 @@
[Serializable] [Serializable]
#endif #endif
public class JsonRequestException public class JsonRequestException
: Exception : Exception {
{
/// <inheritdoc /> /// <inheritdoc />
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T:Unosquare.Swan.Exceptions.JsonRequestException" /> class. /// Initializes a new instance of the <see cref="T:Unosquare.Swan.Exceptions.JsonRequestException" /> class.
@ -19,11 +17,10 @@
/// <param name="message">The message.</param> /// <param name="message">The message.</param>
/// <param name="httpErrorCode">The HTTP error code.</param> /// <param name="httpErrorCode">The HTTP error code.</param>
/// <param name="errorContent">Content of the error.</param> /// <param name="errorContent">Content of the error.</param>
public JsonRequestException(string message, int httpErrorCode = 500, string errorContent = null) public JsonRequestException(String message, Int32 httpErrorCode = 500, String errorContent = null)
: base(message) : base(message) {
{ this.HttpErrorCode = httpErrorCode;
HttpErrorCode = httpErrorCode; this.HttpErrorContent = errorContent;
HttpErrorContent = errorContent;
} }
/// <summary> /// <summary>
@ -32,7 +29,9 @@
/// <value> /// <value>
/// The HTTP error code. /// The HTTP error code.
/// </value> /// </value>
public int HttpErrorCode { get; } public Int32 HttpErrorCode {
get;
}
/// <summary> /// <summary>
/// Gets the content of the HTTP error. /// Gets the content of the HTTP error.
@ -40,9 +39,11 @@
/// <value> /// <value>
/// The content of the HTTP error. /// The content of the HTTP error.
/// </value> /// </value>
public string HttpErrorContent { get; } public String HttpErrorContent {
get;
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => string.IsNullOrEmpty(HttpErrorContent) ? $"HTTP Response Status Code {HttpErrorCode} Error Message: {HttpErrorContent}" : base.ToString(); public override String ToString() => String.IsNullOrEmpty(this.HttpErrorContent) ? $"HTTP Response Status Code {this.HttpErrorCode} Error Message: {this.HttpErrorContent}" : base.ToString();
} }
} }

View File

@ -1,8 +1,7 @@
namespace Unosquare.Swan.Exceptions using System;
{ using Unosquare.Swan.Networking.Ldap;
using System;
using Networking.Ldap;
namespace Unosquare.Swan.Exceptions {
/// <summary> /// <summary>
/// Thrown to indicate that an Ldap exception has occurred. This is a general /// Thrown to indicate that an Ldap exception has occurred. This is a general
/// exception which includes a message and an Ldap result code. /// exception which includes a message and an Ldap result code.
@ -13,15 +12,14 @@
/// </summary> /// </summary>
/// <seealso cref="System.Exception" /> /// <seealso cref="System.Exception" />
public class LdapException public class LdapException
: Exception : Exception {
{ internal const String UnexpectedEnd = "Unexpected end of filter";
internal const string UnexpectedEnd = "Unexpected end of filter"; internal const String MissingLeftParen = "Unmatched parentheses, left parenthesis missing";
internal const string MissingLeftParen = "Unmatched parentheses, left parenthesis missing"; internal const String MissingRightParen = "Unmatched parentheses, right parenthesis missing";
internal const string MissingRightParen = "Unmatched parentheses, right parenthesis missing"; internal const String ExpectingRightParen = "Expecting right parenthesis, found \"{0}\"";
internal const string ExpectingRightParen = "Expecting right parenthesis, found \"{0}\""; internal const String ExpectingLeftParen = "Expecting left parenthesis, found \"{0}\"";
internal const string ExpectingLeftParen = "Expecting left parenthesis, found \"{0}\"";
private readonly string _serverMessage; private readonly String _serverMessage;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapException" /> class. /// Initializes a new instance of the <see cref="LdapException" /> class.
@ -41,17 +39,16 @@
/// be matched by the server on a search operation.</param> /// be matched by the server on a search operation.</param>
/// <param name="rootException">The root exception.</param> /// <param name="rootException">The root exception.</param>
public LdapException( public LdapException(
string message, String message,
LdapStatusCode resultCode, LdapStatusCode resultCode,
string serverMsg = null, String serverMsg = null,
string matchedDN = null, String matchedDN = null,
Exception rootException = null) Exception rootException = null)
: base(message) : base(message) {
{ this.ResultCode = resultCode;
ResultCode = resultCode; this.Cause = rootException;
Cause = rootException; this.MatchedDN = matchedDN;
MatchedDN = matchedDN; this._serverMessage = serverMsg;
_serverMessage = serverMsg;
} }
/// <summary> /// <summary>
@ -62,8 +59,8 @@
/// <returns> /// <returns>
/// The error message or null if the message was not set. /// The error message or null if the message was not set.
/// </returns> /// </returns>
public string LdapErrorMessage => public String LdapErrorMessage =>
_serverMessage != null && _serverMessage.Length == 0 ? null : _serverMessage; this._serverMessage != null && this._serverMessage.Length == 0 ? null : this._serverMessage;
/// <summary> /// <summary>
/// Returns the lower level Exception which caused the failure, if any. /// Returns the lower level Exception which caused the failure, if any.
@ -73,7 +70,9 @@
/// <value> /// <value>
/// The cause. /// The cause.
/// </value> /// </value>
public Exception Cause { get; } public Exception Cause {
get;
}
/// <summary> /// <summary>
/// Returns the result code from the exception. /// Returns the result code from the exception.
@ -86,7 +85,9 @@
/// <value> /// <value>
/// The result code. /// The result code.
/// </value> /// </value>
public LdapStatusCode ResultCode { get; } public LdapStatusCode ResultCode {
get;
}
/// <summary> /// <summary>
/// Returns the part of a submitted distinguished name which could be /// Returns the part of a submitted distinguished name which could be
@ -100,32 +101,30 @@
/// <value> /// <value>
/// The matched dn. /// The matched dn.
/// </value> /// </value>
public string MatchedDN { get; } public String MatchedDN {
get;
}
/// <inheritdoc /> /// <inheritdoc />
public override string Message => ResultCode.ToString().Humanize(); public override String Message => this.ResultCode.ToString().Humanize();
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override String ToString() {
{
// Craft a string from the resource file // Craft a string from the resource file
var msg = $"{nameof(LdapException)}: {base.Message} ({ResultCode}) {ResultCode.ToString().Humanize()}"; String msg = $"{nameof(LdapException)}: {base.Message} ({this.ResultCode}) {this.ResultCode.ToString().Humanize()}";
// Add server message // Add server message
if (!string.IsNullOrEmpty(_serverMessage)) if(!String.IsNullOrEmpty(this._serverMessage)) {
{ msg += $"\r\nServer Message: {this._serverMessage}";
msg += $"\r\nServer Message: {_serverMessage}";
} }
// Add Matched DN message // Add Matched DN message
if (MatchedDN != null) if(this.MatchedDN != null) {
{ msg += $"\r\nMatched DN: {this.MatchedDN}";
msg += $"\r\nMatched DN: {MatchedDN}";
} }
if (Cause != null) if(this.Cause != null) {
{ msg += $"\r\n{this.Cause}";
msg += $"\r\n{Cause}";
} }
return msg; return msg;

View File

@ -1,29 +1,26 @@
#if NET452 || NETSTANDARD2_0 #if NET452 || NETSTANDARD2_0
namespace Unosquare.Swan using System;
{ using System.IO;
using System; using System.Net.Mail;
using System.IO; using System.Reflection;
using System.Net.Mail; namespace Unosquare.Swan {
using System.Reflection;
/// <summary> /// <summary>
/// Extension methods. /// Extension methods.
/// </summary> /// </summary>
public static class SmtpExtensions public static class SmtpExtensions {
{
/// <summary> /// <summary>
/// The raw contents of this MailMessage as a MemoryStream. /// The raw contents of this MailMessage as a MemoryStream.
/// </summary> /// </summary>
/// <param name="self">The caller.</param> /// <param name="self">The caller.</param>
/// <returns>A MemoryStream with the raw contents of this MailMessage.</returns> /// <returns>A MemoryStream with the raw contents of this MailMessage.</returns>
public static MemoryStream ToMimeMessage(this MailMessage self) public static MemoryStream ToMimeMessage(this MailMessage self) {
{ if(self == null) {
if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
}
var result = new MemoryStream(); MemoryStream result = new MemoryStream();
var mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new object[] { result }); Object mailWriter = MimeMessageConstants.MailWriterConstructor.Invoke(new Object[] { result });
MimeMessageConstants.SendMethod.Invoke( _ = MimeMessageConstants.SendMethod.Invoke(
self, self,
MimeMessageConstants.PrivateInstanceFlags, MimeMessageConstants.PrivateInstanceFlags,
null, null,
@ -31,24 +28,23 @@ namespace Unosquare.Swan
null); null);
result = new MemoryStream(result.ToArray()); result = new MemoryStream(result.ToArray());
MimeMessageConstants.CloseMethod.Invoke( _ = MimeMessageConstants.CloseMethod.Invoke(
mailWriter, mailWriter,
MimeMessageConstants.PrivateInstanceFlags, MimeMessageConstants.PrivateInstanceFlags,
null, null,
new object[] { }, new Object[] { },
null); null);
result.Position = 0; result.Position = 0;
return result; return result;
} }
internal static class MimeMessageConstants internal static class MimeMessageConstants {
{
public static readonly BindingFlags PrivateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic; public static readonly BindingFlags PrivateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
public static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter"); public static readonly Type MailWriter = typeof(SmtpClient).Assembly.GetType("System.Net.Mail.MailWriter");
public static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(PrivateInstanceFlags, null, new[] { typeof(Stream) }, null); public static readonly ConstructorInfo MailWriterConstructor = MailWriter.GetConstructor(PrivateInstanceFlags, null, new[] { typeof(Stream) }, null);
public static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags); public static readonly MethodInfo CloseMethod = MailWriter.GetMethod("Close", PrivateInstanceFlags);
public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags); public static readonly MethodInfo SendMethod = typeof(MailMessage).GetMethod("Send", PrivateInstanceFlags);
public static readonly bool IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3; public static readonly Boolean IsRunningInDotNetFourPointFive = SendMethod.GetParameters().Length == 3;
} }
} }
} }

View File

@ -1,15 +1,13 @@
namespace Unosquare.Swan using System;
{ using System.Linq;
using System; using System.Net;
using System.Linq; using System.Net.Sockets;
using System.Net;
using System.Net.Sockets;
namespace Unosquare.Swan {
/// <summary> /// <summary>
/// Provides various extension methods for networking-related tasks. /// Provides various extension methods for networking-related tasks.
/// </summary> /// </summary>
public static class NetworkExtensions public static class NetworkExtensions {
{
/// <summary> /// <summary>
/// Determines whether the IP address is private. /// Determines whether the IP address is private.
/// </summary> /// </summary>
@ -18,15 +16,15 @@
/// True if the IP Address is private; otherwise, false. /// True if the IP Address is private; otherwise, false.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">address.</exception> /// <exception cref="ArgumentNullException">address.</exception>
public static bool IsPrivateAddress(this IPAddress address) public static Boolean IsPrivateAddress(this IPAddress address) {
{ if(address == null) {
if (address == null)
throw new ArgumentNullException(nameof(address)); throw new ArgumentNullException(nameof(address));
}
var octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(byte.Parse).ToArray(); Byte[] octets = address.ToString().Split(new[] { "." }, StringSplitOptions.RemoveEmptyEntries).Select(Byte.Parse).ToArray();
var is24Bit = octets[0] == 10; Boolean is24Bit = octets[0] == 10;
var is20Bit = octets[0] == 172 && (octets[1] >= 16 && octets[1] <= 31); Boolean is20Bit = octets[0] == 172 && octets[1] >= 16 && octets[1] <= 31;
var is16Bit = octets[0] == 192 && octets[1] == 168; Boolean is16Bit = octets[0] == 192 && octets[1] == 168;
return is24Bit || is20Bit || is16Bit; return is24Bit || is20Bit || is16Bit;
} }
@ -40,17 +38,19 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">address.</exception> /// <exception cref="ArgumentNullException">address.</exception>
/// <exception cref="ArgumentException">InterNetwork - address.</exception> /// <exception cref="ArgumentException">InterNetwork - address.</exception>
public static uint ToUInt32(this IPAddress address) public static UInt32 ToUInt32(this IPAddress address) {
{ if(address == null) {
if (address == null)
throw new ArgumentNullException(nameof(address)); throw new ArgumentNullException(nameof(address));
}
if (address.AddressFamily != AddressFamily.InterNetwork) if(address.AddressFamily != AddressFamily.InterNetwork) {
throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address)); throw new ArgumentException($"Address has to be of family '{nameof(AddressFamily.InterNetwork)}'", nameof(address));
}
var addressBytes = address.GetAddressBytes(); Byte[] addressBytes = address.GetAddressBytes();
if (BitConverter.IsLittleEndian) if(BitConverter.IsLittleEndian) {
Array.Reverse(addressBytes); Array.Reverse(addressBytes);
}
return BitConverter.ToUInt32(addressBytes, 0); return BitConverter.ToUInt32(addressBytes, 0);
} }

View File

@ -1,29 +1,26 @@
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
namespace Unosquare.Swan using System;
{ using System.Collections.Generic;
using System; using System.Reflection;
using System.Collections.Generic; using System.Threading;
using System.Reflection;
using System.Threading;
#if NET452 #if NET452
using System.ServiceProcess; using System.ServiceProcess;
#else #else
using Abstractions; using Abstractions;
#endif #endif
namespace Unosquare.Swan {
/// <summary> /// <summary>
/// Extension methods. /// Extension methods.
/// </summary> /// </summary>
public static class WindowsServicesExtensions public static class WindowsServicesExtensions {
{
/// <summary> /// <summary>
/// Runs a service in console mode. /// Runs a service in console mode.
/// </summary> /// </summary>
/// <param name="serviceToRun">The service to run.</param> /// <param name="serviceToRun">The service to run.</param>
public static void RunInConsoleMode(this ServiceBase serviceToRun) public static void RunInConsoleMode(this ServiceBase serviceToRun) {
{ if(serviceToRun == null) {
if (serviceToRun == null)
throw new ArgumentNullException(nameof(serviceToRun)); throw new ArgumentNullException(nameof(serviceToRun));
}
RunInConsoleMode(new[] { serviceToRun }); RunInConsoleMode(new[] { serviceToRun });
} }
@ -32,27 +29,25 @@ namespace Unosquare.Swan
/// Runs a set of services in console mode. /// Runs a set of services in console mode.
/// </summary> /// </summary>
/// <param name="servicesToRun">The services to run.</param> /// <param name="servicesToRun">The services to run.</param>
public static void RunInConsoleMode(this ServiceBase[] servicesToRun) public static void RunInConsoleMode(this ServiceBase[] servicesToRun) {
{ if(servicesToRun == null) {
if (servicesToRun == null)
throw new ArgumentNullException(nameof(servicesToRun)); throw new ArgumentNullException(nameof(servicesToRun));
}
const string onStartMethodName = "OnStart"; const String onStartMethodName = "OnStart";
const string onStopMethodName = "OnStop"; const String onStopMethodName = "OnStop";
var onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName, MethodInfo onStartMethod = typeof(ServiceBase).GetMethod(onStartMethodName,
BindingFlags.Instance | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.NonPublic);
var onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName, MethodInfo onStopMethod = typeof(ServiceBase).GetMethod(onStopMethodName,
BindingFlags.Instance | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.NonPublic);
var serviceThreads = new List<Thread>(); List<Thread> serviceThreads = new List<Thread>();
"Starting services . . .".Info(Runtime.EntryAssemblyName.Name); "Starting services . . .".Info(Runtime.EntryAssemblyName.Name);
foreach (var service in servicesToRun) foreach(ServiceBase service in servicesToRun) {
{ Thread thread = new Thread(() => {
var thread = new Thread(() => _ = onStartMethod.Invoke(service, new Object[] { new String[] { } });
{
onStartMethod.Invoke(service, new object[] { new string[] { } });
$"Started service '{service.GetType().Name}'".Info(service.GetType()); $"Started service '{service.GetType().Name}'".Info(service.GetType());
}); });
@ -61,17 +56,17 @@ namespace Unosquare.Swan
} }
"Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name); "Press any key to stop all services.".Info(Runtime.EntryAssemblyName.Name);
Terminal.ReadKey(true, true); _ = Terminal.ReadKey(true, true);
"Stopping services . . .".Info(Runtime.EntryAssemblyName.Name); "Stopping services . . .".Info(Runtime.EntryAssemblyName.Name);
foreach (var service in servicesToRun) foreach(ServiceBase service in servicesToRun) {
{ _ = onStopMethod.Invoke(service, null);
onStopMethod.Invoke(service, null);
$"Stopped service '{service.GetType().Name}'".Info(service.GetType()); $"Stopped service '{service.GetType().Name}'".Info(service.GetType());
} }
foreach (var thread in serviceThreads) foreach(Thread thread in serviceThreads) {
thread.Join(); thread.Join();
}
"Stopped all services.".Info(Runtime.EntryAssemblyName.Name); "Stopped all services.".Info(Runtime.EntryAssemblyName.Name);
} }

View File

@ -1,12 +1,11 @@
#if NET452 #if NET452
namespace Unosquare.Swan.Formatters using System;
{ using System.Drawing;
using System; using System.Drawing.Imaging;
using System.Drawing; using System.Runtime.InteropServices;
using System.Drawing.Imaging; using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Unosquare.Swan.Formatters {
/// <summary> /// <summary>
/// Represents a buffer of bytes containing pixels in BGRA byte order /// Represents a buffer of bytes containing pixels in BGRA byte order
/// loaded from an image that is passed on to the constructor. /// loaded from an image that is passed on to the constructor.
@ -14,49 +13,45 @@ namespace Unosquare.Swan.Formatters
/// where they can be quickly changed and then a new bitmap /// where they can be quickly changed and then a new bitmap
/// can be created from the byte data. /// can be created from the byte data.
/// </summary> /// </summary>
public class BitmapBuffer public class BitmapBuffer {
{
/// <summary> /// <summary>
/// A constant representing the number of /// A constant representing the number of
/// bytes per pixel in the pixel data. This is /// bytes per pixel in the pixel data. This is
/// always 4 but it is kept here for readability. /// always 4 but it is kept here for readability.
/// </summary> /// </summary>
public const int BytesPerPixel = 4; public const Int32 BytesPerPixel = 4;
/// <summary> /// <summary>
/// The blue byte offset within a pixel offset. This is 0. /// The blue byte offset within a pixel offset. This is 0.
/// </summary> /// </summary>
public const int BOffset = 0; public const Int32 BOffset = 0;
/// <summary> /// <summary>
/// The green byte offset within a pixel offset. This is 1. /// The green byte offset within a pixel offset. This is 1.
/// </summary> /// </summary>
public const int GOffset = 1; public const Int32 GOffset = 1;
/// <summary> /// <summary>
/// The red byte offset within a pixel offset. This is 2. /// The red byte offset within a pixel offset. This is 2.
/// </summary> /// </summary>
public const int ROffset = 2; public const Int32 ROffset = 2;
/// <summary> /// <summary>
/// The alpha byte offset within a pixel offset. This is 3. /// The alpha byte offset within a pixel offset. This is 3.
/// </summary> /// </summary>
public const int AOffset = 3; public const Int32 AOffset = 3;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BitmapBuffer"/> class. /// Initializes a new instance of the <see cref="BitmapBuffer"/> class.
/// Data will not contain left-over stride bytes /// Data will not contain left-over stride bytes
/// </summary> /// </summary>
/// <param name="sourceImage">The source image.</param> /// <param name="sourceImage">The source image.</param>
public BitmapBuffer(Image sourceImage) public BitmapBuffer(Image sourceImage) {
{
// Acquire or create the source bitmap in a manageable format // Acquire or create the source bitmap in a manageable format
var disposeSourceBitmap = false; Boolean disposeSourceBitmap = false;
if (!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != PixelFormat) if(!(sourceImage is Bitmap sourceBitmap) || sourceBitmap.PixelFormat != this.PixelFormat) {
{ sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, this.PixelFormat);
sourceBitmap = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat); using(Graphics g = Graphics.FromImage(sourceBitmap)) {
using (var g = Graphics.FromImage(sourceBitmap))
{
g.DrawImage(sourceImage, 0, 0); g.DrawImage(sourceImage, 0, 0);
} }
@ -65,34 +60,32 @@ namespace Unosquare.Swan.Formatters
} }
// Lock the bits // Lock the bits
var sourceDataLocker = sourceBitmap.LockBits( BitmapData sourceDataLocker = sourceBitmap.LockBits(
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly, ImageLockMode.ReadOnly,
sourceBitmap.PixelFormat); sourceBitmap.PixelFormat);
// Set basic properties // Set basic properties
ImageWidth = sourceBitmap.Width; this.ImageWidth = sourceBitmap.Width;
ImageHeight = sourceBitmap.Height; this.ImageHeight = sourceBitmap.Height;
LineStride = sourceDataLocker.Stride; this.LineStride = sourceDataLocker.Stride;
// State variables // State variables
LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride this.LineLength = sourceBitmap.Width * BytesPerPixel; // may or may not be equal to the Stride
Data = new byte[LineLength * sourceBitmap.Height]; this.Data = new Byte[this.LineLength * sourceBitmap.Height];
// copy line by line in order to ignore the useless left-over stride // copy line by line in order to ignore the useless left-over stride
Parallel.For(0, sourceBitmap.Height, y => _ = Parallel.For(0, sourceBitmap.Height, y => {
{ IntPtr sourceAddress = sourceDataLocker.Scan0 + sourceDataLocker.Stride * y;
var sourceAddress = sourceDataLocker.Scan0 + (sourceDataLocker.Stride * y); Int32 targetAddress = y * this.LineLength;
var targetAddress = y * LineLength; Marshal.Copy(sourceAddress, this.Data, targetAddress, this.LineLength);
Marshal.Copy(sourceAddress, Data, targetAddress, LineLength);
}); });
// finally unlock the bitmap // finally unlock the bitmap
sourceBitmap.UnlockBits(sourceDataLocker); sourceBitmap.UnlockBits(sourceDataLocker);
// dispose the source bitmap if we had to create it // dispose the source bitmap if we had to create it
if (disposeSourceBitmap) if(disposeSourceBitmap) {
{
sourceBitmap.Dispose(); sourceBitmap.Dispose();
} }
} }
@ -103,17 +96,23 @@ namespace Unosquare.Swan.Formatters
/// rather than by LinceStride. The left-over stride bytes /// rather than by LinceStride. The left-over stride bytes
/// are removed. /// are removed.
/// </summary> /// </summary>
public byte[] Data { get; } public Byte[] Data {
get;
}
/// <summary> /// <summary>
/// Gets the width of the image. /// Gets the width of the image.
/// </summary> /// </summary>
public int ImageWidth { get; } public Int32 ImageWidth {
get;
}
/// <summary> /// <summary>
/// Gets the height of the image. /// Gets the height of the image.
/// </summary> /// </summary>
public int ImageHeight { get; } public Int32 ImageHeight {
get;
}
/// <summary> /// <summary>
/// Gets the pixel format. This will always be Format32bppArgb. /// Gets the pixel format. This will always be Format32bppArgb.
@ -125,14 +124,18 @@ namespace Unosquare.Swan.Formatters
/// Basically the same as Line Length except Stride might be a little larger as /// Basically the same as Line Length except Stride might be a little larger as
/// some bitmaps might be DWORD-algned. /// some bitmaps might be DWORD-algned.
/// </summary> /// </summary>
public int LineStride { get; } public Int32 LineStride {
get;
}
/// <summary> /// <summary>
/// Gets the length in bytes of a line of pixel data. /// Gets the length in bytes of a line of pixel data.
/// Basically the same as Stride except Stride might be a little larger as /// Basically the same as Stride except Stride might be a little larger as
/// some bitmaps might be DWORD-algned. /// some bitmaps might be DWORD-algned.
/// </summary> /// </summary>
public int LineLength { get; } public Int32 LineLength {
get;
}
/// <summary> /// <summary>
/// Gets the index of the first byte in the BGRA pixel data for the given image coordinates. /// Gets the index of the first byte in the BGRA pixel data for the given image coordinates.
@ -145,12 +148,16 @@ namespace Unosquare.Swan.Formatters
/// or /// or
/// y. /// y.
/// </exception> /// </exception>
public int GetPixelOffset(int x, int y) public Int32 GetPixelOffset(Int32 x, Int32 y) {
{ if(x < 0 || x > this.ImageWidth) {
if (x < 0 || x > ImageWidth) throw new ArgumentOutOfRangeException(nameof(x)); throw new ArgumentOutOfRangeException(nameof(x));
if (y < 0 || y > ImageHeight) throw new ArgumentOutOfRangeException(nameof(y)); }
return (y * LineLength) + (x * BytesPerPixel); if(y < 0 || y > this.ImageHeight) {
throw new ArgumentOutOfRangeException(nameof(y));
}
return y * this.LineLength + x * BytesPerPixel;
} }
/// <summary> /// <summary>
@ -158,16 +165,14 @@ namespace Unosquare.Swan.Formatters
/// to a 32-bit RGBA bitmap. /// to a 32-bit RGBA bitmap.
/// </summary> /// </summary>
/// <returns>Pixel data for a graphics image and its attribute.</returns> /// <returns>Pixel data for a graphics image and its attribute.</returns>
public Bitmap ToBitmap() public Bitmap ToBitmap() {
{ Bitmap bitmap = new Bitmap(this.ImageWidth, this.ImageHeight, this.PixelFormat);
var bitmap = new Bitmap(ImageWidth, ImageHeight, PixelFormat); BitmapData bitLocker = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
var bitLocker = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
Parallel.For(0, bitmap.Height, y => _ = Parallel.For(0, bitmap.Height, y => {
{ Int32 sourceOffset = this.GetPixelOffset(0, y);
var sourceOffset = GetPixelOffset(0, y); IntPtr targetOffset = bitLocker.Scan0 + y * bitLocker.Stride;
var targetOffset = bitLocker.Scan0 + (y * bitLocker.Stride); Marshal.Copy(this.Data, sourceOffset, targetOffset, bitLocker.Width);
Marshal.Copy(Data, sourceOffset, targetOffset, bitLocker.Width);
}); });
bitmap.UnlockBits(bitLocker); bitmap.UnlockBits(bitLocker);

View File

@ -1,19 +1,19 @@
namespace Unosquare.Swan.Models using System;
{
namespace Unosquare.Swan.Models {
/// <summary> /// <summary>
/// Represents a Ok value or Error value. /// Represents a Ok value or Error value.
/// </summary> /// </summary>
/// <typeparam name="T">The type of OK value.</typeparam> /// <typeparam name="T">The type of OK value.</typeparam>
/// <typeparam name="TError">The type of the error.</typeparam> /// <typeparam name="TError">The type of the error.</typeparam>
public class OkOrError<T, TError> public class OkOrError<T, TError> {
{
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is Ok. /// Gets or sets a value indicating whether this instance is Ok.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if this instance is ok; otherwise, <c>false</c>. /// <c>true</c> if this instance is ok; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsOk => !Equals(Ok, default(T)); public Boolean IsOk => !Equals(this.Ok, default(T));
/// <summary> /// <summary>
/// Gets or sets the ok. /// Gets or sets the ok.
@ -21,7 +21,9 @@
/// <value> /// <value>
/// The ok. /// The ok.
/// </value> /// </value>
public T Ok { get; set; } public T Ok {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the error. /// Gets or sets the error.
@ -29,7 +31,9 @@
/// <value> /// <value>
/// The error. /// The error.
/// </value> /// </value>
public TError Error { get; set; } public TError Error {
get; set;
}
/// <summary> /// <summary>
/// Creates a new OkOrError from the specified Ok object. /// Creates a new OkOrError from the specified Ok object.

View File

@ -1,31 +1,29 @@
namespace Unosquare.Swan using Unosquare.Swan.Networking;
{ using System;
using Networking; using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.Net;
using System.Linq; using System.Net.Http;
using System.Net; using System.Net.NetworkInformation;
using System.Net.Http; using System.Net.Sockets;
using System.Net.NetworkInformation; using System.Threading;
using System.Net.Sockets; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan {
/// <summary> /// <summary>
/// Provides miscellaneous network utilities such as a Public IP finder, /// Provides miscellaneous network utilities such as a Public IP finder,
/// a DNS client to query DNS records of any kind, and an NTP client. /// a DNS client to query DNS records of any kind, and an NTP client.
/// </summary> /// </summary>
public static class Network public static class Network {
{
/// <summary> /// <summary>
/// The DNS default port. /// The DNS default port.
/// </summary> /// </summary>
public const int DnsDefaultPort = 53; public const Int32 DnsDefaultPort = 53;
/// <summary> /// <summary>
/// The NTP default port. /// The NTP default port.
/// </summary> /// </summary>
public const int NtpDefaultPort = 123; public const Int32 NtpDefaultPort = 123;
/// <summary> /// <summary>
/// Gets the name of the host. /// Gets the name of the host.
@ -33,7 +31,7 @@
/// <value> /// <value>
/// The name of the host. /// The name of the host.
/// </value> /// </value>
public static string HostName => IPGlobalProperties.GetIPGlobalProperties().HostName; public static String HostName => IPGlobalProperties.GetIPGlobalProperties().HostName;
/// <summary> /// <summary>
/// Gets the name of the network domain. /// Gets the name of the network domain.
@ -41,7 +39,7 @@
/// <value> /// <value>
/// The name of the network domain. /// The name of the network domain.
/// </value> /// </value>
public static string DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName; public static String DomainName => IPGlobalProperties.GetIPGlobalProperties().DomainName;
#region IP Addresses and Adapters Information Methods #region IP Addresses and Adapters Information Methods
@ -53,31 +51,30 @@
/// A collection of NetworkInterface/IPInterfaceProperties pairs /// A collection of NetworkInterface/IPInterfaceProperties pairs
/// that represents the active IPv4 interfaces. /// that represents the active IPv4 interfaces.
/// </returns> /// </returns>
public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces() public static Dictionary<NetworkInterface, IPInterfaceProperties> GetIPv4Interfaces() {
{
// zero conf ip address // zero conf ip address
var zeroConf = new IPAddress(0); IPAddress zeroConf = new IPAddress(0);
var adapters = NetworkInterface.GetAllNetworkInterfaces() NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces()
.Where(network => .Where(network =>
network.OperationalStatus == OperationalStatus.Up network.OperationalStatus == OperationalStatus.Up
&& network.NetworkInterfaceType != NetworkInterfaceType.Unknown && network.NetworkInterfaceType != NetworkInterfaceType.Unknown
&& network.NetworkInterfaceType != NetworkInterfaceType.Loopback) && network.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.ToArray(); .ToArray();
var result = new Dictionary<NetworkInterface, IPInterfaceProperties>(); Dictionary<NetworkInterface, IPInterfaceProperties> result = new Dictionary<NetworkInterface, IPInterfaceProperties>();
foreach (var adapter in adapters) foreach(NetworkInterface adapter in adapters) {
{ IPInterfaceProperties properties = adapter.GetIPProperties();
var properties = adapter.GetIPProperties(); if(properties == null
if (properties == null
|| properties.GatewayAddresses.Count == 0 || properties.GatewayAddresses.Count == 0
|| properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf)) || properties.GatewayAddresses.All(gateway => Equals(gateway.Address, zeroConf))
|| properties.UnicastAddresses.Count == 0 || properties.UnicastAddresses.Count == 0
|| properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf)) || properties.GatewayAddresses.All(address => Equals(address.Address, zeroConf))
|| properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) == || properties.UnicastAddresses.Any(a => a.Address.AddressFamily == AddressFamily.InterNetwork) ==
false) false) {
continue; continue;
}
result[adapter] = properties; result[adapter] = properties;
} }
@ -90,7 +87,7 @@
/// </summary> /// </summary>
/// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param> /// <param name="includeLoopback">if set to <c>true</c> [include loopback].</param>
/// <returns>An array of local ip addresses.</returns> /// <returns>An array of local ip addresses.</returns>
public static IPAddress[] GetIPv4Addresses(bool includeLoopback = true) => public static IPAddress[] GetIPv4Addresses(Boolean includeLoopback = true) =>
GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback); GetIPv4Addresses(NetworkInterfaceType.Unknown, true, includeLoopback);
/// <summary> /// <summary>
@ -102,11 +99,10 @@
/// <returns>An array of local ip addresses.</returns> /// <returns>An array of local ip addresses.</returns>
public static IPAddress[] GetIPv4Addresses( public static IPAddress[] GetIPv4Addresses(
NetworkInterfaceType interfaceType, NetworkInterfaceType interfaceType,
bool skipTypeFilter = false, Boolean skipTypeFilter = false,
bool includeLoopback = false) Boolean includeLoopback = false) {
{ List<IPAddress> addressList = new List<IPAddress>();
var addressList = new List<IPAddress>(); NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces()
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => .Where(ni =>
#if NET452 #if NET452
ni.IsReceiveOnly == false && ni.IsReceiveOnly == false &&
@ -115,20 +111,21 @@
ni.OperationalStatus == OperationalStatus.Up) ni.OperationalStatus == OperationalStatus.Up)
.ToArray(); .ToArray();
foreach (var networkInterface in interfaces) foreach(NetworkInterface networkInterface in interfaces) {
{ IPInterfaceProperties properties = networkInterface.GetIPProperties();
var properties = networkInterface.GetIPProperties();
if (properties.GatewayAddresses.All(g => g.Address.AddressFamily != AddressFamily.InterNetwork)) if(properties.GatewayAddresses.All(g => g.Address.AddressFamily != AddressFamily.InterNetwork)) {
continue; continue;
}
addressList.AddRange(properties.UnicastAddresses addressList.AddRange(properties.UnicastAddresses
.Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork) .Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork)
.Select(i => i.Address)); .Select(i => i.Address));
} }
if (includeLoopback || interfaceType == NetworkInterfaceType.Loopback) if(includeLoopback || interfaceType == NetworkInterfaceType.Loopback) {
addressList.Add(IPAddress.Loopback); addressList.Add(IPAddress.Loopback);
}
return addressList.ToArray(); return addressList.ToArray();
} }
@ -138,11 +135,9 @@
/// </summary> /// </summary>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A public IP address of the result produced by this Task.</returns> /// <returns>A public IP address of the result produced by this Task.</returns>
public static async Task<IPAddress> GetPublicIPAddressAsync(CancellationToken ct = default) public static async Task<IPAddress> GetPublicIPAddressAsync(CancellationToken ct = default) {
{ using(HttpClient client = new HttpClient()) {
using (var client = new HttpClient()) HttpResponseMessage response = await client.GetAsync("https://api.ipify.org", ct).ConfigureAwait(false);
{
var response = await client.GetAsync("https://api.ipify.org", ct).ConfigureAwait(false);
return IPAddress.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return IPAddress.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
} }
} }
@ -175,9 +170,8 @@
/// </summary> /// </summary>
/// <param name="fqdn">The FQDN.</param> /// <param name="fqdn">The FQDN.</param>
/// <returns>An array of local ip addresses.</returns> /// <returns>An array of local ip addresses.</returns>
public static IPAddress[] GetDnsHostEntry(string fqdn) public static IPAddress[] GetDnsHostEntry(String fqdn) {
{ IPAddress dnsServer = GetIPv4DnsServers().FirstOrDefault() ?? IPAddress.Parse("8.8.8.8");
var dnsServer = GetIPv4DnsServers().FirstOrDefault() ?? IPAddress.Parse("8.8.8.8");
return GetDnsHostEntry(fqdn, dnsServer, DnsDefaultPort); return GetDnsHostEntry(fqdn, dnsServer, DnsDefaultPort);
} }
@ -187,11 +181,8 @@
/// <param name="fqdn">The FQDN.</param> /// <param name="fqdn">The FQDN.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>An array of local ip addresses of the result produced by this task.</returns> /// <returns>An array of local ip addresses of the result produced by this task.</returns>
public static Task<IPAddress[]> GetDnsHostEntryAsync(string fqdn, public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn), ct);
{
return Task.Run(() => GetDnsHostEntry(fqdn), ct);
}
/// <summary> /// <summary>
/// Gets the DNS host entry (a list of IP addresses) for the domain name. /// Gets the DNS host entry (a list of IP addresses) for the domain name.
@ -203,25 +194,25 @@
/// An array of local ip addresses. /// An array of local ip addresses.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">fqdn.</exception> /// <exception cref="ArgumentNullException">fqdn.</exception>
public static IPAddress[] GetDnsHostEntry(string fqdn, IPAddress dnsServer, int port) public static IPAddress[] GetDnsHostEntry(String fqdn, IPAddress dnsServer, Int32 port) {
{ if(fqdn == null) {
if (fqdn == null)
throw new ArgumentNullException(nameof(fqdn)); throw new ArgumentNullException(nameof(fqdn));
}
if (fqdn.IndexOf(".", StringComparison.Ordinal) == -1) if(fqdn.IndexOf(".", StringComparison.Ordinal) == -1) {
{
fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName; fqdn += "." + IPGlobalProperties.GetIPGlobalProperties().DomainName;
} }
while (true) while(true) {
{ if(fqdn.EndsWith(".") == false) {
if (fqdn.EndsWith(".") == false) break; break;
}
fqdn = fqdn.Substring(0, fqdn.Length - 1); fqdn = fqdn.Substring(0, fqdn.Length - 1);
} }
var client = new DnsClient(dnsServer, port); DnsClient client = new DnsClient(dnsServer, port);
var result = client.Lookup(fqdn); IList<IPAddress> result = client.Lookup(fqdn);
return result.ToArray(); return result.ToArray();
} }
@ -233,14 +224,7 @@
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>An array of local ip addresses of the result produced by this task.</returns> /// <returns>An array of local ip addresses of the result produced by this task.</returns>
public static Task<IPAddress[]> GetDnsHostEntryAsync( public static Task<IPAddress[]> GetDnsHostEntryAsync(String fqdn, IPAddress dnsServer, Int32 port, CancellationToken ct = default) => Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct);
string fqdn,
IPAddress dnsServer,
int port,
CancellationToken ct = default)
{
return Task.Run(() => GetDnsHostEntry(fqdn, dnsServer, port), ct);
}
/// <summary> /// <summary>
/// Gets the reverse lookup FQDN of the given IP Address. /// Gets the reverse lookup FQDN of the given IP Address.
@ -249,7 +233,7 @@
/// <param name="dnsServer">The DNS server.</param> /// <param name="dnsServer">The DNS server.</param>
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, int port) => new DnsClient(dnsServer, port).Reverse(query); public static String GetDnsPointerEntry(IPAddress query, IPAddress dnsServer, Int32 port) => new DnsClient(dnsServer, port).Reverse(query);
/// <summary> /// <summary>
/// Gets the reverse lookup FQDN of the given IP Address. /// Gets the reverse lookup FQDN of the given IP Address.
@ -259,21 +243,18 @@
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static Task<string> GetDnsPointerEntryAsync( public static Task<String> GetDnsPointerEntryAsync(
IPAddress query, IPAddress query,
IPAddress dnsServer, IPAddress dnsServer,
int port, Int32 port,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct);
{
return Task.Run(() => GetDnsPointerEntry(query, dnsServer, port), ct);
}
/// <summary> /// <summary>
/// Gets the reverse lookup FQDN of the given IP Address. /// Gets the reverse lookup FQDN of the given IP Address.
/// </summary> /// </summary>
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query); public static String GetDnsPointerEntry(IPAddress query) => new DnsClient(GetIPv4DnsServers().FirstOrDefault()).Reverse(query);
/// <summary> /// <summary>
/// Gets the reverse lookup FQDN of the given IP Address. /// Gets the reverse lookup FQDN of the given IP Address.
@ -281,12 +262,9 @@
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static Task<string> GetDnsPointerEntryAsync( public static Task<String> GetDnsPointerEntryAsync(
IPAddress query, IPAddress query,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => GetDnsPointerEntry(query), ct);
{
return Task.Run(() => GetDnsPointerEntry(query), ct);
}
/// <summary> /// <summary>
/// Queries the DNS server for the specified record type. /// Queries the DNS server for the specified record type.
@ -298,12 +276,12 @@
/// <returns> /// <returns>
/// Appropriate DNS server for the specified record type. /// Appropriate DNS server for the specified record type.
/// </returns> /// </returns>
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType, IPAddress dnsServer, int port) public static DnsQueryResult QueryDns(String query, DnsRecordType recordType, IPAddress dnsServer, Int32 port) {
{ if(query == null) {
if (query == null)
throw new ArgumentNullException(nameof(query)); throw new ArgumentNullException(nameof(query));
}
var response = new DnsClient(dnsServer, port).Resolve(query, recordType); DnsClient.DnsClientResponse response = new DnsClient(dnsServer, port).Resolve(query, recordType);
return new DnsQueryResult(response); return new DnsQueryResult(response);
} }
@ -317,14 +295,11 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns> /// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
public static Task<DnsQueryResult> QueryDnsAsync( public static Task<DnsQueryResult> QueryDnsAsync(
string query, String query,
DnsRecordType recordType, DnsRecordType recordType,
IPAddress dnsServer, IPAddress dnsServer,
int port, Int32 port,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct);
{
return Task.Run(() => QueryDns(query, recordType, dnsServer, port), ct);
}
/// <summary> /// <summary>
/// Queries the DNS server for the specified record type. /// Queries the DNS server for the specified record type.
@ -332,7 +307,7 @@
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <param name="recordType">Type of the record.</param> /// <param name="recordType">Type of the record.</param>
/// <returns>Appropriate DNS server for the specified record type.</returns> /// <returns>Appropriate DNS server for the specified record type.</returns>
public static DnsQueryResult QueryDns(string query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort); public static DnsQueryResult QueryDns(String query, DnsRecordType recordType) => QueryDns(query, recordType, GetIPv4DnsServers().FirstOrDefault(), DnsDefaultPort);
/// <summary> /// <summary>
/// Queries the DNS server for the specified record type. /// Queries the DNS server for the specified record type.
@ -342,12 +317,9 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns> /// <returns>Queries the DNS server for the specified record type of the result produced by this Task.</returns>
public static Task<DnsQueryResult> QueryDnsAsync( public static Task<DnsQueryResult> QueryDnsAsync(
string query, String query,
DnsRecordType recordType, DnsRecordType recordType,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => QueryDns(query, recordType), ct);
{
return Task.Run(() => QueryDns(query, recordType), ct);
}
/// <summary> /// <summary>
/// Gets the UTC time by querying from an NTP server. /// Gets the UTC time by querying from an NTP server.
@ -358,48 +330,47 @@
/// A new instance of the DateTime structure to /// A new instance of the DateTime structure to
/// the specified year, month, day, hour, minute and second. /// the specified year, month, day, hour, minute and second.
/// </returns> /// </returns>
public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, int port = NtpDefaultPort) public static DateTime GetNetworkTimeUtc(IPAddress ntpServerAddress, Int32 port = NtpDefaultPort) {
{ if(ntpServerAddress == null) {
if (ntpServerAddress == null)
throw new ArgumentNullException(nameof(ntpServerAddress)); throw new ArgumentNullException(nameof(ntpServerAddress));
}
// NTP message size - 16 bytes of the digest (RFC 2030) // NTP message size - 16 bytes of the digest (RFC 2030)
var ntpData = new byte[48]; Byte[] ntpData = new Byte[48];
// Setting the Leap Indicator, Version Number and Mode values // Setting the Leap Indicator, Version Number and Mode values
ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode) ntpData[0] = 0x1B; // LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)
// The UDP port number assigned to NTP is 123 // The UDP port number assigned to NTP is 123
var endPoint = new IPEndPoint(ntpServerAddress, port); IPEndPoint endPoint = new IPEndPoint(ntpServerAddress, port);
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Connect(endPoint); socket.Connect(endPoint);
socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked socket.ReceiveTimeout = 3000; // Stops code hang if NTP is blocked
socket.Send(ntpData); _ = socket.Send(ntpData);
socket.Receive(ntpData); _ = socket.Receive(ntpData);
socket.Dispose(); socket.Dispose();
// Offset to get to the "Transmit Timestamp" field (time at which the reply // Offset to get to the "Transmit Timestamp" field (time at which the reply
// departed the server for the client, in 64-bit timestamp format." // departed the server for the client, in 64-bit timestamp format."
const byte serverReplyTime = 40; const Byte serverReplyTime = 40;
// Get the seconds part // Get the seconds part
ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime); UInt64 intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);
// Get the seconds fraction // Get the seconds fraction
ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4); UInt64 fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);
// Convert From big-endian to little-endian to match the platform // Convert From big-endian to little-endian to match the platform
if (BitConverter.IsLittleEndian) if(BitConverter.IsLittleEndian) {
{
intPart = intPart.SwapEndianness(); intPart = intPart.SwapEndianness();
fractPart = intPart.SwapEndianness(); fractPart = intPart.SwapEndianness();
} }
var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L); UInt64 milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L;
// The time is given in UTC // The time is given in UTC
return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long) milliseconds); return new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((Int64)milliseconds);
} }
/// <summary> /// <summary>
@ -408,10 +379,9 @@
/// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param> /// <param name="ntpServerName">The NTP server, by default pool.ntp.org.</param>
/// <param name="port">The port, by default NTP 123.</param> /// <param name="port">The port, by default NTP 123.</param>
/// <returns>The UTC time by querying from an NTP server.</returns> /// <returns>The UTC time by querying from an NTP server.</returns>
public static DateTime GetNetworkTimeUtc(string ntpServerName = "pool.ntp.org", public static DateTime GetNetworkTimeUtc(String ntpServerName = "pool.ntp.org",
int port = NtpDefaultPort) Int32 port = NtpDefaultPort) {
{ IPAddress[] addresses = GetDnsHostEntry(ntpServerName);
var addresses = GetDnsHostEntry(ntpServerName);
return GetNetworkTimeUtc(addresses.First(), port); return GetNetworkTimeUtc(addresses.First(), port);
} }
@ -424,11 +394,8 @@
/// <returns>The UTC time by querying from an NTP server of the result produced by this Task.</returns> /// <returns>The UTC time by querying from an NTP server of the result produced by this Task.</returns>
public static Task<DateTime> GetNetworkTimeUtcAsync( public static Task<DateTime> GetNetworkTimeUtcAsync(
IPAddress ntpServerAddress, IPAddress ntpServerAddress,
int port = NtpDefaultPort, Int32 port = NtpDefaultPort,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => GetNetworkTimeUtc(ntpServerAddress, port), ct);
{
return Task.Run(() => GetNetworkTimeUtc(ntpServerAddress, port), ct);
}
/// <summary> /// <summary>
/// Gets the UTC time by querying from an NTP server. /// Gets the UTC time by querying from an NTP server.
@ -438,12 +405,9 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>The UTC time by querying from an NTP server of the result produced by this Task.</returns> /// <returns>The UTC time by querying from an NTP server of the result produced by this Task.</returns>
public static Task<DateTime> GetNetworkTimeUtcAsync( public static Task<DateTime> GetNetworkTimeUtcAsync(
string ntpServerName = "pool.ntp.org", String ntpServerName = "pool.ntp.org",
int port = NtpDefaultPort, Int32 port = NtpDefaultPort,
CancellationToken ct = default) CancellationToken ct = default) => Task.Run(() => GetNetworkTimeUtc(ntpServerName, port), ct);
{
return Task.Run(() => GetNetworkTimeUtc(ntpServerName, port), ct);
}
#endregion #endregion
} }

View File

@ -1,17 +1,16 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System; using System.IO;
using System.Collections.Generic; using System.Linq;
using System.IO; using System.Net;
using System.Linq; using System.Net.Security;
using System.Net; using System.Net.Sockets;
using System.Net.Security; using System.Security.Cryptography.X509Certificates;
using System.Net.Sockets; using System.Text;
using System.Security.Cryptography.X509Certificates; using System.Threading;
using System.Text; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a network connection either on the server or on the client. It wraps a TcpClient /// Represents a network connection either on the server or on the client. It wraps a TcpClient
/// and its corresponding network streams. It is capable of working in 2 modes. Typically on the server side /// and its corresponding network streams. It is capable of working in 2 modes. Typically on the server side
@ -76,31 +75,30 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public sealed class Connection : IDisposable public sealed class Connection : IDisposable {
{
// New Line definitions for reading. This applies to both, events and read methods // New Line definitions for reading. This applies to both, events and read methods
private readonly string _newLineSequence; private readonly String _newLineSequence;
private readonly byte[] _newLineSequenceBytes; private readonly Byte[] _newLineSequenceBytes;
private readonly char[] _newLineSequenceChars; private readonly Char[] _newLineSequenceChars;
private readonly string[] _newLineSequenceLineSplitter; private readonly String[] _newLineSequenceLineSplitter;
private readonly byte[] _receiveBuffer; private readonly Byte[] _receiveBuffer;
private readonly TimeSpan _continuousReadingInterval = TimeSpan.FromMilliseconds(5); private readonly TimeSpan _continuousReadingInterval = TimeSpan.FromMilliseconds(5);
private readonly Queue<string> _readLineBuffer = new Queue<string>(); private readonly Queue<String> _readLineBuffer = new Queue<String>();
private readonly ManualResetEvent _writeDone = new ManualResetEvent(true); private readonly ManualResetEvent _writeDone = new ManualResetEvent(true);
// Disconnect and Dispose // Disconnect and Dispose
private bool _hasDisposed; private Boolean _hasDisposed;
private int _disconnectCalls; private Int32 _disconnectCalls;
// Continuous Reading // Continuous Reading
private Thread _continuousReadingThread; private Thread _continuousReadingThread;
private int _receiveBufferPointer; private Int32 _receiveBufferPointer;
// Reading and writing // Reading and writing
private Task<int> _readTask; private Task<Int32> _readTask;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Connection"/> class. /// Initializes a new instance of the <see cref="Connection"/> class.
@ -113,57 +111,56 @@
public Connection( public Connection(
TcpClient client, TcpClient client,
Encoding textEncoding, Encoding textEncoding,
string newLineSequence, String newLineSequence,
bool disableContinuousReading, Boolean disableContinuousReading,
int blockSize) Int32 blockSize) {
{
// Setup basic properties // Setup basic properties
Id = Guid.NewGuid(); this.Id = Guid.NewGuid();
TextEncoding = textEncoding; this.TextEncoding = textEncoding;
// Setup new line sequence // Setup new line sequence
if (string.IsNullOrEmpty(newLineSequence)) if(String.IsNullOrEmpty(newLineSequence)) {
throw new ArgumentException("Argument cannot be null", nameof(newLineSequence)); throw new ArgumentException("Argument cannot be null", nameof(newLineSequence));
}
_newLineSequence = newLineSequence; this._newLineSequence = newLineSequence;
_newLineSequenceBytes = TextEncoding.GetBytes(_newLineSequence); this._newLineSequenceBytes = this.TextEncoding.GetBytes(this._newLineSequence);
_newLineSequenceChars = _newLineSequence.ToCharArray(); this._newLineSequenceChars = this._newLineSequence.ToCharArray();
_newLineSequenceLineSplitter = new[] { _newLineSequence }; this._newLineSequenceLineSplitter = new[] { this._newLineSequence };
// Setup Connection timers // Setup Connection timers
ConnectionStartTimeUtc = DateTime.UtcNow; this.ConnectionStartTimeUtc = DateTime.UtcNow;
DataReceivedLastTimeUtc = ConnectionStartTimeUtc; this.DataReceivedLastTimeUtc = this.ConnectionStartTimeUtc;
DataSentLastTimeUtc = ConnectionStartTimeUtc; this.DataSentLastTimeUtc = this.ConnectionStartTimeUtc;
// Setup connection properties // Setup connection properties
RemoteClient = client; this.RemoteClient = client;
LocalEndPoint = client.Client.LocalEndPoint as IPEndPoint; this.LocalEndPoint = client.Client.LocalEndPoint as IPEndPoint;
NetworkStream = RemoteClient.GetStream(); this.NetworkStream = this.RemoteClient.GetStream();
RemoteEndPoint = RemoteClient.Client.RemoteEndPoint as IPEndPoint; this.RemoteEndPoint = this.RemoteClient.Client.RemoteEndPoint as IPEndPoint;
// Setup buffers // Setup buffers
_receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2]; this._receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
ProtocolBlockSize = blockSize; this.ProtocolBlockSize = blockSize;
_receiveBufferPointer = 0; this._receiveBufferPointer = 0;
// Setup continuous reading mode if enabled // Setup continuous reading mode if enabled
if (disableContinuousReading) return; if(disableContinuousReading) {
return;
}
#if NETSTANDARD1_3 #if NETSTANDARD1_3
ThreadPool.QueueUserWorkItem(PerformContinuousReading, this); ThreadPool.QueueUserWorkItem(PerformContinuousReading, this);
#else #else
ThreadPool.GetAvailableThreads(out var availableWorkerThreads, out _); ThreadPool.GetAvailableThreads(out Int32 availableWorkerThreads, out _);
ThreadPool.GetMaxThreads(out var maxWorkerThreads, out var _); ThreadPool.GetMaxThreads(out Int32 maxWorkerThreads, out Int32 _);
var activeThreadPoolTreads = maxWorkerThreads - availableWorkerThreads; Int32 activeThreadPoolTreads = maxWorkerThreads - availableWorkerThreads;
if (activeThreadPoolTreads < Environment.ProcessorCount / 4) if(activeThreadPoolTreads < Environment.ProcessorCount / 4) {
{ ThreadPool.QueueUserWorkItem(this.PerformContinuousReading, this);
ThreadPool.QueueUserWorkItem(PerformContinuousReading, this); } else {
} new Thread(this.PerformContinuousReading) { IsBackground = true }.Start();
else
{
new Thread(PerformContinuousReading) { IsBackground = true }.Start();
} }
#endif #endif
} }
@ -174,8 +171,7 @@
/// </summary> /// </summary>
/// <param name="client">The client.</param> /// <param name="client">The client.</param>
public Connection(TcpClient client) public Connection(TcpClient client)
: this(client, Encoding.UTF8, "\r\n", false, 0) : this(client, Encoding.UTF8, "\r\n", false, 0) {
{
// placeholder // placeholder
} }
@ -185,9 +181,8 @@
/// </summary> /// </summary>
/// <param name="client">The client.</param> /// <param name="client">The client.</param>
/// <param name="blockSize">Size of the block.</param> /// <param name="blockSize">Size of the block.</param>
public Connection(TcpClient client, int blockSize) public Connection(TcpClient client, Int32 blockSize)
: this(client, Encoding.UTF8, new string('\n', blockSize + 1), false, blockSize) : this(client, Encoding.UTF8, new String('\n', blockSize + 1), false, blockSize) {
{
// placeholder // placeholder
} }
@ -219,7 +214,9 @@
/// <value> /// <value>
/// The identifier. /// The identifier.
/// </value> /// </value>
public Guid Id { get; } public Guid Id {
get;
}
/// <summary> /// <summary>
/// Gets the active stream. Returns an SSL stream if the connection is secure, otherwise returns /// Gets the active stream. Returns an SSL stream if the connection is secure, otherwise returns
@ -228,7 +225,7 @@
/// <value> /// <value>
/// The active stream. /// The active stream.
/// </value> /// </value>
public Stream ActiveStream => SecureStream ?? NetworkStream as Stream; public Stream ActiveStream => this.SecureStream ?? this.NetworkStream as Stream;
/// <summary> /// <summary>
/// Gets a value indicating whether the current connection stream is an SSL stream. /// Gets a value indicating whether the current connection stream is an SSL stream.
@ -236,7 +233,7 @@
/// <value> /// <value>
/// <c>true</c> if this instance is active stream secure; otherwise, <c>false</c>. /// <c>true</c> if this instance is active stream secure; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsActiveStreamSecure => SecureStream != null; public Boolean IsActiveStreamSecure => this.SecureStream != null;
/// <summary> /// <summary>
/// Gets the text encoding for send and receive operations. /// Gets the text encoding for send and receive operations.
@ -244,7 +241,9 @@
/// <value> /// <value>
/// The text encoding. /// The text encoding.
/// </value> /// </value>
public Encoding TextEncoding { get; } public Encoding TextEncoding {
get;
}
/// <summary> /// <summary>
/// Gets the remote end point of this TCP connection. /// Gets the remote end point of this TCP connection.
@ -252,7 +251,9 @@
/// <value> /// <value>
/// The remote end point. /// The remote end point.
/// </value> /// </value>
public IPEndPoint RemoteEndPoint { get; } public IPEndPoint RemoteEndPoint {
get;
}
/// <summary> /// <summary>
/// Gets the local end point of this TCP connection. /// Gets the local end point of this TCP connection.
@ -260,7 +261,9 @@
/// <value> /// <value>
/// The local end point. /// The local end point.
/// </value> /// </value>
public IPEndPoint LocalEndPoint { get; } public IPEndPoint LocalEndPoint {
get;
}
/// <summary> /// <summary>
/// Gets the remote client of this TCP connection. /// Gets the remote client of this TCP connection.
@ -268,7 +271,9 @@
/// <value> /// <value>
/// The remote client. /// The remote client.
/// </value> /// </value>
public TcpClient RemoteClient { get; private set; } public TcpClient RemoteClient {
get; private set;
}
/// <summary> /// <summary>
/// When in continuous reading mode, and if set to greater than 0, /// When in continuous reading mode, and if set to greater than 0,
@ -278,7 +283,9 @@
/// <value> /// <value>
/// The size of the protocol block. /// The size of the protocol block.
/// </value> /// </value>
public int ProtocolBlockSize { get; } public Int32 ProtocolBlockSize {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this connection is in continuous reading mode. /// Gets a value indicating whether this connection is in continuous reading mode.
@ -290,7 +297,7 @@
/// <value> /// <value>
/// <c>true</c> if this instance is continuous reading enabled; otherwise, <c>false</c>. /// <c>true</c> if this instance is continuous reading enabled; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsContinuousReadingEnabled => _continuousReadingThread != null; public Boolean IsContinuousReadingEnabled => this._continuousReadingThread != null;
/// <summary> /// <summary>
/// Gets the start time at which the connection was started in UTC. /// Gets the start time at which the connection was started in UTC.
@ -298,7 +305,9 @@
/// <value> /// <value>
/// The connection start time UTC. /// The connection start time UTC.
/// </value> /// </value>
public DateTime ConnectionStartTimeUtc { get; } public DateTime ConnectionStartTimeUtc {
get;
}
/// <summary> /// <summary>
/// Gets the start time at which the connection was started in local time. /// Gets the start time at which the connection was started in local time.
@ -306,7 +315,7 @@
/// <value> /// <value>
/// The connection start time. /// The connection start time.
/// </value> /// </value>
public DateTime ConnectionStartTime => ConnectionStartTimeUtc.ToLocalTime(); public DateTime ConnectionStartTime => this.ConnectionStartTimeUtc.ToLocalTime();
/// <summary> /// <summary>
/// Gets the duration of the connection. /// Gets the duration of the connection.
@ -314,7 +323,7 @@
/// <value> /// <value>
/// The duration of the connection. /// The duration of the connection.
/// </value> /// </value>
public TimeSpan ConnectionDuration => DateTime.UtcNow.Subtract(ConnectionStartTimeUtc); public TimeSpan ConnectionDuration => DateTime.UtcNow.Subtract(this.ConnectionStartTimeUtc);
/// <summary> /// <summary>
/// Gets the last time data was received at in UTC. /// Gets the last time data was received at in UTC.
@ -322,12 +331,14 @@
/// <value> /// <value>
/// The data received last time UTC. /// The data received last time UTC.
/// </value> /// </value>
public DateTime DataReceivedLastTimeUtc { get; private set; } public DateTime DataReceivedLastTimeUtc {
get; private set;
}
/// <summary> /// <summary>
/// Gets how long has elapsed since data was last received. /// Gets how long has elapsed since data was last received.
/// </summary> /// </summary>
public TimeSpan DataReceivedIdleDuration => DateTime.UtcNow.Subtract(DataReceivedLastTimeUtc); public TimeSpan DataReceivedIdleDuration => DateTime.UtcNow.Subtract(this.DataReceivedLastTimeUtc);
/// <summary> /// <summary>
/// Gets the last time at which data was sent in UTC. /// Gets the last time at which data was sent in UTC.
@ -335,7 +346,9 @@
/// <value> /// <value>
/// The data sent last time UTC. /// The data sent last time UTC.
/// </value> /// </value>
public DateTime DataSentLastTimeUtc { get; private set; } public DateTime DataSentLastTimeUtc {
get; private set;
}
/// <summary> /// <summary>
/// Gets how long has elapsed since data was last sent. /// Gets how long has elapsed since data was last sent.
@ -343,7 +356,7 @@
/// <value> /// <value>
/// The duration of the data sent idle. /// The duration of the data sent idle.
/// </value> /// </value>
public TimeSpan DataSentIdleDuration => DateTime.UtcNow.Subtract(DataSentLastTimeUtc); public TimeSpan DataSentIdleDuration => DateTime.UtcNow.Subtract(this.DataSentLastTimeUtc);
/// <summary> /// <summary>
/// Gets a value indicating whether this connection is connected. /// Gets a value indicating whether this connection is connected.
@ -353,35 +366,36 @@
/// <value> /// <value>
/// <c>true</c> if this instance is connected; otherwise, <c>false</c>. /// <c>true</c> if this instance is connected; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsConnected public Boolean IsConnected {
{ get {
get if(this._disconnectCalls > 0) {
{
if (_disconnectCalls > 0)
return false; return false;
}
try try {
{ Socket socket = this.RemoteClient.Client;
var socket = RemoteClient.Client; Boolean pollResult = !(socket.Poll(1000, SelectMode.SelectRead)
var pollResult = !((socket.Poll(1000, SelectMode.SelectRead) && this.NetworkStream.DataAvailable == false || !socket.Connected);
&& (NetworkStream.DataAvailable == false)) || !socket.Connected);
if (pollResult == false) if(pollResult == false) {
Disconnect(); this.Disconnect();
}
return pollResult; return pollResult;
} } catch {
catch this.Disconnect();
{
Disconnect();
return false; return false;
} }
} }
} }
private NetworkStream NetworkStream { get; set; } private NetworkStream NetworkStream {
get; set;
}
private SslStream SecureStream { get; set; } private SslStream SecureStream {
get; set;
}
#endregion #endregion
@ -395,58 +409,47 @@
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
/// <exception cref="InvalidOperationException">Read methods have been disabled because continuous reading is enabled.</exception> /// <exception cref="InvalidOperationException">Read methods have been disabled because continuous reading is enabled.</exception>
/// <exception cref="TimeoutException">Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} m.</exception> /// <exception cref="TimeoutException">Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} m.</exception>
public async Task<byte[]> ReadDataAsync(TimeSpan timeout, CancellationToken ct = default) public async Task<Byte[]> ReadDataAsync(TimeSpan timeout, CancellationToken ct = default) {
{ if(this.IsContinuousReadingEnabled) {
if (IsContinuousReadingEnabled)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"Read methods have been disabled because continuous reading is enabled."); "Read methods have been disabled because continuous reading is enabled.");
} }
if (RemoteClient == null) if(this.RemoteClient == null) {
{
throw new InvalidOperationException("An open connection is required"); throw new InvalidOperationException("An open connection is required");
} }
var receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2]; Byte[] receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
var receiveBuilder = new List<byte>(receiveBuffer.Length); List<Byte> receiveBuilder = new List<Byte>(receiveBuffer.Length);
try try {
{ DateTime startTime = DateTime.UtcNow;
var startTime = DateTime.UtcNow;
while (receiveBuilder.Count <= 0) while(receiveBuilder.Count <= 0) {
{ if(DateTime.UtcNow.Subtract(startTime) >= timeout) {
if (DateTime.UtcNow.Subtract(startTime) >= timeout)
{
throw new TimeoutException( throw new TimeoutException(
$"Reading data from {ActiveStream} timed out in {timeout.TotalMilliseconds} ms"); $"Reading data from {this.ActiveStream} timed out in {timeout.TotalMilliseconds} ms");
} }
if (_readTask == null) if(this._readTask == null) {
_readTask = ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, ct); this._readTask = this.ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length, ct);
}
if (_readTask.Wait(_continuousReadingInterval)) if(this._readTask.Wait(this._continuousReadingInterval)) {
{ Int32 bytesReceivedCount = this._readTask.Result;
var bytesReceivedCount = _readTask.Result; if(bytesReceivedCount > 0) {
if (bytesReceivedCount > 0) this.DataReceivedLastTimeUtc = DateTime.UtcNow;
{ Byte[] buffer = new Byte[bytesReceivedCount];
DataReceivedLastTimeUtc = DateTime.UtcNow;
var buffer = new byte[bytesReceivedCount];
Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount); Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
receiveBuilder.AddRange(buffer); receiveBuilder.AddRange(buffer);
} }
_readTask = null; this._readTask = null;
} } else {
else await Task.Delay(this._continuousReadingInterval, ct).ConfigureAwait(false);
{
await Task.Delay(_continuousReadingInterval, ct).ConfigureAwait(false);
} }
} }
} } catch(Exception ex) {
catch (Exception ex)
{
ex.Error(typeof(Connection).FullName, "Error while reading network stream data asynchronously."); ex.Error(typeof(Connection).FullName, "Error while reading network stream data asynchronously.");
throw; throw;
} }
@ -459,8 +462,8 @@
/// </summary> /// </summary>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A byte array containing the results the specified sequence of bytes.</returns> /// <returns>A byte array containing the results the specified sequence of bytes.</returns>
public Task<byte[]> ReadDataAsync(CancellationToken ct = default) public Task<Byte[]> ReadDataAsync(CancellationToken ct = default)
=> ReadDataAsync(TimeSpan.FromSeconds(5), ct); => this.ReadDataAsync(TimeSpan.FromSeconds(5), ct);
/// <summary> /// <summary>
/// Asynchronously reads data as text with the given timeout. /// Asynchronously reads data as text with the given timeout.
@ -468,10 +471,9 @@
/// <param name="timeout">The timeout.</param> /// <param name="timeout">The timeout.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public async Task<string> ReadTextAsync(TimeSpan timeout, CancellationToken ct = default) public async Task<String> ReadTextAsync(TimeSpan timeout, CancellationToken ct = default) {
{ Byte[] buffer = await this.ReadDataAsync(timeout, ct).ConfigureAwait(false);
var buffer = await ReadDataAsync(timeout, ct).ConfigureAwait(false); return buffer == null ? null : this.TextEncoding.GetString(buffer);
return buffer == null ? null : TextEncoding.GetString(buffer);
} }
/// <summary> /// <summary>
@ -479,8 +481,8 @@
/// </summary> /// </summary>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>When this method completes successfully, it returns the contents of the file as a text string.</returns> /// <returns>When this method completes successfully, it returns the contents of the file as a text string.</returns>
public Task<string> ReadTextAsync(CancellationToken ct = default) public Task<String> ReadTextAsync(CancellationToken ct = default)
=> ReadTextAsync(TimeSpan.FromSeconds(5), ct); => this.ReadTextAsync(TimeSpan.FromSeconds(5), ct);
/// <summary> /// <summary>
/// Performs the same task as this method's overload but it defaults to a read timeout of 30 seconds. /// Performs the same task as this method's overload but it defaults to a read timeout of 30 seconds.
@ -490,8 +492,8 @@
/// A task that represents the asynchronous read operation. The value of the TResult parameter /// A task that represents the asynchronous read operation. The value of the TResult parameter
/// contains the next line from the stream, or is null if all the characters have been read. /// contains the next line from the stream, or is null if all the characters have been read.
/// </returns> /// </returns>
public Task<string> ReadLineAsync(CancellationToken ct = default) public Task<String> ReadLineAsync(CancellationToken ct = default)
=> ReadLineAsync(TimeSpan.FromSeconds(30), ct); => this.ReadLineAsync(TimeSpan.FromSeconds(30), ct);
/// <summary> /// <summary>
/// Reads the next available line of text in queue. Return null when no text is read. /// Reads the next available line of text in queue. Return null when no text is read.
@ -504,38 +506,40 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a string line from the queue.</returns> /// <returns>A task with a string line from the queue.</returns>
/// <exception cref="InvalidOperationException">Read methods have been disabled because continuous reading is enabled.</exception> /// <exception cref="InvalidOperationException">Read methods have been disabled because continuous reading is enabled.</exception>
public async Task<string> ReadLineAsync(TimeSpan timeout, CancellationToken ct = default) public async Task<String> ReadLineAsync(TimeSpan timeout, CancellationToken ct = default) {
{ if(this.IsContinuousReadingEnabled) {
if (IsContinuousReadingEnabled)
{
throw new InvalidOperationException( throw new InvalidOperationException(
"Read methods have been disabled because continuous reading is enabled."); "Read methods have been disabled because continuous reading is enabled.");
} }
if (_readLineBuffer.Count > 0) if(this._readLineBuffer.Count > 0) {
return _readLineBuffer.Dequeue(); return this._readLineBuffer.Dequeue();
}
var builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
while (true) while(true) {
{ String text = await this.ReadTextAsync(timeout, ct).ConfigureAwait(false);
var text = await ReadTextAsync(timeout, ct).ConfigureAwait(false); if(text.Length == 0) {
if (text.Length == 0)
break; break;
}
builder.Append(text); _ = builder.Append(text);
if (text.EndsWith(_newLineSequence) == false) continue; if(text.EndsWith(this._newLineSequence) == false) {
continue;
}
var lines = builder.ToString().TrimEnd(_newLineSequenceChars) String[] lines = builder.ToString().TrimEnd(this._newLineSequenceChars)
.Split(_newLineSequenceLineSplitter, StringSplitOptions.None); .Split(this._newLineSequenceLineSplitter, StringSplitOptions.None);
foreach (var item in lines) foreach(String item in lines) {
_readLineBuffer.Enqueue(item); this._readLineBuffer.Enqueue(item);
}
break; break;
} }
return _readLineBuffer.Count > 0 ? _readLineBuffer.Dequeue() : null; return this._readLineBuffer.Count > 0 ? this._readLineBuffer.Dequeue() : null;
} }
#endregion #endregion
@ -549,21 +553,18 @@
/// <param name="forceFlush">if set to <c>true</c> [force flush].</param> /// <param name="forceFlush">if set to <c>true</c> [force flush].</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
public async Task WriteDataAsync(byte[] buffer, bool forceFlush, CancellationToken ct = default) public async Task WriteDataAsync(Byte[] buffer, Boolean forceFlush, CancellationToken ct = default) {
{ try {
try _ = this._writeDone.WaitOne();
{ _ = this._writeDone.Reset();
_writeDone.WaitOne(); await this.ActiveStream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
_writeDone.Reset(); if(forceFlush) {
await ActiveStream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false); await this.ActiveStream.FlushAsync(ct).ConfigureAwait(false);
if (forceFlush)
await ActiveStream.FlushAsync(ct).ConfigureAwait(false);
DataSentLastTimeUtc = DateTime.UtcNow;
} }
finally
{ this.DataSentLastTimeUtc = DateTime.UtcNow;
_writeDone.Set(); } finally {
_ = this._writeDone.Set();
} }
} }
@ -573,8 +574,8 @@
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteTextAsync(string text, CancellationToken ct = default) public Task WriteTextAsync(String text, CancellationToken ct = default)
=> WriteTextAsync(text, TextEncoding, ct); => this.WriteTextAsync(text, this.TextEncoding, ct);
/// <summary> /// <summary>
/// Writes text asynchronously. /// Writes text asynchronously.
@ -583,8 +584,8 @@
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteTextAsync(string text, Encoding encoding, CancellationToken ct = default) public Task WriteTextAsync(String text, Encoding encoding, CancellationToken ct = default)
=> WriteDataAsync(encoding.GetBytes(text), true, ct); => this.WriteDataAsync(encoding.GetBytes(text), true, ct);
/// <summary> /// <summary>
/// Writes a line of text asynchronously. /// Writes a line of text asynchronously.
@ -594,8 +595,8 @@
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteLineAsync(string line, Encoding encoding, CancellationToken ct = default) public Task WriteLineAsync(String line, Encoding encoding, CancellationToken ct = default)
=> WriteDataAsync(encoding.GetBytes($"{line}{_newLineSequence}"), true, ct); => this.WriteDataAsync(encoding.GetBytes($"{line}{this._newLineSequence}"), true, ct);
/// <summary> /// <summary>
/// Writes a line of text asynchronously. /// Writes a line of text asynchronously.
@ -604,8 +605,8 @@
/// <param name="line">The line.</param> /// <param name="line">The line.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task that represents the asynchronous write operation.</returns> /// <returns>A task that represents the asynchronous write operation.</returns>
public Task WriteLineAsync(string line, CancellationToken ct = default) public Task WriteLineAsync(String line, CancellationToken ct = default)
=> WriteLineAsync(line, TextEncoding, ct); => this.WriteLineAsync(line, this.TextEncoding, ct);
#endregion #endregion
@ -616,24 +617,21 @@
/// </summary> /// </summary>
/// <param name="serverCertificate">The server certificate.</param> /// <param name="serverCertificate">The server certificate.</param>
/// <returns><c>true</c> if the object is hosted in the server; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the object is hosted in the server; otherwise, <c>false</c>.</returns>
public async Task<bool> UpgradeToSecureAsServerAsync(X509Certificate2 serverCertificate) public async Task<Boolean> UpgradeToSecureAsServerAsync(X509Certificate2 serverCertificate) {
{ if(this.IsActiveStreamSecure) {
if (IsActiveStreamSecure)
return true; return true;
}
_writeDone.WaitOne(); _ = this._writeDone.WaitOne();
SslStream secureStream = null; SslStream secureStream = null;
try try {
{ secureStream = new SslStream(this.NetworkStream, true);
secureStream = new SslStream(NetworkStream, true);
await secureStream.AuthenticateAsServerAsync(serverCertificate).ConfigureAwait(false); await secureStream.AuthenticateAsServerAsync(serverCertificate).ConfigureAwait(false);
SecureStream = secureStream; this.SecureStream = secureStream;
return true; return true;
} } catch(Exception ex) {
catch (Exception ex)
{
ConnectionFailure(this, new ConnectionFailureEventArgs(ex)); ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
secureStream?.Dispose(); secureStream?.Dispose();
@ -647,24 +645,21 @@
/// <param name="hostname">The hostname.</param> /// <param name="hostname">The hostname.</param>
/// <param name="callback">The callback.</param> /// <param name="callback">The callback.</param>
/// <returns>A tasks with <c>true</c> if the upgrade to SSL was successful; otherwise, <c>false</c>.</returns> /// <returns>A tasks with <c>true</c> if the upgrade to SSL was successful; otherwise, <c>false</c>.</returns>
public async Task<bool> UpgradeToSecureAsClientAsync( public async Task<Boolean> UpgradeToSecureAsClientAsync(
string hostname = null, String hostname = null,
RemoteCertificateValidationCallback callback = null) RemoteCertificateValidationCallback callback = null) {
{ if(this.IsActiveStreamSecure) {
if (IsActiveStreamSecure)
return true; return true;
var secureStream = callback == null
? new SslStream(NetworkStream, true)
: new SslStream(NetworkStream, true, callback);
try
{
await secureStream.AuthenticateAsClientAsync(hostname ?? Network.HostName.ToLowerInvariant()).ConfigureAwait(false);
SecureStream = secureStream;
} }
catch (Exception ex)
{ SslStream secureStream = callback == null
? new SslStream(this.NetworkStream, true)
: new SslStream(this.NetworkStream, true, callback);
try {
await secureStream.AuthenticateAsClientAsync(hostname ?? Network.HostName.ToLowerInvariant()).ConfigureAwait(false);
this.SecureStream = secureStream;
} catch(Exception ex) {
secureStream.Dispose(); secureStream.Dispose();
ConnectionFailure(this, new ConnectionFailureEventArgs(ex)); ConnectionFailure(this, new ConnectionFailureEventArgs(ex));
return false; return false;
@ -676,45 +671,37 @@
/// <summary> /// <summary>
/// Disconnects this connection. /// Disconnects this connection.
/// </summary> /// </summary>
public void Disconnect() public void Disconnect() {
{ if(this._disconnectCalls > 0) {
if (_disconnectCalls > 0)
return; return;
_disconnectCalls++;
_writeDone.WaitOne();
try
{
ClientDisconnected(this, EventArgs.Empty);
} }
catch
{ this._disconnectCalls++;
this._writeDone.WaitOne();
try {
ClientDisconnected(this, EventArgs.Empty);
} catch {
// ignore // ignore
} }
try try {
{
#if !NET452 #if !NET452
RemoteClient.Dispose(); RemoteClient.Dispose();
SecureStream?.Dispose(); SecureStream?.Dispose();
NetworkStream?.Dispose(); NetworkStream?.Dispose();
#else #else
RemoteClient.Close(); this.RemoteClient.Close();
SecureStream?.Close(); this.SecureStream?.Close();
NetworkStream?.Close(); this.NetworkStream?.Close();
#endif #endif
} } catch {
catch
{
// ignored // ignored
} } finally {
finally this.NetworkStream = null;
{ this.SecureStream = null;
NetworkStream = null; this.RemoteClient = null;
SecureStream = null; this._continuousReadingThread = null;
RemoteClient = null;
_continuousReadingThread = null;
} }
} }
@ -725,17 +712,17 @@
/// <summary> /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary> /// </summary>
public void Dispose() public void Dispose() {
{ if(this._hasDisposed) {
if (_hasDisposed)
return; return;
}
// Release managed resources // Release managed resources
Disconnect(); this.Disconnect();
_continuousReadingThread = null; this._continuousReadingThread = null;
_writeDone.Dispose(); this._writeDone.Dispose();
_hasDisposed = true; this._hasDisposed = true;
} }
#endregion #endregion
@ -747,42 +734,41 @@
/// </summary> /// </summary>
/// <param name="receivedData">The received data.</param> /// <param name="receivedData">The received data.</param>
/// <exception cref="Exception">Split function failed! This is terribly wrong.</exception> /// <exception cref="Exception">Split function failed! This is terribly wrong.</exception>
private void RaiseReceiveBufferEvents(byte[] receivedData) private void RaiseReceiveBufferEvents(Byte[] receivedData) {
{ Boolean moreAvailable = this.RemoteClient.Available > 0;
var moreAvailable = RemoteClient.Available > 0;
foreach (var data in receivedData) foreach(Byte data in receivedData) {
{ this.ProcessReceivedBlock(data, moreAvailable);
ProcessReceivedBlock(data, moreAvailable);
} }
// Check if we are left with some more stuff to handle // Check if we are left with some more stuff to handle
if (_receiveBufferPointer <= 0) if(this._receiveBufferPointer <= 0) {
return; return;
}
// Extract the segments split by newline terminated bytes // Extract the segments split by newline terminated bytes
var sequences = _receiveBuffer.Skip(0).Take(_receiveBufferPointer).ToArray() List<Byte[]> sequences = this._receiveBuffer.Skip(0).Take(this._receiveBufferPointer).ToArray()
.Split(0, _newLineSequenceBytes); .Split(0, this._newLineSequenceBytes);
// Something really wrong happened // Something really wrong happened
if (sequences.Count == 0) if(sequences.Count == 0) {
throw new InvalidOperationException("Split function failed! This is terribly wrong!"); throw new InvalidOperationException("Split function failed! This is terribly wrong!");
}
// We only have one sequence and it is not newline-terminated // We only have one sequence and it is not newline-terminated
// we don't have to do anything. // we don't have to do anything.
if (sequences.Count == 1 && sequences[0].EndsWith(_newLineSequenceBytes) == false) if(sequences.Count == 1 && sequences[0].EndsWith(this._newLineSequenceBytes) == false) {
return; return;
}
// Process the events for each sequence // Process the events for each sequence
for (var i = 0; i < sequences.Count; i++) for(Int32 i = 0; i < sequences.Count; i++) {
{ Byte[] sequenceBytes = sequences[i];
var sequenceBytes = sequences[i]; Boolean isNewLineTerminated = sequences[i].EndsWith(this._newLineSequenceBytes);
var isNewLineTerminated = sequences[i].EndsWith(_newLineSequenceBytes); Boolean isLast = i == sequences.Count - 1;
var isLast = i == sequences.Count - 1;
if (isNewLineTerminated) if(isNewLineTerminated) {
{ ConnectionDataReceivedEventArgs eventArgs = new ConnectionDataReceivedEventArgs(
var eventArgs = new ConnectionDataReceivedEventArgs(
sequenceBytes, sequenceBytes,
ConnectionDataReceivedTrigger.NewLineSequenceEncountered, ConnectionDataReceivedTrigger.NewLineSequenceEncountered,
isLast == false); isLast == false);
@ -790,99 +776,87 @@
} }
// Depending on the last segment determine what to do with the receive buffer // Depending on the last segment determine what to do with the receive buffer
if (!isLast) continue; if(!isLast) {
continue;
if (isNewLineTerminated)
{
// Simply reset the buffer pointer if the last segment was also terminated
_receiveBufferPointer = 0;
} }
else
{ if(isNewLineTerminated) {
// Simply reset the buffer pointer if the last segment was also terminated
this._receiveBufferPointer = 0;
} else {
// If we have not received the termination sequence, then just shift the receive buffer to the left // If we have not received the termination sequence, then just shift the receive buffer to the left
// and adjust the pointer // and adjust the pointer
Array.Copy(sequenceBytes, _receiveBuffer, sequenceBytes.Length); Array.Copy(sequenceBytes, this._receiveBuffer, sequenceBytes.Length);
_receiveBufferPointer = sequenceBytes.Length; this._receiveBufferPointer = sequenceBytes.Length;
} }
} }
} }
private void ProcessReceivedBlock(byte data, bool moreAvailable) private void ProcessReceivedBlock(Byte data, Boolean moreAvailable) {
{ this._receiveBuffer[this._receiveBufferPointer] = data;
_receiveBuffer[_receiveBufferPointer] = data; this._receiveBufferPointer++;
_receiveBufferPointer++;
// Block size reached // Block size reached
if (ProtocolBlockSize > 0 && _receiveBufferPointer >= ProtocolBlockSize) if(this.ProtocolBlockSize > 0 && this._receiveBufferPointer >= this.ProtocolBlockSize) {
{ this.SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BlockSizeReached);
SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BlockSizeReached);
return; return;
} }
// The receive buffer is full. Time to flush // The receive buffer is full. Time to flush
if (_receiveBufferPointer >= _receiveBuffer.Length) if(this._receiveBufferPointer >= this._receiveBuffer.Length) {
{ this.SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BufferFull);
SendBuffer(moreAvailable, ConnectionDataReceivedTrigger.BufferFull);
} }
} }
private void SendBuffer(bool moreAvailable, ConnectionDataReceivedTrigger trigger) private void SendBuffer(Boolean moreAvailable, ConnectionDataReceivedTrigger trigger) {
{ Byte[] eventBuffer = new Byte[this._receiveBuffer.Length];
var eventBuffer = new byte[_receiveBuffer.Length]; Array.Copy(this._receiveBuffer, eventBuffer, eventBuffer.Length);
Array.Copy(_receiveBuffer, eventBuffer, eventBuffer.Length);
DataReceived(this, DataReceived(this,
new ConnectionDataReceivedEventArgs( new ConnectionDataReceivedEventArgs(
eventBuffer, eventBuffer,
trigger, trigger,
moreAvailable)); moreAvailable));
_receiveBufferPointer = 0; this._receiveBufferPointer = 0;
} }
private void PerformContinuousReading(object threadContext) private void PerformContinuousReading(Object threadContext) {
{ this._continuousReadingThread = Thread.CurrentThread;
_continuousReadingThread = Thread.CurrentThread;
// Check if the RemoteClient is still there // Check if the RemoteClient is still there
if (RemoteClient == null) return; if(this.RemoteClient == null) {
return;
}
var receiveBuffer = new byte[RemoteClient.ReceiveBufferSize * 2]; Byte[] receiveBuffer = new Byte[this.RemoteClient.ReceiveBufferSize * 2];
while (IsConnected && _disconnectCalls <= 0) while(this.IsConnected && this._disconnectCalls <= 0) {
{ Boolean doThreadSleep = false;
var doThreadSleep = false;
try try {
{ if(this._readTask == null) {
if (_readTask == null) this._readTask = this.ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
_readTask = ActiveStream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length); }
if (_readTask.Wait(_continuousReadingInterval)) if(this._readTask.Wait(this._continuousReadingInterval)) {
{ Int32 bytesReceivedCount = this._readTask.Result;
var bytesReceivedCount = _readTask.Result; if(bytesReceivedCount > 0) {
if (bytesReceivedCount > 0) this.DataReceivedLastTimeUtc = DateTime.UtcNow;
{ Byte[] buffer = new Byte[bytesReceivedCount];
DataReceivedLastTimeUtc = DateTime.UtcNow;
var buffer = new byte[bytesReceivedCount];
Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount); Array.Copy(receiveBuffer, 0, buffer, 0, bytesReceivedCount);
RaiseReceiveBufferEvents(buffer); this.RaiseReceiveBufferEvents(buffer);
} }
_readTask = null; this._readTask = null;
} else {
doThreadSleep = this._disconnectCalls <= 0;
} }
else } catch(Exception ex) {
{
doThreadSleep = _disconnectCalls <= 0;
}
}
catch (Exception ex)
{
ex.Log(nameof(Connection), "Continuous Read operation errored"); ex.Log(nameof(Connection), "Continuous Read operation errored");
} finally {
if(doThreadSleep) {
Thread.Sleep(this._continuousReadingInterval);
} }
finally
{
if (doThreadSleep)
Thread.Sleep(_continuousReadingInterval);
} }
} }
} }

View File

@ -1,27 +1,25 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Net;
using Swan; using System.Net.Sockets;
using System; using System.Threading;
using System.Net; using System.Threading.Tasks;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// TCP Listener manager with built-in events and asynchronous functionality. /// TCP Listener manager with built-in events and asynchronous functionality.
/// This networking component is typically used when writing server software. /// This networking component is typically used when writing server software.
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> /// <seealso cref="System.IDisposable" />
public sealed class ConnectionListener : IDisposable public sealed class ConnectionListener : IDisposable {
{
#region Private Declarations #region Private Declarations
private readonly object _stateLock = new object(); private readonly Object _stateLock = new Object();
private TcpListener _listenerSocket; private TcpListener _listenerSocket;
private bool _cancellationPending; private Boolean _cancellationPending;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "<Ausstehend>")]
private CancellationTokenSource _cancelListening; private CancellationTokenSource _cancelListening;
private Task _backgroundWorkerTask; private Task _backgroundWorkerTask;
private bool _hasDisposed; private Boolean _hasDisposed;
#endregion #endregion
@ -56,10 +54,9 @@
/// Initializes a new instance of the <see cref="ConnectionListener"/> class. /// Initializes a new instance of the <see cref="ConnectionListener"/> class.
/// </summary> /// </summary>
/// <param name="listenEndPoint">The listen end point.</param> /// <param name="listenEndPoint">The listen end point.</param>
public ConnectionListener(IPEndPoint listenEndPoint) public ConnectionListener(IPEndPoint listenEndPoint) {
{ this.Id = Guid.NewGuid();
Id = Guid.NewGuid(); this.LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
LocalEndPoint = listenEndPoint ?? throw new ArgumentNullException(nameof(listenEndPoint));
} }
/// <summary> /// <summary>
@ -67,9 +64,8 @@
/// It uses the loopback address for listening. /// It uses the loopback address for listening.
/// </summary> /// </summary>
/// <param name="listenPort">The listen port.</param> /// <param name="listenPort">The listen port.</param>
public ConnectionListener(int listenPort) public ConnectionListener(Int32 listenPort)
: this(new IPEndPoint(IPAddress.Loopback, listenPort)) : this(new IPEndPoint(IPAddress.Loopback, listenPort)) {
{
} }
/// <summary> /// <summary>
@ -77,17 +73,15 @@
/// </summary> /// </summary>
/// <param name="listenAddress">The listen address.</param> /// <param name="listenAddress">The listen address.</param>
/// <param name="listenPort">The listen port.</param> /// <param name="listenPort">The listen port.</param>
public ConnectionListener(IPAddress listenAddress, int listenPort) public ConnectionListener(IPAddress listenAddress, Int32 listenPort)
: this(new IPEndPoint(listenAddress, listenPort)) : this(new IPEndPoint(listenAddress, listenPort)) {
{
} }
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="ConnectionListener"/> class. /// Finalizes an instance of the <see cref="ConnectionListener"/> class.
/// </summary> /// </summary>
~ConnectionListener() ~ConnectionListener() {
{ this.Dispose(false);
Dispose(false);
} }
#endregion #endregion
@ -100,7 +94,9 @@
/// <value> /// <value>
/// The local end point. /// The local end point.
/// </value> /// </value>
public IPEndPoint LocalEndPoint { get; } public IPEndPoint LocalEndPoint {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this listener is active. /// Gets a value indicating whether this listener is active.
@ -108,7 +104,7 @@
/// <value> /// <value>
/// <c>true</c> if this instance is listening; otherwise, <c>false</c>. /// <c>true</c> if this instance is listening; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsListening => _backgroundWorkerTask != null; public Boolean IsListening => this._backgroundWorkerTask != null;
/// <summary> /// <summary>
/// Gets a unique identifier that gets automatically assigned upon instantiation of this class. /// Gets a unique identifier that gets automatically assigned upon instantiation of this class.
@ -116,7 +112,9 @@
/// <value> /// <value>
/// The unique identifier. /// The unique identifier.
/// </value> /// </value>
public Guid Id { get; } public Guid Id {
get;
}
#endregion #endregion
@ -127,22 +125,18 @@
/// Subscribe to the events of this class to gain access to connected client sockets. /// Subscribe to the events of this class to gain access to connected client sockets.
/// </summary> /// </summary>
/// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception> /// <exception cref="System.InvalidOperationException">Cancellation has already been requested. This listener is not reusable.</exception>
public void Start() public void Start() {
{ lock(this._stateLock) {
lock (_stateLock) if(this._backgroundWorkerTask != null) {
{
if (_backgroundWorkerTask != null)
{
return; return;
} }
if (_cancellationPending) if(this._cancellationPending) {
{
throw new InvalidOperationException( throw new InvalidOperationException(
"Cancellation has already been requested. This listener is not reusable."); "Cancellation has already been requested. This listener is not reusable.");
} }
_backgroundWorkerTask = DoWorkAsync(); this._backgroundWorkerTask = this.DoWorkAsync();
} }
} }
@ -150,16 +144,14 @@
/// Stops the listener from receiving new connections. /// Stops the listener from receiving new connections.
/// This does not prevent the listener from . /// This does not prevent the listener from .
/// </summary> /// </summary>
public void Stop() public void Stop() {
{ lock(this._stateLock) {
lock (_stateLock) this._cancellationPending = true;
{ this._listenerSocket?.Stop();
_cancellationPending = true; this._cancelListening?.Cancel();
_listenerSocket?.Stop(); this._backgroundWorkerTask?.Wait();
_cancelListening?.Cancel(); this._backgroundWorkerTask = null;
_backgroundWorkerTask?.Wait(); this._cancellationPending = false;
_backgroundWorkerTask = null;
_cancellationPending = false;
} }
} }
@ -169,12 +161,11 @@
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents this instance. /// A <see cref="System.String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() => LocalEndPoint.ToString(); public override String ToString() => this.LocalEndPoint.ToString();
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ this.Dispose(true);
Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@ -182,43 +173,37 @@
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing) private void Dispose(Boolean disposing) {
{ if(this._hasDisposed) {
if (_hasDisposed)
return; return;
if (disposing)
{
// Release managed resources
Stop();
} }
_hasDisposed = true; if(disposing) {
// Release managed resources
this.Stop();
}
this._hasDisposed = true;
} }
/// <summary> /// <summary>
/// Continuously checks for client connections until the Close method has been called. /// Continuously checks for client connections until the Close method has been called.
/// </summary> /// </summary>
/// <returns>A task that represents the asynchronous connection operation.</returns> /// <returns>A task that represents the asynchronous connection operation.</returns>
private async Task DoWorkAsync() private async Task DoWorkAsync() {
{ this._cancellationPending = false;
_cancellationPending = false; this._listenerSocket = new TcpListener(this.LocalEndPoint);
_listenerSocket = new TcpListener(LocalEndPoint); this._listenerSocket.Start();
_listenerSocket.Start(); this._cancelListening = new CancellationTokenSource();
_cancelListening = new CancellationTokenSource();
try try {
{ while(this._cancellationPending == false) {
while (_cancellationPending == false) try {
{ TcpClient client = await Task.Run(() => this._listenerSocket.AcceptTcpClientAsync(), this._cancelListening.Token).ConfigureAwait(false);
try ConnectionAcceptingEventArgs acceptingArgs = new ConnectionAcceptingEventArgs(client);
{
var client = await Task.Run(() => _listenerSocket.AcceptTcpClientAsync(), _cancelListening.Token).ConfigureAwait(false);
var acceptingArgs = new ConnectionAcceptingEventArgs(client);
OnConnectionAccepting(this, acceptingArgs); OnConnectionAccepting(this, acceptingArgs);
if (acceptingArgs.Cancel) if(acceptingArgs.Cancel) {
{
#if !NET452 #if !NET452
client.Dispose(); client.Dispose();
#else #else
@ -228,28 +213,20 @@
} }
OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client)); OnConnectionAccepted(this, new ConnectionAcceptedEventArgs(client));
} } catch(Exception ex) {
catch (Exception ex)
{
OnConnectionFailure(this, new ConnectionFailureEventArgs(ex)); OnConnectionFailure(this, new ConnectionFailureEventArgs(ex));
} }
} }
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint)); OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
} } catch(ObjectDisposedException) {
catch (ObjectDisposedException) OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(this.LocalEndPoint));
{ } catch(Exception ex) {
OnListenerStopped(this, new ConnectionListenerStoppedEventArgs(LocalEndPoint));
}
catch (Exception ex)
{
OnListenerStopped(this, OnListenerStopped(this,
new ConnectionListenerStoppedEventArgs(LocalEndPoint, _cancellationPending ? null : ex)); new ConnectionListenerStoppedEventArgs(this.LocalEndPoint, this._cancellationPending ? null : ex));
} } finally {
finally this._backgroundWorkerTask = null;
{ this._cancellationPending = false;
_backgroundWorkerTask = null;
_cancellationPending = false;
} }
} }

View File

@ -1,60 +1,94 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// DnsClient public interfaces. /// DnsClient public interfaces.
/// </summary> /// </summary>
internal partial class DnsClient internal partial class DnsClient {
{ public interface IDnsMessage {
public interface IDnsMessage IList<DnsQuestion> Questions {
{ get;
IList<DnsQuestion> Questions { get; }
int Size { get; }
byte[] ToArray();
} }
public interface IDnsMessageEntry Int32 Size {
{ get;
DnsDomain Name { get; } }
DnsRecordType Type { get; } Byte[] ToArray();
DnsRecordClass Class { get; }
int Size { get; }
byte[] ToArray();
} }
public interface IDnsResourceRecord : IDnsMessageEntry public interface IDnsMessageEntry {
{ DnsDomain Name {
TimeSpan TimeToLive { get; } get;
int DataLength { get; } }
byte[] Data { get; } DnsRecordType Type {
get;
}
DnsRecordClass Class {
get;
} }
public interface IDnsRequest : IDnsMessage Int32 Size {
{ get;
int Id { get; set; } }
DnsOperationCode OperationCode { get; set; } Byte[] ToArray();
bool RecursionDesired { get; set; }
} }
public interface IDnsResponse : IDnsMessage public interface IDnsResourceRecord : IDnsMessageEntry {
{ TimeSpan TimeToLive {
int Id { get; set; } get;
IList<IDnsResourceRecord> AnswerRecords { get; } }
IList<IDnsResourceRecord> AuthorityRecords { get; } Int32 DataLength {
IList<IDnsResourceRecord> AdditionalRecords { get; } get;
bool IsRecursionAvailable { get; set; } }
bool IsAuthorativeServer { get; set; } Byte[] Data {
bool IsTruncated { get; set; } get;
DnsOperationCode OperationCode { get; set; } }
DnsResponseCode ResponseCode { get; set; }
} }
public interface IDnsRequestResolver public interface IDnsRequest : IDnsMessage {
{ Int32 Id {
get; set;
}
DnsOperationCode OperationCode {
get; set;
}
Boolean RecursionDesired {
get; set;
}
}
public interface IDnsResponse : IDnsMessage {
Int32 Id {
get; set;
}
IList<IDnsResourceRecord> AnswerRecords {
get;
}
IList<IDnsResourceRecord> AuthorityRecords {
get;
}
IList<IDnsResourceRecord> AdditionalRecords {
get;
}
Boolean IsRecursionAvailable {
get; set;
}
Boolean IsAuthorativeServer {
get; set;
}
Boolean IsTruncated {
get; set;
}
DnsOperationCode OperationCode {
get; set;
}
DnsResponseCode ResponseCode {
get; set;
}
}
public interface IDnsRequestResolver {
DnsClientResponse Request(DnsClientRequest request); DnsClientResponse Request(DnsClientRequest request);
} }
} }

View File

@ -1,61 +1,56 @@
namespace Unosquare.Swan.Networking using Unosquare.Swan.Formatters;
{ using System;
using Formatters; using System.Collections.Generic;
using System; using System.IO;
using System.Collections.Generic; using System.Linq;
using System.IO; using System.Net;
using System.Linq; using System.Net.Sockets;
using System.Net; using System.Runtime.InteropServices;
using System.Net.Sockets; using System.Text;
using System.Runtime.InteropServices; using Unosquare.Swan.Exceptions;
using System.Text; using Unosquare.Swan.Attributes;
using Exceptions;
using Attributes;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// DnsClient Request inner class. /// DnsClient Request inner class.
/// </summary> /// </summary>
internal partial class DnsClient internal partial class DnsClient {
{ public class DnsClientRequest : IDnsRequest {
public class DnsClientRequest : IDnsRequest
{
private readonly IDnsRequestResolver _resolver; private readonly IDnsRequestResolver _resolver;
private readonly IDnsRequest _request; private readonly IDnsRequest _request;
public DnsClientRequest(IPEndPoint dns, IDnsRequest request = null, IDnsRequestResolver resolver = null) public DnsClientRequest(IPEndPoint dns, IDnsRequest request = null, IDnsRequestResolver resolver = null) {
{ this.Dns = dns;
Dns = dns; this._request = request == null ? new DnsRequest() : new DnsRequest(request);
_request = request == null ? new DnsRequest() : new DnsRequest(request); this._resolver = resolver ?? new DnsUdpRequestResolver();
_resolver = resolver ?? new DnsUdpRequestResolver();
} }
public int Id public Int32 Id {
{ get => this._request.Id;
get => _request.Id; set => this._request.Id = value;
set => _request.Id = value;
} }
public DnsOperationCode OperationCode public DnsOperationCode OperationCode {
{ get => this._request.OperationCode;
get => _request.OperationCode; set => this._request.OperationCode = value;
set => _request.OperationCode = value;
} }
public bool RecursionDesired public Boolean RecursionDesired {
{ get => this._request.RecursionDesired;
get => _request.RecursionDesired; set => this._request.RecursionDesired = value;
set => _request.RecursionDesired = value;
} }
public IList<DnsQuestion> Questions => _request.Questions; public IList<DnsQuestion> Questions => this._request.Questions;
public int Size => _request.Size; public Int32 Size => this._request.Size;
public IPEndPoint Dns { get; set; } public IPEndPoint Dns {
get; set;
}
public byte[] ToArray() => _request.ToArray(); public Byte[] ToArray() => this._request.ToArray();
public override string ToString() => _request.ToString(); public override String ToString() => this._request.ToString();
/// <summary> /// <summary>
/// Resolves this request into a response using the provided DNS information. The given /// Resolves this request into a response using the provided DNS information. The given
@ -65,143 +60,122 @@
/// <exception cref="IOException">Thrown if a IO error occurs.</exception> /// <exception cref="IOException">Thrown if a IO error occurs.</exception>
/// <exception cref="SocketException">Thrown if a the reading or writing to the socket fails.</exception> /// <exception cref="SocketException">Thrown if a the reading or writing to the socket fails.</exception>
/// <returns>The response received from server.</returns> /// <returns>The response received from server.</returns>
public DnsClientResponse Resolve() public DnsClientResponse Resolve() {
{ try {
try DnsClientResponse response = this._resolver.Request(this);
{
var response = _resolver.Request(this);
if (response.Id != Id) if(response.Id != this.Id) {
{
throw new DnsQueryException(response, "Mismatching request/response IDs"); throw new DnsQueryException(response, "Mismatching request/response IDs");
} }
if (response.ResponseCode != DnsResponseCode.NoError) if(response.ResponseCode != DnsResponseCode.NoError) {
{
throw new DnsQueryException(response); throw new DnsQueryException(response);
} }
return response; return response;
} } catch(ArgumentException e) {
catch (ArgumentException e)
{
throw new DnsQueryException("Invalid response", e); throw new DnsQueryException("Invalid response", e);
} }
} }
} }
public class DnsRequest : IDnsRequest public class DnsRequest : IDnsRequest {
{
private static readonly Random Random = new Random(); private static readonly Random Random = new Random();
private readonly IList<DnsQuestion> questions;
private DnsHeader header; private DnsHeader header;
public DnsRequest() public DnsRequest() {
{ this.Questions = new List<DnsQuestion>();
questions = new List<DnsQuestion>(); this.header = new DnsHeader {
header = new DnsHeader
{
OperationCode = DnsOperationCode.Query, OperationCode = DnsOperationCode.Query,
Response = false, Response = false,
Id = Random.Next(UInt16.MaxValue), Id = Random.Next(UInt16.MaxValue),
}; };
} }
public DnsRequest(IDnsRequest request) public DnsRequest(IDnsRequest request) {
{ this.header = new DnsHeader();
header = new DnsHeader(); this.Questions = new List<DnsQuestion>(request.Questions);
questions = new List<DnsQuestion>(request.Questions);
header.Response = false; this.header.Response = false;
Id = request.Id; this.Id = request.Id;
OperationCode = request.OperationCode; this.OperationCode = request.OperationCode;
RecursionDesired = request.RecursionDesired; this.RecursionDesired = request.RecursionDesired;
} }
public IList<DnsQuestion> Questions => questions; public IList<DnsQuestion> Questions {
get;
public int Size => header.Size + questions.Sum(q => q.Size);
public int Id
{
get => header.Id;
set => header.Id = value;
} }
public DnsOperationCode OperationCode public Int32 Size => this.header.Size + this.Questions.Sum(q => q.Size);
{
get => header.OperationCode; public Int32 Id {
set => header.OperationCode = value; get => this.header.Id;
set => this.header.Id = value;
} }
public bool RecursionDesired public DnsOperationCode OperationCode {
{ get => this.header.OperationCode;
get => header.RecursionDesired; set => this.header.OperationCode = value;
set => header.RecursionDesired = value;
} }
public byte[] ToArray() public Boolean RecursionDesired {
{ get => this.header.RecursionDesired;
UpdateHeader(); set => this.header.RecursionDesired = value;
var result = new MemoryStream(Size); }
result public Byte[] ToArray() {
.Append(header.ToArray()) this.UpdateHeader();
.Append(questions.Select(q => q.ToArray())); MemoryStream result = new MemoryStream(this.Size);
_ = result
.Append(this.header.ToArray())
.Append(this.Questions.Select(q => q.ToArray()));
return result.ToArray(); return result.ToArray();
} }
public override string ToString() public override String ToString() {
{ this.UpdateHeader();
UpdateHeader();
return Json.Serialize(this, true); return Json.Serialize(this, true);
} }
private void UpdateHeader() private void UpdateHeader() => this.header.QuestionCount = this.Questions.Count;
{
header.QuestionCount = questions.Count;
}
} }
public class DnsTcpRequestResolver : IDnsRequestResolver public class DnsTcpRequestResolver : IDnsRequestResolver {
{ public DnsClientResponse Request(DnsClientRequest request) {
public DnsClientResponse Request(DnsClientRequest request) TcpClient tcp = new TcpClient();
{
var tcp = new TcpClient();
try try {
{
tcp.Client.Connect(request.Dns); tcp.Client.Connect(request.Dns);
var stream = tcp.GetStream(); NetworkStream stream = tcp.GetStream();
var buffer = request.ToArray(); Byte[] buffer = request.ToArray();
var length = BitConverter.GetBytes((ushort) buffer.Length); Byte[] length = BitConverter.GetBytes((UInt16)buffer.Length);
if (BitConverter.IsLittleEndian) if(BitConverter.IsLittleEndian) {
Array.Reverse(length); Array.Reverse(length);
}
stream.Write(length, 0, length.Length); stream.Write(length, 0, length.Length);
stream.Write(buffer, 0, buffer.Length); stream.Write(buffer, 0, buffer.Length);
buffer = new byte[2]; buffer = new Byte[2];
Read(stream, buffer); Read(stream, buffer);
if (BitConverter.IsLittleEndian) if(BitConverter.IsLittleEndian) {
Array.Reverse(buffer); Array.Reverse(buffer);
}
buffer = new byte[BitConverter.ToUInt16(buffer, 0)]; buffer = new Byte[BitConverter.ToUInt16(buffer, 0)];
Read(stream, buffer); Read(stream, buffer);
var response = DnsResponse.FromArray(buffer); DnsResponse response = DnsResponse.FromArray(buffer);
return new DnsClientResponse(request, response, buffer); return new DnsClientResponse(request, response, buffer);
} } finally {
finally
{
#if NET452 #if NET452
tcp.Close(); tcp.Close();
#else #else
@ -210,69 +184,54 @@
} }
} }
private static void Read(Stream stream, byte[] buffer) private static void Read(Stream stream, Byte[] buffer) {
{ Int32 length = buffer.Length;
var length = buffer.Length; Int32 offset = 0;
var offset = 0; Int32 size;
int size;
while (length > 0 && (size = stream.Read(buffer, offset, length)) > 0) while(length > 0 && (size = stream.Read(buffer, offset, length)) > 0) {
{
offset += size; offset += size;
length -= size; length -= size;
} }
if (length > 0) if(length > 0) {
{
throw new IOException("Unexpected end of stream"); throw new IOException("Unexpected end of stream");
} }
} }
} }
public class DnsUdpRequestResolver : IDnsRequestResolver public class DnsUdpRequestResolver : IDnsRequestResolver {
{
private readonly IDnsRequestResolver _fallback; private readonly IDnsRequestResolver _fallback;
public DnsUdpRequestResolver(IDnsRequestResolver fallback) public DnsUdpRequestResolver(IDnsRequestResolver fallback) => this._fallback = fallback;
{
_fallback = fallback;
}
public DnsUdpRequestResolver() public DnsUdpRequestResolver() => this._fallback = new DnsNullRequestResolver();
{
_fallback = new DnsNullRequestResolver();
}
public DnsClientResponse Request(DnsClientRequest request) public DnsClientResponse Request(DnsClientRequest request) {
{ UdpClient udp = new UdpClient();
var udp = new UdpClient(); IPEndPoint dns = request.Dns;
var dns = request.Dns;
try try {
{
udp.Client.SendTimeout = 7000; udp.Client.SendTimeout = 7000;
udp.Client.ReceiveTimeout = 7000; udp.Client.ReceiveTimeout = 7000;
udp.Client.Connect(dns); udp.Client.Connect(dns);
udp.Client.Send(request.ToArray()); udp.Client.Send(request.ToArray());
var bufferList = new List<byte>(); List<Byte> bufferList = new List<Byte>();
do do {
{ Byte[] tempBuffer = new Byte[1024];
var tempBuffer = new byte[1024]; Int32 receiveCount = udp.Client.Receive(tempBuffer);
var receiveCount = udp.Client.Receive(tempBuffer);
bufferList.AddRange(tempBuffer.Skip(0).Take(receiveCount)); bufferList.AddRange(tempBuffer.Skip(0).Take(receiveCount));
} while (udp.Client.Available > 0 || bufferList.Count == 0); } while(udp.Client.Available > 0 || bufferList.Count == 0);
var buffer = bufferList.ToArray(); Byte[] buffer = bufferList.ToArray();
var response = DnsResponse.FromArray(buffer); DnsResponse response = DnsResponse.FromArray(buffer);
return response.IsTruncated return response.IsTruncated
? _fallback.Request(request) ? this._fallback.Request(request)
: new DnsClientResponse(request, response, buffer); : new DnsClientResponse(request, response, buffer);
} } finally {
finally
{
#if NET452 #if NET452
udp.Close(); udp.Close();
#else #else
@ -282,246 +241,201 @@
} }
} }
public class DnsNullRequestResolver : IDnsRequestResolver public class DnsNullRequestResolver : IDnsRequestResolver {
{ public DnsClientResponse Request(DnsClientRequest request) => throw new DnsQueryException("Request failed");
public DnsClientResponse Request(DnsClientRequest request)
{
throw new DnsQueryException("Request failed");
}
} }
// 12 bytes message header // 12 bytes message header
[StructEndianness(Endianness.Big)] [StructEndianness(Endianness.Big)]
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DnsHeader public struct DnsHeader {
{ public const Int32 SIZE = 12;
public const int SIZE = 12;
public static DnsHeader FromArray(byte[] header) public static DnsHeader FromArray(Byte[] header) {
{ if(header.Length < SIZE) {
if (header.Length < SIZE)
{
throw new ArgumentException("Header length too small"); throw new ArgumentException("Header length too small");
} }
return header.ToStruct<DnsHeader>(0, SIZE); return header.ToStruct<DnsHeader>(0, SIZE);
} }
private ushort id; private UInt16 id;
private byte flag0;
private byte flag1;
// Question count: number of questions in the Question section // Question count: number of questions in the Question section
private ushort questionCount; private UInt16 questionCount;
// Answer record count: number of records in the Answer section // Answer record count: number of records in the Answer section
private ushort answerCount; private UInt16 answerCount;
// Authority record count: number of records in the Authority section // Authority record count: number of records in the Authority section
private ushort authorityCount; private UInt16 authorityCount;
// Additional record count: number of records in the Additional section // Additional record count: number of records in the Additional section
private ushort addtionalCount; private UInt16 addtionalCount;
public int Id public Int32 Id {
{ get => this.id;
get => id; set => this.id = (UInt16)value;
set => id = (ushort) value;
} }
public int QuestionCount public Int32 QuestionCount {
{ get => this.questionCount;
get => questionCount; set => this.questionCount = (UInt16)value;
set => questionCount = (ushort) value;
} }
public int AnswerRecordCount public Int32 AnswerRecordCount {
{ get => this.answerCount;
get => answerCount; set => this.answerCount = (UInt16)value;
set => answerCount = (ushort) value;
} }
public int AuthorityRecordCount public Int32 AuthorityRecordCount {
{ get => this.authorityCount;
get => authorityCount; set => this.authorityCount = (UInt16)value;
set => authorityCount = (ushort) value;
} }
public int AdditionalRecordCount public Int32 AdditionalRecordCount {
{ get => this.addtionalCount;
get => addtionalCount; set => this.addtionalCount = (UInt16)value;
set => addtionalCount = (ushort) value;
} }
public bool Response public Boolean Response {
{ get => this.Qr == 1;
get => Qr == 1; set => this.Qr = Convert.ToByte(value);
set => Qr = Convert.ToByte(value);
} }
public DnsOperationCode OperationCode public DnsOperationCode OperationCode {
{ get => (DnsOperationCode)this.Opcode;
get => (DnsOperationCode) Opcode; set => this.Opcode = (Byte)value;
set => Opcode = (byte) value;
} }
public bool AuthorativeServer public Boolean AuthorativeServer {
{ get => this.Aa == 1;
get => Aa == 1; set => this.Aa = Convert.ToByte(value);
set => Aa = Convert.ToByte(value);
} }
public bool Truncated public Boolean Truncated {
{ get => this.Tc == 1;
get => Tc == 1; set => this.Tc = Convert.ToByte(value);
set => Tc = Convert.ToByte(value);
} }
public bool RecursionDesired public Boolean RecursionDesired {
{ get => this.Rd == 1;
get => Rd == 1; set => this.Rd = Convert.ToByte(value);
set => Rd = Convert.ToByte(value);
} }
public bool RecursionAvailable public Boolean RecursionAvailable {
{ get => this.Ra == 1;
get => Ra == 1; set => this.Ra = Convert.ToByte(value);
set => Ra = Convert.ToByte(value);
} }
public DnsResponseCode ResponseCode public DnsResponseCode ResponseCode {
{ get => (DnsResponseCode)this.RCode;
get => (DnsResponseCode) RCode; set => this.RCode = (Byte)value;
set => RCode = (byte) value;
} }
public int Size => SIZE; public Int32 Size => SIZE;
// Query/Response Flag // Query/Response Flag
private byte Qr private Byte Qr {
{ get => this.Flag0.GetBitValueAt(7);
get => Flag0.GetBitValueAt(7); set => this.Flag0 = this.Flag0.SetBitValueAt(7, 1, value);
set => Flag0 = Flag0.SetBitValueAt(7, 1, value);
} }
// Operation Code // Operation Code
private byte Opcode private Byte Opcode {
{ get => this.Flag0.GetBitValueAt(3, 4);
get => Flag0.GetBitValueAt(3, 4); set => this.Flag0 = this.Flag0.SetBitValueAt(3, 4, value);
set => Flag0 = Flag0.SetBitValueAt(3, 4, value);
} }
// Authorative Answer Flag // Authorative Answer Flag
private byte Aa private Byte Aa {
{ get => this.Flag0.GetBitValueAt(2);
get => Flag0.GetBitValueAt(2); set => this.Flag0 = this.Flag0.SetBitValueAt(2, 1, value);
set => Flag0 = Flag0.SetBitValueAt(2, 1, value);
} }
// Truncation Flag // Truncation Flag
private byte Tc private Byte Tc {
{ get => this.Flag0.GetBitValueAt(1);
get => Flag0.GetBitValueAt(1); set => this.Flag0 = this.Flag0.SetBitValueAt(1, 1, value);
set => Flag0 = Flag0.SetBitValueAt(1, 1, value);
} }
// Recursion Desired // Recursion Desired
private byte Rd private Byte Rd {
{ get => this.Flag0.GetBitValueAt(0);
get => Flag0.GetBitValueAt(0); set => this.Flag0 = this.Flag0.SetBitValueAt(0, 1, value);
set => Flag0 = Flag0.SetBitValueAt(0, 1, value);
} }
// Recursion Available // Recursion Available
private byte Ra private Byte Ra {
{ get => this.Flag1.GetBitValueAt(7);
get => Flag1.GetBitValueAt(7); set => this.Flag1 = this.Flag1.SetBitValueAt(7, 1, value);
set => Flag1 = Flag1.SetBitValueAt(7, 1, value);
} }
// Zero (Reserved) // Zero (Reserved)
private byte Z private Byte Z {
{ get => this.Flag1.GetBitValueAt(4, 3);
get => Flag1.GetBitValueAt(4, 3); set {
set { } }
} }
// Response Code // Response Code
private byte RCode private Byte RCode {
{ get => this.Flag1.GetBitValueAt(0, 4);
get => Flag1.GetBitValueAt(0, 4); set => this.Flag1 = this.Flag1.SetBitValueAt(0, 4, value);
set => Flag1 = Flag1.SetBitValueAt(0, 4, value);
} }
private byte Flag0 private Byte Flag0 { get;
{ set; }
get => flag0;
set => flag0 = value; private Byte Flag1 { get;
set; }
public Byte[] ToArray() => this.ToBytes();
public override String ToString()
=> Json.SerializeExcluding(this, true, nameof(this.Size));
} }
private byte Flag1 public class DnsDomain : IComparable<DnsDomain> {
{ private readonly String[] _labels;
get => flag1;
set => flag1 = value; public DnsDomain(String domain)
: this(domain.Split('.')) {
} }
public byte[] ToArray() => this.ToBytes(); public DnsDomain(String[] labels) => this._labels = labels;
public override string ToString() public Int32 Size => this._labels.Sum(l => l.Length) + this._labels.Length + 1;
=> Json.SerializeExcluding(this, true, nameof(Size));
}
public class DnsDomain : IComparable<DnsDomain> public static DnsDomain FromArray(Byte[] message, Int32 offset)
{ => FromArray(message, offset, out _);
private readonly string[] _labels;
public DnsDomain(string domain) public static DnsDomain FromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
: this(domain.Split('.')) List<Byte[]> labels = new List<Byte[]>();
{ Boolean endOffsetAssigned = false;
}
public DnsDomain(string[] labels)
{
_labels = labels;
}
public int Size => _labels.Sum(l => l.Length) + _labels.Length + 1;
public static DnsDomain FromArray(byte[] message, int offset)
=> FromArray(message, offset, out offset);
public static DnsDomain FromArray(byte[] message, int offset, out int endOffset)
{
var labels = new List<byte[]>();
var endOffsetAssigned = false;
endOffset = 0; endOffset = 0;
byte lengthOrPointer; Byte lengthOrPointer;
while ((lengthOrPointer = message[offset++]) > 0) while((lengthOrPointer = message[offset++]) > 0) {
{
// Two heighest bits are set (pointer) // Two heighest bits are set (pointer)
if (lengthOrPointer.GetBitValueAt(6, 2) == 3) if(lengthOrPointer.GetBitValueAt(6, 2) == 3) {
{ if(!endOffsetAssigned) {
if (!endOffsetAssigned)
{
endOffsetAssigned = true; endOffsetAssigned = true;
endOffset = offset + 1; endOffset = offset + 1;
} }
ushort pointer = lengthOrPointer.GetBitValueAt(0, 6); UInt16 pointer = lengthOrPointer.GetBitValueAt(0, 6);
offset = (pointer << 8) | message[offset]; offset = (pointer << 8) | message[offset];
continue; continue;
} }
if (lengthOrPointer.GetBitValueAt(6, 2) != 0) if(lengthOrPointer.GetBitValueAt(6, 2) != 0) {
{
throw new ArgumentException("Unexpected bit pattern in label length"); throw new ArgumentException("Unexpected bit pattern in label length");
} }
var length = lengthOrPointer; Byte length = lengthOrPointer;
var label = new byte[length]; Byte[] label = new Byte[length];
Array.Copy(message, offset, label, 0, length); Array.Copy(message, offset, label, 0, length);
labels.Add(label); labels.Add(label);
@ -529,8 +443,7 @@
offset += length; offset += length;
} }
if (!endOffsetAssigned) if(!endOffsetAssigned) {
{
endOffset = offset; endOffset = offset;
} }
@ -540,14 +453,12 @@
public static DnsDomain PointerName(IPAddress ip) public static DnsDomain PointerName(IPAddress ip)
=> new DnsDomain(FormatReverseIP(ip)); => new DnsDomain(FormatReverseIP(ip));
public byte[] ToArray() public Byte[] ToArray() {
{ Byte[] result = new Byte[this.Size];
var result = new byte[Size]; Int32 offset = 0;
var offset = 0;
foreach (var l in _labels.Select(label => Encoding.ASCII.GetBytes(label))) foreach(Byte[] l in this._labels.Select(label => Encoding.ASCII.GetBytes(label))) {
{ result[offset++] = (Byte)l.Length;
result[offset++] = (byte) l.Length;
l.CopyTo(result, offset); l.CopyTo(result, offset);
offset += l.Length; offset += l.Length;
@ -558,59 +469,49 @@
return result; return result;
} }
public override string ToString() public override String ToString()
=> string.Join(".", _labels); => String.Join(".", this._labels);
public int CompareTo(DnsDomain other) public Int32 CompareTo(DnsDomain other)
=> string.Compare(ToString(), other.ToString(), StringComparison.Ordinal); => String.Compare(this.ToString(), other.ToString(), StringComparison.Ordinal);
public override bool Equals(object obj) public override Boolean Equals(Object obj)
=> obj is DnsDomain domain && CompareTo(domain) == 0; => obj is DnsDomain domain && this.CompareTo(domain) == 0;
public override int GetHashCode() => ToString().GetHashCode(); public override Int32 GetHashCode() => this.ToString().GetHashCode();
private static string FormatReverseIP(IPAddress ip) private static String FormatReverseIP(IPAddress ip) {
{ Byte[] address = ip.GetAddressBytes();
var address = ip.GetAddressBytes();
if (address.Length == 4) if(address.Length == 4) {
{ return String.Join(".", address.Reverse().Select(b => b.ToString())) + ".in-addr.arpa";
return string.Join(".", address.Reverse().Select(b => b.ToString())) + ".in-addr.arpa";
} }
var nibbles = new byte[address.Length * 2]; Byte[] nibbles = new Byte[address.Length * 2];
for (int i = 0, j = 0; i < address.Length; i++, j = 2 * i) for(Int32 i = 0, j = 0; i < address.Length; i++, j = 2 * i) {
{ Byte b = address[i];
var b = address[i];
nibbles[j] = b.GetBitValueAt(4, 4); nibbles[j] = b.GetBitValueAt(4, 4);
nibbles[j + 1] = b.GetBitValueAt(0, 4); nibbles[j + 1] = b.GetBitValueAt(0, 4);
} }
return string.Join(".", nibbles.Reverse().Select(b => b.ToString("x"))) + ".ip6.arpa"; return String.Join(".", nibbles.Reverse().Select(b => b.ToString("x"))) + ".ip6.arpa";
} }
} }
public class DnsQuestion : IDnsMessageEntry public class DnsQuestion : IDnsMessageEntry {
{ public static IList<DnsQuestion> GetAllFromArray(Byte[] message, Int32 offset, Int32 questionCount) =>
private readonly DnsDomain _domain; GetAllFromArray(message, offset, questionCount, out _);
private readonly DnsRecordType _type;
private readonly DnsRecordClass _klass;
public static IList<DnsQuestion> GetAllFromArray(byte[] message, int offset, int questionCount) =>
GetAllFromArray(message, offset, questionCount, out offset);
public static IList<DnsQuestion> GetAllFromArray( public static IList<DnsQuestion> GetAllFromArray(
byte[] message, Byte[] message,
int offset, Int32 offset,
int questionCount, Int32 questionCount,
out int endOffset) out Int32 endOffset) {
{
IList<DnsQuestion> questions = new List<DnsQuestion>(questionCount); IList<DnsQuestion> questions = new List<DnsQuestion>(questionCount);
for (var i = 0; i < questionCount; i++) for(Int32 i = 0; i < questionCount; i++) {
{
questions.Add(FromArray(message, offset, out offset)); questions.Add(FromArray(message, offset, out offset));
} }
@ -618,10 +519,9 @@
return questions; return questions;
} }
public static DnsQuestion FromArray(byte[] message, int offset, out int endOffset) public static DnsQuestion FromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
{ DnsDomain domain = DnsDomain.FromArray(message, offset, out offset);
var domain = DnsDomain.FromArray(message, offset, out offset); Tail tail = message.ToStruct<Tail>(offset, Tail.SIZE);
var tail = message.ToStruct<Tail>(offset, Tail.SIZE);
endOffset = offset + Tail.SIZE; endOffset = offset + Tail.SIZE;
@ -631,51 +531,50 @@
public DnsQuestion( public DnsQuestion(
DnsDomain domain, DnsDomain domain,
DnsRecordType type = DnsRecordType.A, DnsRecordType type = DnsRecordType.A,
DnsRecordClass klass = DnsRecordClass.IN) DnsRecordClass klass = DnsRecordClass.IN) {
{ this.Name = domain;
_domain = domain; this.Type = type;
_type = type; this.Class = klass;
_klass = klass;
} }
public DnsDomain Name => _domain; public DnsDomain Name {
get;
}
public DnsRecordType Type => _type; public DnsRecordType Type {
get;
}
public DnsRecordClass Class => _klass; public DnsRecordClass Class {
get;
}
public int Size => _domain.Size + Tail.SIZE; public Int32 Size => this.Name.Size + Tail.SIZE;
public byte[] ToArray() public Byte[] ToArray() => new MemoryStream(this.Size)
{ .Append(this.Name.ToArray())
return new MemoryStream(Size) .Append(new Tail { Type = Type, Class = Class }.ToBytes())
.Append(_domain.ToArray())
.Append(new Tail {Type = Type, Class = Class}.ToBytes())
.ToArray(); .ToArray();
}
public override string ToString() public override String ToString()
=> Json.SerializeOnly(this, true, nameof(Name), nameof(Type), nameof(Class)); => Json.SerializeOnly(this, true, nameof(this.Name), nameof(this.Type), nameof(this.Class));
[StructEndianness(Endianness.Big)] [StructEndianness(Endianness.Big)]
[StructLayout(LayoutKind.Sequential, Pack = 2)] [StructLayout(LayoutKind.Sequential, Pack = 2)]
private struct Tail private struct Tail {
{ public const Int32 SIZE = 4;
public const int SIZE = 4;
private ushort type; private UInt16 type;
private ushort klass; private UInt16 klass;
public DnsRecordType Type public DnsRecordType Type {
{ get => (DnsRecordType)this.type;
get => (DnsRecordType) type; set => this.type = (UInt16)value;
set => type = (ushort) value;
} }
public DnsRecordClass Class public DnsRecordClass Class {
{ get => (DnsRecordClass)this.klass;
get => (DnsRecordClass) klass; set => this.klass = (UInt16)value;
set => klass = (ushort) value;
} }
} }
} }

View File

@ -1,90 +1,90 @@
namespace Unosquare.Swan.Networking using Unosquare.Swan.Attributes;
{ using Unosquare.Swan.Formatters;
using Attributes; using System;
using Formatters; using System.Collections.Generic;
using System; using System.IO;
using System.Collections.Generic; using System.Net;
using System.IO; using System.Runtime.InteropServices;
using System.Net;
using System.Runtime.InteropServices;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// DnsClient public methods. /// DnsClient public methods.
/// </summary> /// </summary>
internal partial class DnsClient internal partial class DnsClient {
{ public abstract class DnsResourceRecordBase : IDnsResourceRecord {
public abstract class DnsResourceRecordBase : IDnsResourceRecord
{
private readonly IDnsResourceRecord _record; private readonly IDnsResourceRecord _record;
protected DnsResourceRecordBase(IDnsResourceRecord record) protected DnsResourceRecordBase(IDnsResourceRecord record) => this._record = record;
{
_record = record; public DnsDomain Name => this._record.Name;
public DnsRecordType Type => this._record.Type;
public DnsRecordClass Class => this._record.Class;
public TimeSpan TimeToLive => this._record.TimeToLive;
public Int32 DataLength => this._record.DataLength;
public Byte[] Data => this._record.Data;
public Int32 Size => this._record.Size;
protected virtual String[] IncludedProperties
=> new[] { nameof(this.Name), nameof(this.Type), nameof(this.Class), nameof(this.TimeToLive), nameof(this.DataLength) };
public Byte[] ToArray() => this._record.ToArray();
public override String ToString()
=> Json.SerializeOnly(this, true, this.IncludedProperties);
} }
public DnsDomain Name => _record.Name; public class DnsResourceRecord : IDnsResourceRecord {
public DnsRecordType Type => _record.Type;
public DnsRecordClass Class => _record.Class;
public TimeSpan TimeToLive => _record.TimeToLive;
public int DataLength => _record.DataLength;
public byte[] Data => _record.Data;
public int Size => _record.Size;
protected virtual string[] IncludedProperties
=> new[] {nameof(Name), nameof(Type), nameof(Class), nameof(TimeToLive), nameof(DataLength)};
public byte[] ToArray() => _record.ToArray();
public override string ToString()
=> Json.SerializeOnly(this, true, IncludedProperties);
}
public class DnsResourceRecord : IDnsResourceRecord
{
public DnsResourceRecord( public DnsResourceRecord(
DnsDomain domain, DnsDomain domain,
byte[] data, Byte[] data,
DnsRecordType type, DnsRecordType type,
DnsRecordClass klass = DnsRecordClass.IN, DnsRecordClass klass = DnsRecordClass.IN,
TimeSpan ttl = default) TimeSpan ttl = default) {
{ this.Name = domain;
Name = domain; this.Type = type;
Type = type; this.Class = klass;
Class = klass; this.TimeToLive = ttl;
TimeToLive = ttl; this.Data = data;
Data = data;
} }
public DnsDomain Name { get; } public DnsDomain Name {
get;
}
public DnsRecordType Type { get; } public DnsRecordType Type {
get;
}
public DnsRecordClass Class { get; } public DnsRecordClass Class {
get;
}
public TimeSpan TimeToLive { get; } public TimeSpan TimeToLive {
get;
}
public int DataLength => Data.Length; public Int32 DataLength => this.Data.Length;
public byte[] Data { get; } public Byte[] Data {
get;
}
public int Size => Name.Size + Tail.SIZE + Data.Length; public Int32 Size => this.Name.Size + Tail.SIZE + this.Data.Length;
public static IList<DnsResourceRecord> GetAllFromArray( public static IList<DnsResourceRecord> GetAllFromArray(
byte[] message, Byte[] message,
int offset, Int32 offset,
int count, Int32 count,
out int endOffset) out Int32 endOffset) {
{
IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count); IList<DnsResourceRecord> records = new List<DnsResourceRecord>(count);
for (var i = 0; i < count; i++) for(Int32 i = 0; i < count; i++) {
{
records.Add(FromArray(message, offset, out offset)); records.Add(FromArray(message, offset, out offset));
} }
@ -92,12 +92,11 @@
return records; return records;
} }
public static DnsResourceRecord FromArray(byte[] message, int offset, out int endOffset) public static DnsResourceRecord FromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
{ DnsDomain domain = DnsDomain.FromArray(message, offset, out offset);
var domain = DnsDomain.FromArray(message, offset, out offset); Tail tail = message.ToStruct<Tail>(offset, Tail.SIZE);
var tail = message.ToStruct<Tail>(offset, Tail.SIZE);
var data = new byte[tail.DataLength]; Byte[] data = new Byte[tail.DataLength];
offset += Tail.SIZE; offset += Tail.SIZE;
Array.Copy(message, offset, data, 0, data.Length); Array.Copy(message, offset, data, 0, data.Length);
@ -107,255 +106,239 @@
return new DnsResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive); return new DnsResourceRecord(domain, data, tail.Type, tail.Class, tail.TimeToLive);
} }
public byte[] ToArray() public Byte[] ToArray() => new MemoryStream(this.Size)
{ .Append(this.Name.ToArray())
return new MemoryStream(Size) .Append(new Tail() {
.Append(Name.ToArray())
.Append(new Tail()
{
Type = Type, Type = Type,
Class = Class, Class = Class,
TimeToLive = TimeToLive, TimeToLive = TimeToLive,
DataLength = Data.Length, DataLength = this.Data.Length,
}.ToBytes()) }.ToBytes())
.Append(Data) .Append(this.Data)
.ToArray(); .ToArray();
}
public override string ToString() public override String ToString() => Json.SerializeOnly(
{
return Json.SerializeOnly(
this, this,
true, true,
nameof(Name), nameof(this.Name),
nameof(Type), nameof(this.Type),
nameof(Class), nameof(this.Class),
nameof(TimeToLive), nameof(this.TimeToLive),
nameof(DataLength)); nameof(this.DataLength));
}
[StructEndianness(Endianness.Big)] [StructEndianness(Endianness.Big)]
[StructLayout(LayoutKind.Sequential, Pack = 2)] [StructLayout(LayoutKind.Sequential, Pack = 2)]
private struct Tail private struct Tail {
{ public const Int32 SIZE = 10;
public const int SIZE = 10;
private ushort type; private UInt16 type;
private ushort klass; private UInt16 klass;
private uint ttl; private UInt32 ttl;
private ushort dataLength; private UInt16 dataLength;
public DnsRecordType Type public DnsRecordType Type {
{ get => (DnsRecordType)this.type;
get => (DnsRecordType) type; set => this.type = (UInt16)value;
set => type = (ushort) value;
} }
public DnsRecordClass Class public DnsRecordClass Class {
{ get => (DnsRecordClass)this.klass;
get => (DnsRecordClass) klass; set => this.klass = (UInt16)value;
set => klass = (ushort) value;
} }
public TimeSpan TimeToLive public TimeSpan TimeToLive {
{ get => TimeSpan.FromSeconds(this.ttl);
get => TimeSpan.FromSeconds(ttl); set => this.ttl = (UInt32)value.TotalSeconds;
set => ttl = (uint) value.TotalSeconds;
} }
public int DataLength public Int32 DataLength {
{ get => this.dataLength;
get => dataLength; set => this.dataLength = (UInt16)value;
set => dataLength = (ushort) value;
} }
} }
} }
public class DnsPointerResourceRecord : DnsResourceRecordBase public class DnsPointerResourceRecord : DnsResourceRecordBase {
{ public DnsPointerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
public DnsPointerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset) : base(record) => this.PointerDomainName = DnsDomain.FromArray(message, dataOffset);
: base(record)
{ public DnsDomain PointerDomainName {
PointerDomainName = DnsDomain.FromArray(message, dataOffset); get;
} }
public DnsDomain PointerDomainName { get; } protected override String[] IncludedProperties {
get {
protected override string[] IncludedProperties List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.PointerDomainName) };
{
get
{
var temp = new List<string>(base.IncludedProperties) {nameof(PointerDomainName)};
return temp.ToArray(); return temp.ToArray();
} }
} }
} }
public class DnsIPAddressResourceRecord : DnsResourceRecordBase public class DnsIPAddressResourceRecord : DnsResourceRecordBase {
{
public DnsIPAddressResourceRecord(IDnsResourceRecord record) public DnsIPAddressResourceRecord(IDnsResourceRecord record)
: base(record) : base(record) => this.IPAddress = new IPAddress(this.Data);
{
IPAddress = new IPAddress(Data); public IPAddress IPAddress {
get;
} }
public IPAddress IPAddress { get; } protected override String[] IncludedProperties {
get {
protected override string[] IncludedProperties List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.IPAddress) };
{
get
{
var temp = new List<string>(base.IncludedProperties) {nameof(IPAddress)};
return temp.ToArray(); return temp.ToArray();
} }
} }
} }
public class DnsNameServerResourceRecord : DnsResourceRecordBase public class DnsNameServerResourceRecord : DnsResourceRecordBase {
{ public DnsNameServerResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
public DnsNameServerResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset) : base(record) => this.NSDomainName = DnsDomain.FromArray(message, dataOffset);
: base(record)
{ public DnsDomain NSDomainName {
NSDomainName = DnsDomain.FromArray(message, dataOffset); get;
} }
public DnsDomain NSDomainName { get; } protected override String[] IncludedProperties {
get {
protected override string[] IncludedProperties List<String> temp = new List<String>(base.IncludedProperties) { nameof(this.NSDomainName) };
{
get
{
var temp = new List<string>(base.IncludedProperties) {nameof(NSDomainName)};
return temp.ToArray(); return temp.ToArray();
} }
} }
} }
public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase public class DnsCanonicalNameResourceRecord : DnsResourceRecordBase {
{ public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
public DnsCanonicalNameResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset) : base(record) => this.CanonicalDomainName = DnsDomain.FromArray(message, dataOffset);
: base(record)
{ public DnsDomain CanonicalDomainName {
CanonicalDomainName = DnsDomain.FromArray(message, dataOffset); get;
} }
public DnsDomain CanonicalDomainName { get; } protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties)
{ {
nameof(CanonicalDomainName), nameof(this.CanonicalDomainName),
}.ToArray(); }.ToArray();
} }
public class DnsMailExchangeResourceRecord : DnsResourceRecordBase public class DnsMailExchangeResourceRecord : DnsResourceRecordBase {
{ private const Int32 PreferenceSize = 2;
private const int PreferenceSize = 2;
public DnsMailExchangeResourceRecord( public DnsMailExchangeResourceRecord(
IDnsResourceRecord record, IDnsResourceRecord record,
byte[] message, Byte[] message,
int dataOffset) Int32 dataOffset)
: base(record) : base(record) {
{ Byte[] preference = new Byte[PreferenceSize];
var preference = new byte[PreferenceSize];
Array.Copy(message, dataOffset, preference, 0, preference.Length); Array.Copy(message, dataOffset, preference, 0, preference.Length);
if (BitConverter.IsLittleEndian) if(BitConverter.IsLittleEndian) {
{
Array.Reverse(preference); Array.Reverse(preference);
} }
dataOffset += PreferenceSize; dataOffset += PreferenceSize;
Preference = BitConverter.ToUInt16(preference, 0); this.Preference = BitConverter.ToUInt16(preference, 0);
ExchangeDomainName = DnsDomain.FromArray(message, dataOffset); this.ExchangeDomainName = DnsDomain.FromArray(message, dataOffset);
} }
public int Preference { get; } public Int32 Preference {
get;
}
public DnsDomain ExchangeDomainName { get; } public DnsDomain ExchangeDomainName {
get;
}
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties) protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
{ {
nameof(Preference), nameof(this.Preference),
nameof(ExchangeDomainName), nameof(this.ExchangeDomainName),
}.ToArray(); }.ToArray();
} }
public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase public class DnsStartOfAuthorityResourceRecord : DnsResourceRecordBase {
{ public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, Byte[] message, Int32 dataOffset)
public DnsStartOfAuthorityResourceRecord(IDnsResourceRecord record, byte[] message, int dataOffset) : base(record) {
: base(record) this.MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
{ this.ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
MasterDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
ResponsibleDomainName = DnsDomain.FromArray(message, dataOffset, out dataOffset);
var tail = message.ToStruct<Options>(dataOffset, Options.SIZE); Options tail = message.ToStruct<Options>(dataOffset, Options.SIZE);
SerialNumber = tail.SerialNumber; this.SerialNumber = tail.SerialNumber;
RefreshInterval = tail.RefreshInterval; this.RefreshInterval = tail.RefreshInterval;
RetryInterval = tail.RetryInterval; this.RetryInterval = tail.RetryInterval;
ExpireInterval = tail.ExpireInterval; this.ExpireInterval = tail.ExpireInterval;
MinimumTimeToLive = tail.MinimumTimeToLive; this.MinimumTimeToLive = tail.MinimumTimeToLive;
} }
public DnsStartOfAuthorityResourceRecord( public DnsStartOfAuthorityResourceRecord(
DnsDomain domain, DnsDomain domain,
DnsDomain master, DnsDomain master,
DnsDomain responsible, DnsDomain responsible,
long serial, Int64 serial,
TimeSpan refresh, TimeSpan refresh,
TimeSpan retry, TimeSpan retry,
TimeSpan expire, TimeSpan expire,
TimeSpan minTtl, TimeSpan minTtl,
TimeSpan ttl = default) TimeSpan ttl = default)
: base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) : base(Create(domain, master, responsible, serial, refresh, retry, expire, minTtl, ttl)) {
{ this.MasterDomainName = master;
MasterDomainName = master; this.ResponsibleDomainName = responsible;
ResponsibleDomainName = responsible;
SerialNumber = serial; this.SerialNumber = serial;
RefreshInterval = refresh; this.RefreshInterval = refresh;
RetryInterval = retry; this.RetryInterval = retry;
ExpireInterval = expire; this.ExpireInterval = expire;
MinimumTimeToLive = minTtl; this.MinimumTimeToLive = minTtl;
} }
public DnsDomain MasterDomainName { get; } public DnsDomain MasterDomainName {
get;
}
public DnsDomain ResponsibleDomainName { get; } public DnsDomain ResponsibleDomainName {
get;
}
public long SerialNumber { get; } public Int64 SerialNumber {
get;
}
public TimeSpan RefreshInterval { get; } public TimeSpan RefreshInterval {
get;
}
public TimeSpan RetryInterval { get; } public TimeSpan RetryInterval {
get;
}
public TimeSpan ExpireInterval { get; } public TimeSpan ExpireInterval {
get;
}
public TimeSpan MinimumTimeToLive { get; } public TimeSpan MinimumTimeToLive {
get;
}
protected override string[] IncludedProperties => new List<string>(base.IncludedProperties) protected override String[] IncludedProperties => new List<String>(base.IncludedProperties)
{ {
nameof(MasterDomainName), nameof(this.MasterDomainName),
nameof(ResponsibleDomainName), nameof(this.ResponsibleDomainName),
nameof(SerialNumber), nameof(this.SerialNumber),
}.ToArray(); }.ToArray();
private static IDnsResourceRecord Create( private static IDnsResourceRecord Create(
DnsDomain domain, DnsDomain domain,
DnsDomain master, DnsDomain master,
DnsDomain responsible, DnsDomain responsible,
long serial, Int64 serial,
TimeSpan refresh, TimeSpan refresh,
TimeSpan retry, TimeSpan retry,
TimeSpan expire, TimeSpan expire,
TimeSpan minTtl, TimeSpan minTtl,
TimeSpan ttl) TimeSpan ttl) {
{ MemoryStream data = new MemoryStream(Options.SIZE + master.Size + responsible.Size);
var data = new MemoryStream(Options.SIZE + master.Size + responsible.Size); Options tail = new Options {
var tail = new Options
{
SerialNumber = serial, SerialNumber = serial,
RefreshInterval = refresh, RefreshInterval = refresh,
RetryInterval = retry, RetryInterval = retry,
@ -363,67 +346,58 @@
MinimumTimeToLive = minTtl, MinimumTimeToLive = minTtl,
}; };
data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes()); _ = data.Append(master.ToArray()).Append(responsible.ToArray()).Append(tail.ToBytes());
return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl); return new DnsResourceRecord(domain, data.ToArray(), DnsRecordType.SOA, DnsRecordClass.IN, ttl);
} }
[StructEndianness(Endianness.Big)] [StructEndianness(Endianness.Big)]
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct Options public struct Options {
{ public const Int32 SIZE = 20;
public const int SIZE = 20;
private uint serialNumber; private UInt32 serialNumber;
private uint refreshInterval; private UInt32 refreshInterval;
private uint retryInterval; private UInt32 retryInterval;
private uint expireInterval; private UInt32 expireInterval;
private uint ttl; private UInt32 ttl;
public long SerialNumber public Int64 SerialNumber {
{ get => this.serialNumber;
get => serialNumber; set => this.serialNumber = (UInt32)value;
set => serialNumber = (uint) value;
} }
public TimeSpan RefreshInterval public TimeSpan RefreshInterval {
{ get => TimeSpan.FromSeconds(this.refreshInterval);
get => TimeSpan.FromSeconds(refreshInterval); set => this.refreshInterval = (UInt32)value.TotalSeconds;
set => refreshInterval = (uint) value.TotalSeconds;
} }
public TimeSpan RetryInterval public TimeSpan RetryInterval {
{ get => TimeSpan.FromSeconds(this.retryInterval);
get => TimeSpan.FromSeconds(retryInterval); set => this.retryInterval = (UInt32)value.TotalSeconds;
set => retryInterval = (uint) value.TotalSeconds;
} }
public TimeSpan ExpireInterval public TimeSpan ExpireInterval {
{ get => TimeSpan.FromSeconds(this.expireInterval);
get => TimeSpan.FromSeconds(expireInterval); set => this.expireInterval = (UInt32)value.TotalSeconds;
set => expireInterval = (uint) value.TotalSeconds;
} }
public TimeSpan MinimumTimeToLive public TimeSpan MinimumTimeToLive {
{ get => TimeSpan.FromSeconds(this.ttl);
get => TimeSpan.FromSeconds(ttl); set => this.ttl = (UInt32)value.TotalSeconds;
set => ttl = (uint) value.TotalSeconds;
} }
} }
} }
private static class DnsResourceRecordFactory private static class DnsResourceRecordFactory {
{
public static IList<IDnsResourceRecord> GetAllFromArray( public static IList<IDnsResourceRecord> GetAllFromArray(
byte[] message, Byte[] message,
int offset, Int32 offset,
int count, Int32 count,
out int endOffset) out Int32 endOffset) {
{ List<IDnsResourceRecord> result = new List<IDnsResourceRecord>(count);
var result = new List<IDnsResourceRecord>(count);
for (var i = 0; i < count; i++) for(Int32 i = 0; i < count; i++) {
{
result.Add(GetFromArray(message, offset, out offset)); result.Add(GetFromArray(message, offset, out offset));
} }
@ -431,13 +405,11 @@
return result; return result;
} }
private static IDnsResourceRecord GetFromArray(byte[] message, int offset, out int endOffset) private static IDnsResourceRecord GetFromArray(Byte[] message, Int32 offset, out Int32 endOffset) {
{ DnsResourceRecord record = DnsResourceRecord.FromArray(message, offset, out endOffset);
var record = DnsResourceRecord.FromArray(message, offset, out endOffset); Int32 dataOffset = endOffset - record.DataLength;
var dataOffset = endOffset - record.DataLength;
switch (record.Type) switch(record.Type) {
{
case DnsRecordType.A: case DnsRecordType.A:
case DnsRecordType.AAAA: case DnsRecordType.AAAA:
return new DnsIPAddressResourceRecord(record); return new DnsIPAddressResourceRecord(record);

View File

@ -1,87 +1,84 @@
namespace Unosquare.Swan.Networking using Unosquare.Swan.Formatters;
{ using System;
using Formatters; using System.Collections.Generic;
using System; using System.Collections.ObjectModel;
using System.Collections.Generic; using System.IO;
using System.Collections.ObjectModel; using System.Linq;
using System.IO;
using System.Linq;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// DnsClient Response inner class. /// DnsClient Response inner class.
/// </summary> /// </summary>
internal partial class DnsClient internal partial class DnsClient {
{ public class DnsClientResponse : IDnsResponse {
public class DnsClientResponse : IDnsResponse
{
private readonly DnsResponse _response; private readonly DnsResponse _response;
private readonly byte[] _message; private readonly Byte[] _message;
internal DnsClientResponse(DnsClientRequest request, DnsResponse response, byte[] message) internal DnsClientResponse(DnsClientRequest request, DnsResponse response, Byte[] message) {
{ this.Request = request;
Request = request;
_message = message; this._message = message;
_response = response; this._response = response;
} }
public DnsClientRequest Request { get; } public DnsClientRequest Request {
get;
public int Id
{
get { return _response.Id; }
set { }
} }
public IList<IDnsResourceRecord> AnswerRecords => _response.AnswerRecords; public Int32 Id {
get => this._response.Id;
set {
}
}
public IList<IDnsResourceRecord> AnswerRecords => this._response.AnswerRecords;
public IList<IDnsResourceRecord> AuthorityRecords => public IList<IDnsResourceRecord> AuthorityRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(_response.AuthorityRecords); new ReadOnlyCollection<IDnsResourceRecord>(this._response.AuthorityRecords);
public IList<IDnsResourceRecord> AdditionalRecords => public IList<IDnsResourceRecord> AdditionalRecords =>
new ReadOnlyCollection<IDnsResourceRecord>(_response.AdditionalRecords); new ReadOnlyCollection<IDnsResourceRecord>(this._response.AdditionalRecords);
public bool IsRecursionAvailable public Boolean IsRecursionAvailable {
{ get => this._response.IsRecursionAvailable;
get { return _response.IsRecursionAvailable; } set {
set { } }
} }
public bool IsAuthorativeServer public Boolean IsAuthorativeServer {
{ get => this._response.IsAuthorativeServer;
get { return _response.IsAuthorativeServer; } set {
set { } }
} }
public bool IsTruncated public Boolean IsTruncated {
{ get => this._response.IsTruncated;
get { return _response.IsTruncated; } set {
set { } }
} }
public DnsOperationCode OperationCode public DnsOperationCode OperationCode {
{ get => this._response.OperationCode;
get { return _response.OperationCode; } set {
set { } }
} }
public DnsResponseCode ResponseCode public DnsResponseCode ResponseCode {
{ get => this._response.ResponseCode;
get { return _response.ResponseCode; } set {
set { } }
} }
public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(_response.Questions); public IList<DnsQuestion> Questions => new ReadOnlyCollection<DnsQuestion>(this._response.Questions);
public int Size => _message.Length; public Int32 Size => this._message.Length;
public byte[] ToArray() => _message; public Byte[] ToArray() => this._message;
public override string ToString() => _response.ToString(); public override String ToString() => this._response.ToString();
} }
public class DnsResponse : IDnsResponse public class DnsResponse : IDnsResponse {
{
private DnsHeader _header; private DnsHeader _header;
public DnsResponse( public DnsResponse(
@ -89,126 +86,119 @@
IList<DnsQuestion> questions, IList<DnsQuestion> questions,
IList<IDnsResourceRecord> answers, IList<IDnsResourceRecord> answers,
IList<IDnsResourceRecord> authority, IList<IDnsResourceRecord> authority,
IList<IDnsResourceRecord> additional) IList<IDnsResourceRecord> additional) {
{ this._header = header;
_header = header; this.Questions = questions;
Questions = questions; this.AnswerRecords = answers;
AnswerRecords = answers; this.AuthorityRecords = authority;
AuthorityRecords = authority; this.AdditionalRecords = additional;
AdditionalRecords = additional;
} }
public IList<DnsQuestion> Questions { get; } public IList<DnsQuestion> Questions {
get;
public IList<IDnsResourceRecord> AnswerRecords { get; }
public IList<IDnsResourceRecord> AuthorityRecords { get; }
public IList<IDnsResourceRecord> AdditionalRecords { get; }
public int Id
{
get => _header.Id;
set => _header.Id = value;
} }
public bool IsRecursionAvailable public IList<IDnsResourceRecord> AnswerRecords {
{ get;
get => _header.RecursionAvailable;
set => _header.RecursionAvailable = value;
} }
public bool IsAuthorativeServer public IList<IDnsResourceRecord> AuthorityRecords {
{ get;
get => _header.AuthorativeServer;
set => _header.AuthorativeServer = value;
} }
public bool IsTruncated public IList<IDnsResourceRecord> AdditionalRecords {
{ get;
get => _header.Truncated;
set => _header.Truncated = value;
} }
public DnsOperationCode OperationCode public Int32 Id {
{ get => this._header.Id;
get => _header.OperationCode; set => this._header.Id = value;
set => _header.OperationCode = value;
} }
public DnsResponseCode ResponseCode public Boolean IsRecursionAvailable {
{ get => this._header.RecursionAvailable;
get => _header.ResponseCode; set => this._header.RecursionAvailable = value;
set => _header.ResponseCode = value;
} }
public int Size public Boolean IsAuthorativeServer {
=> _header.Size + get => this._header.AuthorativeServer;
Questions.Sum(q => q.Size) + set => this._header.AuthorativeServer = value;
AnswerRecords.Sum(a => a.Size) + }
AuthorityRecords.Sum(a => a.Size) +
AdditionalRecords.Sum(a => a.Size);
public static DnsResponse FromArray(byte[] message) public Boolean IsTruncated {
{ get => this._header.Truncated;
var header = DnsHeader.FromArray(message); set => this._header.Truncated = value;
var offset = header.Size; }
if (!header.Response || header.QuestionCount == 0) public DnsOperationCode OperationCode {
{ get => this._header.OperationCode;
set => this._header.OperationCode = value;
}
public DnsResponseCode ResponseCode {
get => this._header.ResponseCode;
set => this._header.ResponseCode = value;
}
public Int32 Size
=> this._header.Size +
this.Questions.Sum(q => q.Size) +
this.AnswerRecords.Sum(a => a.Size) +
this.AuthorityRecords.Sum(a => a.Size) +
this.AdditionalRecords.Sum(a => a.Size);
public static DnsResponse FromArray(Byte[] message) {
DnsHeader header = DnsHeader.FromArray(message);
Int32 offset = header.Size;
if(!header.Response || header.QuestionCount == 0) {
throw new ArgumentException("Invalid response message"); throw new ArgumentException("Invalid response message");
} }
if (header.Truncated) return header.Truncated
{ ? new DnsResponse(header,
return new DnsResponse(header,
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount), DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount),
new List<IDnsResourceRecord>(), new List<IDnsResourceRecord>(),
new List<IDnsResourceRecord>(), new List<IDnsResourceRecord>(),
new List<IDnsResourceRecord>()); new List<IDnsResourceRecord>())
} : new DnsResponse(header,
return new DnsResponse(header,
DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset), DnsQuestion.GetAllFromArray(message, offset, header.QuestionCount, out offset),
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset), DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AnswerRecordCount, out offset),
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset), DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AuthorityRecordCount, out offset),
DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out offset)); DnsResourceRecordFactory.GetAllFromArray(message, offset, header.AdditionalRecordCount, out _));
} }
public byte[] ToArray() public Byte[] ToArray() {
{ this.UpdateHeader();
UpdateHeader(); MemoryStream result = new MemoryStream(this.Size);
var result = new MemoryStream(Size);
result _ = result
.Append(_header.ToArray()) .Append(this._header.ToArray())
.Append(Questions.Select(q => q.ToArray())) .Append(this.Questions.Select(q => q.ToArray()))
.Append(AnswerRecords.Select(a => a.ToArray())) .Append(this.AnswerRecords.Select(a => a.ToArray()))
.Append(AuthorityRecords.Select(a => a.ToArray())) .Append(this.AuthorityRecords.Select(a => a.ToArray()))
.Append(AdditionalRecords.Select(a => a.ToArray())); .Append(this.AdditionalRecords.Select(a => a.ToArray()));
return result.ToArray(); return result.ToArray();
} }
public override string ToString() public override String ToString() {
{ this.UpdateHeader();
UpdateHeader();
return Json.SerializeOnly( return Json.SerializeOnly(
this, this,
true, true,
nameof(Questions), nameof(this.Questions),
nameof(AnswerRecords), nameof(this.AnswerRecords),
nameof(AuthorityRecords), nameof(this.AuthorityRecords),
nameof(AdditionalRecords)); nameof(this.AdditionalRecords));
} }
private void UpdateHeader() private void UpdateHeader() {
{ this._header.QuestionCount = this.Questions.Count;
_header.QuestionCount = Questions.Count; this._header.AnswerRecordCount = this.AnswerRecords.Count;
_header.AnswerRecordCount = AnswerRecords.Count; this._header.AuthorityRecordCount = this.AuthorityRecords.Count;
_header.AuthorityRecordCount = AuthorityRecords.Count; this._header.AdditionalRecordCount = this.AdditionalRecords.Count;
_header.AdditionalRecordCount = AdditionalRecords.Count;
} }
} }
} }

View File

@ -1,82 +1,71 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.Net;
using System.Linq; using Unosquare.Swan.Exceptions;
using System.Net;
using Exceptions;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// DnsClient public methods. /// DnsClient public methods.
/// </summary> /// </summary>
internal partial class DnsClient internal partial class DnsClient {
{
private readonly IPEndPoint _dns; private readonly IPEndPoint _dns;
private readonly IDnsRequestResolver _resolver; private readonly IDnsRequestResolver _resolver;
public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null) public DnsClient(IPEndPoint dns, IDnsRequestResolver resolver = null) {
{ this._dns = dns;
_dns = dns; this._resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver());
_resolver = resolver ?? new DnsUdpRequestResolver(new DnsTcpRequestResolver());
} }
public DnsClient(IPAddress ip, int port = Network.DnsDefaultPort, IDnsRequestResolver resolver = null) public DnsClient(IPAddress ip, Int32 port = Network.DnsDefaultPort, IDnsRequestResolver resolver = null)
: this(new IPEndPoint(ip, port), resolver) : this(new IPEndPoint(ip, port), resolver) {
{
} }
public DnsClientRequest Create(IDnsRequest request = null) public DnsClientRequest Create(IDnsRequest request = null) => new DnsClientRequest(this._dns, request, this._resolver);
{
return new DnsClientRequest(_dns, request, _resolver);
}
public IList<IPAddress> Lookup(string domain, DnsRecordType type = DnsRecordType.A) public IList<IPAddress> Lookup(String domain, DnsRecordType type = DnsRecordType.A) {
{ if(String.IsNullOrWhiteSpace(domain)) {
if (string.IsNullOrWhiteSpace(domain))
throw new ArgumentNullException(nameof(domain)); throw new ArgumentNullException(nameof(domain));
}
if (type != DnsRecordType.A && type != DnsRecordType.AAAA) if(type != DnsRecordType.A && type != DnsRecordType.AAAA) {
{
throw new ArgumentException("Invalid record type " + type); throw new ArgumentException("Invalid record type " + type);
} }
var response = Resolve(domain, type); DnsClientResponse response = this.Resolve(domain, type);
var ips = response.AnswerRecords List<IPAddress> ips = response.AnswerRecords
.Where(r => r.Type == type) .Where(r => r.Type == type)
.Cast<DnsIPAddressResourceRecord>() .Cast<DnsIPAddressResourceRecord>()
.Select(r => r.IPAddress) .Select(r => r.IPAddress)
.ToList(); .ToList();
if (ips.Count == 0) if(ips.Count == 0) {
{
throw new DnsQueryException(response, "No matching records"); throw new DnsQueryException(response, "No matching records");
} }
return ips; return ips;
} }
public string Reverse(IPAddress ip) public String Reverse(IPAddress ip) {
{ if(ip == null) {
if (ip == null)
throw new ArgumentNullException(nameof(ip)); throw new ArgumentNullException(nameof(ip));
}
var response = Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR); DnsClientResponse response = this.Resolve(DnsDomain.PointerName(ip), DnsRecordType.PTR);
var ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR); IDnsResourceRecord ptr = response.AnswerRecords.FirstOrDefault(r => r.Type == DnsRecordType.PTR);
if (ptr == null) if(ptr == null) {
{
throw new DnsQueryException(response, "No matching records"); throw new DnsQueryException(response, "No matching records");
} }
return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString(); return ((DnsPointerResourceRecord)ptr).PointerDomainName.ToString();
} }
public DnsClientResponse Resolve(string domain, DnsRecordType type) => Resolve(new DnsDomain(domain), type); public DnsClientResponse Resolve(String domain, DnsRecordType type) => this.Resolve(new DnsDomain(domain), type);
public DnsClientResponse Resolve(DnsDomain domain, DnsRecordType type) public DnsClientResponse Resolve(DnsDomain domain, DnsRecordType type) {
{ DnsClientRequest request = this.Create();
var request = Create(); DnsQuestion question = new DnsQuestion(domain, type);
var question = new DnsQuestion(domain, type);
request.Questions.Add(question); request.Questions.Add(question);
request.OperationCode = DnsOperationCode.Query; request.OperationCode = DnsOperationCode.Query;

View File

@ -1,12 +1,11 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System.Collections.Generic;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a response from a DNS server. /// Represents a response from a DNS server.
/// </summary> /// </summary>
public class DnsQueryResult public class DnsQueryResult {
{
private readonly List<DnsRecord> m_AnswerRecords = new List<DnsRecord>(); private readonly List<DnsRecord> m_AnswerRecords = new List<DnsRecord>();
private readonly List<DnsRecord> m_AdditionalRecords = new List<DnsRecord>(); private readonly List<DnsRecord> m_AdditionalRecords = new List<DnsRecord>();
private readonly List<DnsRecord> m_AuthorityRecords = new List<DnsRecord>(); private readonly List<DnsRecord> m_AuthorityRecords = new List<DnsRecord>();
@ -16,36 +15,34 @@
/// </summary> /// </summary>
/// <param name="response">The response.</param> /// <param name="response">The response.</param>
internal DnsQueryResult(DnsClient.DnsClientResponse response) internal DnsQueryResult(DnsClient.DnsClientResponse response)
: this() : this() {
{ this.Id = response.Id;
Id = response.Id; this.IsAuthoritativeServer = response.IsAuthorativeServer;
IsAuthoritativeServer = response.IsAuthorativeServer; this.IsRecursionAvailable = response.IsRecursionAvailable;
IsRecursionAvailable = response.IsRecursionAvailable; this.IsTruncated = response.IsTruncated;
IsTruncated = response.IsTruncated; this.OperationCode = response.OperationCode;
OperationCode = response.OperationCode; this.ResponseCode = response.ResponseCode;
ResponseCode = response.ResponseCode;
if (response.AnswerRecords != null) if(response.AnswerRecords != null) {
{ foreach(DnsClient.IDnsResourceRecord record in response.AnswerRecords) {
foreach (var record in response.AnswerRecords) this.AnswerRecords.Add(new DnsRecord(record));
AnswerRecords.Add(new DnsRecord(record));
}
if (response.AuthorityRecords != null)
{
foreach (var record in response.AuthorityRecords)
AuthorityRecords.Add(new DnsRecord(record));
}
if (response.AdditionalRecords != null)
{
foreach (var record in response.AdditionalRecords)
AdditionalRecords.Add(new DnsRecord(record));
} }
} }
private DnsQueryResult() if(response.AuthorityRecords != null) {
{ foreach(DnsClient.IDnsResourceRecord record in response.AuthorityRecords) {
this.AuthorityRecords.Add(new DnsRecord(record));
}
}
if(response.AdditionalRecords != null) {
foreach(DnsClient.IDnsResourceRecord record in response.AdditionalRecords) {
this.AdditionalRecords.Add(new DnsRecord(record));
}
}
}
private DnsQueryResult() {
} }
/// <summary> /// <summary>
@ -54,7 +51,9 @@
/// <value> /// <value>
/// The identifier. /// The identifier.
/// </value> /// </value>
public int Id { get; } public Int32 Id {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is authoritative server. /// Gets a value indicating whether this instance is authoritative server.
@ -62,7 +61,9 @@
/// <value> /// <value>
/// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>. /// <c>true</c> if this instance is authoritative server; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsAuthoritativeServer { get; } public Boolean IsAuthoritativeServer {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is truncated. /// Gets a value indicating whether this instance is truncated.
@ -70,7 +71,9 @@
/// <value> /// <value>
/// <c>true</c> if this instance is truncated; otherwise, <c>false</c>. /// <c>true</c> if this instance is truncated; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsTruncated { get; } public Boolean IsTruncated {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is recursion available. /// Gets a value indicating whether this instance is recursion available.
@ -78,7 +81,9 @@
/// <value> /// <value>
/// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>. /// <c>true</c> if this instance is recursion available; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsRecursionAvailable { get; } public Boolean IsRecursionAvailable {
get;
}
/// <summary> /// <summary>
/// Gets the operation code. /// Gets the operation code.
@ -86,7 +91,9 @@
/// <value> /// <value>
/// The operation code. /// The operation code.
/// </value> /// </value>
public DnsOperationCode OperationCode { get; } public DnsOperationCode OperationCode {
get;
}
/// <summary> /// <summary>
/// Gets the response code. /// Gets the response code.
@ -94,7 +101,9 @@
/// <value> /// <value>
/// The response code. /// The response code.
/// </value> /// </value>
public DnsResponseCode ResponseCode { get; } public DnsResponseCode ResponseCode {
get;
}
/// <summary> /// <summary>
/// Gets the answer records. /// Gets the answer records.
@ -102,7 +111,7 @@
/// <value> /// <value>
/// The answer records. /// The answer records.
/// </value> /// </value>
public IList<DnsRecord> AnswerRecords => m_AnswerRecords; public IList<DnsRecord> AnswerRecords => this.m_AnswerRecords;
/// <summary> /// <summary>
/// Gets the additional records. /// Gets the additional records.
@ -110,7 +119,7 @@
/// <value> /// <value>
/// The additional records. /// The additional records.
/// </value> /// </value>
public IList<DnsRecord> AdditionalRecords => m_AdditionalRecords; public IList<DnsRecord> AdditionalRecords => this.m_AdditionalRecords;
/// <summary> /// <summary>
/// Gets the authority records. /// Gets the authority records.
@ -118,6 +127,6 @@
/// <value> /// <value>
/// The authority records. /// The authority records.
/// </value> /// </value>
public IList<DnsRecord> AuthorityRecords => m_AuthorityRecords; public IList<DnsRecord> AuthorityRecords => this.m_AuthorityRecords;
} }
} }

View File

@ -1,55 +1,51 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Net;
using System; using System.Text;
using System.Net;
using System.Text;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a DNS record entry. /// Represents a DNS record entry.
/// </summary> /// </summary>
public class DnsRecord public class DnsRecord {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DnsRecord"/> class. /// Initializes a new instance of the <see cref="DnsRecord"/> class.
/// </summary> /// </summary>
/// <param name="record">The record.</param> /// <param name="record">The record.</param>
internal DnsRecord(DnsClient.IDnsResourceRecord record) internal DnsRecord(DnsClient.IDnsResourceRecord record)
: this() : this() {
{ this.Name = record.Name.ToString();
Name = record.Name.ToString(); this.Type = record.Type;
Type = record.Type; this.Class = record.Class;
Class = record.Class; this.TimeToLive = record.TimeToLive;
TimeToLive = record.TimeToLive; this.Data = record.Data;
Data = record.Data;
// PTR // PTR
PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString(); this.PointerDomainName = (record as DnsClient.DnsPointerResourceRecord)?.PointerDomainName?.ToString();
// A // A
IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress; this.IPAddress = (record as DnsClient.DnsIPAddressResourceRecord)?.IPAddress;
// NS // NS
NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString(); this.NameServerDomainName = (record as DnsClient.DnsNameServerResourceRecord)?.NSDomainName?.ToString();
// CNAME // CNAME
CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString(); this.CanonicalDomainName = (record as DnsClient.DnsCanonicalNameResourceRecord)?.CanonicalDomainName.ToString();
// MX // MX
MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString(); this.MailExchangerDomainName = (record as DnsClient.DnsMailExchangeResourceRecord)?.ExchangeDomainName.ToString();
MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference; this.MailExchangerPreference = (record as DnsClient.DnsMailExchangeResourceRecord)?.Preference;
// SOA // SOA
SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString(); this.SoaMasterDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MasterDomainName.ToString();
SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString(); this.SoaResponsibleDomainName = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ResponsibleDomainName.ToString();
SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber; this.SoaSerialNumber = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.SerialNumber;
SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval; this.SoaRefreshInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RefreshInterval;
SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval; this.SoaRetryInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.RetryInterval;
SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval; this.SoaExpireInterval = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.ExpireInterval;
SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive; this.SoaMinimumTimeToLive = (record as DnsClient.DnsStartOfAuthorityResourceRecord)?.MinimumTimeToLive;
} }
private DnsRecord() private DnsRecord() {
{
// placeholder // placeholder
} }
@ -59,7 +55,9 @@
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; } public String Name {
get;
}
/// <summary> /// <summary>
/// Gets the type. /// Gets the type.
@ -67,7 +65,9 @@
/// <value> /// <value>
/// The type. /// The type.
/// </value> /// </value>
public DnsRecordType Type { get; } public DnsRecordType Type {
get;
}
/// <summary> /// <summary>
/// Gets the class. /// Gets the class.
@ -75,7 +75,9 @@
/// <value> /// <value>
/// The class. /// The class.
/// </value> /// </value>
public DnsRecordClass Class { get; } public DnsRecordClass Class {
get;
}
/// <summary> /// <summary>
/// Gets the time to live. /// Gets the time to live.
@ -83,7 +85,9 @@
/// <value> /// <value>
/// The time to live. /// The time to live.
/// </value> /// </value>
public TimeSpan TimeToLive { get; } public TimeSpan TimeToLive {
get;
}
/// <summary> /// <summary>
/// Gets the raw data of the record. /// Gets the raw data of the record.
@ -91,7 +95,9 @@
/// <value> /// <value>
/// The data. /// The data.
/// </value> /// </value>
public byte[] Data { get; } public Byte[] Data {
get;
}
/// <summary> /// <summary>
/// Gets the data text bytes in ASCII encoding. /// Gets the data text bytes in ASCII encoding.
@ -99,7 +105,7 @@
/// <value> /// <value>
/// The data text. /// The data text.
/// </value> /// </value>
public string DataText => Data == null ? string.Empty : Encoding.ASCII.GetString(Data); public String DataText => this.Data == null ? String.Empty : Encoding.ASCII.GetString(this.Data);
/// <summary> /// <summary>
/// Gets the name of the pointer domain. /// Gets the name of the pointer domain.
@ -107,7 +113,9 @@
/// <value> /// <value>
/// The name of the pointer domain. /// The name of the pointer domain.
/// </value> /// </value>
public string PointerDomainName { get; } public String PointerDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the ip address. /// Gets the ip address.
@ -115,7 +123,9 @@
/// <value> /// <value>
/// The ip address. /// The ip address.
/// </value> /// </value>
public IPAddress IPAddress { get; } public IPAddress IPAddress {
get;
}
/// <summary> /// <summary>
/// Gets the name of the name server domain. /// Gets the name of the name server domain.
@ -123,7 +133,9 @@
/// <value> /// <value>
/// The name of the name server domain. /// The name of the name server domain.
/// </value> /// </value>
public string NameServerDomainName { get; } public String NameServerDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the name of the canonical domain. /// Gets the name of the canonical domain.
@ -131,7 +143,9 @@
/// <value> /// <value>
/// The name of the canonical domain. /// The name of the canonical domain.
/// </value> /// </value>
public string CanonicalDomainName { get; } public String CanonicalDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the mail exchanger preference. /// Gets the mail exchanger preference.
@ -139,7 +153,9 @@
/// <value> /// <value>
/// The mail exchanger preference. /// The mail exchanger preference.
/// </value> /// </value>
public int? MailExchangerPreference { get; } public Int32? MailExchangerPreference {
get;
}
/// <summary> /// <summary>
/// Gets the name of the mail exchanger domain. /// Gets the name of the mail exchanger domain.
@ -147,7 +163,9 @@
/// <value> /// <value>
/// The name of the mail exchanger domain. /// The name of the mail exchanger domain.
/// </value> /// </value>
public string MailExchangerDomainName { get; } public String MailExchangerDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the name of the soa master domain. /// Gets the name of the soa master domain.
@ -155,7 +173,9 @@
/// <value> /// <value>
/// The name of the soa master domain. /// The name of the soa master domain.
/// </value> /// </value>
public string SoaMasterDomainName { get; } public String SoaMasterDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the name of the soa responsible domain. /// Gets the name of the soa responsible domain.
@ -163,7 +183,9 @@
/// <value> /// <value>
/// The name of the soa responsible domain. /// The name of the soa responsible domain.
/// </value> /// </value>
public string SoaResponsibleDomainName { get; } public String SoaResponsibleDomainName {
get;
}
/// <summary> /// <summary>
/// Gets the soa serial number. /// Gets the soa serial number.
@ -171,7 +193,9 @@
/// <value> /// <value>
/// The soa serial number. /// The soa serial number.
/// </value> /// </value>
public long? SoaSerialNumber { get; } public Int64? SoaSerialNumber {
get;
}
/// <summary> /// <summary>
/// Gets the soa refresh interval. /// Gets the soa refresh interval.
@ -179,7 +203,9 @@
/// <value> /// <value>
/// The soa refresh interval. /// The soa refresh interval.
/// </value> /// </value>
public TimeSpan? SoaRefreshInterval { get; } public TimeSpan? SoaRefreshInterval {
get;
}
/// <summary> /// <summary>
/// Gets the soa retry interval. /// Gets the soa retry interval.
@ -187,7 +213,9 @@
/// <value> /// <value>
/// The soa retry interval. /// The soa retry interval.
/// </value> /// </value>
public TimeSpan? SoaRetryInterval { get; } public TimeSpan? SoaRetryInterval {
get;
}
/// <summary> /// <summary>
/// Gets the soa expire interval. /// Gets the soa expire interval.
@ -195,7 +223,9 @@
/// <value> /// <value>
/// The soa expire interval. /// The soa expire interval.
/// </value> /// </value>
public TimeSpan? SoaExpireInterval { get; } public TimeSpan? SoaExpireInterval {
get;
}
/// <summary> /// <summary>
/// Gets the soa minimum time to live. /// Gets the soa minimum time to live.
@ -203,6 +233,8 @@
/// <value> /// <value>
/// The soa minimum time to live. /// The soa minimum time to live.
/// </value> /// </value>
public TimeSpan? SoaMinimumTimeToLive { get; } public TimeSpan? SoaMinimumTimeToLive {
get;
}
} }
} }

View File

@ -1,13 +1,11 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace Unosquare.Swan.Networking namespace Unosquare.Swan.Networking {
{
#region DNS #region DNS
/// <summary> /// <summary>
/// Enumerates the different DNS record types. /// Enumerates the different DNS record types.
/// </summary> /// </summary>
public enum DnsRecordType public enum DnsRecordType {
{
/// <summary> /// <summary>
/// A records /// A records
/// </summary> /// </summary>
@ -67,8 +65,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates the different DNS record classes. /// Enumerates the different DNS record classes.
/// </summary> /// </summary>
public enum DnsRecordClass public enum DnsRecordClass {
{
/// <summary> /// <summary>
/// IN records /// IN records
/// </summary> /// </summary>
@ -83,8 +80,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates the different DNS operation codes. /// Enumerates the different DNS operation codes.
/// </summary> /// </summary>
public enum DnsOperationCode public enum DnsOperationCode {
{
/// <summary> /// <summary>
/// Query operation /// Query operation
/// </summary> /// </summary>
@ -114,8 +110,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates the different DNS query response codes. /// Enumerates the different DNS query response codes.
/// </summary> /// </summary>
public enum DnsResponseCode public enum DnsResponseCode {
{
/// <summary> /// <summary>
/// No error /// No error
/// </summary> /// </summary>

View File

@ -1,6 +1,5 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace Unosquare.Swan.Networking namespace Unosquare.Swan.Networking {
{
#if NETSTANDARD1_3 #if NETSTANDARD1_3
/// <summary> /// <summary>
@ -138,8 +137,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates all of the well-known SMTP command names. /// Enumerates all of the well-known SMTP command names.
/// </summary> /// </summary>
public enum SmtpCommandNames public enum SmtpCommandNames {
{
/// <summary> /// <summary>
/// An unknown command /// An unknown command
/// </summary> /// </summary>
@ -229,8 +227,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates the reply code severities. /// Enumerates the reply code severities.
/// </summary> /// </summary>
public enum SmtpReplyCodeSeverities public enum SmtpReplyCodeSeverities {
{
/// <summary> /// <summary>
/// The unknown severity /// The unknown severity
/// </summary> /// </summary>
@ -260,8 +257,7 @@ namespace Unosquare.Swan.Networking
/// <summary> /// <summary>
/// Enumerates the reply code categories. /// Enumerates the reply code categories.
/// </summary> /// </summary>
public enum SmtpReplyCodeCategories public enum SmtpReplyCodeCategories {
{
/// <summary> /// <summary>
/// The unknown category /// The unknown category
/// </summary> /// </summary>

View File

@ -1,23 +1,21 @@
namespace Unosquare.Swan.Networking using System;
{ using Unosquare.Swan.Exceptions;
using System; using Unosquare.Swan.Models;
using Exceptions; using Unosquare.Swan.Formatters;
using Models; using System.Collections.Generic;
using Formatters; using System.Net.Http;
using System.Collections.Generic; using System.Security;
using System.Net.Http; using System.Text;
using System.Security; using System.Threading;
using System.Text; using System.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a HttpClient with extended methods to use with JSON payloads /// Represents a HttpClient with extended methods to use with JSON payloads
/// and bearer tokens authentication. /// and bearer tokens authentication.
/// </summary> /// </summary>
public static class JsonClient public static class JsonClient {
{ private const String JsonMimeType = "application/json";
private const string JsonMimeType = "application/json";
/// <summary> /// <summary>
/// Post a object as JSON with optional authorization token. /// Post a object as JSON with optional authorization token.
@ -29,14 +27,13 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested type.</returns> /// <returns>A task with a result of the requested type.</returns>
public static async Task<T> Post<T>( public static async Task<T> Post<T>(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
} }
/// <summary> /// <summary>
@ -52,35 +49,27 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested type or an error object.</returns> /// <returns>A task with a result of the requested type or an error object.</returns>
public static async Task<OkOrError<T, TE>> PostOrError<T, TE>( public static async Task<OkOrError<T, TE>> PostOrError<T, TE>(
string url, String url,
object payload, Object payload,
int httpStatusError = 500, Int32 httpStatusError = 500,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization)) StringContent payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
{
var payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
var response = await httpClient.PostAsync(url, payloadJson, ct).ConfigureAwait(false); HttpResponseMessage response = await httpClient.PostAsync(url, payloadJson, ct).ConfigureAwait(false);
var jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); String jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.OK) return response.StatusCode == System.Net.HttpStatusCode.OK
{ ? OkOrError<T, TE>.FromOk(!String.IsNullOrEmpty(jsonString)
return OkOrError<T, TE>.FromOk(!string.IsNullOrEmpty(jsonString)
? Json.Deserialize<T>(jsonString) ? Json.Deserialize<T>(jsonString)
: default); : default)
} : (Int32)response.StatusCode == httpStatusError
? OkOrError<T, TE>.FromError(!String.IsNullOrEmpty(jsonString)
if ((int) response.StatusCode == httpStatusError)
{
return OkOrError<T, TE>.FromError(!string.IsNullOrEmpty(jsonString)
? Json.Deserialize<TE>(jsonString) ? Json.Deserialize<TE>(jsonString)
: default); : default)
} : new OkOrError<T, TE>();
return new OkOrError<T, TE>();
} }
} }
@ -92,17 +81,16 @@
/// <param name="authorization">The authorization.</param> /// <param name="authorization">The authorization.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result as a collection of key/value pairs.</returns> /// <returns>A task with a result as a collection of key/value pairs.</returns>
public static async Task<IDictionary<string, object>> Post( public static async Task<IDictionary<String, Object>> Post(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ String jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
var jsonString = await PostString(url, payload, authorization, ct).ConfigureAwait(false);
return string.IsNullOrWhiteSpace(jsonString) return String.IsNullOrWhiteSpace(jsonString)
? default ? default
: Json.Deserialize(jsonString) as IDictionary<string, object>; : Json.Deserialize(jsonString) as IDictionary<String, Object>;
} }
/// <summary> /// <summary>
@ -117,10 +105,10 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">url.</exception> /// <exception cref="ArgumentNullException">url.</exception>
/// <exception cref="JsonRequestException">Error POST JSON.</exception> /// <exception cref="JsonRequestException">Error POST JSON.</exception>
public static Task<string> PostString( public static Task<String> PostString(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct); CancellationToken ct = default) => SendAsync(HttpMethod.Post, url, payload, authorization, ct);
/// <summary> /// <summary>
@ -133,14 +121,13 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested type.</returns> /// <returns>A task with a result of the requested type.</returns>
public static async Task<T> Put<T>( public static async Task<T> Put<T>(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ String jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
var jsonString = await PutString(url, payload, authorization, ct).ConfigureAwait(false);
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
} }
/// <summary> /// <summary>
@ -151,15 +138,14 @@
/// <param name="authorization">The authorization.</param> /// <param name="authorization">The authorization.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested collection of key/value pairs.</returns> /// <returns>A task with a result of the requested collection of key/value pairs.</returns>
public static async Task<IDictionary<string, object>> Put( public static async Task<IDictionary<String, Object>> Put(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ Object response = await Put<Object>(url, payload, authorization, ct).ConfigureAwait(false);
var response = await Put<object>(url, payload, authorization, ct).ConfigureAwait(false);
return response as IDictionary<string, object>; return response as IDictionary<String, Object>;
} }
/// <summary> /// <summary>
@ -174,10 +160,10 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">url.</exception> /// <exception cref="ArgumentNullException">url.</exception>
/// <exception cref="JsonRequestException">Error PUT JSON.</exception> /// <exception cref="JsonRequestException">Error PUT JSON.</exception>
public static Task<string> PutString( public static Task<String> PutString(
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct); CancellationToken ct = default) => SendAsync(HttpMethod.Put, url, payload, authorization, ct);
/// <summary> /// <summary>
@ -191,12 +177,11 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">url.</exception> /// <exception cref="ArgumentNullException">url.</exception>
/// <exception cref="JsonRequestException">Error GET JSON.</exception> /// <exception cref="JsonRequestException">Error GET JSON.</exception>
public static async Task<string> GetString( public static async Task<String> GetString(
string url, String url,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
return await response.ReadAsStringAsync().ConfigureAwait(false); return await response.ReadAsStringAsync().ConfigureAwait(false);
} }
@ -211,13 +196,12 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested type.</returns> /// <returns>A task with a result of the requested type.</returns>
public static async Task<T> Get<T>( public static async Task<T> Get<T>(
string url, String url,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ String jsonString = await GetString(url, authorization, ct).ConfigureAwait(false);
var jsonString = await GetString(url, authorization, ct).ConfigureAwait(false);
return !string.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default; return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize<T>(jsonString) : default;
} }
/// <summary> /// <summary>
@ -231,12 +215,11 @@
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">url.</exception> /// <exception cref="ArgumentNullException">url.</exception>
/// <exception cref="JsonRequestException">Error GET Binary.</exception> /// <exception cref="JsonRequestException">Error GET Binary.</exception>
public static async Task<byte[]> GetBinary( public static async Task<Byte[]> GetBinary(
string url, String url,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ HttpContent response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
var response = await GetHttpContent(url, authorization, ct).ConfigureAwait(false);
return await response.ReadAsByteArrayAsync().ConfigureAwait(false); return await response.ReadAsByteArrayAsync().ConfigureAwait(false);
} }
@ -257,33 +240,34 @@
/// username. /// username.
/// </exception> /// </exception>
/// <exception cref="SecurityException">Error Authenticating.</exception> /// <exception cref="SecurityException">Error Authenticating.</exception>
public static async Task<IDictionary<string, object>> Authenticate( public static async Task<IDictionary<String, Object>> Authenticate(
string url, String url,
string username, String username,
string password, String password,
CancellationToken ct = default) CancellationToken ct = default) {
{ if(String.IsNullOrWhiteSpace(url)) {
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); throw new ArgumentNullException(nameof(url));
}
if (string.IsNullOrWhiteSpace(username)) if(String.IsNullOrWhiteSpace(username)) {
throw new ArgumentNullException(nameof(username)); throw new ArgumentNullException(nameof(username));
}
using (var httpClient = new HttpClient()) using(HttpClient httpClient = new HttpClient()) {
{
// ignore empty password for now // ignore empty password for now
var requestContent = new StringContent( StringContent requestContent = new StringContent(
$"grant_type=password&username={username}&password={password}", $"grant_type=password&username={username}&password={password}",
Encoding.UTF8, Encoding.UTF8,
"application/x-www-form-urlencoded"); "application/x-www-form-urlencoded");
var response = await httpClient.PostAsync(url, requestContent, ct).ConfigureAwait(false); HttpResponseMessage response = await httpClient.PostAsync(url, requestContent, ct).ConfigureAwait(false);
if (response.IsSuccessStatusCode == false) if(response.IsSuccessStatusCode == false) {
throw new SecurityException($"Error Authenticating. Status code: {response.StatusCode}."); throw new SecurityException($"Error Authenticating. Status code: {response.StatusCode}.");
}
var jsonPayload = await response.Content.ReadAsStringAsync().ConfigureAwait(false); String jsonPayload = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return Json.Deserialize(jsonPayload) as IDictionary<string, object>; return Json.Deserialize(jsonPayload) as IDictionary<String, Object>;
} }
} }
@ -298,15 +282,14 @@
/// <returns> /// <returns>
/// A task with a result of the requested string. /// A task with a result of the requested string.
/// </returns> /// </returns>
public static Task<string> PostFileString( public static Task<String> PostFileString(
string url, String url,
byte[] buffer, Byte[] buffer,
string fileName, String fileName,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) => PostString(url, new {
{ Filename = fileName, Data = buffer
return PostString(url, new {Filename = fileName, Data = buffer}, authorization, ct); }, authorization, ct);
}
/// <summary> /// <summary>
/// Posts the file. /// Posts the file.
@ -319,14 +302,13 @@
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested string.</returns> /// <returns>A task with a result of the requested string.</returns>
public static Task<T> PostFile<T>( public static Task<T> PostFile<T>(
string url, String url,
byte[] buffer, Byte[] buffer,
string fileName, String fileName,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) => Post<T>(url, new {
{ Filename = fileName, Data = buffer
return Post<T>(url, new {Filename = fileName, Data = buffer}, authorization, ct); }, authorization, ct);
}
/// <summary> /// <summary>
/// Sends the asynchronous request. /// Sends the asynchronous request.
@ -337,27 +319,25 @@
/// <param name="authorization">The authorization.</param> /// <param name="authorization">The authorization.</param>
/// <param name="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
/// <returns>A task with a result of the requested string.</returns> /// <returns>A task with a result of the requested string.</returns>
public static async Task<string> SendAsync(HttpMethod method, public static async Task<String> SendAsync(HttpMethod method,
string url, String url,
object payload, Object payload,
string authorization = null, String authorization = null,
CancellationToken ct = default) CancellationToken ct = default) {
{ if(String.IsNullOrWhiteSpace(url)) {
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); throw new ArgumentNullException(nameof(url));
}
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization)) using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
{ StringContent payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
var payloadJson = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType);
var response = await httpClient HttpResponseMessage response = await httpClient
.SendAsync(new HttpRequestMessage(method, url) {Content = payloadJson}, ct).ConfigureAwait(false); .SendAsync(new HttpRequestMessage(method, url) { Content = payloadJson }, ct).ConfigureAwait(false);
if (response.IsSuccessStatusCode == false) if(response.IsSuccessStatusCode == false) {
{
throw new JsonRequestException( throw new JsonRequestException(
$"Error {method} JSON", $"Error {method} JSON",
(int) response.StatusCode, (Int32)response.StatusCode,
await response.Content.ReadAsStringAsync().ConfigureAwait(false)); await response.Content.ReadAsStringAsync().ConfigureAwait(false));
} }
@ -365,12 +345,10 @@
} }
} }
private static HttpClient GetHttpClientWithAuthorizationHeader(string authorization) private static HttpClient GetHttpClientWithAuthorizationHeader(String authorization) {
{ HttpClient httpClient = new HttpClient();
var httpClient = new HttpClient();
if (string.IsNullOrWhiteSpace(authorization) == false) if(String.IsNullOrWhiteSpace(authorization) == false) {
{
httpClient.DefaultRequestHeaders.Authorization = httpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization); new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authorization);
} }
@ -379,19 +357,19 @@
} }
private static async Task<HttpContent> GetHttpContent( private static async Task<HttpContent> GetHttpContent(
string url, String url,
string authorization, String authorization,
CancellationToken ct) CancellationToken ct) {
{ if(String.IsNullOrWhiteSpace(url)) {
if (string.IsNullOrWhiteSpace(url))
throw new ArgumentNullException(nameof(url)); throw new ArgumentNullException(nameof(url));
}
using (var httpClient = GetHttpClientWithAuthorizationHeader(authorization)) using(HttpClient httpClient = GetHttpClientWithAuthorizationHeader(authorization)) {
{ HttpResponseMessage response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);
if (response.IsSuccessStatusCode == false) if(response.IsSuccessStatusCode == false) {
throw new JsonRequestException("Error GET", (int) response.StatusCode); throw new JsonRequestException("Error GET", (Int32)response.StatusCode);
}
return response.Content; return response.Content;
} }

View File

@ -1,9 +1,8 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System; using System.Text;
using System.IO;
using System.Text;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// The Asn1Set class can hold an unordered collection of components with /// The Asn1Set class can hold an unordered collection of components with
/// identical type. This class inherits from the Asn1Structured class /// identical type. This class inherits from the Asn1Structured class
@ -11,18 +10,16 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Structured" /> /// <seealso cref="Asn1Structured" />
internal class Asn1SetOf internal class Asn1SetOf
: Asn1Structured : Asn1Structured {
{ public const Int32 Tag = 0x11;
public const int Tag = 0x11;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag);
public Asn1SetOf(int size = 10) public Asn1SetOf(Int32 size = 10)
: base(Id, size) : base(Id, size) {
{
} }
public override string ToString() => ToString("SET OF: { "); public override String ToString() => this.ToString("SET OF: { ");
} }
/// <summary> /// <summary>
@ -31,26 +28,21 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal class Asn1Choice internal class Asn1Choice
: Asn1Object : Asn1Object {
{
private Asn1Object _content; private Asn1Object _content;
public Asn1Choice(Asn1Object content = null) public Asn1Choice(Asn1Object content = null) => this._content = content;
{
_content = content; protected internal virtual Asn1Object ChoiceValue {
get => this._content;
set => this._content = value;
} }
protected internal virtual Asn1Object ChoiceValue public override Asn1Identifier GetIdentifier() => this._content.GetIdentifier();
{
get => _content;
set => _content = value;
}
public override Asn1Identifier GetIdentifier() => _content.GetIdentifier(); public override void SetIdentifier(Asn1Identifier id) => this._content.SetIdentifier(id);
public override void SetIdentifier(Asn1Identifier id) => _content.SetIdentifier(id); public override String ToString() => this._content.ToString();
public override string ToString() => _content.ToString();
} }
/// <summary> /// <summary>
@ -84,68 +76,72 @@
/// 1 1 1 1 0 (0-30) single octet tag /// 1 1 1 1 0 (0-30) single octet tag
/// 1 1 1 1 1 (&gt; 30) multiple octet tag, more octets follow /// 1 1 1 1 1 (&gt; 30) multiple octet tag, more octets follow
/// </pre></summary> /// </pre></summary>
internal sealed class Asn1Identifier internal sealed class Asn1Identifier {
{ public Asn1Identifier(Asn1IdentifierTag tagClass, Boolean constructed, Int32 tag) {
public Asn1Identifier(Asn1IdentifierTag tagClass, bool constructed, int tag) this.Asn1Class = tagClass;
{ this.Constructed = constructed;
Asn1Class = tagClass; this.Tag = tag;
Constructed = constructed;
Tag = tag;
} }
public Asn1Identifier(LdapOperation tag) public Asn1Identifier(LdapOperation tag)
: this(Asn1IdentifierTag.Application, true, (int) tag) : this(Asn1IdentifierTag.Application, true, (Int32)tag) {
{
} }
public Asn1Identifier(int contextTag, bool constructed = false) public Asn1Identifier(Int32 contextTag, Boolean constructed = false)
: this(Asn1IdentifierTag.Context, constructed, contextTag) : this(Asn1IdentifierTag.Context, constructed, contextTag) {
{
} }
public Asn1Identifier(Stream stream) public Asn1Identifier(Stream stream) {
{ Int32 r = stream.ReadByte();
var r = stream.ReadByte(); this.EncodedLength++;
EncodedLength++;
if (r < 0) if(r < 0) {
throw new EndOfStreamException("BERDecoder: decode: EOF in Identifier"); throw new EndOfStreamException("BERDecoder: decode: EOF in Identifier");
}
Asn1Class = (Asn1IdentifierTag) (r >> 6); this.Asn1Class = (Asn1IdentifierTag)(r >> 6);
Constructed = (r & 0x20) != 0; this.Constructed = (r & 0x20) != 0;
Tag = r & 0x1F; // if tag < 30 then its a single octet identifier. this.Tag = r & 0x1F; // if tag < 30 then its a single octet identifier.
if (Tag == 0x1F) if(this.Tag == 0x1F) {
{
// if true, its a multiple octet identifier. // if true, its a multiple octet identifier.
Tag = DecodeTagNumber(stream); this.Tag = this.DecodeTagNumber(stream);
} }
} }
public Asn1IdentifierTag Asn1Class { get; } public Asn1IdentifierTag Asn1Class {
get;
}
public bool Constructed { get; } public Boolean Constructed {
get;
}
public int Tag { get; } public Int32 Tag {
get;
}
public int EncodedLength { get; private set; } public Int32 EncodedLength {
get; private set;
}
public bool Universal => Asn1Class == Asn1IdentifierTag.Universal; public Boolean Universal => this.Asn1Class == Asn1IdentifierTag.Universal;
public object Clone() => MemberwiseClone(); public Object Clone() => this.MemberwiseClone();
private int DecodeTagNumber(Stream stream) private Int32 DecodeTagNumber(Stream stream) {
{ Int32 n = 0;
var n = 0; while(true) {
while (true) Int32 r = stream.ReadByte();
{ this.EncodedLength++;
var r = stream.ReadByte(); if(r < 0) {
EncodedLength++;
if (r < 0)
throw new EndOfStreamException("BERDecoder: decode: EOF in tag number"); throw new EndOfStreamException("BERDecoder: decode: EOF in tag number");
}
n = (n << 7) + (r & 0x7F); n = (n << 7) + (r & 0x7F);
if ((r & 0x80) == 0) break; if((r & 0x80) == 0) {
break;
}
} }
return n; return n;
@ -155,26 +151,21 @@
/// <summary> /// <summary>
/// This is the base class for all other Asn1 types. /// This is the base class for all other Asn1 types.
/// </summary> /// </summary>
internal abstract class Asn1Object internal abstract class Asn1Object {
{ private static readonly String[] ClassTypes = { "[UNIVERSAL ", "[APPLICATION ", "[", "[PRIVATE " };
private static readonly string[] ClassTypes = {"[UNIVERSAL ", "[APPLICATION ", "[", "[PRIVATE "};
private Asn1Identifier _id; private Asn1Identifier _id;
protected Asn1Object(Asn1Identifier id = null) protected Asn1Object(Asn1Identifier id = null) => this._id = id;
{
_id = id;
}
public virtual Asn1Identifier GetIdentifier() => _id; public virtual Asn1Identifier GetIdentifier() => this._id;
public virtual void SetIdentifier(Asn1Identifier identifier) => _id = identifier; public virtual void SetIdentifier(Asn1Identifier identifier) => this._id = identifier;
public override string ToString() public override String ToString() {
{ Asn1Identifier identifier = this.GetIdentifier();
var identifier = GetIdentifier();
return $"{ClassTypes[(int) identifier.Asn1Class]}{identifier.Tag}]"; return $"{ClassTypes[(Int32)identifier.Asn1Class]}{identifier.Tag}]";
} }
} }
@ -183,37 +174,27 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal sealed class Asn1OctetString internal sealed class Asn1OctetString
: Asn1Object : Asn1Object {
{ public const Int32 Tag = 0x04;
public const int Tag = 0x04;
private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag);
private readonly sbyte[] _content; private readonly SByte[] _content;
public Asn1OctetString(sbyte[] content) public Asn1OctetString(SByte[] content)
: base(Id) : base(Id) => this._content = content;
{
_content = content;
}
public Asn1OctetString(string content) public Asn1OctetString(String content)
: base(Id) : base(Id) => this._content = Encoding.UTF8.GetSBytes(content);
{
_content = Encoding.UTF8.GetSBytes(content);
}
public Asn1OctetString(Stream stream, int len) public Asn1OctetString(Stream stream, Int32 len)
: base(Id) : base(Id) => this._content = len > 0 ? (SByte[])LberDecoder.DecodeOctetString(stream, len) : new SByte[0];
{
_content = len > 0 ? (sbyte[]) LberDecoder.DecodeOctetString(stream, len) : new sbyte[0];
}
public sbyte[] ByteValue() => _content; public SByte[] ByteValue() => this._content;
public string StringValue() => Encoding.UTF8.GetString(_content); public String StringValue() => Encoding.UTF8.GetString(this._content);
public override string ToString() => base.ToString() + "OCTET STRING: " + StringValue(); public override String ToString() => base.ToString() + "OCTET STRING: " + this.StringValue();
} }
/// <summary> /// <summary>
@ -228,58 +209,51 @@
/// usual after the Asn1Tagged identifier has been encoded. /// usual after the Asn1Tagged identifier has been encoded.
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal class Asn1Tagged : Asn1Object internal class Asn1Tagged : Asn1Object {
{
private Asn1Object _content; private Asn1Object _content;
public Asn1Tagged(Asn1Identifier identifier, Asn1Object obj = null, bool isExplicit = true) public Asn1Tagged(Asn1Identifier identifier, Asn1Object obj = null, Boolean isExplicit = true)
: base(identifier) : base(identifier) {
{ this._content = obj;
_content = obj; this.Explicit = isExplicit;
Explicit = isExplicit;
if (!isExplicit) if(!isExplicit) {
{
// replace object's id with new tag. // replace object's id with new tag.
_content?.SetIdentifier(identifier); this._content?.SetIdentifier(identifier);
} }
} }
public Asn1Tagged(Asn1Identifier identifier, sbyte[] vals) public Asn1Tagged(Asn1Identifier identifier, SByte[] vals)
: base(identifier) : base(identifier) {
{ this._content = new Asn1OctetString(vals);
_content = new Asn1OctetString(vals); this.Explicit = false;
Explicit = false;
} }
public Asn1Tagged(Stream stream, int len, Asn1Identifier identifier) public Asn1Tagged(Stream stream, Int32 len, Asn1Identifier identifier)
: base(identifier) : base(identifier) =>
{
// If we are decoding an implicit tag, there is no way to know at this // If we are decoding an implicit tag, there is no way to know at this
// low level what the base type really is. We can place the content // low level what the base type really is. We can place the content
// into an Asn1OctetString type and pass it back to the application who // into an Asn1OctetString type and pass it back to the application who
// will be able to create the appropriate ASN.1 type for this tag. // will be able to create the appropriate ASN.1 type for this tag.
_content = new Asn1OctetString(stream, len); this._content = new Asn1OctetString(stream, len);
}
public Asn1Object TaggedValue public Asn1Object TaggedValue {
{ get => this._content;
get => _content;
set set {
{ this._content = value;
_content = value; if(!this.Explicit) {
if (!Explicit)
{
// replace object's id with new tag. // replace object's id with new tag.
value?.SetIdentifier(GetIdentifier()); value?.SetIdentifier(this.GetIdentifier());
} }
} }
} }
public bool Explicit { get; } public Boolean Explicit {
get;
}
public override string ToString() => Explicit ? base.ToString() + _content : _content.ToString(); public override String ToString() => this.Explicit ? base.ToString() + this._content : this._content.ToString();
} }
/// <summary> /// <summary>
@ -287,84 +261,70 @@
/// structured types. /// structured types.
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal abstract class Asn1Structured : Asn1Object internal abstract class Asn1Structured : Asn1Object {
{
private Asn1Object[] _content; private Asn1Object[] _content;
private int _contentIndex; private Int32 _contentIndex;
protected internal Asn1Structured(Asn1Identifier id, int size = 10) protected internal Asn1Structured(Asn1Identifier id, Int32 size = 10)
: base(id) : base(id) => this._content = new Asn1Object[size];
{
_content = new Asn1Object[size];
}
public Asn1Object[] ToArray() public Asn1Object[] ToArray() {
{ Asn1Object[] cloneArray = new Asn1Object[this._contentIndex];
var cloneArray = new Asn1Object[_contentIndex]; Array.Copy(this._content, 0, cloneArray, 0, this._contentIndex);
Array.Copy(_content, 0, cloneArray, 0, _contentIndex);
return cloneArray; return cloneArray;
} }
public void Add(string s) => Add(new Asn1OctetString(s)); public void Add(String s) => this.Add(new Asn1OctetString(s));
public void Add(Asn1Object obj) public void Add(Asn1Object obj) {
{ if(this._contentIndex == this._content.Length) {
if (_contentIndex == _content.Length)
{
// Array too small, need to expand it, double length // Array too small, need to expand it, double length
var newArray = new Asn1Object[_contentIndex + _contentIndex]; Asn1Object[] newArray = new Asn1Object[this._contentIndex + this._contentIndex];
Array.Copy(_content, 0, newArray, 0, _contentIndex); Array.Copy(this._content, 0, newArray, 0, this._contentIndex);
_content = newArray; this._content = newArray;
} }
_content[_contentIndex++] = obj; this._content[this._contentIndex++] = obj;
} }
public void Set(int index, Asn1Object value) public void Set(Int32 index, Asn1Object value) {
{ if(index >= this._contentIndex || index < 0) {
if (index >= _contentIndex || index < 0) throw new IndexOutOfRangeException($"Asn1Structured: get: index {index}, size {this._contentIndex}");
{
throw new IndexOutOfRangeException($"Asn1Structured: get: index {index}, size {_contentIndex}");
} }
_content[index] = value; this._content[index] = value;
} }
public Asn1Object Get(int index) public Asn1Object Get(Int32 index) {
{ if(index >= this._contentIndex || index < 0) {
if (index >= _contentIndex || index < 0) throw new IndexOutOfRangeException($"Asn1Structured: set: index {index}, size {this._contentIndex}");
{
throw new IndexOutOfRangeException($"Asn1Structured: set: index {index}, size {_contentIndex}");
} }
return _content[index]; return this._content[index];
} }
public int Size() => _contentIndex; public Int32 Size() => this._contentIndex;
public string ToString(string type) public String ToString(String type) {
{ StringBuilder sb = new StringBuilder().Append(type);
var sb = new StringBuilder().Append(type);
for (var i = 0; i < _contentIndex; i++) for(Int32 i = 0; i < this._contentIndex; i++) {
{ _ = sb.Append(this._content[i]);
sb.Append(_content[i]); if(i != this._contentIndex - 1) {
if (i != _contentIndex - 1) _ = sb.Append(", ");
sb.Append(", "); }
} }
sb.Append(" }"); _ = sb.Append(" }");
return base.ToString() + sb; return base.ToString() + sb;
} }
protected internal void DecodeStructured(Stream stream, int len) protected internal void DecodeStructured(Stream stream, Int32 len) {
{ Int32[] componentLen = new Int32[1]; // collects length of component
var componentLen = new int[1]; // collects length of component
while (len > 0) while(len > 0) {
{ this.Add(LberDecoder.Decode(stream, componentLen));
Add(LberDecoder.Decode(stream, componentLen));
len -= componentLen[0]; len -= componentLen[0];
} }
} }
@ -375,29 +335,22 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal class Asn1Boolean internal class Asn1Boolean
: Asn1Object : Asn1Object {
{ public const Int32 Tag = 0x01;
public const int Tag = 0x01;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag);
private readonly bool _content; private readonly Boolean _content;
public Asn1Boolean(bool content) public Asn1Boolean(Boolean content)
: base(Id) : base(Id) => this._content = content;
{
_content = content;
}
public Asn1Boolean(Stream stream, int len) public Asn1Boolean(Stream stream, Int32 len)
: base(Id) : base(Id) => this._content = LberDecoder.DecodeBoolean(stream, len);
{
_content = LberDecoder.DecodeBoolean(stream, len);
}
public bool BooleanValue() => _content; public Boolean BooleanValue() => this._content;
public override string ToString() => $"{base.ToString()}BOOLEAN: {_content}"; public override String ToString() => $"{base.ToString()}BOOLEAN: {this._content}";
} }
/// <summary> /// <summary>
@ -405,18 +358,16 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal sealed class Asn1Null internal sealed class Asn1Null
: Asn1Object : Asn1Object {
{ public const Int32 Tag = 0x05;
public const int Tag = 0x05;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag);
public Asn1Null() public Asn1Null()
: base(Id) : base(Id) {
{
} }
public override string ToString() => $"{base.ToString()}NULL: \"\""; public override String ToString() => $"{base.ToString()}NULL: \"\"";
} }
/// <summary> /// <summary>
@ -425,62 +376,53 @@
/// Asn1Integer and Asn1Enumerated. /// Asn1Integer and Asn1Enumerated.
/// </summary> /// </summary>
/// <seealso cref="Asn1Object" /> /// <seealso cref="Asn1Object" />
internal abstract class Asn1Numeric : Asn1Object internal abstract class Asn1Numeric : Asn1Object {
{ private readonly Int64 _content;
private readonly long _content;
internal Asn1Numeric(Asn1Identifier id, int numericValue) internal Asn1Numeric(Asn1Identifier id, Int32 numericValue)
: base(id) : base(id) => this._content = numericValue;
{
_content = numericValue;
}
internal Asn1Numeric(Asn1Identifier id, long numericValue) internal Asn1Numeric(Asn1Identifier id, Int64 numericValue)
: base(id) : base(id) => this._content = numericValue;
{
_content = numericValue;
}
public int IntValue() => (int) _content; public Int32 IntValue() => (Int32)this._content;
public long LongValue() => _content; public Int64 LongValue() => this._content;
} }
/// <summary> /// <summary>
/// This class provides a means to manipulate ASN.1 Length's. It will /// This class provides a means to manipulate ASN.1 Length's. It will
/// be used by Asn1Encoder's and Asn1Decoder's by composition. /// be used by Asn1Encoder's and Asn1Decoder's by composition.
/// </summary> /// </summary>
internal sealed class Asn1Length internal sealed class Asn1Length {
{ public Asn1Length(Stream stream) {
public Asn1Length(Stream stream) Int32 r = stream.ReadByte();
{ this.EncodedLength++;
var r = stream.ReadByte(); if(r == 0x80) {
EncodedLength++; this.Length = -1;
if (r == 0x80) } else if(r < 0x80) {
{ this.Length = r;
Length = -1; } else {
} this.Length = 0;
else if (r < 0x80) for(r &= 0x7F; r > 0; r--) {
{ Int32 part = stream.ReadByte();
Length = r; this.EncodedLength++;
} if(part < 0) {
else
{
Length = 0;
for (r = r & 0x7F; r > 0; r--)
{
var part = stream.ReadByte();
EncodedLength++;
if (part < 0)
throw new EndOfStreamException("BERDecoder: decode: EOF in Asn1Length"); throw new EndOfStreamException("BERDecoder: decode: EOF in Asn1Length");
Length = (Length << 8) + part; }
this.Length = (this.Length << 8) + part;
} }
} }
} }
public int Length { get; } public Int32 Length {
get;
}
public int EncodedLength { get; } public Int32 EncodedLength {
get;
}
} }
/// <summary> /// <summary>
@ -491,24 +433,19 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Structured" /> /// <seealso cref="Asn1Structured" />
internal class Asn1Sequence internal class Asn1Sequence
: Asn1Structured : Asn1Structured {
{ public const Int32 Tag = 0x10;
public const int Tag = 0x10;
private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); private static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag);
public Asn1Sequence(int size) public Asn1Sequence(Int32 size)
: base(Id, size) : base(Id, size) {
{
} }
public Asn1Sequence(Stream stream, int len) public Asn1Sequence(Stream stream, Int32 len)
: base(Id) : base(Id) => this.DecodeStructured(stream, len);
{
DecodeStructured(stream, len);
}
public override string ToString() => ToString("SEQUENCE: { "); public override String ToString() => this.ToString("SEQUENCE: { ");
} }
/// <summary> /// <summary>
@ -518,19 +455,15 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Structured" /> /// <seealso cref="Asn1Structured" />
internal sealed class Asn1Set internal sealed class Asn1Set
: Asn1Structured : Asn1Structured {
{ public const Int32 Tag = 0x11;
public const int Tag = 0x11;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag);
public Asn1Set(Stream stream, int len) public Asn1Set(Stream stream, Int32 len)
: base(Id) : base(Id) => this.DecodeStructured(stream, len);
{
DecodeStructured(stream, len);
}
public override string ToString() => ToString("SET: { "); public override String ToString() => this.ToString("SET: { ");
} }
/// <summary> /// <summary>
@ -538,43 +471,37 @@
/// </summary> /// </summary>
/// <seealso cref="Asn1Numeric" /> /// <seealso cref="Asn1Numeric" />
internal class Asn1Integer internal class Asn1Integer
: Asn1Numeric : Asn1Numeric {
{ public const Int32 Tag = 0x02;
public const int Tag = 0x02;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag);
public Asn1Integer(int content) public Asn1Integer(Int32 content)
: base(Id, content) : base(Id, content) {
{
} }
public Asn1Integer(Stream stream, int len) public Asn1Integer(Stream stream, Int32 len)
: base(Id, LberDecoder.DecodeNumeric(stream, len)) : base(Id, LberDecoder.DecodeNumeric(stream, len)) {
{
} }
public override string ToString() => base.ToString() + "INTEGER: " + LongValue(); public override String ToString() => base.ToString() + "INTEGER: " + this.LongValue();
} }
/// <summary> /// <summary>
/// This class encapsulates the ASN.1 ENUMERATED type. /// This class encapsulates the ASN.1 ENUMERATED type.
/// </summary> /// </summary>
/// <seealso cref="Asn1Numeric" /> /// <seealso cref="Asn1Numeric" />
internal sealed class Asn1Enumerated : Asn1Numeric internal sealed class Asn1Enumerated : Asn1Numeric {
{ public const Int32 Tag = 0x0a;
public const int Tag = 0x0a;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, false, Tag);
public Asn1Enumerated(LdapScope content) public Asn1Enumerated(LdapScope content)
: base(Id, (int) content) : base(Id, (Int32)content) {
{
} }
public Asn1Enumerated(int content) public Asn1Enumerated(Int32 content)
: base(Id, content) : base(Id, content) {
{
} }
/// <summary> /// <summary>
@ -584,12 +511,11 @@
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="len">The length.</param> /// <param name="len">The length.</param>
public Asn1Enumerated(Stream stream, int len) public Asn1Enumerated(Stream stream, Int32 len)
: base(Id, LberDecoder.DecodeNumeric(stream, len)) : base(Id, LberDecoder.DecodeNumeric(stream, len)) {
{
} }
public override string ToString() => base.ToString() + "ENUMERATED: " + LongValue(); public override String ToString() => base.ToString() + "ENUMERATED: " + this.LongValue();
} }
/// <summary> /// <summary>
@ -599,23 +525,18 @@
/// functionality to hold multiple Asn1 components. /// functionality to hold multiple Asn1 components.
/// </summary> /// </summary>
/// <seealso cref="Asn1Structured" /> /// <seealso cref="Asn1Structured" />
internal class Asn1SequenceOf : Asn1Structured internal class Asn1SequenceOf : Asn1Structured {
{ public const Int32 Tag = 0x10;
public const int Tag = 0x10;
public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag); public static readonly Asn1Identifier Id = new Asn1Identifier(Asn1IdentifierTag.Universal, true, Tag);
public Asn1SequenceOf(int size) public Asn1SequenceOf(Int32 size)
: base(Id, size) : base(Id, size) {
{
} }
public Asn1SequenceOf(Stream stream, int len) public Asn1SequenceOf(Stream stream, Int32 len)
: base(Id) : base(Id) => this.DecodeStructured(stream, len);
{
DecodeStructured(stream, len);
}
public override string ToString() => ToString("SEQUENCE OF: { "); public override String ToString() => this.ToString("SEQUENCE OF: { ");
} }
} }

View File

@ -1,7 +1,7 @@
namespace Unosquare.Swan.Networking.Ldap using System.IO;
{ using System;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// This class provides LBER decoding routines for ASN.1 Types. LBER is a /// This class provides LBER decoding routines for ASN.1 Types. LBER is a
/// subset of BER as described in the following taken from 5.1 of RFC 2251: /// subset of BER as described in the following taken from 5.1 of RFC 2251:
@ -28,8 +28,7 @@
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic, /// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994. /// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary> /// </summary>
internal static class LberDecoder internal static class LberDecoder {
{
/// <summary> /// <summary>
/// Decode an LBER encoded value into an Asn1Object from an InputStream. /// Decode an LBER encoded value into an Asn1Object from an InputStream.
/// This method also returns the total length of this encoded /// This method also returns the total length of this encoded
@ -43,19 +42,18 @@
/// Decoded Asn1Obect. /// Decoded Asn1Obect.
/// </returns> /// </returns>
/// <exception cref="EndOfStreamException">Unknown tag.</exception> /// <exception cref="EndOfStreamException">Unknown tag.</exception>
public static Asn1Object Decode(Stream stream, int[] len) public static Asn1Object Decode(Stream stream, Int32[] len) {
{ Asn1Identifier asn1Id = new Asn1Identifier(stream);
var asn1Id = new Asn1Identifier(stream); Asn1Length asn1Len = new Asn1Length(stream);
var asn1Len = new Asn1Length(stream);
var length = asn1Len.Length; Int32 length = asn1Len.Length;
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length; len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
if (asn1Id.Universal == false) if(asn1Id.Universal == false) {
return new Asn1Tagged(stream, length, (Asn1Identifier) asn1Id.Clone()); return new Asn1Tagged(stream, length, (Asn1Identifier)asn1Id.Clone());
}
switch (asn1Id.Tag) switch(asn1Id.Tag) {
{
case Asn1Sequence.Tag: case Asn1Sequence.Tag:
return new Asn1Sequence(stream, length); return new Asn1Sequence(stream, length);
@ -91,12 +89,12 @@
/// Decoded boolean object. /// Decoded boolean object.
/// </returns> /// </returns>
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception> /// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
public static bool DecodeBoolean(Stream stream, int len) public static Boolean DecodeBoolean(Stream stream, Int32 len) {
{ SByte[] lber = new SByte[len];
var lber = new sbyte[len];
if (stream.ReadInput(ref lber, 0, lber.Length) != len) if(stream.ReadInput(ref lber, 0, lber.Length) != len) {
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF"); throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
}
return lber[0] != 0x00; return lber[0] != 0x00;
} }
@ -115,29 +113,32 @@
/// or /// or
/// LBER: NUMERIC: decode error: EOF. /// LBER: NUMERIC: decode error: EOF.
/// </exception> /// </exception>
public static long DecodeNumeric(Stream stream, int len) public static Int64 DecodeNumeric(Stream stream, Int32 len) {
{ Int64 l = 0;
long l = 0; Int32 r = stream.ReadByte();
var r = stream.ReadByte();
if (r < 0) if(r < 0) {
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF"); throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
}
if ((r & 0x80) != 0) if((r & 0x80) != 0) {
{
// check for negative number // check for negative number
l = -1; l = -1;
} }
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
l = (l << 8) | r; l = (l << 8) | r;
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
for (var i = 1; i < len; i++) for(Int32 i = 1; i < len; i++) {
{
r = stream.ReadByte(); r = stream.ReadByte();
if (r < 0) if(r < 0) {
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF"); throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
}
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
l = (l << 8) | r; l = (l << 8) | r;
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
} }
return l; return l;
@ -149,13 +150,11 @@
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="len">Length in bytes.</param> /// <param name="len">Length in bytes.</param>
/// <returns>Decoded octet. </returns> /// <returns>Decoded octet. </returns>
public static object DecodeOctetString(Stream stream, int len) public static Object DecodeOctetString(Stream stream, Int32 len) {
{ SByte[] octets = new SByte[len];
var octets = new sbyte[len]; Int32 totalLen = 0;
var totalLen = 0;
while (totalLen < len) while(totalLen < len) {
{
// Make sure we have read all the data // Make sure we have read all the data
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen); totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
} }

View File

@ -1,7 +1,7 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// This class provides LBER encoding routines for ASN.1 Types. LBER is a /// This class provides LBER encoding routines for ASN.1 Types. LBER is a
/// subset of BER as described in the following taken from 5.1 of RFC 2251: /// subset of BER as described in the following taken from 5.1 of RFC 2251:
@ -28,18 +28,16 @@
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic, /// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
/// Canonical, and Distinguished Encoding Rules", 1994. /// Canonical, and Distinguished Encoding Rules", 1994.
/// </summary> /// </summary>
internal static class LberEncoder internal static class LberEncoder {
{
/// <summary> /// <summary>
/// BER Encode an Asn1Boolean directly into the specified output stream. /// BER Encode an Asn1Boolean directly into the specified output stream.
/// </summary> /// </summary>
/// <param name="b">The Asn1Boolean object to encode.</param> /// <param name="b">The Asn1Boolean object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1Boolean b, Stream stream) public static void Encode(Asn1Boolean b, Stream stream) {
{
Encode(b.GetIdentifier(), stream); Encode(b.GetIdentifier(), stream);
stream.WriteByte(0x01); stream.WriteByte(0x01);
stream.WriteByte((byte) (b.BooleanValue() ? 0xff : 0x00)); stream.WriteByte((Byte)(b.BooleanValue() ? 0xff : 0x00));
} }
/// <summary> /// <summary>
@ -50,26 +48,23 @@
/// </summary> /// </summary>
/// <param name="n">The Asn1Numeric object to encode.</param> /// <param name="n">The Asn1Numeric object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1Numeric n, Stream stream) public static void Encode(Asn1Numeric n, Stream stream) {
{ SByte[] octets = new SByte[8];
var octets = new sbyte[8]; SByte len;
sbyte len; Int64 longValue = n.LongValue();
var longValue = n.LongValue(); Int64 endValue = longValue < 0 ? -1 : 0;
long endValue = longValue < 0 ? -1 : 0; Int64 endSign = endValue & 0x80;
var endSign = endValue & 0x80;
for (len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++) for(len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++) {
{ octets[len] = (SByte)(longValue & 0xFF);
octets[len] = (sbyte)(longValue & 0xFF);
longValue >>= 8; longValue >>= 8;
} }
Encode(n.GetIdentifier(), stream); Encode(n.GetIdentifier(), stream);
stream.WriteByte((byte)len); stream.WriteByte((Byte)len);
for (var i = len - 1; i >= 0; i--) for(Int32 i = len - 1; i >= 0; i--) {
{ stream.WriteByte((Byte)octets[i]);
stream.WriteByte((byte) octets[i]);
} }
} }
@ -78,18 +73,15 @@
/// </summary> /// </summary>
/// <param name="os">The Asn1OctetString object to encode.</param> /// <param name="os">The Asn1OctetString object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1OctetString os, Stream stream) public static void Encode(Asn1OctetString os, Stream stream) {
{
Encode(os.GetIdentifier(), stream); Encode(os.GetIdentifier(), stream);
EncodeLength(os.ByteValue().Length, stream); EncodeLength(os.ByteValue().Length, stream);
var tempSbyteArray = os.ByteValue(); SByte[] tempSbyteArray = os.ByteValue();
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length); stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
} }
public static void Encode(Asn1Object obj, Stream stream) public static void Encode(Asn1Object obj, Stream stream) {
{ switch(obj) {
switch (obj)
{
case Asn1Boolean b: case Asn1Boolean b:
Encode(b, stream); Encode(b, stream);
break; break;
@ -123,22 +115,19 @@
/// </summary> /// </summary>
/// <param name="c">The Asn1Structured object to encode.</param> /// <param name="c">The Asn1Structured object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1Structured c, Stream stream) public static void Encode(Asn1Structured c, Stream stream) {
{
Encode(c.GetIdentifier(), stream); Encode(c.GetIdentifier(), stream);
var arrayValue = c.ToArray(); Asn1Object[] arrayValue = c.ToArray();
using (var output = new MemoryStream()) using(MemoryStream output = new MemoryStream()) {
{ foreach(Asn1Object obj in arrayValue) {
foreach (var obj in arrayValue)
{
Encode(obj, output); Encode(obj, output);
} }
EncodeLength((int) output.Length, stream); EncodeLength((Int32)output.Length, stream);
var tempSbyteArray = output.ToArray(); Byte[] tempSbyteArray = output.ToArray();
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length); stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
} }
} }
@ -148,10 +137,8 @@
/// </summary> /// </summary>
/// <param name="t">The Asn1Tagged object to encode.</param> /// <param name="t">The Asn1Tagged object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1Tagged t, Stream stream) public static void Encode(Asn1Tagged t, Stream stream) {
{ if(!t.Explicit) {
if (!t.Explicit)
{
Encode(t.TaggedValue, stream); Encode(t.TaggedValue, stream);
return; return;
} }
@ -159,12 +146,11 @@
Encode(t.GetIdentifier(), stream); Encode(t.GetIdentifier(), stream);
// determine the encoded length of the base type. // determine the encoded length of the base type.
using (var encodedContent = new MemoryStream()) using(MemoryStream encodedContent = new MemoryStream()) {
{
Encode(t.TaggedValue, encodedContent); Encode(t.TaggedValue, encodedContent);
EncodeLength((int) encodedContent.Length, stream); EncodeLength((Int32)encodedContent.Length, stream);
var tempSbyteArray = encodedContent.ToArray().ToSByteArray(); SByte[] tempSbyteArray = encodedContent.ToArray().ToSByteArray();
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length); stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
} }
} }
@ -174,19 +160,17 @@
/// </summary> /// </summary>
/// <param name="id">The Asn1Identifier object to encode.</param> /// <param name="id">The Asn1Identifier object to encode.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public static void Encode(Asn1Identifier id, Stream stream) public static void Encode(Asn1Identifier id, Stream stream) {
{ Int32 c = (Int32)id.Asn1Class;
var c = (int) id.Asn1Class; Int32 t = id.Tag;
var t = id.Tag; SByte ccf = (SByte)((c << 6) | (id.Constructed ? 0x20 : 0));
var ccf = (sbyte)((c << 6) | (id.Constructed ? 0x20 : 0));
if (t < 30) if(t < 30) {
{ #pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
stream.WriteByte((byte)(ccf | t)); stream.WriteByte((Byte)(ccf | t));
} #pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
else } else {
{ stream.WriteByte((Byte)(ccf | 0x1F));
stream.WriteByte((byte)(ccf | 0x1F));
EncodeTagInteger(t, stream); EncodeTagInteger(t, stream);
} }
} }
@ -196,26 +180,22 @@
/// </summary> /// </summary>
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private static void EncodeLength(int length, Stream stream) private static void EncodeLength(Int32 length, Stream stream) {
{ if(length < 0x80) {
if (length < 0x80) stream.WriteByte((Byte)length);
{ } else {
stream.WriteByte((byte)length); SByte[] octets = new SByte[4]; // 4 bytes sufficient for 32 bit int.
} SByte n;
else for(n = 0; length != 0; n++) {
{ octets[n] = (SByte)(length & 0xFF);
var octets = new sbyte[4]; // 4 bytes sufficient for 32 bit int.
sbyte n;
for (n = 0; length != 0; n++)
{
octets[n] = (sbyte)(length & 0xFF);
length >>= 8; length >>= 8;
} }
stream.WriteByte((byte)(0x80 | n)); stream.WriteByte((Byte)(0x80 | n));
for (var i = n - 1; i >= 0; i--) for(Int32 i = n - 1; i >= 0; i--) {
stream.WriteByte((byte)octets[i]); stream.WriteByte((Byte)octets[i]);
}
} }
} }
@ -224,23 +204,20 @@
/// </summary> /// </summary>
/// <param name="val">The value.</param> /// <param name="val">The value.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private static void EncodeTagInteger(int val, Stream stream) private static void EncodeTagInteger(Int32 val, Stream stream) {
{ SByte[] octets = new SByte[5];
var octets = new sbyte[5]; Int32 n;
int n;
for (n = 0; val != 0; n++) for(n = 0; val != 0; n++) {
{ octets[n] = (SByte)(val & 0x7F);
octets[n] = (sbyte)(val & 0x7F); val >>= 7;
val = val >> 7;
} }
for (var i = n - 1; i > 0; i--) for(Int32 i = n - 1; i > 0; i--) {
{ stream.WriteByte((Byte)(octets[i] | 0x80));
stream.WriteByte((byte)(octets[i] | 0x80));
} }
stream.WriteByte((byte)octets[0]); stream.WriteByte((Byte)octets[0]);
} }
} }
} }

View File

@ -1,15 +1,14 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.IO;
using System.Linq; using System.Net.Sockets;
using System.IO; using System.Text;
using System.Net.Sockets; using System.Threading;
using System.Text; using System.Threading.Tasks;
using System.Threading; using Unosquare.Swan.Exceptions;
using System.Threading.Tasks;
using Exceptions;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// The central class that encapsulates the connection /// The central class that encapsulates the connection
/// to a directory server through the Ldap protocol. /// to a directory server through the Ldap protocol.
@ -93,14 +92,13 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public class LdapConnection : IDisposable public class LdapConnection : IDisposable {
{ private const Int32 LdapV3 = 3;
private const int LdapV3 = 3;
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly CancellationTokenSource _cts = new CancellationTokenSource();
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "<Ausstehend>")]
private Connection _conn; private Connection _conn;
private bool _isDisposing; private Boolean _isDisposing;
/// <summary> /// <summary>
/// Returns the protocol version uses to authenticate. /// Returns the protocol version uses to authenticate.
@ -109,7 +107,7 @@
/// <value> /// <value>
/// The protocol version. /// The protocol version.
/// </value> /// </value>
public int ProtocolVersion => BindProperties?.ProtocolVersion ?? LdapV3; public Int32 ProtocolVersion => this.BindProperties?.ProtocolVersion ?? LdapV3;
/// <summary> /// <summary>
/// Returns the distinguished name (DN) used for as the bind name during /// Returns the distinguished name (DN) used for as the bind name during
@ -120,7 +118,7 @@
/// <value> /// <value>
/// The authentication dn. /// The authentication dn.
/// </value> /// </value>
public string AuthenticationDn => BindProperties == null ? null : (BindProperties.Anonymous ? null : BindProperties.AuthenticationDN); public String AuthenticationDn => this.BindProperties == null ? null : (this.BindProperties.Anonymous ? null : this.BindProperties.AuthenticationDN);
/// <summary> /// <summary>
/// Returns the method used to authenticate the connection. The return /// Returns the method used to authenticate the connection. The return
@ -133,7 +131,7 @@
/// <value> /// <value>
/// The authentication method. /// The authentication method.
/// </value> /// </value>
public string AuthenticationMethod => BindProperties == null ? "simple" : BindProperties.AuthenticationMethod; public String AuthenticationMethod => this.BindProperties == null ? "simple" : this.BindProperties.AuthenticationMethod;
/// <summary> /// <summary>
/// Indicates whether the connection represented by this object is open /// Indicates whether the connection represented by this object is open
@ -142,20 +140,23 @@
/// <returns> /// <returns>
/// True if connection is open; false if the connection is closed. /// True if connection is open; false if the connection is closed.
/// </returns> /// </returns>
public bool Connected => _conn?.IsConnected == true; public Boolean Connected => this._conn?.IsConnected == true;
internal BindProperties BindProperties { get; set; } internal BindProperties BindProperties {
get; set;
}
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>(); internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._isDisposing) {
if (_isDisposing) return; return;
}
_isDisposing = true; this._isDisposing = true;
Disconnect(); this.Disconnect();
_cts?.Dispose(); this._cts?.Dispose();
} }
/// <summary> /// <summary>
@ -182,7 +183,7 @@
/// <returns> /// <returns>
/// A <see cref="Task" /> representing the asynchronous operation. /// A <see cref="Task" /> representing the asynchronous operation.
/// </returns> /// </returns>
public Task Bind(string dn, string password) => Bind(LdapV3, dn, password); public Task Bind(String dn, String password) => this.Bind(LdapV3, dn, password);
/// <summary> /// <summary>
/// Synchronously authenticates to the Ldap server (that the object is /// Synchronously authenticates to the Ldap server (that the object is
@ -208,22 +209,20 @@
/// in objects that are serialized. The LdapConnection /// in objects that are serialized. The LdapConnection
/// keeps no long lived instances of these objects.</param> /// keeps no long lived instances of these objects.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public Task Bind(int version, string dn, string password) public Task Bind(Int32 version, String dn, String password) {
{ dn = String.IsNullOrEmpty(dn) ? String.Empty : dn.Trim();
dn = string.IsNullOrEmpty(dn) ? string.Empty : dn.Trim(); SByte[] passwordData = String.IsNullOrWhiteSpace(password) ? new SByte[] { } : Encoding.UTF8.GetSBytes(password);
var passwordData = string.IsNullOrWhiteSpace(password) ? new sbyte[] { } : Encoding.UTF8.GetSBytes(password);
var anonymous = false; Boolean anonymous = false;
if (passwordData.Length == 0) if(passwordData.Length == 0) {
{
anonymous = true; // anonymous, password length zero with simple bind anonymous = true; // anonymous, password length zero with simple bind
dn = string.Empty; // set to null if anonymous dn = String.Empty; // set to null if anonymous
} }
BindProperties = new BindProperties(version, dn, "simple", anonymous); this.BindProperties = new BindProperties(version, dn, "simple", anonymous);
return RequestLdapMessage(new LdapBindRequest(version, dn, passwordData)); return this.RequestLdapMessage(new LdapBindRequest(version, dn, passwordData));
} }
/// <summary> /// <summary>
@ -238,14 +237,13 @@
/// <param name="port">The TCP or UDP port number to connect to or contact. /// <param name="port">The TCP or UDP port number to connect to or contact.
/// The default Ldap port is 389.</param> /// The default Ldap port is 389.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task Connect(string host, int port) public async Task Connect(String host, Int32 port) {
{ TcpClient tcpClient = new TcpClient();
var tcpClient = new TcpClient();
await tcpClient.ConnectAsync(host, port).ConfigureAwait(false); await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
_conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0); this._conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0);
#pragma warning disable 4014 #pragma warning disable 4014
Task.Run(() => RetrieveMessages(), _cts.Token); _ = Task.Run(() => this.RetrieveMessages(), this._cts.Token);
#pragma warning restore 4014 #pragma warning restore 4014
} }
@ -256,11 +254,10 @@
/// The disconnect method abandons any outstanding requests, issues an /// The disconnect method abandons any outstanding requests, issues an
/// unbind request to the server, and then closes the socket. /// unbind request to the server, and then closes the socket.
/// </summary> /// </summary>
public void Disconnect() public void Disconnect() {
{
// disconnect from API call // disconnect from API call
_cts.Cancel(); this._cts.Cancel();
_conn.Disconnect(); this._conn.Disconnect();
} }
/// <summary> /// <summary>
@ -275,16 +272,13 @@
/// the LdapEntry read from the server. /// the LdapEntry read from the server.
/// </returns> /// </returns>
/// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception> /// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception>
public async Task<LdapEntry> Read(string dn, string[] attrs = null, CancellationToken ct = default) public async Task<LdapEntry> Read(String dn, String[] attrs = null, CancellationToken ct = default) {
{ LdapSearchResults sr = await this.Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
var sr = await Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
LdapEntry ret = null; LdapEntry ret = null;
if (sr.HasMore()) if(sr.HasMore()) {
{
ret = sr.Next(); ret = sr.Next();
if (sr.HasMore()) if(sr.HasMore()) {
{
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse); throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
} }
} }
@ -310,19 +304,18 @@
/// A <see cref="Task" /> representing the asynchronous operation. /// A <see cref="Task" /> representing the asynchronous operation.
/// </returns> /// </returns>
public async Task<LdapSearchResults> Search( public async Task<LdapSearchResults> Search(
string @base, String @base,
LdapScope scope, LdapScope scope,
string filter = "objectClass=*", String filter = "objectClass=*",
string[] attrs = null, String[] attrs = null,
bool typesOnly = false, Boolean typesOnly = false,
CancellationToken ct = default) CancellationToken ct = default) {
{
// TODO: Add Search options // TODO: Add Search options
var msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null); LdapSearchRequest msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
await RequestLdapMessage(msg, ct).ConfigureAwait(false); await this.RequestLdapMessage(msg, ct).ConfigureAwait(false);
return new LdapSearchResults(Messages, msg.MessageId); return new LdapSearchResults(this.Messages, msg.MessageId);
} }
/// <summary> /// <summary>
@ -335,63 +328,50 @@
/// A <see cref="Task" /> representing the asynchronous operation. /// A <see cref="Task" /> representing the asynchronous operation.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">distinguishedName.</exception> /// <exception cref="ArgumentNullException">distinguishedName.</exception>
public Task Modify(string distinguishedName, LdapModification[] mods, CancellationToken ct = default) public Task Modify(String distinguishedName, LdapModification[] mods, CancellationToken ct = default) {
{ if(distinguishedName == null) {
if (distinguishedName == null)
{
throw new ArgumentNullException(nameof(distinguishedName)); throw new ArgumentNullException(nameof(distinguishedName));
} }
return RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct); return this.RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
} }
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) {
{ using(MemoryStream stream = new MemoryStream()) {
using (var stream = new MemoryStream())
{
LberEncoder.Encode(msg.Asn1Object, stream); LberEncoder.Encode(msg.Asn1Object, stream);
await _conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false); await this._conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
try try {
{ while(new List<RfcLdapMessage>(this.Messages).Any(x => x.MessageId == msg.MessageId) == false) {
while (new List<RfcLdapMessage>(Messages).Any(x => x.MessageId == msg.MessageId) == false)
await Task.Delay(100, ct).ConfigureAwait(false); await Task.Delay(100, ct).ConfigureAwait(false);
} }
catch (ArgumentException) } catch(ArgumentException) {
{
// expected // expected
} }
var first = new List<RfcLdapMessage>(Messages).FirstOrDefault(x => x.MessageId == msg.MessageId); RfcLdapMessage first = new List<RfcLdapMessage>(this.Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
if (first != null) if(first != null) {
{ LdapResponse response = new LdapResponse(first);
var response = new LdapResponse(first);
response.ChkResultCode(); response.ChkResultCode();
} }
} }
} }
internal void RetrieveMessages() internal void RetrieveMessages() {
{ while(!this._cts.IsCancellationRequested) {
while (!_cts.IsCancellationRequested) try {
{ Asn1Identifier asn1Id = new Asn1Identifier(this._conn.ActiveStream);
try
{
var asn1Id = new Asn1Identifier(_conn.ActiveStream);
if (asn1Id.Tag != Asn1Sequence.Tag) if(asn1Id.Tag != Asn1Sequence.Tag) {
{
continue; // loop looking for an RfcLdapMessage identifier continue; // loop looking for an RfcLdapMessage identifier
} }
// Turn the message into an RfcMessage class // Turn the message into an RfcMessage class
var asn1Len = new Asn1Length(_conn.ActiveStream); Asn1Length asn1Len = new Asn1Length(this._conn.ActiveStream);
Messages.Add(new RfcLdapMessage(_conn.ActiveStream, asn1Len.Length)); this.Messages.Add(new RfcLdapMessage(this._conn.ActiveStream, asn1Len.Length));
} } catch(IOException) {
catch (IOException)
{
// ignore // ignore
} }
} }

View File

@ -1,9 +1,8 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections.Generic;
using System; using Unosquare.Swan.Exceptions;
using System.Collections.Generic;
using Exceptions;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Encapsulates optional additional parameters or constraints to be applied to /// Encapsulates optional additional parameters or constraints to be applied to
/// an Ldap operation. /// an Ldap operation.
@ -11,8 +10,7 @@
/// on an LdapConnection or with a specific operation request, it is /// on an LdapConnection or with a specific operation request, it is
/// sent to the server along with operation requests. /// sent to the server along with operation requests.
/// </summary> /// </summary>
public class LdapControl public class LdapControl {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapControl"/> class. /// Initializes a new instance of the <see cref="LdapControl"/> class.
/// Constructs a new LdapControl object using the specified values. /// Constructs a new LdapControl object using the specified values.
@ -23,14 +21,12 @@
/// the operation can be processed without the control.</param> /// the operation can be processed without the control.</param>
/// <param name="values">The control-specific data.</param> /// <param name="values">The control-specific data.</param>
/// <exception cref="ArgumentException">An OID must be specified.</exception> /// <exception cref="ArgumentException">An OID must be specified.</exception>
public LdapControl(string oid, bool critical, sbyte[] values) public LdapControl(String oid, Boolean critical, SByte[] values) {
{ if(oid == null) {
if (oid == null)
{
throw new ArgumentException("An OID must be specified"); throw new ArgumentException("An OID must be specified");
} }
Asn1Object = new RfcControl( this.Asn1Object = new RfcControl(
oid, oid,
new Asn1Boolean(critical), new Asn1Boolean(critical),
values == null ? null : new Asn1OctetString(values)); values == null ? null : new Asn1OctetString(values));
@ -42,7 +38,7 @@
/// <value> /// <value>
/// The identifier. /// The identifier.
/// </value> /// </value>
public string Id => Asn1Object.ControlType.StringValue(); public String Id => this.Asn1Object.ControlType.StringValue();
/// <summary> /// <summary>
/// Returns whether the control is critical for the operation. /// Returns whether the control is critical for the operation.
@ -50,11 +46,13 @@
/// <value> /// <value>
/// <c>true</c> if critical; otherwise, <c>false</c>. /// <c>true</c> if critical; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Critical => Asn1Object.Criticality.BooleanValue(); public Boolean Critical => this.Asn1Object.Criticality.BooleanValue();
internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5); internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
internal RfcControl Asn1Object { get; } internal RfcControl Asn1Object {
get;
}
/// <summary> /// <summary>
/// Registers a class to be instantiated on receipt of a control with the /// Registers a class to be instantiated on receipt of a control with the
@ -64,7 +62,7 @@
/// </summary> /// </summary>
/// <param name="oid">The object identifier of the control.</param> /// <param name="oid">The object identifier of the control.</param>
/// <param name="controlClass">A class which can instantiate an LdapControl.</param> /// <param name="controlClass">A class which can instantiate an LdapControl.</param>
public static void Register(string oid, Type controlClass) public static void Register(String oid, Type controlClass)
=> RegisteredControls.RegisterResponseControl(oid, controlClass); => RegisteredControls.RegisterResponseControl(oid, controlClass);
/// <summary> /// <summary>
@ -74,20 +72,16 @@
/// The control-specific data of the object as a byte array, /// The control-specific data of the object as a byte array,
/// or null if the control has no data. /// or null if the control has no data.
/// </returns> /// </returns>
public sbyte[] GetValue() => Asn1Object.ControlValue?.ByteValue(); public SByte[] GetValue() => this.Asn1Object.ControlValue?.ByteValue();
internal void SetValue(sbyte[] controlValue) internal void SetValue(SByte[] controlValue) => this.Asn1Object.ControlValue = new Asn1OctetString(controlValue);
{
Asn1Object.ControlValue = new Asn1OctetString(controlValue);
}
} }
/// <summary> /// <summary>
/// Represents a simple bind request. /// Represents a simple bind request.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
public class LdapBindRequest : LdapMessage public class LdapBindRequest : LdapMessage {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapBindRequest"/> class. /// Initializes a new instance of the <see cref="LdapBindRequest"/> class.
/// Constructs a simple bind request. /// Constructs a simple bind request.
@ -102,9 +96,8 @@
/// connection and all operations through it should /// connection and all operations through it should
/// be authenticated with dn as the distinguished /// be authenticated with dn as the distinguished
/// name and passwd as password.</param> /// name and passwd as password.</param>
public LdapBindRequest(int version, string dn, sbyte[] password) public LdapBindRequest(Int32 version, String dn, SByte[] password)
: base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password)) : base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password)) {
{
} }
/// <summary> /// <summary>
@ -113,26 +106,24 @@
/// <value> /// <value>
/// The authentication dn. /// The authentication dn.
/// </value> /// </value>
public string AuthenticationDN => Asn1Object.RequestDn; public String AuthenticationDN => this.Asn1Object.RequestDn;
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => Asn1Object.ToString(); public override String ToString() => this.Asn1Object.ToString();
} }
/// <summary> /// <summary>
/// Encapsulates a continuation reference from an asynchronous search operation. /// Encapsulates a continuation reference from an asynchronous search operation.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
internal class LdapSearchResultReference : LdapMessage internal class LdapSearchResultReference : LdapMessage {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class. /// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class.
/// Constructs an LdapSearchResultReference object. /// Constructs an LdapSearchResultReference object.
/// </summary> /// </summary>
/// <param name="message">The LdapMessage with a search reference.</param> /// <param name="message">The LdapMessage with a search reference.</param>
internal LdapSearchResultReference(RfcLdapMessage message) internal LdapSearchResultReference(RfcLdapMessage message)
: base(message) : base(message) {
{
} }
/// <summary> /// <summary>
@ -141,14 +132,11 @@
/// <value> /// <value>
/// The referrals. /// The referrals.
/// </value> /// </value>
public string[] Referrals public String[] Referrals {
{ get {
get Asn1Object[] references = ((RfcSearchResultReference)this.Message.Response).ToArray();
{ String[] srefs = new String[references.Length];
var references = ((RfcSearchResultReference)Message.Response).ToArray(); for(Int32 i = 0; i < references.Length; i++) {
var srefs = new string[references.Length];
for (var i = 0; i < references.Length; i++)
{
srefs[i] = ((Asn1OctetString)references[i]).StringValue(); srefs[i] = ((Asn1OctetString)references[i]).StringValue();
} }
@ -157,33 +145,30 @@
} }
} }
internal class LdapResponse : LdapMessage internal class LdapResponse : LdapMessage {
{
internal LdapResponse(RfcLdapMessage message) internal LdapResponse(RfcLdapMessage message)
: base(message) : base(message) {
{
} }
public string ErrorMessage => ((IRfcResponse)Message.Response).GetErrorMessage().StringValue(); public String ErrorMessage => ((IRfcResponse)this.Message.Response).GetErrorMessage().StringValue();
public string MatchedDN => ((IRfcResponse)Message.Response).GetMatchedDN().StringValue(); public String MatchedDN => ((IRfcResponse)this.Message.Response).GetMatchedDN().StringValue();
public LdapStatusCode ResultCode => Message.Response is RfcSearchResultEntry || public LdapStatusCode ResultCode => this.Message.Response is RfcSearchResultEntry ||
(IRfcResponse)Message.Response is RfcIntermediateResponse (IRfcResponse)this.Message.Response is RfcIntermediateResponse
? LdapStatusCode.Success ? LdapStatusCode.Success
: (LdapStatusCode)((IRfcResponse)Message.Response).GetResultCode().IntValue(); : (LdapStatusCode)((IRfcResponse)this.Message.Response).GetResultCode().IntValue();
internal LdapException Exception { get; set; } internal LdapException Exception {
get; set;
internal void ChkResultCode()
{
if (Exception != null)
{
throw Exception;
} }
switch (ResultCode) internal void ChkResultCode() {
{ if(this.Exception != null) {
throw this.Exception;
}
switch(this.ResultCode) {
case LdapStatusCode.Success: case LdapStatusCode.Success:
case LdapStatusCode.CompareTrue: case LdapStatusCode.CompareTrue:
case LdapStatusCode.CompareFalse: case LdapStatusCode.CompareFalse:
@ -192,9 +177,9 @@
throw new LdapException( throw new LdapException(
"Automatic referral following not enabled", "Automatic referral following not enabled",
LdapStatusCode.Referral, LdapStatusCode.Referral,
ErrorMessage); this.ErrorMessage);
default: default:
throw new LdapException(ResultCode.ToString().Humanize(), ResultCode, ErrorMessage, MatchedDN); throw new LdapException(this.ResultCode.ToString().Humanize(), this.ResultCode, this.ErrorMessage, this.MatchedDN);
} }
} }
} }
@ -204,20 +189,16 @@
/// existing Vector class so that it can be used to maintain a /// existing Vector class so that it can be used to maintain a
/// list of currently registered control responses. /// list of currently registered control responses.
/// </summary> /// </summary>
internal class RespControlVector : List<RespControlVector.RegisteredControl> internal class RespControlVector : List<RespControlVector.RegisteredControl> {
{ private readonly Object _syncLock = new Object();
private readonly object _syncLock = new object();
public RespControlVector(int cap) public RespControlVector(Int32 cap)
: base(cap) : base(cap) {
{
} }
public void RegisterResponseControl(string oid, Type controlClass) public void RegisterResponseControl(String oid, Type controlClass) {
{ lock(this._syncLock) {
lock (_syncLock) this.Add(new RegisteredControl(this, oid, controlClass));
{
Add(new RegisteredControl(this, oid, controlClass));
} }
} }
@ -225,20 +206,26 @@
/// Inner class defined to create a temporary object to encapsulate /// Inner class defined to create a temporary object to encapsulate
/// all registration information about a response control. /// all registration information about a response control.
/// </summary> /// </summary>
internal class RegisteredControl internal class RegisteredControl {
{ public RegisteredControl(RespControlVector enclosingInstance, String oid, Type controlClass) {
public RegisteredControl(RespControlVector enclosingInstance, string oid, Type controlClass) this.EnclosingInstance = enclosingInstance;
{ this.MyOid = oid;
EnclosingInstance = enclosingInstance; this.MyClass = controlClass;
MyOid = oid;
MyClass = controlClass;
} }
internal Type MyClass { get; } internal Type MyClass {
get;
}
internal string MyOid { get; } internal String MyOid {
get;
}
private RespControlVector EnclosingInstance { get; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private RespControlVector EnclosingInstance {
get;
}
} }
} }
@ -253,40 +240,36 @@
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="IRfcRequest" /> /// <seealso cref="IRfcRequest" />
internal sealed class RfcBindRequest internal sealed class RfcBindRequest
: Asn1Sequence, IRfcRequest : Asn1Sequence, IRfcRequest {
{ [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private readonly sbyte[] _password; private readonly SByte[] _password;
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest); private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest);
public RfcBindRequest(int version, string name, sbyte[] password) public RfcBindRequest(Int32 version, String name, SByte[] password)
: base(3) : base(3) {
{ this._password = password;
_password = password; this.Add(new Asn1Integer(version));
Add(new Asn1Integer(version)); this.Add(name);
Add(name); this.Add(new RfcAuthenticationChoice(password));
Add(new RfcAuthenticationChoice(password));
} }
public Asn1Integer Version public Asn1Integer Version {
{ get => (Asn1Integer)this.Get(0);
get => (Asn1Integer)Get(0); set => this.Set(0, value);
set => Set(0, value);
} }
public Asn1OctetString Name public Asn1OctetString Name {
{ get => (Asn1OctetString)this.Get(1);
get => (Asn1OctetString)Get(1); set => this.Set(1, value);
set => Set(1, value);
} }
public RfcAuthenticationChoice AuthenticationChoice public RfcAuthenticationChoice AuthenticationChoice {
{ get => (RfcAuthenticationChoice)this.Get(2);
get => (RfcAuthenticationChoice)Get(2); set => this.Set(2, value);
set => Set(2, value);
} }
public override Asn1Identifier GetIdentifier() => Id; public override Asn1Identifier GetIdentifier() => Id;
public string GetRequestDN() => ((Asn1OctetString)Get(1)).StringValue(); public String GetRequestDN() => ((Asn1OctetString)this.Get(1)).StringValue();
} }
} }

View File

@ -1,10 +1,9 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Linq;
using System.Linq; using System.Collections.Generic;
using System; using System.Text;
using System.Collections.Generic;
using System.Text;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents a single entry in a directory, consisting of /// Represents a single entry in a directory, consisting of
/// a distinguished name (DN) and zero or more attributes. /// a distinguished name (DN) and zero or more attributes.
@ -15,8 +14,7 @@
/// </summary> /// </summary>
/// <seealso cref="LdapAttribute"></seealso> /// <seealso cref="LdapAttribute"></seealso>
/// <seealso cref="LdapAttributeSet"></seealso> /// <seealso cref="LdapAttributeSet"></seealso>
public class LdapEntry public class LdapEntry {
{
private readonly LdapAttributeSet _attrs; private readonly LdapAttributeSet _attrs;
/// <summary> /// <summary>
@ -29,10 +27,9 @@
/// name will cause operations using this entry to fail.</param> /// name will cause operations using this entry to fail.</param>
/// <param name="attrs">The initial set of attributes assigned to the /// <param name="attrs">The initial set of attributes assigned to the
/// entry.</param> /// entry.</param>
public LdapEntry(string dn = null, LdapAttributeSet attrs = null) public LdapEntry(String dn = null, LdapAttributeSet attrs = null) {
{ this.DN = dn ?? String.Empty;
DN = dn ?? string.Empty; this._attrs = attrs ?? new LdapAttributeSet();
_attrs = attrs ?? new LdapAttributeSet();
} }
/// <summary> /// <summary>
@ -41,7 +38,9 @@
/// <value> /// <value>
/// The dn. /// The dn.
/// </value> /// </value>
public string DN { get; } public String DN {
get;
}
/// <summary> /// <summary>
/// Returns the attributes matching the specified attrName. /// Returns the attributes matching the specified attrName.
@ -50,7 +49,7 @@
/// <returns> /// <returns>
/// The attribute matching the name. /// The attribute matching the name.
/// </returns> /// </returns>
public LdapAttribute GetAttribute(string attrName) => _attrs[attrName]; public LdapAttribute GetAttribute(String attrName) => this._attrs[attrName];
/// <summary> /// <summary>
/// Returns the attribute set of the entry. /// Returns the attribute set of the entry.
@ -61,7 +60,7 @@
/// <returns> /// <returns>
/// The attribute set of the entry. /// The attribute set of the entry.
/// </returns> /// </returns>
public LdapAttributeSet GetAttributeSet() => _attrs; public LdapAttributeSet GetAttributeSet() => this._attrs;
/// <summary> /// <summary>
/// Returns an attribute set from the entry, consisting of only those /// Returns an attribute set from the entry, consisting of only those
@ -83,7 +82,7 @@
/// match the specified subtypes or an empty set if no attributes /// match the specified subtypes or an empty set if no attributes
/// match. /// match.
/// </returns> /// </returns>
public LdapAttributeSet GetAttributeSet(string subtype) => _attrs.GetSubset(subtype); public LdapAttributeSet GetAttributeSet(String subtype) => this._attrs.GetSubset(subtype);
} }
/// <summary> /// <summary>
@ -94,11 +93,10 @@
/// LdapAttributeSet when retrieving or adding multiple /// LdapAttributeSet when retrieving or adding multiple
/// attributes to an entry. /// attributes to an entry.
/// </summary> /// </summary>
public class LdapAttribute public class LdapAttribute {
{ private readonly String _baseName; // cn of cn;lang-ja;phonetic
private readonly string _baseName; // cn of cn;lang-ja;phonetic private readonly String[] _subTypes; // lang-ja of cn;lang-ja
private readonly string[] _subTypes; // lang-ja of cn;lang-ja private Object[] _values; // Array of byte[] attribute values
private object[] _values; // Array of byte[] attribute values
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapAttribute"/> class. /// Initializes a new instance of the <see cref="LdapAttribute"/> class.
@ -106,11 +104,10 @@
/// </summary> /// </summary>
/// <param name="attrName">Name of the attribute.</param> /// <param name="attrName">Name of the attribute.</param>
/// <exception cref="ArgumentException">Attribute name cannot be null.</exception> /// <exception cref="ArgumentException">Attribute name cannot be null.</exception>
public LdapAttribute(string attrName) public LdapAttribute(String attrName) {
{ this.Name = attrName ?? throw new ArgumentNullException(nameof(attrName));
Name = attrName ?? throw new ArgumentNullException(nameof(attrName)); this._baseName = GetBaseName(attrName);
_baseName = GetBaseName(attrName); this._subTypes = GetSubtypes(attrName);
_subTypes = GetSubtypes(attrName);
} }
/// <summary> /// <summary>
@ -120,11 +117,8 @@
/// <param name="attrName">Name of the attribute.</param> /// <param name="attrName">Name of the attribute.</param>
/// <param name="attrString">Value of the attribute as a string.</param> /// <param name="attrString">Value of the attribute as a string.</param>
/// <exception cref="ArgumentException">Attribute value cannot be null.</exception> /// <exception cref="ArgumentException">Attribute value cannot be null.</exception>
public LdapAttribute(string attrName, string attrString) public LdapAttribute(String attrName, String attrString)
: this(attrName) : this(attrName) => this.Add(Encoding.UTF8.GetSBytes(attrString));
{
Add(Encoding.UTF8.GetSBytes(attrString));
}
/// <summary> /// <summary>
/// Returns the values of the attribute as an array of bytes. /// Returns the values of the attribute as an array of bytes.
@ -132,21 +126,19 @@
/// <value> /// <value>
/// The byte value array. /// The byte value array.
/// </value> /// </value>
public sbyte[][] ByteValueArray public SByte[][] ByteValueArray {
{ get {
get if(this._values == null) {
{ return new SByte[0][];
if (_values == null) }
return new sbyte[0][];
var size = _values.Length; Int32 size = this._values.Length;
var bva = new sbyte[size][]; SByte[][] bva = new SByte[size][];
// Deep copy so application cannot change values // Deep copy so application cannot change values
for (int i = 0, u = size; i < u; i++) for(Int32 i = 0, u = size; i < u; i++) {
{ bva[i] = new SByte[((SByte[])this._values[i]).Length];
bva[i] = new sbyte[((sbyte[])_values[i]).Length]; Array.Copy((Array)this._values[i], 0, bva[i], 0, bva[i].Length);
Array.Copy((Array)_values[i], 0, bva[i], 0, bva[i].Length);
} }
return bva; return bva;
@ -159,19 +151,17 @@
/// <value> /// <value>
/// The string value array. /// The string value array.
/// </value> /// </value>
public string[] StringValueArray public String[] StringValueArray {
{ get {
get if(this._values == null) {
{ return new String[0];
if (_values == null) }
return new string[0];
var size = _values.Length; Int32 size = this._values.Length;
var sva = new string[size]; String[] sva = new String[size];
for (var j = 0; j < size; j++) for(Int32 j = 0; j < size; j++) {
{ sva[j] = Encoding.UTF8.GetString((SByte[])this._values[j]);
sva[j] = Encoding.UTF8.GetString((sbyte[])_values[j]);
} }
return sva; return sva;
@ -184,7 +174,7 @@
/// <value> /// <value>
/// The string value. /// The string value.
/// </value> /// </value>
public string StringValue => _values == null ? null : Encoding.UTF8.GetString((sbyte[])_values[0]); public String StringValue => this._values == null ? null : Encoding.UTF8.GetString((SByte[])this._values[0]);
/// <summary> /// <summary>
/// Returns the first value of the attribute as a byte array or null. /// Returns the first value of the attribute as a byte array or null.
@ -192,15 +182,15 @@
/// <value> /// <value>
/// The byte value. /// The byte value.
/// </value> /// </value>
public sbyte[] ByteValue public SByte[] ByteValue {
{ get {
get if(this._values == null) {
{ return null;
if (_values == null) return null; }
// Deep copy so app can't change the value // Deep copy so app can't change the value
var bva = new sbyte[((sbyte[])_values[0]).Length]; SByte[] bva = new SByte[((SByte[])this._values[0]).Length];
Array.Copy((Array)_values[0], 0, bva, 0, bva.Length); Array.Copy((Array)this._values[0], 0, bva, 0, bva.Length);
return bva; return bva;
} }
@ -214,7 +204,7 @@
/// <value> /// <value>
/// The language subtype. /// The language subtype.
/// </value> /// </value>
public string LangSubtype => _subTypes?.FirstOrDefault(t => t.StartsWith("lang-")); public String LangSubtype => this._subTypes?.FirstOrDefault(t => t.StartsWith("lang-"));
/// <summary> /// <summary>
/// Returns the name of the attribute. /// Returns the name of the attribute.
@ -222,15 +212,15 @@
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; } public String Name {
get;
}
internal string Value internal String Value {
{ set {
set this._values = null;
{
_values = null;
Add(Encoding.UTF8.GetSBytes(value)); this.Add(Encoding.UTF8.GetSBytes(value));
} }
} }
@ -245,24 +235,20 @@
/// An array subtypes or null if the attribute has none. /// An array subtypes or null if the attribute has none.
/// </returns> /// </returns>
/// <exception cref="ArgumentException">Attribute name cannot be null.</exception> /// <exception cref="ArgumentException">Attribute name cannot be null.</exception>
public static string[] GetSubtypes(string attrName) public static String[] GetSubtypes(String attrName) {
{ if(attrName == null) {
if (attrName == null)
{
throw new ArgumentException("Attribute name cannot be null"); throw new ArgumentException("Attribute name cannot be null");
} }
var st = new Tokenizer(attrName, ";"); Tokenizer st = new Tokenizer(attrName, ";");
string[] subTypes = null; String[] subTypes = null;
var cnt = st.Count; Int32 cnt = st.Count;
if (cnt > 0) if(cnt > 0) {
{ _ = st.NextToken(); // skip over basename
st.NextToken(); // skip over basename subTypes = new String[cnt - 1];
subTypes = new string[cnt - 1]; Int32 i = 0;
var i = 0; while(st.HasMoreTokens()) {
while (st.HasMoreTokens())
{
subTypes[i++] = st.NextToken(); subTypes[i++] = st.NextToken();
} }
} }
@ -279,14 +265,12 @@
/// base name.</param> /// base name.</param>
/// <returns> The base name of the attribute. </returns> /// <returns> The base name of the attribute. </returns>
/// <exception cref="ArgumentException">Attribute name cannot be null.</exception> /// <exception cref="ArgumentException">Attribute name cannot be null.</exception>
public static string GetBaseName(string attrName) public static String GetBaseName(String attrName) {
{ if(attrName == null) {
if (attrName == null)
{
throw new ArgumentException("Attribute name cannot be null"); throw new ArgumentException("Attribute name cannot be null");
} }
var idx = attrName.IndexOf(';'); Int32 idx = attrName.IndexOf(';');
return idx == -1 ? attrName : attrName.Substring(0, idx - 0); return idx == -1 ? attrName : attrName.Substring(0, idx - 0);
} }
@ -294,15 +278,13 @@
/// Clones this instance. /// Clones this instance.
/// </summary> /// </summary>
/// <returns>A cloned instance.</returns> /// <returns>A cloned instance.</returns>
public LdapAttribute Clone() public LdapAttribute Clone() {
{ Object newObj = this.MemberwiseClone();
var newObj = MemberwiseClone(); if(this._values != null) {
if (_values != null) Array.Copy(this._values, 0, ((LdapAttribute)newObj)._values, 0, this._values.Length);
{
Array.Copy(_values, 0, ((LdapAttribute)newObj)._values, 0, _values.Length);
} }
return (LdapAttribute) newObj; return (LdapAttribute)newObj;
} }
/// <summary> /// <summary>
@ -310,14 +292,12 @@
/// </summary> /// </summary>
/// <param name="attrString">Value of the attribute as a String.</param> /// <param name="attrString">Value of the attribute as a String.</param>
/// <exception cref="ArgumentException">Attribute value cannot be null.</exception> /// <exception cref="ArgumentException">Attribute value cannot be null.</exception>
public void AddValue(string attrString) public void AddValue(String attrString) {
{ if(attrString == null) {
if (attrString == null)
{
throw new ArgumentException("Attribute value cannot be null"); throw new ArgumentException("Attribute value cannot be null");
} }
Add(Encoding.UTF8.GetSBytes(attrString)); this.Add(Encoding.UTF8.GetSBytes(attrString));
} }
/// <summary> /// <summary>
@ -326,14 +306,12 @@
/// <param name="attrBytes">Value of the attribute as raw bytes. /// <param name="attrBytes">Value of the attribute as raw bytes.
/// Note: If attrBytes represents a string it should be UTF-8 encoded.</param> /// Note: If attrBytes represents a string it should be UTF-8 encoded.</param>
/// <exception cref="ArgumentException">Attribute value cannot be null.</exception> /// <exception cref="ArgumentException">Attribute value cannot be null.</exception>
public void AddValue(sbyte[] attrBytes) public void AddValue(SByte[] attrBytes) {
{ if(attrBytes == null) {
if (attrBytes == null)
{
throw new ArgumentException("Attribute value cannot be null"); throw new ArgumentException("Attribute value cannot be null");
} }
Add(attrBytes); this.Add(attrBytes);
} }
/// <summary> /// <summary>
@ -343,14 +321,12 @@
/// </summary> /// </summary>
/// <param name="attrString">The base64 value of the attribute as a String.</param> /// <param name="attrString">The base64 value of the attribute as a String.</param>
/// <exception cref="ArgumentException">Attribute value cannot be null.</exception> /// <exception cref="ArgumentException">Attribute value cannot be null.</exception>
public void AddBase64Value(string attrString) public void AddBase64Value(String attrString) {
{ if(attrString == null) {
if (attrString == null)
{
throw new ArgumentException("Attribute value cannot be null"); throw new ArgumentException("Attribute value cannot be null");
} }
Add(Convert.FromBase64String(attrString).ToSByteArray()); this.Add(Convert.FromBase64String(attrString).ToSByteArray());
} }
/// <summary> /// <summary>
@ -362,14 +338,12 @@
/// <param name="start">The start index of base64 encoded part, inclusive.</param> /// <param name="start">The start index of base64 encoded part, inclusive.</param>
/// <param name="end">The end index of base encoded part, exclusive.</param> /// <param name="end">The end index of base encoded part, exclusive.</param>
/// <exception cref="ArgumentNullException">attrString.</exception> /// <exception cref="ArgumentNullException">attrString.</exception>
public void AddBase64Value(StringBuilder attrString, int start, int end) public void AddBase64Value(StringBuilder attrString, Int32 start, Int32 end) {
{ if(attrString == null) {
if (attrString == null)
{
throw new ArgumentNullException(nameof(attrString)); throw new ArgumentNullException(nameof(attrString));
} }
Add(Convert.FromBase64String(attrString.ToString(start, end)).ToSByteArray()); this.Add(Convert.FromBase64String(attrString.ToString(start, end)).ToSByteArray());
} }
/// <summary> /// <summary>
@ -380,14 +354,12 @@
/// <param name="attrChars">The base64 value of the attribute as an array of /// <param name="attrChars">The base64 value of the attribute as an array of
/// characters.</param> /// characters.</param>
/// <exception cref="ArgumentNullException">attrChars.</exception> /// <exception cref="ArgumentNullException">attrChars.</exception>
public void AddBase64Value(char[] attrChars) public void AddBase64Value(Char[] attrChars) {
{ if(attrChars == null) {
if (attrChars == null)
{
throw new ArgumentNullException(nameof(attrChars)); throw new ArgumentNullException(nameof(attrChars));
} }
Add(Convert.FromBase64CharArray(attrChars, 0, attrChars.Length).ToSByteArray()); this.Add(Convert.FromBase64CharArray(attrChars, 0, attrChars.Length).ToSByteArray());
} }
/// <summary> /// <summary>
@ -398,7 +370,7 @@
/// <returns> /// <returns>
/// The base name of the attribute. /// The base name of the attribute.
/// </returns> /// </returns>
public string GetBaseName() => _baseName; public String GetBaseName() => this._baseName;
/// <summary> /// <summary>
/// Extracts the subtypes from the attribute name. /// Extracts the subtypes from the attribute name.
@ -408,7 +380,7 @@
/// <returns> /// <returns>
/// An array subtypes or null if the attribute has none. /// An array subtypes or null if the attribute has none.
/// </returns> /// </returns>
public string[] GetSubtypes() => _subTypes; public String[] GetSubtypes() => this._subTypes;
/// <summary> /// <summary>
/// Reports if the attribute name contains the specified subtype. /// Reports if the attribute name contains the specified subtype.
@ -422,14 +394,12 @@
/// True, if the attribute has the specified subtype; /// True, if the attribute has the specified subtype;
/// false, if it doesn't. /// false, if it doesn't.
/// </returns> /// </returns>
public bool HasSubtype(string subtype) public Boolean HasSubtype(String subtype) {
{ if(subtype == null) {
if (subtype == null)
{
throw new ArgumentNullException(nameof(subtype)); throw new ArgumentNullException(nameof(subtype));
} }
return _subTypes != null && _subTypes.Any(t => string.Equals(t, subtype, StringComparison.OrdinalIgnoreCase)); return this._subTypes != null && this._subTypes.Any(t => String.Equals(t, subtype, StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>
@ -446,24 +416,18 @@
/// True, if the attribute has all the specified subtypes; /// True, if the attribute has all the specified subtypes;
/// false, if it doesn't have all the subtypes. /// false, if it doesn't have all the subtypes.
/// </returns> /// </returns>
public bool HasSubtypes(string[] subtypes) public Boolean HasSubtypes(String[] subtypes) {
{ if(subtypes == null) {
if (subtypes == null)
{
throw new ArgumentNullException(nameof(subtypes)); throw new ArgumentNullException(nameof(subtypes));
} }
for (var i = 0; i < subtypes.Length; i++) for(Int32 i = 0; i < subtypes.Length; i++) {
{ foreach(String sub in this._subTypes) {
foreach (var sub in _subTypes) if(sub == null) {
{
if (sub == null)
{
throw new ArgumentException($"subtype at array index {i} cannot be null"); throw new ArgumentException($"subtype at array index {i} cannot be null");
} }
if (string.Equals(sub, subtypes[i], StringComparison.OrdinalIgnoreCase)) if(String.Equals(sub, subtypes[i], StringComparison.OrdinalIgnoreCase)) {
{
return true; return true;
} }
} }
@ -479,14 +443,12 @@
/// Note: Removing a value which is not present in the attribute has /// Note: Removing a value which is not present in the attribute has
/// no effect.</param> /// no effect.</param>
/// <exception cref="ArgumentNullException">attrString.</exception> /// <exception cref="ArgumentNullException">attrString.</exception>
public void RemoveValue(string attrString) public void RemoveValue(String attrString) {
{ if(attrString == null) {
if (attrString == null)
{
throw new ArgumentNullException(nameof(attrString)); throw new ArgumentNullException(nameof(attrString));
} }
RemoveValue(Encoding.UTF8.GetSBytes(attrString)); this.RemoveValue(Encoding.UTF8.GetSBytes(attrString));
} }
/// <summary> /// <summary>
@ -498,43 +460,36 @@
/// no effect. /// no effect.
/// </param> /// </param>
/// <exception cref="ArgumentNullException">attrBytes.</exception> /// <exception cref="ArgumentNullException">attrBytes.</exception>
public void RemoveValue(sbyte[] attrBytes) public void RemoveValue(SByte[] attrBytes) {
{ if(attrBytes == null) {
if (attrBytes == null)
{
throw new ArgumentNullException(nameof(attrBytes)); throw new ArgumentNullException(nameof(attrBytes));
} }
for (var i = 0; i < _values.Length; i++) for(Int32 i = 0; i < this._values.Length; i++) {
{ if(!Equals(attrBytes, (SByte[])this._values[i])) {
if (!Equals(attrBytes, (sbyte[])_values[i])) continue; continue;
}
if (i == 0 && _values.Length == 1) if(i == 0 && this._values.Length == 1) {
{
// Optimize if first element of a single valued attr // Optimize if first element of a single valued attr
_values = null; this._values = null;
return; return;
} }
if (_values.Length == 1) if(this._values.Length == 1) {
{ this._values = null;
_values = null; } else {
} Int32 moved = this._values.Length - i - 1;
else Object[] tmp = new Object[this._values.Length - 1];
{ if(i != 0) {
var moved = _values.Length - i - 1; Array.Copy(this._values, 0, tmp, 0, i);
var tmp = new object[_values.Length - 1];
if (i != 0)
{
Array.Copy(_values, 0, tmp, 0, i);
} }
if (moved != 0) if(moved != 0) {
{ Array.Copy(this._values, i + 1, tmp, i, moved);
Array.Copy(_values, i + 1, tmp, i, moved);
} }
_values = tmp; this._values = tmp;
} }
break; break;
@ -547,7 +502,7 @@
/// <returns> /// <returns>
/// The number of values in the attribute. /// The number of values in the attribute.
/// </returns> /// </returns>
public int Size() => _values?.Length ?? 0; public Int32 Size() => this._values?.Length ?? 0;
/// <summary> /// <summary>
/// Compares this object with the specified object for order. /// Compares this object with the specified object for order.
@ -559,8 +514,8 @@
/// integer as this object is less than, equal to, or greater than the /// integer as this object is less than, equal to, or greater than the
/// specified object. /// specified object.
/// </returns> /// </returns>
public int CompareTo(object attribute) public Int32 CompareTo(Object attribute)
=> string.Compare(Name, ((LdapAttribute)attribute).Name, StringComparison.Ordinal); => String.Compare(this.Name, ((LdapAttribute)attribute).Name, StringComparison.Ordinal);
/// <summary> /// <summary>
/// Returns a string representation of this LdapAttribute. /// Returns a string representation of this LdapAttribute.
@ -569,45 +524,39 @@
/// a string representation of this LdapAttribute. /// a string representation of this LdapAttribute.
/// </returns> /// </returns>
/// <exception cref="Exception">NullReferenceException.</exception> /// <exception cref="Exception">NullReferenceException.</exception>
public override string ToString() public override String ToString() {
{ StringBuilder result = new StringBuilder("LdapAttribute: ");
var result = new StringBuilder("LdapAttribute: ");
result.Append("{type='" + Name + "'"); _ = result.Append("{type='" + this.Name + "'");
if (_values != null) if(this._values != null) {
{ _ = result
result
.Append(", ") .Append(", ")
.Append(_values.Length == 1 ? "value='" : "values='"); .Append(this._values.Length == 1 ? "value='" : "values='");
for (var i = 0; i < _values.Length; i++) for(Int32 i = 0; i < this._values.Length; i++) {
{ if(i != 0) {
if (i != 0) _ = result.Append("','");
{
result.Append("','");
} }
if (((sbyte[])_values[i]).Length == 0) if(((SByte[])this._values[i]).Length == 0) {
{
continue; continue;
} }
var sval = Encoding.UTF8.GetString((sbyte[])_values[i]); String sval = Encoding.UTF8.GetString((SByte[])this._values[i]);
if (sval.Length == 0) if(sval.Length == 0) {
{
// didn't decode well, must be binary // didn't decode well, must be binary
result.Append("<binary value, length:" + sval.Length); _ = result.Append("<binary value, length:" + sval.Length);
continue; continue;
} }
result.Append(sval); _ = result.Append(sval);
} }
result.Append("'"); _ = result.Append("'");
} }
result.Append("}"); _ = result.Append("}");
return result.ToString(); return result.ToString();
} }
@ -619,48 +568,45 @@
/// as binary data so we simplify the process by requiring /// as binary data so we simplify the process by requiring
/// that all data added to our list is in binary form. /// that all data added to our list is in binary form.
/// Note: If attrBytes represents a string it should be UTF-8 encoded.</param> /// Note: If attrBytes represents a string it should be UTF-8 encoded.</param>
private void Add(sbyte[] bytes) private void Add(SByte[] bytes) {
{ if(this._values == null) {
if (_values == null) this._values = new Object[] { bytes };
{ } else {
_values = new object[] { bytes };
}
else
{
// Duplicate attribute values not allowed // Duplicate attribute values not allowed
if (_values.Any(t => Equals(bytes, (sbyte[])t))) if(this._values.Any(t => Equals(bytes, (SByte[])t))) {
{
return; // Duplicate, don't add return; // Duplicate, don't add
} }
var tmp = new object[_values.Length + 1]; Object[] tmp = new Object[this._values.Length + 1];
Array.Copy(_values, 0, tmp, 0, _values.Length); Array.Copy(this._values, 0, tmp, 0, this._values.Length);
tmp[_values.Length] = bytes; tmp[this._values.Length] = bytes;
_values = tmp; this._values = tmp;
} }
} }
private static bool Equals(sbyte[] e1, sbyte[] e2) private static Boolean Equals(SByte[] e1, SByte[] e2) {
{
// If same object, they compare true // If same object, they compare true
if (e1 == e2) if(e1 == e2) {
return true; return true;
}
// If either but not both are null, they compare false // If either but not both are null, they compare false
if (e1 == null || e2 == null) if(e1 == null || e2 == null) {
return false; return false;
}
// If arrays have different length, they compare false // If arrays have different length, they compare false
var length = e1.Length; Int32 length = e1.Length;
if (e2.Length != length) if(e2.Length != length) {
return false; return false;
}
// If any of the bytes are different, they compare false // If any of the bytes are different, they compare false
for (var i = 0; i < length; i++) for(Int32 i = 0; i < length; i++) {
{ if(e1[i] != e2[i]) {
if (e1[i] != e2[i])
return false; return false;
} }
}
return true; return true;
} }
@ -675,14 +621,12 @@
/// </summary> /// </summary>
/// <seealso cref="LdapAttribute"></seealso> /// <seealso cref="LdapAttribute"></seealso>
/// <seealso cref="LdapEntry"></seealso> /// <seealso cref="LdapEntry"></seealso>
public class LdapAttributeSet : Dictionary<string, LdapAttribute> public class LdapAttributeSet : Dictionary<String, LdapAttribute> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapAttributeSet"/> class. /// Initializes a new instance of the <see cref="LdapAttributeSet"/> class.
/// </summary> /// </summary>
public LdapAttributeSet() public LdapAttributeSet()
: base(StringComparer.OrdinalIgnoreCase) : base(StringComparer.OrdinalIgnoreCase) {
{
// placeholder // placeholder
} }
@ -709,15 +653,14 @@
/// An attribute set containing the attributes that match the /// An attribute set containing the attributes that match the
/// specified subtype. /// specified subtype.
/// </returns> /// </returns>
public LdapAttributeSet GetSubset(string subtype) public LdapAttributeSet GetSubset(String subtype) {
{
// Create a new tempAttributeSet // Create a new tempAttributeSet
var tempAttributeSet = new LdapAttributeSet(); LdapAttributeSet tempAttributeSet = new LdapAttributeSet();
foreach (var kvp in this) foreach(KeyValuePair<String, LdapAttribute> kvp in this) {
{ if(kvp.Value.HasSubtype(subtype)) {
if (kvp.Value.HasSubtype(subtype)) _ = tempAttributeSet.Add(kvp.Value.Clone());
tempAttributeSet.Add(kvp.Value.Clone()); }
} }
return tempAttributeSet; return tempAttributeSet;
@ -731,7 +674,7 @@
/// <returns> /// <returns>
/// true if this set contains the specified attribute. /// true if this set contains the specified attribute.
/// </returns> /// </returns>
public bool Contains(object attr) => ContainsKey(((LdapAttribute)attr).Name); public Boolean Contains(Object attr) => this.ContainsKey(((LdapAttribute)attr).Name);
/// <summary> /// <summary>
/// Adds the specified attribute to this set if it is not already present. /// Adds the specified attribute to this set if it is not already present.
@ -742,12 +685,12 @@
/// <returns> /// <returns>
/// <c>true</c> if the attribute was added. /// <c>true</c> if the attribute was added.
/// </returns> /// </returns>
public bool Add(LdapAttribute attr) public Boolean Add(LdapAttribute attr) {
{ String name = attr.Name;
var name = attr.Name;
if (ContainsKey(name)) if(this.ContainsKey(name)) {
return false; return false;
}
this[name] = attr; this[name] = attr;
return true; return true;
@ -764,7 +707,7 @@
/// <returns> /// <returns>
/// true if the object was removed. /// true if the object was removed.
/// </returns> /// </returns>
public bool Remove(LdapAttribute entry) => Remove(entry.Name); public Boolean Remove(LdapAttribute entry) => this.Remove(entry.Name);
/// <summary> /// <summary>
/// Returns a <see cref="System.String" /> that represents this instance. /// Returns a <see cref="System.String" /> that represents this instance.
@ -772,20 +715,17 @@
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents this instance. /// A <see cref="System.String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() public override String ToString() {
{ StringBuilder retValue = new StringBuilder("LdapAttributeSet: ");
var retValue = new StringBuilder("LdapAttributeSet: "); Boolean first = true;
var first = true;
foreach (var attr in this) foreach(KeyValuePair<String, LdapAttribute> attr in this) {
{ if(!first) {
if (!first) _ = retValue.Append(" ");
{
retValue.Append(" ");
} }
first = false; first = false;
retValue.Append(attr); _ = retValue.Append(attr);
} }
return retValue.ToString(); return retValue.ToString();

View File

@ -1,10 +1,8 @@
namespace Unosquare.Swan.Networking.Ldap namespace Unosquare.Swan.Networking.Ldap {
{
/// <summary> /// <summary>
/// Ldap Modification Operators. /// Ldap Modification Operators.
/// </summary> /// </summary>
public enum LdapModificationOp public enum LdapModificationOp {
{
/// <summary> /// <summary>
/// Adds the listed values to the given attribute, creating /// Adds the listed values to the given attribute, creating
/// the attribute if it does not already exist. /// the attribute if it does not already exist.
@ -32,8 +30,7 @@
/// <summary> /// <summary>
/// LDAP valid scopes. /// LDAP valid scopes.
/// </summary> /// </summary>
public enum LdapScope public enum LdapScope {
{
/// <summary> /// <summary>
/// Used with search to specify that the scope of entrys to search is to /// Used with search to specify that the scope of entrys to search is to
/// search only the base object. /// search only the base object.
@ -56,8 +53,7 @@
/// <summary> /// <summary>
/// Substring Operators. /// Substring Operators.
/// </summary> /// </summary>
internal enum SubstringOp internal enum SubstringOp {
{
/// <summary> /// <summary>
/// Search Filter Identifier for an INITIAL component of a SUBSTRING. /// Search Filter Identifier for an INITIAL component of a SUBSTRING.
/// Note: An initial SUBSTRING is represented as "value*". /// Note: An initial SUBSTRING is represented as "value*".
@ -80,8 +76,7 @@
/// <summary> /// <summary>
/// Filtering Operators. /// Filtering Operators.
/// </summary> /// </summary>
internal enum FilterOp internal enum FilterOp {
{
/// <summary> /// <summary>
/// Identifier for AND component. /// Identifier for AND component.
/// </summary> /// </summary>

View File

@ -1,21 +1,19 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// The base class for Ldap request and response messages. /// The base class for Ldap request and response messages.
/// Subclassed by response messages used in asynchronous operations. /// Subclassed by response messages used in asynchronous operations.
/// </summary> /// </summary>
public class LdapMessage public class LdapMessage {
{
internal RfcLdapMessage Message; internal RfcLdapMessage Message;
private int _imsgNum = -1; // This instance LdapMessage number private Int32 _imsgNum = -1; // This instance LdapMessage number
private LdapOperation _messageType = LdapOperation.Unknown; private LdapOperation _messageType = LdapOperation.Unknown;
private string _stringTag; private String _stringTag;
internal LdapMessage() internal LdapMessage() {
{
} }
/// <summary> /// <summary>
@ -27,25 +25,22 @@
/// <param name="op">The operation type of message.</param> /// <param name="op">The operation type of message.</param>
/// <param name="controls">The controls to use with the operation.</param> /// <param name="controls">The controls to use with the operation.</param>
/// <seealso cref="Type"></seealso> /// <seealso cref="Type"></seealso>
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) {
{
// Get a unique number for this request message // Get a unique number for this request message
_messageType = type; this._messageType = type;
RfcControls asn1Ctrls = null; RfcControls asn1Ctrls = null;
if (controls != null) if(controls != null) {
{
// Move LdapControls into an RFC 2251 Controls object. // Move LdapControls into an RFC 2251 Controls object.
asn1Ctrls = new RfcControls(); asn1Ctrls = new RfcControls();
foreach (var t in controls) foreach(LdapControl t in controls) {
{
asn1Ctrls.Add(t.Asn1Object); asn1Ctrls.Add(t.Asn1Object);
} }
} }
// create RFC 2251 LdapMessage // create RFC 2251 LdapMessage
Message = new RfcLdapMessage(op, asn1Ctrls); this.Message = new RfcLdapMessage(op, asn1Ctrls);
} }
/// <summary> /// <summary>
@ -54,7 +49,7 @@
/// from a command. /// from a command.
/// </summary> /// </summary>
/// <param name="message">A response message.</param> /// <param name="message">A response message.</param>
internal LdapMessage(RfcLdapMessage message) => Message = message; internal LdapMessage(RfcLdapMessage message) => this.Message = message;
/// <summary> /// <summary>
/// Returns the message ID. The message ID is an integer value /// Returns the message ID. The message ID is an integer value
@ -63,16 +58,13 @@
/// <value> /// <value>
/// The message identifier. /// The message identifier.
/// </value> /// </value>
public virtual int MessageId public virtual Int32 MessageId {
{ get {
get if(this._imsgNum == -1) {
{ this._imsgNum = this.Message.MessageId;
if (_imsgNum == -1)
{
_imsgNum = Message.MessageId;
} }
return _imsgNum; return this._imsgNum;
} }
} }
@ -82,24 +74,21 @@
/// <value> /// <value>
/// <c>true</c> if request; otherwise, <c>false</c>. /// <c>true</c> if request; otherwise, <c>false</c>.
/// </value> /// </value>
public virtual bool Request => Message.IsRequest(); public virtual Boolean Request => this.Message.IsRequest();
internal LdapOperation Type internal LdapOperation Type {
{ get {
get if(this._messageType == LdapOperation.Unknown) {
{ this._messageType = this.Message.Type;
if (_messageType == LdapOperation.Unknown)
{
_messageType = Message.Type;
} }
return _messageType; return this._messageType;
} }
} }
internal virtual RfcLdapMessage Asn1Object => Message; internal virtual RfcLdapMessage Asn1Object => this.Message;
internal virtual LdapMessage RequestingMessage => Message.RequestingMessage; internal virtual LdapMessage RequestingMessage => this.Message.RequestingMessage;
/// <summary> /// <summary>
/// Retrieves the identifier tag for this message. /// Retrieves the identifier tag for this message.
@ -112,22 +101,13 @@
/// <value> /// <value>
/// The tag. /// The tag.
/// </value> /// </value>
public virtual string Tag public virtual String Tag {
{ get => this._stringTag ?? (this.Request ? null : this.RequestingMessage?._stringTag);
get
{ set => this._stringTag = value;
if (_stringTag != null)
{
return _stringTag;
} }
return Request ? null : RequestingMessage?._stringTag; private String Name => this.Type.ToString();
}
set => _stringTag = value;
}
private string Name => Type.ToString();
/// <summary> /// <summary>
/// Returns a <see cref="System.String" /> that represents this instance. /// Returns a <see cref="System.String" /> that represents this instance.
@ -135,6 +115,6 @@
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents this instance. /// A <see cref="System.String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() => $"{Name}({MessageId}): {Message}"; public override String ToString() => $"{this.Name}({this.MessageId}): {this.Message}";
} }
} }

View File

@ -1,5 +1,5 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// A single add, delete, or replace operation to an LdapAttribute. /// A single add, delete, or replace operation to an LdapAttribute.
/// An LdapModification contains information on the type of modification /// An LdapModification contains information on the type of modification
@ -33,18 +33,16 @@
/// </summary> /// </summary>
/// <seealso cref="LdapConnection.Modify"></seealso> /// <seealso cref="LdapConnection.Modify"></seealso>
/// <seealso cref="LdapAttribute"></seealso> /// <seealso cref="LdapAttribute"></seealso>
public sealed class LdapModification : LdapMessage public sealed class LdapModification : LdapMessage {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapModification" /> class. /// Initializes a new instance of the <see cref="LdapModification" /> class.
/// Specifies a modification to be made to an attribute. /// Specifies a modification to be made to an attribute.
/// </summary> /// </summary>
/// <param name="op">The op.</param> /// <param name="op">The op.</param>
/// <param name="attr">The attribute to modify.</param> /// <param name="attr">The attribute to modify.</param>
public LdapModification(LdapModificationOp op, LdapAttribute attr) public LdapModification(LdapModificationOp op, LdapAttribute attr) {
{ this.Op = op;
Op = op; this.Attribute = attr;
Attribute = attr;
} }
/// <summary> /// <summary>
@ -53,9 +51,8 @@
/// <param name="op">The op.</param> /// <param name="op">The op.</param>
/// <param name="attrName">Name of the attribute.</param> /// <param name="attrName">Name of the attribute.</param>
/// <param name="attrValue">The attribute value.</param> /// <param name="attrValue">The attribute value.</param>
public LdapModification(LdapModificationOp op, string attrName, string attrValue) public LdapModification(LdapModificationOp op, String attrName, String attrValue)
: this(op, new LdapAttribute(attrName, attrValue)) : this(op, new LdapAttribute(attrName, attrValue)) {
{
// placeholder // placeholder
} }
@ -65,7 +62,9 @@
/// <value> /// <value>
/// The attribute. /// The attribute.
/// </value> /// </value>
public LdapAttribute Attribute { get; } public LdapAttribute Attribute {
get;
}
/// <summary> /// <summary>
/// Returns the type of modification specified by this object. /// Returns the type of modification specified by this object.
@ -73,6 +72,8 @@
/// <value> /// <value>
/// The op. /// The op.
/// </value> /// </value>
public LdapModificationOp Op { get; } public LdapModificationOp Op {
get;
}
} }
} }

View File

@ -1,20 +1,18 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents a LDAP Modification Request Message. /// Represents a LDAP Modification Request Message.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
public sealed class LdapModifyRequest : LdapMessage public sealed class LdapModifyRequest : LdapMessage {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapModifyRequest"/> class. /// Initializes a new instance of the <see cref="LdapModifyRequest"/> class.
/// </summary> /// </summary>
/// <param name="dn">The dn.</param> /// <param name="dn">The dn.</param>
/// <param name="modifications">The modifications.</param> /// <param name="modifications">The modifications.</param>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
public LdapModifyRequest(string dn, LdapModification[] modifications, LdapControl[] control) public LdapModifyRequest(String dn, LdapModification[] modifications, LdapControl[] control)
: base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control) : base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control) {
{
} }
/// <summary> /// <summary>
@ -23,30 +21,26 @@
/// <value> /// <value>
/// The dn. /// The dn.
/// </value> /// </value>
public string DN => Asn1Object.RequestDn; public String DN => this.Asn1Object.RequestDn;
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => Asn1Object.ToString(); public override String ToString() => this.Asn1Object.ToString();
private static Asn1SequenceOf EncodeModifications(LdapModification[] mods) private static Asn1SequenceOf EncodeModifications(LdapModification[] mods) {
{ Asn1SequenceOf rfcMods = new Asn1SequenceOf(mods.Length);
var rfcMods = new Asn1SequenceOf(mods.Length);
foreach (var t in mods) foreach(LdapModification t in mods) {
{ LdapAttribute attr = t.Attribute;
var attr = t.Attribute;
var vals = new Asn1SetOf(attr.Size()); Asn1SetOf vals = new Asn1SetOf(attr.Size());
if (attr.Size() > 0) if(attr.Size() > 0) {
{ foreach(SByte[] val in attr.ByteValueArray) {
foreach (var val in attr.ByteValueArray)
{
vals.Add(new Asn1OctetString(val)); vals.Add(new Asn1OctetString(val));
} }
} }
var rfcMod = new Asn1Sequence(2); Asn1Sequence rfcMod = new Asn1Sequence(2);
rfcMod.Add(new Asn1Enumerated((int) t.Op)); rfcMod.Add(new Asn1Enumerated((Int32)t.Op));
rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals)); rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals));
rfcMods.Add(rfcMod); rfcMods.Add(rfcMod);
@ -55,13 +49,11 @@
return rfcMods; return rfcMods;
} }
internal class RfcAttributeTypeAndValues : Asn1Sequence internal class RfcAttributeTypeAndValues : Asn1Sequence {
{ public RfcAttributeTypeAndValues(String type, Asn1Object vals)
public RfcAttributeTypeAndValues(string type, Asn1Object vals) : base(2) {
: base(2) this.Add(type);
{ this.Add(vals);
Add(type);
Add(vals);
} }
} }
} }

View File

@ -1,10 +1,8 @@
namespace Unosquare.Swan.Networking.Ldap namespace Unosquare.Swan.Networking.Ldap {
{
/// <summary> /// <summary>
/// LDAP Operation. /// LDAP Operation.
/// </summary> /// </summary>
internal enum LdapOperation internal enum LdapOperation {
{
/// <summary> /// <summary>
/// The unknown /// The unknown
/// </summary> /// </summary>
@ -92,8 +90,7 @@
/// <summary> /// <summary>
/// ASN1 tags. /// ASN1 tags.
/// </summary> /// </summary>
internal enum Asn1IdentifierTag internal enum Asn1IdentifierTag {
{
/// <summary> /// <summary>
/// Universal tag class. /// Universal tag class.
/// </summary> /// </summary>

View File

@ -1,13 +1,12 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections;
using System.Collections;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents an Ldap Search request. /// Represents an Ldap Search request.
/// </summary> /// </summary>
/// <seealso cref="LdapMessage" /> /// <seealso cref="LdapMessage" />
internal sealed class LdapSearchRequest : LdapMessage internal sealed class LdapSearchRequest : LdapMessage {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class. /// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
/// </summary> /// </summary>
@ -41,20 +40,19 @@
/// or null if none.</param> /// or null if none.</param>
/// <seealso cref="LdapConnection.Search"></seealso> /// <seealso cref="LdapConnection.Search"></seealso>
public LdapSearchRequest( public LdapSearchRequest(
string ldapBase, String ldapBase,
LdapScope scope, LdapScope scope,
string filter, String filter,
string[] attrs, String[] attrs,
int dereference, Int32 dereference,
int maxResults, Int32 maxResults,
int serverTimeLimit, Int32 serverTimeLimit,
bool typesOnly, Boolean typesOnly,
LdapControl[] cont) LdapControl[] cont)
: base( : base(
LdapOperation.SearchRequest, LdapOperation.SearchRequest,
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs), new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
cont) cont) {
{
} }
/// <summary> /// <summary>
@ -94,7 +92,7 @@
/// <value> /// <value>
/// The search filter. /// The search filter.
/// </value> /// </value>
public IEnumerator SearchFilter => RfcFilter.GetFilterIterator(); public IEnumerator SearchFilter => this.RfcFilter.GetFilterIterator();
/// <summary> /// <summary>
/// Retrieves the Base DN for a search request. /// Retrieves the Base DN for a search request.
@ -102,7 +100,7 @@
/// <returns> /// <returns>
/// the base DN for a search request. /// the base DN for a search request.
/// </returns> /// </returns>
public string DN => Asn1Object.RequestDn; public String DN => this.Asn1Object.RequestDn;
/// <summary> /// <summary>
/// Retrieves the scope of a search request. /// Retrieves the scope of a search request.
@ -110,7 +108,7 @@
/// <value> /// <value>
/// The scope. /// The scope.
/// </value> /// </value>
public int Scope => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(1)).IntValue(); public Int32 Scope => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(1)).IntValue();
/// <summary> /// <summary>
/// Retrieves the behaviour of dereferencing aliases on a search request. /// Retrieves the behaviour of dereferencing aliases on a search request.
@ -118,7 +116,7 @@
/// <value> /// <value>
/// The dereference. /// The dereference.
/// </value> /// </value>
public int Dereference => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(2)).IntValue(); public Int32 Dereference => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(2)).IntValue();
/// <summary> /// <summary>
/// Retrieves the maximum number of entries to be returned on a search. /// Retrieves the maximum number of entries to be returned on a search.
@ -126,7 +124,7 @@
/// <value> /// <value>
/// The maximum results. /// The maximum results.
/// </value> /// </value>
public int MaxResults => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(3)).IntValue(); public Int32 MaxResults => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(3)).IntValue();
/// <summary> /// <summary>
/// Retrieves the server time limit for a search request. /// Retrieves the server time limit for a search request.
@ -134,7 +132,7 @@
/// <value> /// <value>
/// The server time limit. /// The server time limit.
/// </value> /// </value>
public int ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(4)).IntValue(); public Int32 ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(4)).IntValue();
/// <summary> /// <summary>
/// Retrieves whether attribute values or only attribute types(names) should /// Retrieves whether attribute values or only attribute types(names) should
@ -143,7 +141,7 @@
/// <value> /// <value>
/// <c>true</c> if [types only]; otherwise, <c>false</c>. /// <c>true</c> if [types only]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool TypesOnly => ((Asn1Boolean)((RfcSearchRequest)Asn1Object.Get(1)).Get(5)).BooleanValue(); public Boolean TypesOnly => ((Asn1Boolean)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(5)).BooleanValue();
/// <summary> /// <summary>
/// Retrieves an array of attribute names to request for in a search. /// Retrieves an array of attribute names to request for in a search.
@ -151,14 +149,11 @@
/// <value> /// <value>
/// The attributes. /// The attributes.
/// </value> /// </value>
public string[] Attributes public String[] Attributes {
{ get {
get RfcAttributeDescriptionList attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(7);
{ String[] values = new String[attrs.Size()];
var attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)Asn1Object.Get(1)).Get(7); for(Int32 i = 0; i < values.Length; i++) {
var values = new string[attrs.Size()];
for (var i = 0; i < values.Length; i++)
{
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue(); values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
} }
@ -172,7 +167,7 @@
/// <value> /// <value>
/// The string filter. /// The string filter.
/// </value> /// </value>
public string StringFilter => RfcFilter.FilterToString(); public String StringFilter => this.RfcFilter.FilterToString();
/// <summary> /// <summary>
/// Retrieves an SearchFilter object representing a filter for a search request. /// Retrieves an SearchFilter object representing a filter for a search request.
@ -180,6 +175,6 @@
/// <value> /// <value>
/// The RFC filter. /// The RFC filter.
/// </value> /// </value>
private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)Asn1Object.Get(1)).Get(6); private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(6);
} }
} }

View File

@ -1,29 +1,26 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// An LdapSearchResults object is returned from a synchronous search /// An LdapSearchResults object is returned from a synchronous search
/// operation. It provides access to all results received during the /// operation. It provides access to all results received during the
/// operation (entries and exceptions). /// operation (entries and exceptions).
/// </summary> /// </summary>
/// <seealso cref="LdapConnection.Search"></seealso> /// <seealso cref="LdapConnection.Search"></seealso>
public sealed class LdapSearchResults public sealed class LdapSearchResults {
{
private readonly List<RfcLdapMessage> _messages; private readonly List<RfcLdapMessage> _messages;
private readonly int _messageId; private readonly Int32 _messageId;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class. /// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
/// </summary> /// </summary>
/// <param name="messages">The messages.</param> /// <param name="messages">The messages.</param>
/// <param name="messageId">The message identifier.</param> /// <param name="messageId">The message identifier.</param>
internal LdapSearchResults(List<RfcLdapMessage> messages, int messageId) internal LdapSearchResults(List<RfcLdapMessage> messages, Int32 messageId) {
{ this._messages = messages;
_messages = messages; this._messageId = messageId;
_messageId = messageId;
} }
/// <summary> /// <summary>
@ -38,8 +35,8 @@
/// <value> /// <value>
/// The count. /// The count.
/// </value> /// </value>
public int Count => new List<RfcLdapMessage>(_messages) public Int32 Count => new List<RfcLdapMessage>(this._messages)
.Count(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult); .Count(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
/// <summary> /// <summary>
/// Reports if there are more search results. /// Reports if there are more search results.
@ -47,8 +44,8 @@
/// <returns> /// <returns>
/// true if there are more search results. /// true if there are more search results.
/// </returns> /// </returns>
public bool HasMore() => new List<RfcLdapMessage>(_messages) public Boolean HasMore() => new List<RfcLdapMessage>(this._messages)
.Any(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult); .Any(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
/// <summary> /// <summary>
/// Returns the next result as an LdapEntry. /// Returns the next result as an LdapEntry.
@ -60,18 +57,15 @@
/// The next search result as an LdapEntry. /// The next search result as an LdapEntry.
/// </returns> /// </returns>
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception> /// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
public LdapEntry Next() public LdapEntry Next() {
{ IEnumerable<RfcLdapMessage> list = new List<RfcLdapMessage>(this._messages)
var list = new List<RfcLdapMessage>(_messages) .Where(x => x.MessageId == this._messageId);
.Where(x => x.MessageId == _messageId);
foreach (var item in list) foreach(RfcLdapMessage item in list) {
{ _ = this._messages.Remove(item);
_messages.Remove(item); LdapMessage response = GetResponse(item);
var response = GetResponse(item);
if (response is LdapSearchResult result) if(response is LdapSearchResult result) {
{
return result.Entry; return result.Entry;
} }
} }
@ -79,10 +73,8 @@
throw new ArgumentOutOfRangeException(nameof(Next), "No more results"); throw new ArgumentOutOfRangeException(nameof(Next), "No more results");
} }
private static LdapMessage GetResponse(RfcLdapMessage item) private static LdapMessage GetResponse(RfcLdapMessage item) {
{ switch(item.Type) {
switch (item.Type)
{
case LdapOperation.SearchResponse: case LdapOperation.SearchResponse:
return new LdapSearchResult(item); return new LdapSearchResult(item);
case LdapOperation.SearchResultReference: case LdapOperation.SearchResultReference:

View File

@ -1,10 +1,8 @@
namespace Unosquare.Swan.Networking.Ldap namespace Unosquare.Swan.Networking.Ldap {
{
/// <summary> /// <summary>
/// LDAP Connection Status Code. /// LDAP Connection Status Code.
/// </summary> /// </summary>
public enum LdapStatusCode public enum LdapStatusCode {
{
/// <summary> /// <summary>
/// Indicates the requested client operation completed successfully. /// Indicates the requested client operation completed successfully.
/// SUCCESS = 0<p /> /// SUCCESS = 0<p />

View File

@ -1,21 +1,19 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// The class performs token processing from strings. /// The class performs token processing from strings.
/// </summary> /// </summary>
internal class Tokenizer internal class Tokenizer {
{
// The tokenizer uses the default delimiter set: the space character, the tab character, the newline character, and the carriage-return character // The tokenizer uses the default delimiter set: the space character, the tab character, the newline character, and the carriage-return character
private readonly string _delimiters = " \t\n\r"; private readonly String _delimiters = " \t\n\r";
private readonly bool _returnDelims; private readonly Boolean _returnDelims;
private List<string> _elements; private List<String> _elements;
private string _source; private String _source;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Tokenizer" /> class. /// Initializes a new instance of the <see cref="Tokenizer" /> class.
@ -25,96 +23,85 @@
/// <param name="source">String to tokenize.</param> /// <param name="source">String to tokenize.</param>
/// <param name="delimiters">String containing the delimiters.</param> /// <param name="delimiters">String containing the delimiters.</param>
/// <param name="retDel">if set to <c>true</c> [ret delete].</param> /// <param name="retDel">if set to <c>true</c> [ret delete].</param>
public Tokenizer(string source, string delimiters, bool retDel = false) public Tokenizer(String source, String delimiters, Boolean retDel = false) {
{ this._elements = new List<String>();
_elements = new List<string>(); this._delimiters = delimiters ?? this._delimiters;
_delimiters = delimiters ?? _delimiters; this._source = source;
_source = source; this._returnDelims = retDel;
_returnDelims = retDel; if(this._returnDelims) {
if (_returnDelims) this.Tokenize();
Tokenize(); } else {
else this._elements.AddRange(source.Split(this._delimiters.ToCharArray()));
_elements.AddRange(source.Split(_delimiters.ToCharArray()));
RemoveEmptyStrings();
} }
public int Count => _elements.Count; this.RemoveEmptyStrings();
}
public bool HasMoreTokens() => _elements.Count > 0; public Int32 Count => this._elements.Count;
public string NextToken() public Boolean HasMoreTokens() => this._elements.Count > 0;
{
if (_source == string.Empty) throw new InvalidOperationException();
string result; public String NextToken() {
if (_returnDelims) if(this._source == String.Empty) {
{ throw new InvalidOperationException();
RemoveEmptyStrings(); }
result = _elements[0];
_elements.RemoveAt(0); String result;
if(this._returnDelims) {
this.RemoveEmptyStrings();
result = this._elements[0];
this._elements.RemoveAt(0);
return result; return result;
} }
_elements = new List<string>(); this._elements = new List<String>();
_elements.AddRange(_source.Split(_delimiters.ToCharArray())); this._elements.AddRange(this._source.Split(this._delimiters.ToCharArray()));
RemoveEmptyStrings(); this.RemoveEmptyStrings();
result = _elements[0]; result = this._elements[0];
_elements.RemoveAt(0); this._elements.RemoveAt(0);
_source = _source.Remove(_source.IndexOf(result, StringComparison.Ordinal), result.Length); this._source = this._source.Remove(this._source.IndexOf(result, StringComparison.Ordinal), result.Length);
_source = _source.TrimStart(_delimiters.ToCharArray()); this._source = this._source.TrimStart(this._delimiters.ToCharArray());
return result; return result;
} }
private void RemoveEmptyStrings() private void RemoveEmptyStrings() {
{ for(Int32 index = 0; index < this._elements.Count; index++) {
for (var index = 0; index < _elements.Count; index++) if(this._elements[index] != String.Empty) {
{ continue;
if (_elements[index] != string.Empty) continue; }
_elements.RemoveAt(index); this._elements.RemoveAt(index);
index--; index--;
} }
} }
private void Tokenize() private void Tokenize() {
{ String tempstr = this._source;
var tempstr = _source; if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length > 0) {
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length > 0) this._elements.Add(tempstr);
{ } else if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length <= 0) {
_elements.Add(tempstr);
}
else if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length <= 0)
{
return; return;
} }
while (tempstr.IndexOfAny(_delimiters.ToCharArray()) >= 0) while(tempstr.IndexOfAny(this._delimiters.ToCharArray()) >= 0) {
{ if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) == 0) {
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) == 0) if(tempstr.Length > 1) {
{ this._elements.Add(tempstr.Substring(0, 1));
if (tempstr.Length > 1)
{
_elements.Add(tempstr.Substring(0, 1));
tempstr = tempstr.Substring(1); tempstr = tempstr.Substring(1);
} else {
tempstr = String.Empty;
} }
else } else {
{ String toks = tempstr.Substring(0, tempstr.IndexOfAny(this._delimiters.ToCharArray()));
tempstr = string.Empty; this._elements.Add(toks);
} this._elements.Add(tempstr.Substring(toks.Length, 1));
}
else
{
var toks = tempstr.Substring(0, tempstr.IndexOfAny(_delimiters.ToCharArray()));
_elements.Add(toks);
_elements.Add(tempstr.Substring(toks.Length, 1));
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : string.Empty; tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : String.Empty;
} }
} }
if (tempstr.Length > 0) if(tempstr.Length > 0) {
{ this._elements.Add(tempstr);
_elements.Add(tempstr);
} }
} }
} }
@ -129,26 +116,28 @@
/// dnAttributes [4] BOOLEAN DEFAULT FALSE } /// dnAttributes [4] BOOLEAN DEFAULT FALSE }
/// </pre></summary> /// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcMatchingRuleAssertion : Asn1Sequence internal class RfcMatchingRuleAssertion : Asn1Sequence {
{
public RfcMatchingRuleAssertion( public RfcMatchingRuleAssertion(
string matchingRule, String matchingRule,
string type, String type,
sbyte[] matchValue, SByte[] matchValue,
Asn1Boolean dnAttributes = null) Asn1Boolean dnAttributes = null)
: base(4) : base(4) {
{ if(matchingRule != null) {
if (matchingRule != null) this.Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false));
Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false)); }
if (type != null)
Add(new Asn1Tagged(new Asn1Identifier(2), new Asn1OctetString(type), false));
Add(new Asn1Tagged(new Asn1Identifier(3), new Asn1OctetString(matchValue), false)); if(type != null) {
this.Add(new Asn1Tagged(new Asn1Identifier(2), new Asn1OctetString(type), false));
}
this.Add(new Asn1Tagged(new Asn1Identifier(3), new Asn1OctetString(matchValue), false));
// if dnAttributes if false, that is the default value and we must not // if dnAttributes if false, that is the default value and we must not
// encode it. (See RFC 2251 5.1 number 4) // encode it. (See RFC 2251 5.1 number 4)
if (dnAttributes != null && dnAttributes.BooleanValue()) if(dnAttributes != null && dnAttributes.BooleanValue()) {
Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false)); this.Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false));
}
} }
} }
@ -160,16 +149,15 @@
/// AttributeDescription /// AttributeDescription
/// </pre></summary> /// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcAttributeDescriptionList : Asn1SequenceOf internal class RfcAttributeDescriptionList : Asn1SequenceOf {
{ public RfcAttributeDescriptionList(String[] attrs)
public RfcAttributeDescriptionList(string[] attrs) : base(attrs?.Length ?? 0) {
: base(attrs?.Length ?? 0) if(attrs == null) {
{ return;
if (attrs == null) return; }
foreach (var attr in attrs) foreach(String attr in attrs) {
{ this.Add(attr);
Add(attr);
} }
} }
} }
@ -197,32 +185,30 @@
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
internal class RfcSearchRequest : Asn1Sequence, IRfcRequest internal class RfcSearchRequest : Asn1Sequence, IRfcRequest {
{
public RfcSearchRequest( public RfcSearchRequest(
string basePath, String basePath,
LdapScope scope, LdapScope scope,
int derefAliases, Int32 derefAliases,
int sizeLimit, Int32 sizeLimit,
int timeLimit, Int32 timeLimit,
bool typesOnly, Boolean typesOnly,
string filter, String filter,
string[] attributes) String[] attributes)
: base(8) : base(8) {
{ this.Add(basePath);
Add(basePath); this.Add(new Asn1Enumerated(scope));
Add(new Asn1Enumerated(scope)); this.Add(new Asn1Enumerated(derefAliases));
Add(new Asn1Enumerated(derefAliases)); this.Add(new Asn1Integer(sizeLimit));
Add(new Asn1Integer(sizeLimit)); this.Add(new Asn1Integer(timeLimit));
Add(new Asn1Integer(timeLimit)); this.Add(new Asn1Boolean(typesOnly));
Add(new Asn1Boolean(typesOnly)); this.Add(new RfcFilter(filter));
Add(new RfcFilter(filter)); this.Add(new RfcAttributeDescriptionList(attributes));
Add(new RfcAttributeDescriptionList(attributes));
} }
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest);
public string GetRequestDN() => ((Asn1OctetString) Get(0)).StringValue(); public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
} }
/// <summary> /// <summary>
@ -238,13 +224,11 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcSubstringFilter : Asn1Sequence internal class RfcSubstringFilter : Asn1Sequence {
{ public RfcSubstringFilter(String type, Asn1Object substrings)
public RfcSubstringFilter(string type, Asn1Object substrings) : base(2) {
: base(2) this.Add(type);
{ this.Add(substrings);
Add(type);
Add(substrings);
} }
} }
@ -257,23 +241,20 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcAttributeValueAssertion : Asn1Sequence internal class RfcAttributeValueAssertion : Asn1Sequence {
{ public RfcAttributeValueAssertion(String ad, SByte[] av)
public RfcAttributeValueAssertion(string ad, sbyte[] av) : base(2) {
: base(2) this.Add(ad);
{ this.Add(new Asn1OctetString(av));
Add(ad);
Add(new Asn1OctetString(av));
} }
public string AttributeDescription => ((Asn1OctetString) Get(0)).StringValue(); public String AttributeDescription => ((Asn1OctetString)this.Get(0)).StringValue();
public sbyte[] AssertionValue => ((Asn1OctetString) Get(1)).ByteValue(); public SByte[] AssertionValue => ((Asn1OctetString)this.Get(1)).ByteValue();
} }
/// <summary> Encapsulates an Ldap Bind properties.</summary> /// <summary> Encapsulates an Ldap Bind properties.</summary>
internal class BindProperties internal class BindProperties {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BindProperties" /> class. /// Initializes a new instance of the <see cref="BindProperties" /> class.
/// </summary> /// </summary>
@ -282,23 +263,30 @@
/// <param name="method">The method.</param> /// <param name="method">The method.</param>
/// <param name="anonymous">if set to <c>true</c> [anonymous].</param> /// <param name="anonymous">if set to <c>true</c> [anonymous].</param>
public BindProperties( public BindProperties(
int version, Int32 version,
string dn, String dn,
string method, String method,
bool anonymous) Boolean anonymous) {
{ this.ProtocolVersion = version;
ProtocolVersion = version; this.AuthenticationDN = dn;
AuthenticationDN = dn; this.AuthenticationMethod = method;
AuthenticationMethod = method; this.Anonymous = anonymous;
Anonymous = anonymous;
} }
public int ProtocolVersion { get; } public Int32 ProtocolVersion {
get;
}
public string AuthenticationDN { get; } public String AuthenticationDN {
get;
}
public string AuthenticationMethod { get; } public String AuthenticationMethod {
get;
}
public bool Anonymous { get; } public Boolean Anonymous {
get;
}
} }
} }

View File

@ -1,5 +1,5 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents an Ldap Control. /// Represents an Ldap Control.
/// <pre> /// <pre>
@ -10,8 +10,7 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcControl : Asn1Sequence internal class RfcControl : Asn1Sequence {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcControl"/> class. /// Initializes a new instance of the <see cref="RfcControl"/> class.
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part /// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
@ -21,66 +20,58 @@
/// <param name="controlType">Type of the control.</param> /// <param name="controlType">Type of the control.</param>
/// <param name="criticality">The criticality.</param> /// <param name="criticality">The criticality.</param>
/// <param name="controlValue">The control value.</param> /// <param name="controlValue">The control value.</param>
public RfcControl(string controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null) public RfcControl(String controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null)
: base(3) : base(3) {
{ this.Add(controlType);
Add(controlType); this.Add(criticality ?? new Asn1Boolean(false));
Add(criticality ?? new Asn1Boolean(false));
if (controlValue != null) if(controlValue != null) {
Add(controlValue); this.Add(controlValue);
}
} }
public RfcControl(Asn1Structured seqObj) public RfcControl(Asn1Structured seqObj)
: base(3) : base(3) {
{ for(Int32 i = 0; i < seqObj.Size(); i++) {
for (var i = 0; i < seqObj.Size(); i++) this.Add(seqObj.Get(i));
Add(seqObj.Get(i)); }
} }
public Asn1OctetString ControlType => (Asn1OctetString)Get(0); public Asn1OctetString ControlType => (Asn1OctetString)this.Get(0);
public Asn1Boolean Criticality => Size() > 1 && Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false); public Asn1Boolean Criticality => this.Size() > 1 && this.Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
public Asn1OctetString ControlValue public Asn1OctetString ControlValue {
{ get {
get if(this.Size() > 2) {
{
if (Size() > 2)
{
// MUST be a control value // MUST be a control value
return (Asn1OctetString)Get(2); return (Asn1OctetString)this.Get(2);
} }
return Size() > 1 && Get(1) is Asn1OctetString s ? s : null; return this.Size() > 1 && this.Get(1) is Asn1OctetString s ? s : null;
} }
set set {
{ if(value == null) {
if (value == null)
return; return;
}
if (Size() == 3) if(this.Size() == 3) {
{
// We already have a control value, replace it // We already have a control value, replace it
Set(2, value); this.Set(2, value);
return; return;
} }
if (Size() == 2) if(this.Size() == 2) {
{
// Get the second element // Get the second element
var obj = Get(1); Asn1Object obj = this.Get(1);
// Is this a control value // Is this a control value
if (obj is Asn1OctetString) if(obj is Asn1OctetString) {
{
// replace this one // replace this one
Set(1, value); this.Set(1, value);
} } else {
else
{
// add a new one at the end // add a new one at the end
Add(value); this.Add(value);
} }
} }
} }
@ -95,14 +86,13 @@
/// credentials OCTET STRING OPTIONAL } /// credentials OCTET STRING OPTIONAL }
/// </pre></summary> /// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal class RfcSaslCredentials : Asn1Sequence internal class RfcSaslCredentials : Asn1Sequence {
{ public RfcSaslCredentials(String mechanism, SByte[] credentials = null)
public RfcSaslCredentials(string mechanism, sbyte[] credentials = null) : base(2) {
: base(2) this.Add(mechanism);
{ if(credentials != null) {
Add(mechanism); this.Add(new Asn1OctetString(credentials));
if (credentials != null) }
Add(new Asn1OctetString(credentials));
} }
} }
@ -115,16 +105,13 @@
/// sasl [3] SaslCredentials } /// sasl [3] SaslCredentials }
/// </pre></summary> /// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" />
internal class RfcAuthenticationChoice : Asn1Choice internal class RfcAuthenticationChoice : Asn1Choice {
{ public RfcAuthenticationChoice(SByte[] passwd)
public RfcAuthenticationChoice(sbyte[] passwd) : base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false)) {
: base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false))
{
} }
public RfcAuthenticationChoice(string mechanism, sbyte[] credentials) public RfcAuthenticationChoice(String mechanism, SByte[] credentials)
: base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false)) : base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false)) {
{
// implicit tagging // implicit tagging
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,47 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Encapsulates a single search result that is in response to an asynchronous /// Encapsulates a single search result that is in response to an asynchronous
/// search operation. /// search operation.
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
internal class LdapSearchResult : LdapMessage internal class LdapSearchResult : LdapMessage {
{
private LdapEntry _entry; private LdapEntry _entry;
internal LdapSearchResult(RfcLdapMessage message) internal LdapSearchResult(RfcLdapMessage message)
: base(message) : base(message) {
{
} }
public LdapEntry Entry public LdapEntry Entry {
{ get {
get if(this._entry != null) {
{ return this._entry;
if (_entry != null) return _entry; }
var attrs = new LdapAttributeSet(); LdapAttributeSet attrs = new LdapAttributeSet();
var entry = (RfcSearchResultEntry) Message.Response; RfcSearchResultEntry entry = (RfcSearchResultEntry)this.Message.Response;
foreach (var o in entry.Attributes.ToArray()) foreach(Asn1Object o in entry.Attributes.ToArray()) {
{ Asn1Sequence seq = (Asn1Sequence)o;
var seq = (Asn1Sequence) o; LdapAttribute attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
var attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue()); Asn1Set set = (Asn1Set)seq.Get(1);
var set = (Asn1Set)seq.Get(1);
foreach (var t in set.ToArray()) foreach(Asn1Object t in set.ToArray()) {
{
attr.AddValue(((Asn1OctetString)t).ByteValue()); attr.AddValue(((Asn1OctetString)t).ByteValue());
} }
attrs.Add(attr); _ = attrs.Add(attr);
} }
_entry = new LdapEntry(entry.ObjectName, attrs); this._entry = new LdapEntry(entry.ObjectName, attrs);
return _entry; return this._entry;
} }
} }
public override string ToString() => _entry?.ToString() ?? base.ToString(); public override String ToString() => this._entry?.ToString() ?? base.ToString();
} }
/// <summary> /// <summary>
@ -55,8 +51,7 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcSearchResultReference : Asn1SequenceOf internal class RfcSearchResultReference : Asn1SequenceOf {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class. /// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
/// The only time a client will create a SearchResultReference is when it is /// The only time a client will create a SearchResultReference is when it is
@ -64,9 +59,8 @@
/// </summary> /// </summary>
/// <param name="stream">The streab.</param> /// <param name="stream">The streab.</param>
/// <param name="len">The length.</param> /// <param name="len">The length.</param>
public RfcSearchResultReference(Stream stream, int len) public RfcSearchResultReference(Stream stream, Int32 len)
: base(stream, len) : base(stream, len) {
{
} }
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
@ -83,15 +77,14 @@
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse {
{ public const Int32 ResponseNameCode = 10;
public const int ResponseNameCode = 10;
public const int ResponseCode = 11; public const Int32 ResponseCode = 11;
private readonly int _referralIndex; private readonly Int32 _referralIndex;
private readonly int _responseNameIndex; private readonly Int32 _responseNameIndex;
private readonly int _responseIndex; private readonly Int32 _responseIndex;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class. /// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class.
@ -100,50 +93,50 @@
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="len">The length.</param> /// <param name="len">The length.</param>
public RfcExtendedResponse(Stream stream, int len) public RfcExtendedResponse(Stream stream, Int32 len)
: base(stream, len) : base(stream, len) {
{ if(this.Size() <= 3) {
if (Size() <= 3) return; return;
}
for (var i = 3; i < Size(); i++) for(Int32 i = 3; i < this.Size(); i++) {
{ Asn1Tagged obj = (Asn1Tagged)this.Get(i);
var obj = (Asn1Tagged) Get(i); Asn1Identifier id = obj.GetIdentifier();
var id = obj.GetIdentifier();
switch (id.Tag) switch(id.Tag) {
{
case RfcLdapResult.Referral: case RfcLdapResult.Referral:
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue(); SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
using (var bais = new MemoryStream(content.ToByteArray())) using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
Set(i, new Asn1SequenceOf(bais, content.Length)); this.Set(i, new Asn1SequenceOf(bais, content.Length));
}
_referralIndex = i; this._referralIndex = i;
break; break;
case ResponseNameCode: case ResponseNameCode:
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue())); this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
_responseNameIndex = i; this._responseNameIndex = i;
break; break;
case ResponseCode: case ResponseCode:
Set(i, obj.TaggedValue); this.Set(i, obj.TaggedValue);
_responseIndex = i; this._responseIndex = i;
break; break;
} }
} }
} }
public Asn1OctetString ResponseName => _responseNameIndex != 0 ? (Asn1OctetString) Get(_responseNameIndex) : null; public Asn1OctetString ResponseName => this._responseNameIndex != 0 ? (Asn1OctetString)this.Get(this._responseNameIndex) : null;
public Asn1OctetString Response => _responseIndex != 0 ? (Asn1OctetString) Get(_responseIndex) : null; public Asn1OctetString Response => this._responseIndex != 0 ? (Asn1OctetString)this.Get(this._responseIndex) : null;
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0); public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()); public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()); public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
public Asn1SequenceOf GetReferral() public Asn1SequenceOf GetReferral()
=> _referralIndex != 0 ? (Asn1SequenceOf) Get(_referralIndex) : null; => this._referralIndex != 0 ? (Asn1SequenceOf)this.Get(this._referralIndex) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse);
} }
@ -158,8 +151,7 @@
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcBindResponse : Asn1Sequence, IRfcResponse internal class RfcBindResponse : Asn1Sequence, IRfcResponse {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcBindResponse"/> class. /// Initializes a new instance of the <see cref="RfcBindResponse"/> class.
/// The only time a client will create a BindResponse is when it is /// The only time a client will create a BindResponse is when it is
@ -169,29 +161,33 @@
/// </summary> /// </summary>
/// <param name="stream">The in renamed.</param> /// <param name="stream">The in renamed.</param>
/// <param name="len">The length.</param> /// <param name="len">The length.</param>
public RfcBindResponse(Stream stream, int len) public RfcBindResponse(Stream stream, Int32 len)
: base(stream, len) : base(stream, len) {
{
// Decode optional referral from Asn1OctetString to Referral. // Decode optional referral from Asn1OctetString to Referral.
if (Size() <= 3) return; if(this.Size() <= 3) {
return;
var obj = (Asn1Tagged) Get(3);
if (obj.GetIdentifier().Tag != RfcLdapResult.Referral) return;
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
using (var bais = new MemoryStream(content.ToByteArray()))
Set(3, new Asn1SequenceOf(bais, content.Length));
} }
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0); Asn1Tagged obj = (Asn1Tagged)this.Get(3);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()); if(obj.GetIdentifier().Tag != RfcLdapResult.Referral) {
return;
}
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()); SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
public Asn1SequenceOf GetReferral() => Size() > 3 && Get(3) is Asn1SequenceOf ? (Asn1SequenceOf) Get(3) : null; using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
this.Set(3, new Asn1SequenceOf(bais, content.Length));
}
}
public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
public Asn1SequenceOf GetReferral() => this.Size() > 3 && this.Get(3) is Asn1SequenceOf ? (Asn1SequenceOf)this.Get(3) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
} }
@ -206,40 +202,36 @@
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse {
{ public const Int32 TagResponseName = 0;
public const int TagResponseName = 0; public const Int32 TagResponse = 1;
public const int TagResponse = 1;
public RfcIntermediateResponse(Stream stream, int len) public RfcIntermediateResponse(Stream stream, Int32 len)
: base(stream, len) : base(stream, len) {
{ Int32 i = this.Size() >= 3 ? 3 : 0;
var i = Size() >= 3 ? 3 : 0;
for (; i < Size(); i++) for(; i < this.Size(); i++) {
{ Asn1Tagged obj = (Asn1Tagged)this.Get(i);
var obj = (Asn1Tagged) Get(i);
switch (obj.GetIdentifier().Tag) switch(obj.GetIdentifier().Tag) {
{
case TagResponseName: case TagResponseName:
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue())); this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
break; break;
case TagResponse: case TagResponse:
Set(i, obj.TaggedValue); this.Set(i, obj.TaggedValue);
break; break;
} }
} }
} }
public Asn1Enumerated GetResultCode() => Size() > 3 ? (Asn1Enumerated) Get(0) : null; public Asn1Enumerated GetResultCode() => this.Size() > 3 ? (Asn1Enumerated)this.Get(0) : null;
public Asn1OctetString GetMatchedDN() => Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()) : null; public Asn1OctetString GetMatchedDN() => this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue()) : null;
public Asn1OctetString GetErrorMessage() => public Asn1OctetString GetErrorMessage() =>
Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()) : null; this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue()) : null;
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null; public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
} }

View File

@ -1,8 +1,7 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents an Ldap Message. /// Represents an Ldap Message.
/// <pre> /// <pre>
@ -39,8 +38,7 @@
/// isn't arbitrarily run up.). /// isn't arbitrarily run up.).
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal sealed class RfcLdapMessage : Asn1Sequence internal sealed class RfcLdapMessage : Asn1Sequence {
{
private readonly Asn1Object _op; private readonly Asn1Object _op;
/// <summary> /// <summary>
@ -50,18 +48,17 @@
/// <param name="op">The op.</param> /// <param name="op">The op.</param>
/// <param name="controls">The controls.</param> /// <param name="controls">The controls.</param>
public RfcLdapMessage(IRfcRequest op, RfcControls controls) public RfcLdapMessage(IRfcRequest op, RfcControls controls)
: base(3) : base(3) {
{ this._op = (Asn1Object)op;
_op = (Asn1Object) op;
Add(new RfcMessageID()); // MessageID has static counter this.Add(new RfcMessageID()); // MessageID has static counter
Add((Asn1Object) op); this.Add((Asn1Object)op);
if (controls != null) if(controls != null) {
{ this.Add(controls);
Add(controls);
} }
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class. /// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
/// Will decode an RfcLdapMessage directly from an InputStream. /// Will decode an RfcLdapMessage directly from an InputStream.
@ -69,43 +66,42 @@
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="len">The length.</param> /// <param name="len">The length.</param>
/// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception> /// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
public RfcLdapMessage(Stream stream, int len) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
: base(stream, len) public RfcLdapMessage(Stream stream, Int32 len)
{ : base(stream, len) {
// Decode implicitly tagged protocol operation from an Asn1Tagged type // Decode implicitly tagged protocol operation from an Asn1Tagged type
// to its appropriate application type. // to its appropriate application type.
var protocolOp = (Asn1Tagged) Get(1); Asn1Tagged protocolOp = (Asn1Tagged)this.Get(1);
var protocolOpId = protocolOp.GetIdentifier(); Asn1Identifier protocolOpId = protocolOp.GetIdentifier();
var content = ((Asn1OctetString) protocolOp.TaggedValue).ByteValue(); SByte[] content = ((Asn1OctetString)protocolOp.TaggedValue).ByteValue();
var bais = new MemoryStream(content.ToByteArray()); MemoryStream bais = new MemoryStream(content.ToByteArray());
switch ((LdapOperation) protocolOpId.Tag) switch((LdapOperation)protocolOpId.Tag) {
{
case LdapOperation.SearchResponse: case LdapOperation.SearchResponse:
Set(1, new RfcSearchResultEntry(bais, content.Length)); this.Set(1, new RfcSearchResultEntry(bais, content.Length));
break; break;
case LdapOperation.SearchResult: case LdapOperation.SearchResult:
Set(1, new RfcSearchResultDone(bais, content.Length)); this.Set(1, new RfcSearchResultDone(bais, content.Length));
break; break;
case LdapOperation.SearchResultReference: case LdapOperation.SearchResultReference:
Set(1, new RfcSearchResultReference(bais, content.Length)); this.Set(1, new RfcSearchResultReference(bais, content.Length));
break; break;
case LdapOperation.BindResponse: case LdapOperation.BindResponse:
Set(1, new RfcBindResponse(bais, content.Length)); this.Set(1, new RfcBindResponse(bais, content.Length));
break; break;
case LdapOperation.ExtendedResponse: case LdapOperation.ExtendedResponse:
Set(1, new RfcExtendedResponse(bais, content.Length)); this.Set(1, new RfcExtendedResponse(bais, content.Length));
break; break;
case LdapOperation.IntermediateResponse: case LdapOperation.IntermediateResponse:
Set(1, new RfcIntermediateResponse(bais, content.Length)); this.Set(1, new RfcIntermediateResponse(bais, content.Length));
break; break;
case LdapOperation.ModifyResponse: case LdapOperation.ModifyResponse:
Set(1, new RfcModifyResponse(bais, content.Length)); this.Set(1, new RfcModifyResponse(bais, content.Length));
break; break;
default: default:
@ -114,29 +110,34 @@
// decode optional implicitly tagged controls from Asn1Tagged type to // decode optional implicitly tagged controls from Asn1Tagged type to
// to RFC 2251 types. // to RFC 2251 types.
if (Size() <= 2) return; if(this.Size() <= 2) {
return;
var controls = (Asn1Tagged) Get(2);
content = ((Asn1OctetString) controls.TaggedValue).ByteValue();
using (var ms = new MemoryStream(content.ToByteArray()))
Set(2, new RfcControls(ms, content.Length));
} }
public int MessageId => ((Asn1Integer) Get(0)).IntValue(); Asn1Tagged controls = (Asn1Tagged)this.Get(2);
content = ((Asn1OctetString)controls.TaggedValue).ByteValue();
using(MemoryStream ms = new MemoryStream(content.ToByteArray())) {
this.Set(2, new RfcControls(ms, content.Length));
}
}
public Int32 MessageId => ((Asn1Integer)this.Get(0)).IntValue();
/// <summary> Returns this RfcLdapMessage's message type.</summary> /// <summary> Returns this RfcLdapMessage's message type.</summary>
public LdapOperation Type => (LdapOperation) Get(1).GetIdentifier().Tag; public LdapOperation Type => (LdapOperation)this.Get(1).GetIdentifier().Tag;
public Asn1Object Response => Get(1); public Asn1Object Response => this.Get(1);
public string RequestDn => ((IRfcRequest) _op).GetRequestDN(); public String RequestDn => ((IRfcRequest)this._op).GetRequestDN();
public LdapMessage RequestingMessage { get; set; } public LdapMessage RequestingMessage {
get; set;
}
public IRfcRequest GetRequest() => (IRfcRequest) Get(1); public IRfcRequest GetRequest() => (IRfcRequest)this.Get(1);
public bool IsRequest() => Get(1) is IRfcRequest; public Boolean IsRequest() => this.Get(1) is IRfcRequest;
} }
/// <summary> /// <summary>
@ -146,29 +147,25 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
internal class RfcControls : Asn1SequenceOf internal class RfcControls : Asn1SequenceOf {
{ public const Int32 Controls = 0;
public const int Controls = 0;
public RfcControls() public RfcControls()
: base(5) : base(5) {
{
} }
public RfcControls(Stream stream, int len) public RfcControls(Stream stream, Int32 len)
: base(stream, len) : base(stream, len) {
{
// Convert each SEQUENCE element to a Control // Convert each SEQUENCE element to a Control
for (var i = 0; i < Size(); i++) for(Int32 i = 0; i < this.Size(); i++) {
{ RfcControl tempControl = new RfcControl((Asn1Sequence)this.Get(i));
var tempControl = new RfcControl((Asn1Sequence) Get(i)); this.Set(i, tempControl);
Set(i, tempControl);
} }
} }
public void Add(RfcControl control) => base.Add(control); public void Add(RfcControl control) => base.Add(control);
public void Set(int index, RfcControl control) => base.Set(index, control); public void Set(Int32 index, RfcControl control) => base.Set(index, control);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
} }
@ -179,8 +176,7 @@
/// If the protocol operation of the RfcLdapMessage is of this type, /// If the protocol operation of the RfcLdapMessage is of this type,
/// it contains at least an RfcLdapResult. /// it contains at least an RfcLdapResult.
/// </summary> /// </summary>
internal interface IRfcResponse internal interface IRfcResponse {
{
/// <summary> /// <summary>
/// Gets the result code. /// Gets the result code.
/// </summary> /// </summary>
@ -210,13 +206,12 @@
/// This interface represents Protocol Operations that are requests from a /// This interface represents Protocol Operations that are requests from a
/// client. /// client.
/// </summary> /// </summary>
internal interface IRfcRequest internal interface IRfcRequest {
{
/// <summary> /// <summary>
/// Builds a new request using the data from the this object. /// Builds a new request using the data from the this object.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String" />.</returns> /// <returns>A <see cref="System.String" />.</returns>
string GetRequestDN(); String GetRequestDN();
} }
/// <summary> /// <summary>
@ -278,32 +273,35 @@
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
internal class RfcLdapResult : Asn1Sequence, IRfcResponse internal class RfcLdapResult : Asn1Sequence, IRfcResponse {
{ public const Int32 Referral = 3;
public const int Referral = 3;
public RfcLdapResult(Stream stream, int len) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
: base(stream, len) public RfcLdapResult(Stream stream, Int32 len)
{ : base(stream, len) {
// Decode optional referral from Asn1OctetString to Referral. // Decode optional referral from Asn1OctetString to Referral.
if (Size() <= 3) return; if(this.Size() <= 3) {
return;
var obj = (Asn1Tagged) Get(3);
var id = obj.GetIdentifier();
if (id.Tag != Referral) return;
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), content.Length));
} }
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0); Asn1Tagged obj = (Asn1Tagged)this.Get(3);
Asn1Identifier id = obj.GetIdentifier();
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()); if(id.Tag != Referral) {
return;
}
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()); SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
this.Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), content.Length));
}
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null; public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
} }
/// <summary> /// <summary>
@ -313,11 +311,9 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" />
internal class RfcSearchResultDone : RfcLdapResult internal class RfcSearchResultDone : RfcLdapResult {
{ public RfcSearchResultDone(Stream stream, Int32 len)
public RfcSearchResultDone(Stream stream, int len) : base(stream, len) {
: base(stream, len)
{
} }
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult);
@ -332,16 +328,14 @@
/// </pre> /// </pre>
/// </summary> /// </summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
internal sealed class RfcSearchResultEntry : Asn1Sequence internal sealed class RfcSearchResultEntry : Asn1Sequence {
{ public RfcSearchResultEntry(Stream stream, Int32 len)
public RfcSearchResultEntry(Stream stream, int len) : base(stream, len) {
: base(stream, len)
{
} }
public string ObjectName => ((Asn1OctetString) Get(0)).StringValue(); public String ObjectName => ((Asn1OctetString)this.Get(0)).StringValue();
public Asn1Sequence Attributes => (Asn1Sequence) Get(1); public Asn1Sequence Attributes => (Asn1Sequence)this.Get(1);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse);
} }
@ -359,10 +353,9 @@
/// arbitrarily run up.) /// arbitrarily run up.)
/// </pre></summary> /// </pre></summary>
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" />
internal class RfcMessageID : Asn1Integer internal class RfcMessageID : Asn1Integer {
{ private static Int32 _messageId;
private static int _messageId; private static readonly Object SyncRoot = new Object();
private static readonly object SyncRoot = new object();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RfcMessageID"/> class. /// Initializes a new instance of the <see cref="RfcMessageID"/> class.
@ -372,17 +365,13 @@
/// start the messages with one. /// start the messages with one.
/// </summary> /// </summary>
protected internal RfcMessageID() protected internal RfcMessageID()
: base(MessageId) : base(MessageId) {
{
} }
private static int MessageId private static Int32 MessageId {
{ get {
get lock(SyncRoot) {
{ return _messageId < Int32.MaxValue ? ++_messageId : (_messageId = 1);
lock (SyncRoot)
{
return _messageId < int.MaxValue ? ++_messageId : (_messageId = 1);
} }
} }
} }

View File

@ -1,7 +1,7 @@
namespace Unosquare.Swan.Networking.Ldap using System;
{ using System.IO;
using System.IO;
namespace Unosquare.Swan.Networking.Ldap {
/// <summary> /// <summary>
/// Represents an Ldap Modify Request. /// Represents an Ldap Modify Request.
/// <pre> /// <pre>
@ -18,27 +18,23 @@
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
internal sealed class RfcModifyRequest internal sealed class RfcModifyRequest
: Asn1Sequence, IRfcRequest : Asn1Sequence, IRfcRequest {
{ public RfcModifyRequest(String obj, Asn1SequenceOf modification)
public RfcModifyRequest(string obj, Asn1SequenceOf modification) : base(2) {
: base(2) this.Add(obj);
{ this.Add(modification);
Add(obj);
Add(modification);
} }
public Asn1SequenceOf Modifications => (Asn1SequenceOf)Get(1); public Asn1SequenceOf Modifications => (Asn1SequenceOf)this.Get(1);
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
public string GetRequestDN() => ((Asn1OctetString)Get(0)).StringValue(); public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
} }
internal class RfcModifyResponse : RfcLdapResult internal class RfcModifyResponse : RfcLdapResult {
{ public RfcModifyResponse(Stream stream, Int32 len)
public RfcModifyResponse(Stream stream, int len) : base(stream, len) {
: base(stream, len)
{
} }
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);

View File

@ -1,21 +1,20 @@
namespace Unosquare.Swan.Networking using System.Threading;
{ using System;
using System.Threading; using System.Linq;
using System; using System.Net;
using System.Linq; using System.Net.Sockets;
using System.Net; using System.Security;
using System.Net.Sockets; using System.Text;
using System.Security; using System.Net.Security;
using System.Text; using System.Threading.Tasks;
using System.Net.Security; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Collections.Generic;
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
using System.Net.Mail; using System.Net.Mail;
#else #else
using Exceptions; using Exceptions;
#endif #endif
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a basic SMTP client that is capable of submitting messages to an SMTP server. /// Represents a basic SMTP client that is capable of submitting messages to an SMTP server.
/// </summary> /// </summary>
@ -93,19 +92,17 @@
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public class SmtpClient public class SmtpClient {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SmtpClient" /> class. /// Initializes a new instance of the <see cref="SmtpClient" /> class.
/// </summary> /// </summary>
/// <param name="host">The host.</param> /// <param name="host">The host.</param>
/// <param name="port">The port.</param> /// <param name="port">The port.</param>
/// <exception cref="ArgumentNullException">host.</exception> /// <exception cref="ArgumentNullException">host.</exception>
public SmtpClient(string host, int port) public SmtpClient(String host, Int32 port) {
{ this.Host = host ?? throw new ArgumentNullException(nameof(host));
Host = host ?? throw new ArgumentNullException(nameof(host)); this.Port = port;
Port = port; this.ClientHostname = Network.HostName;
ClientHostname = Network.HostName;
} }
/// <summary> /// <summary>
@ -114,7 +111,9 @@
/// <value> /// <value>
/// The credentials. /// The credentials.
/// </value> /// </value>
public NetworkCredential Credentials { get; set; } public NetworkCredential Credentials {
get; set;
}
/// <summary> /// <summary>
/// Gets the host. /// Gets the host.
@ -122,7 +121,9 @@
/// <value> /// <value>
/// The host. /// The host.
/// </value> /// </value>
public string Host { get; } public String Host {
get;
}
/// <summary> /// <summary>
/// Gets the port. /// Gets the port.
@ -130,7 +131,9 @@
/// <value> /// <value>
/// The port. /// The port.
/// </value> /// </value>
public int Port { get; } public Int32 Port {
get;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the SSL is enabled. /// Gets or sets a value indicating whether the SSL is enabled.
@ -139,7 +142,9 @@
/// <value> /// <value>
/// <c>true</c> if [enable SSL]; otherwise, <c>false</c>. /// <c>true</c> if [enable SSL]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool EnableSsl { get; set; } public Boolean EnableSsl {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the name of the client that gets announced to the server. /// Gets or sets the name of the client that gets announced to the server.
@ -147,7 +152,9 @@
/// <value> /// <value>
/// The client hostname. /// The client hostname.
/// </value> /// </value>
public string ClientHostname { get; set; } public String ClientHostname {
get; set;
}
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
@ -161,37 +168,35 @@
/// A task that represents the asynchronous of send email operation. /// A task that represents the asynchronous of send email operation.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">message.</exception> /// <exception cref="ArgumentNullException">message.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public Task SendMailAsync( public Task SendMailAsync(
MailMessage message, MailMessage message,
string sessionId = null, String sessionId = null,
CancellationToken ct = default, CancellationToken ct = default,
RemoteCertificateValidationCallback callback = null) RemoteCertificateValidationCallback callback = null) {
{ if(message == null) {
if (message == null)
throw new ArgumentNullException(nameof(message)); throw new ArgumentNullException(nameof(message));
}
var state = new SmtpSessionState SmtpSessionState state = new SmtpSessionState {
{ AuthMode = this.Credentials == null ? String.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
AuthMode = Credentials == null ? string.Empty : SmtpDefinitions.SmtpAuthMethods.Login,
ClientHostname = ClientHostname, ClientHostname = ClientHostname,
IsChannelSecure = EnableSsl, IsChannelSecure = EnableSsl,
SenderAddress = message.From.Address, SenderAddress = message.From.Address,
}; };
if (Credentials != null) if(this.Credentials != null) {
{ state.Username = this.Credentials.UserName;
state.Username = Credentials.UserName; state.Password = this.Credentials.Password;
state.Password = Credentials.Password;
} }
foreach (var recipient in message.To) foreach(MailAddress recipient in message.To) {
{
state.Recipients.Add(recipient.Address); state.Recipients.Add(recipient.Address);
} }
state.DataBuffer.AddRange(message.ToMimeMessage().ToArray()); state.DataBuffer.AddRange(message.ToMimeMessage().ToArray());
return SendMailAsync(state, sessionId, ct, callback); return this.SendMailAsync(state, sessionId, ct, callback);
} }
#endif #endif
@ -210,14 +215,14 @@
/// <exception cref="ArgumentNullException">sessionState.</exception> /// <exception cref="ArgumentNullException">sessionState.</exception>
public Task SendMailAsync( public Task SendMailAsync(
SmtpSessionState sessionState, SmtpSessionState sessionState,
string sessionId = null, String sessionId = null,
CancellationToken ct = default, CancellationToken ct = default,
RemoteCertificateValidationCallback callback = null) RemoteCertificateValidationCallback callback = null) {
{ if(sessionState == null) {
if (sessionState == null)
throw new ArgumentNullException(nameof(sessionState)); throw new ArgumentNullException(nameof(sessionState));
}
return SendMailAsync(new[] { sessionState }, sessionId, ct, callback); return this.SendMailAsync(new[] { sessionState }, sessionId, ct, callback);
} }
/// <summary> /// <summary>
@ -237,54 +242,49 @@
/// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception> /// <exception cref="SmtpException">Defines an SMTP Exceptions class.</exception>
public async Task SendMailAsync( public async Task SendMailAsync(
IEnumerable<SmtpSessionState> sessionStates, IEnumerable<SmtpSessionState> sessionStates,
string sessionId = null, String sessionId = null,
CancellationToken ct = default, CancellationToken ct = default,
RemoteCertificateValidationCallback callback = null) RemoteCertificateValidationCallback callback = null) {
{ if(sessionStates == null) {
if (sessionStates == null)
throw new ArgumentNullException(nameof(sessionStates)); throw new ArgumentNullException(nameof(sessionStates));
}
using (var tcpClient = new TcpClient()) using(TcpClient tcpClient = new TcpClient()) {
{ await tcpClient.ConnectAsync(this.Host, this.Port).ConfigureAwait(false);
await tcpClient.ConnectAsync(Host, Port).ConfigureAwait(false);
using (var connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000)) using(Connection connection = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 1000)) {
{ SmtpSender sender = new SmtpSender(sessionId);
var sender = new SmtpSender(sessionId);
try try {
{
// Read the greeting message // Read the greeting message
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
// EHLO 1 // EHLO 1
await SendEhlo(ct, sender, connection).ConfigureAwait(false); await this.SendEhlo(ct, sender, connection).ConfigureAwait(false);
// STARTTLS // STARTTLS
if (EnableSsl) if(this.EnableSsl) {
{
sender.RequestText = $"{SmtpCommandNames.STARTTLS}"; sender.RequestText = $"{SmtpCommandNames.STARTTLS}";
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
sender.ValidateReply(); sender.ValidateReply();
if (await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false) if(await connection.UpgradeToSecureAsClientAsync(callback: callback).ConfigureAwait(false) == false) {
throw new SecurityException("Could not upgrade the channel to SSL."); throw new SecurityException("Could not upgrade the channel to SSL.");
} }
}
// EHLO 2 // EHLO 2
await SendEhlo(ct, sender, connection).ConfigureAwait(false); await this.SendEhlo(ct, sender, connection).ConfigureAwait(false);
// AUTH // AUTH
if (Credentials != null) if(this.Credentials != null) {
{ ConnectionAuth auth = new ConnectionAuth(connection, sender, this.Credentials);
var auth = new ConnectionAuth(connection, sender, Credentials);
await auth.AuthenticateAsync(ct).ConfigureAwait(false); await auth.AuthenticateAsync(ct).ConfigureAwait(false);
} }
foreach (var sessionState in sessionStates) foreach(SmtpSessionState sessionState in sessionStates) {
{
{ {
// MAIL FROM // MAIL FROM
sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>"; sender.RequestText = $"{SmtpCommandNames.MAIL} FROM:<{sessionState.SenderAddress}>";
@ -295,8 +295,7 @@
} }
// RCPT TO // RCPT TO
foreach (var recipient in sessionState.Recipients) foreach(String recipient in sessionState.Recipients) {
{
sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>"; sender.RequestText = $"{SmtpCommandNames.RCPT} TO:<{recipient}>";
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
@ -315,15 +314,16 @@
{ {
// CONTENT // CONTENT
var dataTerminator = sessionState.DataBuffer String dataTerminator = sessionState.DataBuffer
.Skip(sessionState.DataBuffer.Count - 5) .Skip(sessionState.DataBuffer.Count - 5)
.ToText(); .ToText();
sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)"; sender.RequestText = $"Buffer ({sessionState.DataBuffer.Count} bytes)";
await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false); await connection.WriteDataAsync(sessionState.DataBuffer.ToArray(), true, ct).ConfigureAwait(false);
if (dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator) == false) if(dataTerminator.EndsWith(SmtpDefinitions.SmtpDataCommandTerminator) == false) {
await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, ct).ConfigureAwait(false); await connection.WriteTextAsync(SmtpDefinitions.SmtpDataCommandTerminator, ct).ConfigureAwait(false);
}
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
sender.ValidateReply(); sender.ValidateReply();
@ -338,10 +338,8 @@
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
sender.ValidateReply(); sender.ValidateReply();
} }
} } catch(Exception ex) {
catch (Exception ex) String errorMessage =
{
var errorMessage =
$"Could not send email. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}"; $"Could not send email. {ex.Message}\r\n Last Request: {sender.RequestText}\r\n Last Reply: {sender.ReplyText}";
errorMessage.Error(typeof(SmtpClient).FullName, sessionId); errorMessage.Error(typeof(SmtpClient).FullName, sessionId);
@ -351,46 +349,41 @@
} }
} }
private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection) private async Task SendEhlo(CancellationToken ct, SmtpSender sender, Connection connection) {
{ sender.RequestText = $"{SmtpCommandNames.EHLO} {this.ClientHostname}";
sender.RequestText = $"{SmtpCommandNames.EHLO} {ClientHostname}";
await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false); await connection.WriteLineAsync(sender.RequestText, ct).ConfigureAwait(false);
do do {
{
sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false); sender.ReplyText = await connection.ReadLineAsync(ct).ConfigureAwait(false);
} while (!sender.IsReplyOk); } while(!sender.IsReplyOk);
sender.ValidateReply(); sender.ValidateReply();
} }
private class ConnectionAuth private class ConnectionAuth {
{
private readonly SmtpSender _sender; private readonly SmtpSender _sender;
private readonly Connection _connection; private readonly Connection _connection;
private readonly NetworkCredential _credentials; private readonly NetworkCredential _credentials;
public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) public ConnectionAuth(Connection connection, SmtpSender sender, NetworkCredential credentials) {
{ this._connection = connection;
_connection = connection; this._sender = sender;
_sender = sender; this._credentials = credentials;
_credentials = credentials;
} }
public async Task AuthenticateAsync(CancellationToken ct) public async Task AuthenticateAsync(CancellationToken ct) {
{ this._sender.RequestText =
_sender.RequestText = $"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.UserName))}";
$"{SmtpCommandNames.AUTH} {SmtpDefinitions.SmtpAuthMethods.Login} {Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.UserName))}";
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false); await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false); this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply(); this._sender.ValidateReply();
_sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(_credentials.Password)); this._sender.RequestText = Convert.ToBase64String(Encoding.UTF8.GetBytes(this._credentials.Password));
await _connection.WriteLineAsync(_sender.RequestText, ct).ConfigureAwait(false); await this._connection.WriteLineAsync(this._sender.RequestText, ct).ConfigureAwait(false);
_sender.ReplyText = await _connection.ReadLineAsync(ct).ConfigureAwait(false); this._sender.ReplyText = await this._connection.ReadLineAsync(ct).ConfigureAwait(false);
_sender.ValidateReply(); this._sender.ValidateReply();
} }
} }
} }

View File

@ -1,29 +1,28 @@
namespace Unosquare.Swan.Networking using System;
{
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// Contains useful constants and definitions.
/// </summary> /// </summary>
public static class SmtpDefinitions public static class SmtpDefinitions {
{
/// <summary> /// <summary>
/// The string sequence that delimits the end of the DATA command. /// The string sequence that delimits the end of the DATA command.
/// </summary> /// </summary>
public const string SmtpDataCommandTerminator = "\r\n.\r\n"; public const String SmtpDataCommandTerminator = "\r\n.\r\n";
/// <summary> /// <summary>
/// Lists the AUTH methods supported by default. /// Lists the AUTH methods supported by default.
/// </summary> /// </summary>
public static class SmtpAuthMethods public static class SmtpAuthMethods {
{
/// <summary> /// <summary>
/// The plain method. /// The plain method.
/// </summary> /// </summary>
public const string Plain = "PLAIN"; public const String Plain = "PLAIN";
/// <summary> /// <summary>
/// The login method. /// The login method.
/// </summary> /// </summary>
public const string Login = "LOGIN"; public const String Login = "LOGIN";
} }
} }
} }

View File

@ -1,59 +1,54 @@
namespace Unosquare.Swan.Networking using System;
{
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
using System.Net.Mail; using System.Net.Mail;
#else #else
using Exceptions; using Exceptions;
#endif #endif
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Use this class to store the sender session data. /// Use this class to store the sender session data.
/// </summary> /// </summary>
internal class SmtpSender internal class SmtpSender {
{ private readonly String _sessionId;
private readonly string _sessionId; private String _requestText;
private string _requestText;
public SmtpSender(string sessionId) public SmtpSender(String sessionId) => this._sessionId = sessionId;
{
_sessionId = sessionId;
}
public string RequestText public String RequestText {
{ get => this._requestText;
get => _requestText; set {
set this._requestText = value;
{ $" TX {this._requestText}".Debug(typeof(SmtpClient), this._sessionId);
_requestText = value;
$" TX {_requestText}".Debug(typeof(SmtpClient), _sessionId);
} }
} }
public string ReplyText { get; set; } public String ReplyText {
get; set;
}
public bool IsReplyOk => ReplyText.StartsWith("250 "); public Boolean IsReplyOk => this.ReplyText.StartsWith("250 ");
public void ValidateReply() public void ValidateReply() {
{ if(this.ReplyText == null) {
if (ReplyText == null)
throw new SmtpException("There was no response from the server"); throw new SmtpException("There was no response from the server");
}
try try {
{ SmtpServerReply response = SmtpServerReply.Parse(this.ReplyText);
var response = SmtpServerReply.Parse(ReplyText); $" RX {this.ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), this._sessionId);
$" RX {ReplyText} - {response.IsPositive}".Debug(typeof(SmtpClient), _sessionId);
if (response.IsPositive) return; if(response.IsPositive) {
return;
}
var responseContent = string.Empty; String responseContent = String.Empty;
if (response.Content.Count > 0) if(response.Content.Count > 0) {
responseContent = string.Join(";", response.Content.ToArray()); responseContent = String.Join(";", response.Content.ToArray());
}
throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent); throw new SmtpException((SmtpStatusCode)response.ReplyCode, responseContent);
} } catch {
catch throw new SmtpException($"Could not parse server response: {this.ReplyText}");
{
throw new SmtpException($"Could not parse server response: {ReplyText}");
} }
} }
} }

View File

@ -1,16 +1,14 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System; using System.Globalization;
using System.Collections.Generic; using System.Linq;
using System.Globalization; using System.Text;
using System.Linq;
using System.Text;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents an SMTP server response object. /// Represents an SMTP server response object.
/// </summary> /// </summary>
public class SmtpServerReply public class SmtpServerReply {
{
#region Constructors #region Constructors
/// <summary> /// <summary>
@ -19,27 +17,43 @@
/// <param name="responseCode">The response code.</param> /// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param> /// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param> /// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string statusCode, params string[] content) public SmtpServerReply(Int32 responseCode, String statusCode, params String[] content) {
{ this.Content = new List<String>();
Content = new List<string>(); this.ReplyCode = responseCode;
ReplyCode = responseCode; this.EnhancedStatusCode = statusCode;
EnhancedStatusCode = statusCode; this.Content.AddRange(content);
Content.AddRange(content); this.IsValid = responseCode >= 200 && responseCode < 600;
IsValid = responseCode >= 200 && responseCode < 600; this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown; this.ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
ReplyCodeCategory = SmtpReplyCodeCategories.Unknown;
if (!IsValid) return; if(!this.IsValid) {
if (responseCode >= 200) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion; return;
if (responseCode >= 300) ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate; }
if (responseCode >= 400) ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
if (responseCode >= 500) ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
if (responseCode >= 600) ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
if (int.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out var middleDigit)) if(responseCode >= 200) {
{ this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion;
if (middleDigit >= 0 && middleDigit <= 5) }
ReplyCodeCategory = (SmtpReplyCodeCategories) middleDigit;
if(responseCode >= 300) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate;
}
if(responseCode >= 400) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative;
}
if(responseCode >= 500) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative;
}
if(responseCode >= 600) {
this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown;
}
if(Int32.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out Int32 middleDigit)) {
if(middleDigit >= 0 && middleDigit <= 5) {
this.ReplyCodeCategory = (SmtpReplyCodeCategories)middleDigit;
}
} }
} }
@ -47,8 +61,7 @@
/// Initializes a new instance of the <see cref="SmtpServerReply"/> class. /// Initializes a new instance of the <see cref="SmtpServerReply"/> class.
/// </summary> /// </summary>
public SmtpServerReply() public SmtpServerReply()
: this(0, string.Empty, string.Empty) : this(0, String.Empty, String.Empty) {
{
// placeholder // placeholder
} }
@ -58,9 +71,8 @@
/// <param name="responseCode">The response code.</param> /// <param name="responseCode">The response code.</param>
/// <param name="statusCode">The status code.</param> /// <param name="statusCode">The status code.</param>
/// <param name="content">The content.</param> /// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string statusCode, string content) public SmtpServerReply(Int32 responseCode, String statusCode, String content)
: this(responseCode, statusCode, new[] {content}) : this(responseCode, statusCode, new[] { content }) {
{
} }
/// <summary> /// <summary>
@ -68,9 +80,8 @@
/// </summary> /// </summary>
/// <param name="responseCode">The response code.</param> /// <param name="responseCode">The response code.</param>
/// <param name="content">The content.</param> /// <param name="content">The content.</param>
public SmtpServerReply(int responseCode, string content) public SmtpServerReply(Int32 responseCode, String content)
: this(responseCode, string.Empty, content) : this(responseCode, String.Empty, content) {
{
} }
#endregion #endregion
@ -138,37 +149,49 @@
/// <summary> /// <summary>
/// Gets the response severity. /// Gets the response severity.
/// </summary> /// </summary>
public SmtpReplyCodeSeverities ReplyCodeSeverity { get; } public SmtpReplyCodeSeverities ReplyCodeSeverity {
get;
}
/// <summary> /// <summary>
/// Gets the response category. /// Gets the response category.
/// </summary> /// </summary>
public SmtpReplyCodeCategories ReplyCodeCategory { get; } public SmtpReplyCodeCategories ReplyCodeCategory {
get;
}
/// <summary> /// <summary>
/// Gets the numeric response code. /// Gets the numeric response code.
/// </summary> /// </summary>
public int ReplyCode { get; } public Int32 ReplyCode {
get;
}
/// <summary> /// <summary>
/// Gets the enhanced status code. /// Gets the enhanced status code.
/// </summary> /// </summary>
public string EnhancedStatusCode { get; } public String EnhancedStatusCode {
get;
}
/// <summary> /// <summary>
/// Gets the content. /// Gets the content.
/// </summary> /// </summary>
public List<string> Content { get; } public List<String> Content {
get;
}
/// <summary> /// <summary>
/// Returns true if the response code is between 200 and 599. /// Returns true if the response code is between 200 and 599.
/// </summary> /// </summary>
public bool IsValid { get; } public Boolean IsValid {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is positive. /// Gets a value indicating whether this instance is positive.
/// </summary> /// </summary>
public bool IsPositive => ReplyCode >= 200 && ReplyCode <= 399; public Boolean IsPositive => this.ReplyCode >= 200 && this.ReplyCode <= 399;
#endregion #endregion
@ -179,30 +202,31 @@
/// </summary> /// </summary>
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <returns>A new instance of SMTP server response object.</returns> /// <returns>A new instance of SMTP server response object.</returns>
public static SmtpServerReply Parse(string text) public static SmtpServerReply Parse(String text) {
{ String[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
var lines = text.Split(new[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries); if(lines.Length == 0) {
if (lines.Length == 0) return new SmtpServerReply(); return new SmtpServerReply();
var lastLineParts = lines.Last().Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
var enhancedStatusCode = string.Empty;
int.TryParse(lastLineParts[0], out var responseCode);
if (lastLineParts.Length > 1)
{
if (lastLineParts[1].Split('.').Length == 3)
enhancedStatusCode = lastLineParts[1];
} }
var content = new List<string>(); String[] lastLineParts = lines.Last().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
String enhancedStatusCode = String.Empty;
_ = Int32.TryParse(lastLineParts[0], out Int32 responseCode);
if(lastLineParts.Length > 1) {
if(lastLineParts[1].Split('.').Length == 3) {
enhancedStatusCode = lastLineParts[1];
}
}
for (var i = 0; i < lines.Length; i++) List<String> content = new List<String>();
{
var splitChar = i == lines.Length - 1 ? " " : "-";
var lineParts = lines[i].Split(new[] {splitChar}, 2, StringSplitOptions.None); for(Int32 i = 0; i < lines.Length; i++) {
var lineContent = lineParts.Last(); String splitChar = i == lines.Length - 1 ? " " : "-";
if (string.IsNullOrWhiteSpace(enhancedStatusCode) == false)
lineContent = lineContent.Replace(enhancedStatusCode, string.Empty).Trim(); String[] lineParts = lines[i].Split(new[] { splitChar }, 2, StringSplitOptions.None);
String lineContent = lineParts.Last();
if(String.IsNullOrWhiteSpace(enhancedStatusCode) == false) {
lineContent = lineContent.Replace(enhancedStatusCode, String.Empty).Trim();
}
content.Add(lineContent); content.Add(lineContent);
} }
@ -216,23 +240,23 @@
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents this instance. /// A <see cref="System.String" /> that represents this instance.
/// </returns> /// </returns>
public override string ToString() public override String ToString() {
{ String responseCodeText = this.ReplyCode.ToString(CultureInfo.InvariantCulture);
var responseCodeText = ReplyCode.ToString(CultureInfo.InvariantCulture); String statusCodeText = String.IsNullOrWhiteSpace(this.EnhancedStatusCode)
var statusCodeText = string.IsNullOrWhiteSpace(EnhancedStatusCode) ? String.Empty
? string.Empty : $" {this.EnhancedStatusCode.Trim()}";
: $" {EnhancedStatusCode.Trim()}"; if(this.Content.Count == 0) {
if (Content.Count == 0) return $"{responseCodeText}{statusCodeText}"; return $"{responseCodeText}{statusCodeText}";
}
var builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (var i = 0; i < Content.Count; i++) for(Int32 i = 0; i < this.Content.Count; i++) {
{ Boolean isLastLine = i == this.Content.Count - 1;
var isLastLine = i == Content.Count - 1;
builder.Append(isLastLine _ = builder.Append(isLastLine
? $"{responseCodeText}{statusCodeText} {Content[i]}" ? $"{responseCodeText}{statusCodeText} {this.Content[i]}"
: $"{responseCodeText}-{Content[i]}\r\n"); : $"{responseCodeText}-{this.Content[i]}\r\n");
} }
return builder.ToString(); return builder.ToString();

View File

@ -1,22 +1,20 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System.Collections.Generic;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents the state of an SMTP session associated with a client. /// Represents the state of an SMTP session associated with a client.
/// </summary> /// </summary>
public class SmtpSessionState public class SmtpSessionState {
{
#region Constructors #region Constructors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SmtpSessionState"/> class. /// Initializes a new instance of the <see cref="SmtpSessionState"/> class.
/// </summary> /// </summary>
public SmtpSessionState() public SmtpSessionState() {
{ this.DataBuffer = new List<Byte>();
DataBuffer = new List<byte>(); this.Reset(true);
Reset(true); this.ResetAuthentication();
ResetAuthentication();
} }
#endregion #endregion
@ -26,42 +24,56 @@
/// <summary> /// <summary>
/// Gets the contents of the data buffer. /// Gets the contents of the data buffer.
/// </summary> /// </summary>
public List<byte> DataBuffer { get; protected set; } public List<Byte> DataBuffer {
get; protected set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance has initiated. /// Gets or sets a value indicating whether this instance has initiated.
/// </summary> /// </summary>
public bool HasInitiated { get; set; } public Boolean HasInitiated {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the current session supports extensions. /// Gets or sets a value indicating whether the current session supports extensions.
/// </summary> /// </summary>
public bool SupportsExtensions { get; set; } public Boolean SupportsExtensions {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the client hostname. /// Gets or sets the client hostname.
/// </summary> /// </summary>
public string ClientHostname { get; set; } public String ClientHostname {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the session is currently receiving DATA. /// Gets or sets a value indicating whether the session is currently receiving DATA.
/// </summary> /// </summary>
public bool IsInDataMode { get; set; } public Boolean IsInDataMode {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the sender address. /// Gets or sets the sender address.
/// </summary> /// </summary>
public string SenderAddress { get; set; } public String SenderAddress {
get; set;
}
/// <summary> /// <summary>
/// Gets the recipients. /// Gets the recipients.
/// </summary> /// </summary>
public List<string> Recipients { get; } = new List<string>(); public List<String> Recipients { get; } = new List<String>();
/// <summary> /// <summary>
/// Gets or sets the extended data supporting any additional field for storage by a responder implementation. /// Gets or sets the extended data supporting any additional field for storage by a responder implementation.
/// </summary> /// </summary>
public object ExtendedData { get; set; } public Object ExtendedData {
get; set;
}
#endregion #endregion
@ -70,48 +82,59 @@
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is in authentication mode. /// Gets or sets a value indicating whether this instance is in authentication mode.
/// </summary> /// </summary>
public bool IsInAuthMode { get; set; } public Boolean IsInAuthMode {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the username. /// Gets or sets the username.
/// </summary> /// </summary>
public string Username { get; set; } public String Username {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the password. /// Gets or sets the password.
/// </summary> /// </summary>
public string Password { get; set; } public String Password {
get; set;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance has provided username. /// Gets a value indicating whether this instance has provided username.
/// </summary> /// </summary>
public bool HasProvidedUsername => string.IsNullOrWhiteSpace(Username) == false; public Boolean HasProvidedUsername => String.IsNullOrWhiteSpace(this.Username) == false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is authenticated. /// Gets or sets a value indicating whether this instance is authenticated.
/// </summary> /// </summary>
public bool IsAuthenticated { get; set; } public Boolean IsAuthenticated {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the authentication mode. /// Gets or sets the authentication mode.
/// </summary> /// </summary>
public string AuthMode { get; set; } public String AuthMode {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is channel secure. /// Gets or sets a value indicating whether this instance is channel secure.
/// </summary> /// </summary>
public bool IsChannelSecure { get; set; } public Boolean IsChannelSecure {
get; set;
}
/// <summary> /// <summary>
/// Resets the authentication state. /// Resets the authentication state.
/// </summary> /// </summary>
public void ResetAuthentication() public void ResetAuthentication() {
{ this.Username = String.Empty;
Username = string.Empty; this.Password = String.Empty;
Password = string.Empty; this.AuthMode = String.Empty;
AuthMode = string.Empty; this.IsInAuthMode = false;
IsInAuthMode = false; this.IsAuthenticated = false;
IsAuthenticated = false;
} }
#endregion #endregion
@ -121,38 +144,36 @@
/// <summary> /// <summary>
/// Resets the data mode to false, clears the recipients, the sender address and the data buffer. /// Resets the data mode to false, clears the recipients, the sender address and the data buffer.
/// </summary> /// </summary>
public void ResetEmail() public void ResetEmail() {
{ this.IsInDataMode = false;
IsInDataMode = false; this.Recipients.Clear();
Recipients.Clear(); this.SenderAddress = String.Empty;
SenderAddress = string.Empty; this.DataBuffer.Clear();
DataBuffer.Clear();
} }
/// <summary> /// <summary>
/// Resets the state table entirely. /// Resets the state table entirely.
/// </summary> /// </summary>
/// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param> /// <param name="clearExtensionData">if set to <c>true</c> [clear extension data].</param>
public void Reset(bool clearExtensionData) public void Reset(Boolean clearExtensionData) {
{ this.HasInitiated = false;
HasInitiated = false; this.SupportsExtensions = false;
SupportsExtensions = false; this.ClientHostname = String.Empty;
ClientHostname = string.Empty; this.ResetEmail();
ResetEmail();
if (clearExtensionData) if(clearExtensionData) {
ExtendedData = null; this.ExtendedData = null;
}
} }
/// <summary> /// <summary>
/// Creates a new object that is a copy of the current instance. /// Creates a new object that is a copy of the current instance.
/// </summary> /// </summary>
/// <returns>A clone.</returns> /// <returns>A clone.</returns>
public virtual SmtpSessionState Clone() public virtual SmtpSessionState Clone() {
{ SmtpSessionState clonedState = this.CopyPropertiesToNew<SmtpSessionState>(new[] { nameof(this.DataBuffer) });
var clonedState = this.CopyPropertiesToNew<SmtpSessionState>(new[] {nameof(DataBuffer)}); clonedState.DataBuffer.AddRange(this.DataBuffer);
clonedState.DataBuffer.AddRange(DataBuffer); clonedState.Recipients.AddRange(this.Recipients);
clonedState.Recipients.AddRange(Recipients);
return clonedState; return clonedState;
} }

View File

@ -1,18 +1,16 @@
namespace Unosquare.Swan.Networking using System;
{ using System.Collections.Generic;
using System; using System.Net;
using System.Collections.Generic; using System.Net.Sockets;
using System.Net; using System.Text;
using System.Net.Sockets; using System.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
namespace Unosquare.Swan.Networking {
/// <summary> /// <summary>
/// Represents a little SNMP client based on http://www.java2s.com/Code/CSharp/Network/SimpleSNMP.htm. /// Represents a little SNMP client based on http://www.java2s.com/Code/CSharp/Network/SimpleSNMP.htm.
/// </summary> /// </summary>
public static class SnmpClient public static class SnmpClient {
{ private static readonly Byte[] DiscoverMessage =
private static readonly byte[] DiscoverMessage =
{ {
48, 41, 2, 1, 1, 4, 6, 112, 117, 98, 108, 105, 99, 160, 28, 2, 4, 111, 81, 45, 144, 2, 1, 0, 2, 1, 0, 48, 48, 41, 2, 1, 1, 4, 6, 112, 117, 98, 108, 105, 99, 160, 28, 2, 4, 111, 81, 45, 144, 2, 1, 0, 2, 1, 0, 48,
14, 48, 12, 6, 8, 43, 6, 1, 2, 1, 1, 1, 0, 5, 0, 14, 48, 12, 6, 8, 43, 6, 1, 2, 1, 1, 1, 0, 5, 0,
@ -23,15 +21,14 @@
/// </summary> /// </summary>
/// <param name="snmpTimeOut">The SNMP time out.</param> /// <param name="snmpTimeOut">The SNMP time out.</param>
/// <returns>An array of network endpoint as an IP address and a port number.</returns> /// <returns>An array of network endpoint as an IP address and a port number.</returns>
public static IPEndPoint[] Discover(int snmpTimeOut = 6000) public static IPEndPoint[] Discover(Int32 snmpTimeOut = 6000) {
{ List<IPEndPoint> endpoints = new List<IPEndPoint>();
var endpoints = new List<IPEndPoint>();
Task[] tasks = Task[] tasks =
{ {
Task.Run(async () => Task.Run(async () =>
{ {
using (var udp = new UdpClient(IPAddress.Broadcast.AddressFamily)) using (UdpClient udp = new UdpClient(IPAddress.Broadcast.AddressFamily))
{ {
udp.EnableBroadcast = true; udp.EnableBroadcast = true;
await udp.SendAsync( await udp.SendAsync(
@ -43,7 +40,7 @@
{ {
try try
{ {
var buffer = new byte[udp.Client.ReceiveBufferSize]; Byte[] buffer = new Byte[udp.Client.ReceiveBufferSize];
EndPoint remote = new IPEndPoint(IPAddress.Any, 0); EndPoint remote = new IPEndPoint(IPAddress.Any, 0);
udp.Client.ReceiveFrom(buffer, ref remote); udp.Client.ReceiveFrom(buffer, ref remote);
endpoints.Add(remote as IPEndPoint); endpoints.Add(remote as IPEndPoint);
@ -74,7 +71,7 @@
/// A string that contains the results of decoding the specified sequence /// A string that contains the results of decoding the specified sequence
/// of bytes ref=GetString". /// of bytes ref=GetString".
/// </returns> /// </returns>
public static string GetPublicName(IPEndPoint host) => GetString(host, "1.3.6.1.2.1.1.5.0"); public static String GetPublicName(IPEndPoint host) => GetString(host, "1.3.6.1.2.1.1.5.0");
/// <summary> /// <summary>
/// Gets the up-time. /// Gets the up-time.
@ -85,23 +82,23 @@
/// A time interval that represents a specified number of seconds, /// A time interval that represents a specified number of seconds,
/// where the specification is accurate to the nearest millisecond. /// where the specification is accurate to the nearest millisecond.
/// </returns> /// </returns>
public static TimeSpan GetUptime(IPEndPoint host, string mibString = "1.3.6.1.2.1.1.3.0") public static TimeSpan GetUptime(IPEndPoint host, String mibString = "1.3.6.1.2.1.1.3.0") {
{ Byte[] response = Get(host, mibString);
var response = Get(host, mibString); if(response[0] == 0xff) {
if (response[0] == 0xff) return TimeSpan.Zero; return TimeSpan.Zero;
}
// If response, get the community name and MIB lengths // If response, get the community name and MIB lengths
var commlength = Convert.ToInt16(response[6]); Int16 commlength = Convert.ToInt16(response[6]);
var miblength = Convert.ToInt16(response[23 + commlength]); Int16 miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response // Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]); Int16 datalength = Convert.ToInt16(response[25 + commlength + miblength]);
var datastart = 26 + commlength + miblength; Int32 datastart = 26 + commlength + miblength;
var uptime = 0; Int32 uptime = 0;
while (datalength > 0) while(datalength > 0) {
{
uptime = (uptime << 8) + response[datastart++]; uptime = (uptime << 8) + response[datastart++];
datalength--; datalength--;
} }
@ -115,18 +112,19 @@
/// <param name="host">The host.</param> /// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param> /// <param name="mibString">The mibString.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static string GetString(IPEndPoint host, string mibString) public static String GetString(IPEndPoint host, String mibString) {
{ Byte[] response = Get(host, mibString);
var response = Get(host, mibString); if(response[0] == 0xff) {
if (response[0] == 0xff) return string.Empty; return String.Empty;
}
// If response, get the community name and MIB lengths // If response, get the community name and MIB lengths
var commlength = Convert.ToInt16(response[6]); Int16 commlength = Convert.ToInt16(response[6]);
var miblength = Convert.ToInt16(response[23 + commlength]); Int16 miblength = Convert.ToInt16(response[23 + commlength]);
// Extract the MIB data from the SNMP response // Extract the MIB data from the SNMP response
var datalength = Convert.ToInt16(response[25 + commlength + miblength]); Int16 datalength = Convert.ToInt16(response[25 + commlength + miblength]);
var datastart = 26 + commlength + miblength; Int32 datastart = 26 + commlength + miblength;
return Encoding.ASCII.GetString(response, datastart, datalength); return Encoding.ASCII.GetString(response, datastart, datalength);
} }
@ -137,7 +135,7 @@
/// <param name="host">The host.</param> /// <param name="host">The host.</param>
/// <param name="mibString">The mibString.</param> /// <param name="mibString">The mibString.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] Get(IPEndPoint host, string mibString) => Get("get", host, "public", mibString); public static Byte[] Get(IPEndPoint host, String mibString) => Get("get", host, "public", mibString);
/// <summary> /// <summary>
/// Gets the specified request. /// Gets the specified request.
@ -147,38 +145,33 @@
/// <param name="community">The community.</param> /// <param name="community">The community.</param>
/// <param name="mibString">The mibString.</param> /// <param name="mibString">The mibString.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] Get(string request, IPEndPoint host, string community, string mibString) public static Byte[] Get(String request, IPEndPoint host, String community, String mibString) {
{ Byte[] packet = new Byte[1024];
var packet = new byte[1024]; Byte[] mib = new Byte[1024];
var mib = new byte[1024]; Int32 comlen = community.Length;
var comlen = community.Length; String[] mibvals = mibString.Split('.');
var mibvals = mibString.Split('.'); Int32 miblen = mibvals.Length;
var miblen = mibvals.Length; Int32 cnt = 0;
var cnt = 0; Int32 orgmiblen = miblen;
var orgmiblen = miblen; Int32 pos = 0;
var pos = 0;
// Convert the string MIB into a byte array of integer values // Convert the string MIB into a byte array of integer values
// Unfortunately, values over 128 require multiple bytes // Unfortunately, values over 128 require multiple bytes
// which also increases the MIB length // which also increases the MIB length
for (var i = 0; i < orgmiblen; i++) for(Int32 i = 0; i < orgmiblen; i++) {
{ Int32 temp = Convert.ToInt16(mibvals[i]);
int temp = Convert.ToInt16(mibvals[i]); if(temp > 127) {
if (temp > 127) mib[cnt] = Convert.ToByte(128 + temp / 128);
{ mib[cnt + 1] = Convert.ToByte(temp - temp / 128 * 128);
mib[cnt] = Convert.ToByte(128 + (temp / 128));
mib[cnt + 1] = Convert.ToByte(temp - ((temp / 128) * 128));
cnt += 2; cnt += 2;
miblen++; miblen++;
} } else {
else
{
mib[cnt] = Convert.ToByte(temp); mib[cnt] = Convert.ToByte(temp);
cnt++; cnt++;
} }
} }
var snmplen = 29 + comlen + miblen - 1; Int32 snmplen = 29 + comlen + miblen - 1;
// The SNMP sequence start // The SNMP sequence start
packet[pos++] = 0x30; // Sequence start packet[pos++] = 0x30; // Sequence start
@ -194,18 +187,14 @@
packet[pos++] = Convert.ToByte(comlen); // length packet[pos++] = Convert.ToByte(comlen); // length
// Convert community name to byte array // Convert community name to byte array
var data = Encoding.ASCII.GetBytes(community); Byte[] data = Encoding.ASCII.GetBytes(community);
foreach (var t in data) foreach(Byte t in data) {
{
packet[pos++] = t; packet[pos++] = t;
} }
// Add GetRequest or GetNextRequest value // Add GetRequest or GetNextRequest value
if (request == "get") packet[pos++] = request == "get" ? (Byte)0xA0 : (Byte)0xA1;
packet[pos++] = 0xA0;
else
packet[pos++] = 0xA1;
packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB packet[pos++] = Convert.ToByte(20 + miblen - 1); // Size of total MIB
@ -241,8 +230,9 @@
packet[pos++] = 0x2b; packet[pos++] = 0x2b;
// Place MIB array in packet // Place MIB array in packet
for (var i = 2; i < miblen; i++) for(Int32 i = 2; i < miblen; i++) {
packet[pos++] = Convert.ToByte(mib[i]); packet[pos++] = Convert.ToByte(mib[i]);
}
packet[pos++] = 0x05; // Null object value packet[pos++] = 0x05; // Null object value
packet[pos] = 0x00; // Null packet[pos] = 0x00; // Null
@ -253,9 +243,9 @@
return packet; return packet;
} }
private static void SendPacket(IPEndPoint host, byte[] packet, int length) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ private static void SendPacket(IPEndPoint host, Byte[] packet, Int32 length) {
var sock = new Socket( Socket sock = new Socket(
AddressFamily.InterNetwork, AddressFamily.InterNetwork,
SocketType.Dgram, SocketType.Dgram,
ProtocolType.Udp); ProtocolType.Udp);
@ -263,16 +253,13 @@
SocketOptionLevel.Socket, SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, SocketOptionName.ReceiveTimeout,
5000); 5000);
var ep = (EndPoint) host; EndPoint ep = (EndPoint)host;
sock.SendTo(packet, length, SocketFlags.None, host); _ = sock.SendTo(packet, length, SocketFlags.None, host);
// Receive response from packet // Receive response from packet
try try {
{ _ = sock.ReceiveFrom(packet, ref ep);
sock.ReceiveFrom(packet, ref ep); } catch(SocketException) {
}
catch (SocketException)
{
packet[0] = 0xff; packet[0] = 0xff;
} }
} }